Files
SPT-Server-Build/Libraries/SPTarkov.Server.Core/Helpers/RepeatableQuestHelper.cs
T
Cj 371c9d58f0 Repeatable quest generation MongoID conversion (#439)
* Update repeatable quest generation for mongoid, add new server localizations, switch `Traders` to MongoId

* Give error default value
2025-07-02 12:44:49 +01:00

230 lines
8.7 KiB
C#

using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class RepeatableQuestHelper(
ISptLogger<RepeatableQuestHelper> logger,
DatabaseService databaseService,
ServerLocalisationService serverLocalisationService,
HashUtil hashUtil,
ICloner cloner,
ConfigServer configServer
)
{
protected readonly QuestConfig QuestConfig = configServer.GetConfig<QuestConfig>();
/// <summary>
/// Get the relevant elimination config based on the current players PMC level
/// </summary>
/// <param name="pmcLevel">Level of PMC character</param>
/// <param name="repeatableConfig">Main repeatable config</param>
/// <returns>EliminationConfig</returns>
public EliminationConfig? GetEliminationConfigByPmcLevel(
int pmcLevel,
RepeatableQuestConfig repeatableConfig
)
{
return repeatableConfig.QuestConfig.Elimination.FirstOrDefault(x =>
pmcLevel >= x.LevelRange.Min && pmcLevel <= x.LevelRange.Max
);
}
/// <summary>
/// Returns the repeatable template ids for the provided side
/// </summary>
/// <param name="playerGroup">Side to get the templates for</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public Dictionary<string, MongoId> GetRepeatableQuestTemplatesByGroup(PlayerGroup playerGroup)
{
var templates = QuestConfig.RepeatableQuestTemplates;
return playerGroup switch
{
PlayerGroup.Pmc => templates.Pmc,
PlayerGroup.Scav => templates.Scav,
_ => throw new ArgumentOutOfRangeException(nameof(playerGroup), playerGroup, null),
};
}
/// <summary>
/// Gets a cloned repeatable quest template for the provided type with a unique id
/// </summary>
/// <param name="type">Type of template to retrieve</param>
/// <param name="traderId">TraderId that should provide this quest</param>
/// <returns>Cloned quest template</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public RepeatableQuest? GetClonedQuestTemplateForType(RepeatableQuestType type, string traderId)
{
var quest = type switch
{
RepeatableQuestType.Elimination => cloner.Clone(
databaseService.GetTemplates().RepeatableQuests?.Templates?.Elimination
),
RepeatableQuestType.Completion => cloner.Clone(
databaseService.GetTemplates().RepeatableQuests?.Templates?.Completion
),
RepeatableQuestType.Exploration => cloner.Clone(
databaseService.GetTemplates().RepeatableQuests?.Templates?.Exploration
),
RepeatableQuestType.Pickup => cloner.Clone(
databaseService.GetTemplates().RepeatableQuests?.Templates?.Pickup
),
_ => null,
};
if (quest is null)
{
return null;
}
quest.Id = hashUtil.Generate();
quest.TraderId = traderId;
return quest;
}
/// <summary>
/// Generates the base object of quest type format given as templates in
/// assets/database/templates/repeatableQuests.json
/// The templates include Elimination, Completion and Extraction quest types
/// </summary>
/// <param name="type">Quest type: "Elimination", "Completion" or "Extraction"</param>
/// <param name="traderId">Trader from which the quest will be provided</param>
/// <param name="playerGroup">Scav daily or pmc daily/weekly quest</param>
/// <param name="sessionId">sessionId to generate template for</param>
/// <returns>
/// Object which contains the base elements for repeatable quests of the requests type
/// (needs to be filled with reward and conditions by called to make a valid quest)
/// </returns>
public RepeatableQuest? GenerateRepeatableTemplate(
RepeatableQuestType type,
MongoId traderId,
PlayerGroup playerGroup,
string sessionId
)
{
var questData = GetClonedQuestTemplateForType(type, traderId);
if (questData is null)
{
logger.Error(
serverLocalisationService.GetText(
"repeatable-quest_helper_template_not_found",
type
)
);
return null;
}
// Get template id from config based on side and type of quest
var typeIds = GetRepeatableQuestTemplatesByGroup(playerGroup);
var templateName = Enum.GetName(type);
if (templateName is null)
{
logger.Error(
serverLocalisationService.GetText(
"repeatable-quest_helper_template_name_not_found",
type
)
);
return null;
}
questData.TemplateId = typeIds[templateName];
// Force REF templates to use prapors ID - solves missing text issue
// TODO: Get rid of this new mongoid generation, needs handled in `Traders` but can't be done right now.
var desiredTraderId = traderId == Traders.REF ? new MongoId(Traders.PRAPOR) : traderId;
/* in locale, these id correspond to the text of quests
template ids -pmc : Elimination = 616052ea3054fc0e2c24ce6e / Completion = 61604635c725987e815b1a46 / Exploration = 616041eb031af660100c9967
template ids -scav : Elimination = 62825ef60e88d037dc1eb428 / Completion = 628f588ebb558574b2260fe5 / Exploration = 62825ef60e88d037dc1eb42c
*/
questData.Name = questData
.Name.Replace("{traderId}", traderId)
.Replace("{templateId}", questData.TemplateId);
questData.Note = questData
.Note?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.Description = questData
.Description.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.SuccessMessageText = questData
.SuccessMessageText?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.FailMessageText = questData
.FailMessageText?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.StartedMessageText = questData
.StartedMessageText?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.ChangeQuestMessageText = questData
.ChangeQuestMessageText?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.AcceptPlayerMessage = questData
.AcceptPlayerMessage?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.DeclinePlayerMessage = questData
.DeclinePlayerMessage?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
questData.CompletePlayerMessage = questData
.CompletePlayerMessage?.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questData.TemplateId);
if (questData.QuestStatus is null)
{
logger.Error(
serverLocalisationService.GetText("repeatable-quest_helper_no_status", type)
);
return null;
}
questData.QuestStatus.Id = hashUtil.Generate();
questData.QuestStatus.Uid = sessionId; // Needs to match user id
questData.QuestStatus.QId = questData.Id; // Needs to match quest id
return questData;
}
/// <summary>
/// Convert a raw location string into a location code can read (e.g. factory4_day into 55f2d3fd4bdc2d5f408b4567)
/// </summary>
/// <param name="locationKey">e.g. factory4_day</param>
/// <returns>guid</returns>
public string? GetQuestLocationByMapId(string locationKey)
{
if (!QuestConfig.LocationIdMap.TryGetValue(locationKey, out var locationId))
{
logger.Error(
serverLocalisationService.GetText("repeatable-quest_helper_no_loc_id", locationKey)
);
return null;
}
return locationId;
}
}