Custom quest service (#589)
* Initial work * add todo * Fix up errors * More work on CustomQuestService * Fix mistake * Remove cloning work, its cancer * clean-up * Use TryAdd as a guard * localize errors * remove unused exception * fix using * fix not passing logging params
This commit is contained in:
@@ -62,6 +62,12 @@
|
||||
"chat-unable_to_register_command_already_registered": "Unable to register already registered command: %s",
|
||||
"client_request": "[Client Request] %s",
|
||||
"client_request_ip": "[Client Request] {{ip}} {{url}}",
|
||||
"custom-quest-service_quest_id_already_exists": "A quest with the id: {{questId}} already exists.",
|
||||
"custom-quest-service_no_languages_for_quest": "No languages have been added for custom quest id: {{questId}}",
|
||||
"custom-quest-service_no_entries_for_language": "No locale entries have been added for language key: {{languageKey}}, was this intentional?",
|
||||
"custom-quest-service_could_not_find_language_key": "Could not find language key: {{languageKey}} in global locales when adding a custom quest. This is either a typo, or this language is not supported.",
|
||||
"custom-quest-service_locale_data_null": "Locale data is null for language: {{languageKey}}",
|
||||
"custom-quest-service_invalid_side": "QuestId: {{questId}} Savage is not a valid side for a side locked quest.",
|
||||
"customisation-item_already_purchased": "Clothing item {{itemId}} {{itemName}} already purchased",
|
||||
"customisation-suit_lacks_upd_or_stack_property": "Suit with tpl: %s lacks a upd object or stackobjectcount property",
|
||||
"customisation-unable_to_find_clothing_item_in_inventory": "Clothing item not found in inventory with id: %s",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using SPTarkov.Server.Core.Models.Common;
|
||||
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
|
||||
using SPTarkov.Server.Core.Models.Enums;
|
||||
|
||||
namespace SPTarkov.Server.Core.Models.Spt.Mod;
|
||||
|
||||
/// <summary>
|
||||
/// New quest detail object for use with the CustomQuestService.
|
||||
/// </summary>
|
||||
public record NewQuestDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// Quest to be added to the database
|
||||
/// </summary>
|
||||
[JsonPropertyName("newQuest")]
|
||||
public required Quest NewQuest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Locales for this quest. The primary key is the language to add to locale entries to<br/>
|
||||
/// The secondary key is the locale key, the value is the locale text itself.
|
||||
/// </summary>
|
||||
[JsonPropertyName("locales")]
|
||||
public required Dictionary<string, Dictionary<string, string>> Locales { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Only Usec and Bear are valid entries here,
|
||||
/// if used it will lock that quest to only being available to that specific side.<br/><br/>
|
||||
///
|
||||
/// If not used, this should be left null to keep the quest open to both Usec and Bears.
|
||||
/// </summary>
|
||||
[JsonPropertyName("lockedToSide")]
|
||||
public PlayerSide? LockedToSide { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result from either creating a new quest or cloning one.
|
||||
/// </summary>
|
||||
public record CreateQuestResult(bool Success, MongoId? QuestId)
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; set; } = Success;
|
||||
|
||||
[JsonPropertyName("questId")]
|
||||
public MongoId? QuestId { get; set; } = QuestId;
|
||||
|
||||
[JsonPropertyName("errors")]
|
||||
public List<string> Errors { get; } = [];
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Common;
|
||||
using SPTarkov.Server.Core.Models.Enums;
|
||||
using SPTarkov.Server.Core.Models.Spt.Config;
|
||||
using SPTarkov.Server.Core.Models.Spt.Mod;
|
||||
using SPTarkov.Server.Core.Servers;
|
||||
|
||||
namespace SPTarkov.Server.Core.Services.Mod;
|
||||
|
||||
[Injectable]
|
||||
public class CustomQuestService(
|
||||
DatabaseService databaseService,
|
||||
ConfigServer configServer,
|
||||
ServerLocalisationService serverLocalisationService
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new custom quest from a NewQuestDetails object.
|
||||
/// </summary>
|
||||
/// <param name="newQuestDetails">Quest details to be used for creation</param>
|
||||
/// <returns>Result of the quest creation, remember to check it for errors!</returns>
|
||||
public CreateQuestResult CreateQuest(NewQuestDetails newQuestDetails)
|
||||
{
|
||||
var quest = newQuestDetails.NewQuest;
|
||||
var result = new CreateQuestResult(false, newQuestDetails.NewQuest.Id);
|
||||
|
||||
var databaseQuests = databaseService.GetTables().Templates.Quests;
|
||||
if (!databaseQuests.TryAdd(quest.Id, quest))
|
||||
{
|
||||
result.Errors.Add(serverLocalisationService.GetText("custom-quest-service_quest_id_already_exists", quest.Id));
|
||||
return result;
|
||||
}
|
||||
|
||||
var locales = newQuestDetails.Locales;
|
||||
if (locales.Count == 0)
|
||||
{
|
||||
result.Errors.Add(serverLocalisationService.GetText("custom-quest-service_no_languages_for_quest", quest.Id));
|
||||
return result;
|
||||
}
|
||||
|
||||
AddQuestLocales(locales, result);
|
||||
|
||||
var side = newQuestDetails.LockedToSide;
|
||||
if (side.HasValue)
|
||||
{
|
||||
RestrictQuestSide(quest.Id, side.Value, result);
|
||||
}
|
||||
|
||||
// No errors mean success
|
||||
result.Success = result.Errors.Count == 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds quest locales to the database
|
||||
/// </summary>
|
||||
/// <param name="locales">locales to add</param>
|
||||
/// <param name="result">create quest result</param>
|
||||
private void AddQuestLocales(Dictionary<string, Dictionary<string, string>> locales, CreateQuestResult result)
|
||||
{
|
||||
var globalLocales = databaseService.GetLocales().Global;
|
||||
|
||||
foreach (var (languageKey, entries) in locales)
|
||||
{
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
result.Errors?.Add(serverLocalisationService.GetText("custom-quest-service_no_entries_for_language", languageKey));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!globalLocales.TryGetValue(languageKey, out var lazyLoadedLocales))
|
||||
{
|
||||
result.Errors?.Add(serverLocalisationService.GetText("custom-quest-service_could_not_find_language_key", languageKey));
|
||||
continue;
|
||||
}
|
||||
|
||||
lazyLoadedLocales.AddTransformer(localeData =>
|
||||
{
|
||||
if (localeData is null)
|
||||
{
|
||||
result.Errors?.Add(serverLocalisationService.GetText("custom-quest-service_locale_data_null", languageKey));
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var (key, entry) in entries)
|
||||
{
|
||||
localeData.TryAdd(key, entry);
|
||||
}
|
||||
|
||||
return localeData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restricts a custom quest to a specific side.
|
||||
/// </summary>
|
||||
/// <param name="questId">Quest id to restrict</param>
|
||||
/// <param name="side">Side to restrict it to</param>
|
||||
/// <param name="result">Result of the quest creation</param>
|
||||
private void RestrictQuestSide(MongoId questId, PlayerSide side, CreateQuestResult result)
|
||||
{
|
||||
var questConfig = configServer.GetConfig<QuestConfig>();
|
||||
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (side)
|
||||
{
|
||||
case PlayerSide.Usec:
|
||||
questConfig.UsecOnlyQuests.Add(questId);
|
||||
break;
|
||||
|
||||
case PlayerSide.Bear:
|
||||
questConfig.BearOnlyQuests.Add(questId);
|
||||
break;
|
||||
|
||||
case PlayerSide.Savage:
|
||||
result.Errors.Add(serverLocalisationService.GetText("custom-quest-service_invalid_side", result.QuestId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user