Merge branch 'main' of https://github.com/sp-tarkov/server-csharp
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Bot;
|
||||
@@ -60,7 +60,7 @@ public class BotCallbacks
|
||||
if (difficulty == "core")
|
||||
return _httpResponseUtil.NoBody(_botController.GetBotCoreDifficulty());
|
||||
|
||||
var raidConfig = (GetRaidConfigurationRequestData)_applicationContext.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)?.Value;
|
||||
var raidConfig = _applicationContext.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)?.GetValue<GetRaidConfigurationRequestData>();
|
||||
|
||||
return _httpResponseUtil.NoBody(_botController.GetBotDifficulty(type, difficulty, raidConfig));
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ public class DataCallbacks
|
||||
/// <returns></returns>
|
||||
public string GetSettings(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
return _httpResponseUtil.GetBody(_databaseService.GetSettings());
|
||||
var returns = _httpResponseUtil.GetBody(_databaseService.GetSettings());
|
||||
return returns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,9 +63,10 @@ public class DataCallbacks
|
||||
public string GetGlobals(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
var globals = _databaseService.GetGlobals();
|
||||
globals.Time = _timeUtil.GetTimeStamp();
|
||||
globals.Time = _timeUtil.GetTimeStamp() / 1000;
|
||||
var returns = _httpResponseUtil.GetBody(globals);
|
||||
|
||||
return _httpResponseUtil.GetBody(globals);
|
||||
return returns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using Core.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Enums;
|
||||
@@ -13,9 +14,9 @@ namespace Core.Callbacks;
|
||||
[Injectable(InjectableTypeOverride = typeof(OnUpdate), TypePriority = OnUpdateOrder.HideoutCallbacks)]
|
||||
public class HideoutCallbacks : OnUpdate
|
||||
{
|
||||
protected HideoutController _hideoutController;
|
||||
protected ConfigServer _configServer;
|
||||
protected HideoutConfig _hideoutConfig;
|
||||
private readonly HideoutController _hideoutController;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly HideoutConfig _hideoutConfig;
|
||||
|
||||
public HideoutCallbacks
|
||||
(
|
||||
@@ -31,227 +32,144 @@ public class HideoutCallbacks : OnUpdate
|
||||
/// <summary>
|
||||
/// Handle HideoutUpgrade event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse Upgrade(PmcData pmcData, HideoutUpgradeRequestData info, string sessionID, ItemEventRouterResponse output)
|
||||
public ItemEventRouterResponse Upgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
// _hideoutController.StartUpgrade(pmcData, info, sessionID, output);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
_hideoutController.StartUpgrade(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutUpgradeComplete event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData info, string sessionID, ItemEventRouterResponse output)
|
||||
public ItemEventRouterResponse UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
// _hideoutController.UpgradeComplete(pmcData, info, sessionID, output);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
_hideoutController.UpgradeComplete(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutPutItemsInAreaSlots
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.PutItemsInAreaSlots(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutTakeItemsFromAreaSlots event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.TakeItemsFromAreaSlots(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutToggleArea event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.ToggleArea(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.ToggleArea(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutSingleProductionStart event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.SingleProductionStart(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.SingleProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutScavCaseProductionStart event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.ScavCaseProductionStart(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutContinuousProductionStart
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.ContinuousProductionStart(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutTakeProduction event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.TakeProduction(pmcData, info, sessionID);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.TakeProduction(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutQuickTimeEvent
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <param name="output"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse HandleQTEEvent(PmcData pmcData, HandleQTEEventRequestData info, string sessionID, ItemEventRouterResponse output)
|
||||
public ItemEventRouterResponse HandleQTEEvent(PmcData pmcData, HandleQTEEventRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
// _hideoutController.HandleQTEEventOutcome(sessionID, pmcData, info, output);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
_hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <param name="output"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints info, string sessionID, ItemEventRouterResponse output)
|
||||
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
// _hideoutController.RecordShootingRangePoints(sessionID, pmcData, info);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
_hideoutController.RecordShootingRangePoints(sessionID, pmcData, request);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse ImproveArea(PmcData pmcData, HideoutImproveAreaRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse ImproveArea(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.ImproveArea(sessionID, pmcData, info);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.ImproveArea(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCancelProductionCommand
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutImproveAreaRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.CancelProduction(sessionID, pmcData, info);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.CancelProduction(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCircleOfCultistProductionStart
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse CicleOfCultistProductionStart(PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse CicleOfCultistProductionStart(PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, info);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutDeleteProductionCommand
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse HideoutDeleteProductionCommand(PmcData pmcData, HideoutDeleteProductionRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse HideoutDeleteProductionCommand(PmcData pmcData, HideoutDeleteProductionRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, info);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCustomizationApply
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse HideoutCustomizationApplyCommand(PmcData pmcData, HideoutCustomizationApplyRequestData info, string sessionID)
|
||||
public ItemEventRouterResponse HideoutCustomizationApplyCommand(PmcData pmcData, HideoutCustomizationApplyRequestData request, string sessionID)
|
||||
{
|
||||
// return _hideoutController.HideoutCustomizationApply(sessionID, pmcData, info);
|
||||
// TODO: HideoutController is not implemented rn
|
||||
throw new NotImplementedException();
|
||||
return _hideoutController.HideoutCustomizationApply(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/game/profile/items/moving - hideoutCustomizationSetMannequinPose
|
||||
*/
|
||||
public ItemEventRouterResponse HideoutCustomizationSetMannequinPose(PmcData pmcData, HideoutCustomizationSetMannequinPoseRequest request, string sessionId) {
|
||||
return _hideoutController.HideoutCustomizationSetMannequinPose(sessionId, pmcData, request);
|
||||
}
|
||||
|
||||
public async Task<bool> OnUpdate(long timeSinceLastRun)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Context;
|
||||
using Core.DI;
|
||||
using Core.Servers;
|
||||
@@ -18,7 +18,7 @@ public class HttpCallbacks : OnLoad
|
||||
|
||||
public async Task OnLoad()
|
||||
{
|
||||
_httpServer.Load((WebApplicationBuilder) _applicationContext.GetLatestValue(ContextVariableType.APP_BUILDER).Value);
|
||||
_httpServer.Load( _applicationContext.GetLatestValue(ContextVariableType.APP_BUILDER).GetValue<WebApplicationBuilder>());
|
||||
_applicationContext.ClearValues(ContextVariableType.APP_BUILDER);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
namespace Core.Context;
|
||||
namespace Core.Context;
|
||||
|
||||
public class ContextVariable(object value, ContextVariableType contextVariableType)
|
||||
public class ContextVariable
|
||||
{
|
||||
public object Value { get; } = value;
|
||||
public DateTime Timestamp { get; } = DateTime.Now;
|
||||
public ContextVariableType Type { get; } = contextVariableType;
|
||||
}
|
||||
private readonly object _value;
|
||||
private readonly ContextVariableType _internalType;
|
||||
private readonly DateTime _timestamp;
|
||||
|
||||
public ContextVariable(object value, ContextVariableType contextVariableInternalType)
|
||||
{
|
||||
_value = value;
|
||||
_timestamp = DateTime.Now;
|
||||
_internalType = contextVariableInternalType;
|
||||
}
|
||||
|
||||
public T GetValue<T>() {
|
||||
return (T)_value;
|
||||
}
|
||||
|
||||
public DateTime GetTimestamp()
|
||||
{
|
||||
return _timestamp;
|
||||
}
|
||||
|
||||
public ContextVariableType GetContextType()
|
||||
{
|
||||
return _internalType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
@@ -14,7 +14,6 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using Condition = Core.Models.Spt.Config.Condition;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Controllers;
|
||||
@@ -33,6 +32,7 @@ public class BotController
|
||||
protected MatchBotDeatilsCacheService _matchBotDeatilsCacheService;
|
||||
protected LocalisationService _localisationService;
|
||||
protected SeasonalEventService _seasonalEventService;
|
||||
private readonly MatchBotDetailsCacheService _matchBotDetailsCacheService;
|
||||
protected ProfileHelper _profileHelper;
|
||||
protected ConfigServer _configServer;
|
||||
protected ApplicationContext _applicationContext;
|
||||
@@ -54,6 +54,7 @@ public class BotController
|
||||
MatchBotDeatilsCacheService matchBotDeatilsCacheService,
|
||||
LocalisationService localisationService,
|
||||
SeasonalEventService seasonalEventService,
|
||||
MatchBotDetailsCacheService matchBotDetailsCacheService,
|
||||
ProfileHelper profileHelper,
|
||||
ConfigServer configServer,
|
||||
ApplicationContext applicationContext,
|
||||
@@ -71,6 +72,7 @@ public class BotController
|
||||
_matchBotDeatilsCacheService = matchBotDeatilsCacheService;
|
||||
_localisationService = localisationService;
|
||||
_seasonalEventService = seasonalEventService;
|
||||
_matchBotDetailsCacheService = matchBotDetailsCacheService;
|
||||
_profileHelper = profileHelper;
|
||||
_configServer = configServer;
|
||||
_applicationContext = applicationContext;
|
||||
@@ -129,33 +131,264 @@ public class BotController
|
||||
|
||||
public List<BotBase> Generate(string sessionId, GenerateBotsRequestData info)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
|
||||
|
||||
// Use this opportunity to create and cache bots for later retrieval
|
||||
var multipleBotTypesRequested = info.Conditions.Count > 1;
|
||||
if (multipleBotTypesRequested)
|
||||
{
|
||||
return GenerateMultipleBotsAndCache(info, pmcProfile, sessionId);
|
||||
}
|
||||
|
||||
return ReturnSingleBotFromCache(sessionId, info);
|
||||
}
|
||||
|
||||
public async Task<List<BotBase>> GenerateMultipleBotsAndCache()
|
||||
public List<BotBase> GenerateMultipleBotsAndCache(GenerateBotsRequestData request, PmcData pmcProfile, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var raidSettings = GetMostRecentRaidSettings();
|
||||
|
||||
var allPmcsHaveSameNameAsPlayer = _randomUtil.GetChance100(
|
||||
_pmcConfig.AllPMCsHavePlayerNameWithRandomPrefixChance);
|
||||
|
||||
// Map conditions to promises for bot generation
|
||||
foreach (var condition in request.Conditions)
|
||||
{
|
||||
var botGenerationDetails = GetBotGenerationDetailsForWave(
|
||||
condition,
|
||||
pmcProfile,
|
||||
allPmcsHaveSameNameAsPlayer,
|
||||
raidSettings,
|
||||
_botConfig.PresetBatch.GetValueOrDefault(condition.Role, 15),
|
||||
_botHelper.IsBotPmc(condition.Role));
|
||||
|
||||
// Generate bots for the current condition
|
||||
GenerateWithBotDetails(condition, botGenerationDetails, sessionId);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private void GenerateWithBotDetails(GenerateCondition condition, BotGenerationDetails botGenerationDetails, string sessionId)
|
||||
{
|
||||
var isEventBot = condition.Role.ToLower().Contains("event");
|
||||
if (isEventBot)
|
||||
{
|
||||
// Add eventRole data + reassign role property to be base type
|
||||
botGenerationDetails.EventRole = condition.Role;
|
||||
botGenerationDetails.Role = _seasonalEventService.GetBaseRoleForEventBot(
|
||||
botGenerationDetails.EventRole);
|
||||
}
|
||||
|
||||
// Create a compound key to store bots in cache against
|
||||
var cacheKey = _botGenerationCacheService.CreateCacheKey(
|
||||
botGenerationDetails.EventRole ?? botGenerationDetails.Role,
|
||||
botGenerationDetails.BotDifficulty);
|
||||
|
||||
// Get number of bots we have in cache
|
||||
var botCacheCount = _botGenerationCacheService.GetCachedBotCount(cacheKey);
|
||||
|
||||
if (botCacheCount >= botGenerationDetails.BotCountToGenerate)
|
||||
{
|
||||
_logger.Debug($"Cache already has sufficient bots: { botCacheCount}");
|
||||
return;
|
||||
}
|
||||
|
||||
// We're below desired count, add bots to cache
|
||||
var botsToGenerate = botGenerationDetails.BotCountToGenerate - botCacheCount;
|
||||
var progressWriter = new ProgressWriter(botGenerationDetails.BotCountToGenerate.Value);
|
||||
|
||||
_logger.Debug($"Generating { botsToGenerate} bots for cacheKey: {cacheKey}");
|
||||
|
||||
for (var i = 0; i < botsToGenerate; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var detailsClone = _cloner.Clone(botGenerationDetails);
|
||||
GenerateSingleBotAndStoreInCache(detailsClone, sessionId, cacheKey);
|
||||
progressWriter.Increment();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error($"Failed to generate bot #{i + 1}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug($"Generated { botGenerationDetails.BotCountToGenerate} { botGenerationDetails.Role}" +
|
||||
$"({botGenerationDetails.EventRole ?? botGenerationDetails.Role ?? ""}) { botGenerationDetails.BotDifficulty}bots");
|
||||
}
|
||||
|
||||
private List<BotBase> ReturnSingleBotFromCache(string sessionId, GenerateBotsRequestData request)
|
||||
{
|
||||
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
|
||||
var requestedBot = request.Conditions[0];
|
||||
|
||||
var raidSettings = GetMostRecentRaidSettings();
|
||||
|
||||
// Create generation request for when cache is empty
|
||||
var condition = new GenerateCondition{
|
||||
Role = requestedBot.Role,
|
||||
Limit= 5,
|
||||
Difficulty= requestedBot.Difficulty,
|
||||
};
|
||||
var botGenerationDetails = GetBotGenerationDetailsForWave(
|
||||
condition,
|
||||
pmcProfile,
|
||||
false,
|
||||
raidSettings,
|
||||
_botConfig.PresetBatch[requestedBot.Role],
|
||||
_botHelper.IsBotPmc(requestedBot.Role)
|
||||
);
|
||||
|
||||
// Event bots need special actions to occur, set data up for them
|
||||
var isEventBot = requestedBot.Role.ToLower().Contains("event");
|
||||
if (isEventBot)
|
||||
{
|
||||
// Add eventRole data + reassign role property
|
||||
botGenerationDetails.EventRole = requestedBot.Role;
|
||||
botGenerationDetails.Role = _seasonalEventService.GetBaseRoleForEventBot(
|
||||
botGenerationDetails.EventRole);
|
||||
}
|
||||
|
||||
// Does non pmc bot have a chance of being converted into a pmc
|
||||
var convertIntoPmcChanceMinMax = GetPmcConversionMinMaxForLocation(
|
||||
requestedBot.Role,
|
||||
raidSettings?.Location);
|
||||
if (convertIntoPmcChanceMinMax is not null && !botGenerationDetails.IsPmc.Value)
|
||||
{
|
||||
// Bot has % chance to become pmc and isnt one pmc already
|
||||
var convertToPmc = _botHelper.RollChanceToBePmc(convertIntoPmcChanceMinMax);
|
||||
if (convertToPmc)
|
||||
{
|
||||
// Update requirements
|
||||
botGenerationDetails.IsPmc = true;
|
||||
botGenerationDetails.Role = _botHelper.GetRandomizedPmcRole();
|
||||
botGenerationDetails.Side = _botHelper.GetPmcSideByRole(botGenerationDetails.Role);
|
||||
botGenerationDetails.BotDifficulty = GetPmcDifficulty(requestedBot.Difficulty);
|
||||
botGenerationDetails.BotCountToGenerate = _botConfig.PresetBatch[botGenerationDetails.Role];
|
||||
}
|
||||
}
|
||||
// Only convert to boss when not already converted to PMC & Boss Convert is enabled
|
||||
var bossConvertEnabled = _botConfig.AssaultToBossConversion.BossConvertEnabled;
|
||||
var bossConvertMinMax = _botConfig.AssaultToBossConversion.BossConvertMinMax;
|
||||
var bossesToConvertToWeights = _botConfig.AssaultToBossConversion.BossesToConvertToWeights;
|
||||
if (bossConvertEnabled && !botGenerationDetails.IsPmc.Value)
|
||||
{
|
||||
var bossConvertPercent = bossConvertMinMax[requestedBot.Role.ToLower()];
|
||||
if (bossConvertPercent is not null)
|
||||
{
|
||||
// Roll a percentage check if we should convert scav to boss
|
||||
if (
|
||||
_randomUtil.GetChance100(_randomUtil.GetDouble(bossConvertPercent.Min.Value, bossConvertPercent.Max.Value))
|
||||
)
|
||||
{
|
||||
UpdateBotGenerationDetailsToRandomBoss(botGenerationDetails, bossesToConvertToWeights);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a compound key to store bots in cache against
|
||||
var cacheKey = _botGenerationCacheService.CreateCacheKey(
|
||||
botGenerationDetails.EventRole ?? botGenerationDetails.Role,
|
||||
botGenerationDetails.BotDifficulty);
|
||||
|
||||
// Check cache for bot using above key
|
||||
if (!_botGenerationCacheService.CacheHasBotWithKey(cacheKey))
|
||||
{
|
||||
// No bot in cache, generate new and store in cache
|
||||
GenerateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey);
|
||||
|
||||
_logger.Debug($"Generated { botGenerationDetails.BotCountToGenerate} " +
|
||||
$"{ botGenerationDetails.Role} ({botGenerationDetails.EventRole ?? ""}) {botGenerationDetails.BotDifficulty} bots");
|
||||
}
|
||||
|
||||
var desiredBot = _botGenerationCacheService.GetBot(cacheKey);
|
||||
_botGenerationCacheService.StoreUsedBot(desiredBot);
|
||||
|
||||
return [desiredBot];
|
||||
}
|
||||
|
||||
private void GenerateSingleBotAndStoreInCache(BotGenerationDetails botGenerationDetails, string sessionId, string cacheKey)
|
||||
{
|
||||
var botToCache = _botGenerator.PrepareAndGenerateBot(sessionId, botGenerationDetails);
|
||||
_botGenerationCacheService.StoreBots(cacheKey, [botToCache]);
|
||||
|
||||
// Store bot details in cache so post-raid PMC messages can use data
|
||||
_matchBotDetailsCacheService.CacheBot(botToCache);
|
||||
}
|
||||
|
||||
private void UpdateBotGenerationDetailsToRandomBoss(BotGenerationDetails botGenerationDetails, Dictionary<string, int> bossesToConvertToWeights)
|
||||
{
|
||||
// Seems Actual bosses have the same Brain issues like PMC gaining Boss Brains We can't use all bosses
|
||||
botGenerationDetails.Role = _weightedRandomHelper.GetWeightedValue(bossesToConvertToWeights);
|
||||
|
||||
// Bosses are only ever 'normal'
|
||||
botGenerationDetails.BotDifficulty = "normal";
|
||||
botGenerationDetails.BotCountToGenerate = _botConfig.PresetBatch[botGenerationDetails.Role];
|
||||
}
|
||||
|
||||
private string GetPmcDifficulty(string requestedBotDifficulty)
|
||||
{
|
||||
var difficulty = _pmcConfig.Difficulty.ToLower();
|
||||
return difficulty switch
|
||||
{
|
||||
"asonline" => requestedBotDifficulty,
|
||||
"random" => _botDifficultyHelper.ChooseRandomDifficulty(),
|
||||
_ => _pmcConfig.Difficulty
|
||||
};
|
||||
}
|
||||
|
||||
private MinMax GetPmcConversionMinMaxForLocation(string requestedBotRole, string location)
|
||||
{
|
||||
var mapSpecificConversionValues = _pmcConfig.ConvertIntoPmcChance.GetValueOrDefault(location.ToLower(), null);
|
||||
if (mapSpecificConversionValues is null)
|
||||
{
|
||||
return _pmcConfig.ConvertIntoPmcChance["default"][requestedBotRole];
|
||||
}
|
||||
|
||||
return mapSpecificConversionValues[requestedBotRole.ToLower()];
|
||||
}
|
||||
|
||||
public GetRaidConfigurationRequestData GetMostRecentRaidSettings()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var raidSettings = _applicationContext
|
||||
.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
.GetValue<GetRaidConfigurationRequestData>();
|
||||
|
||||
if (raidSettings is null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext"));
|
||||
}
|
||||
|
||||
return raidSettings;
|
||||
}
|
||||
|
||||
public MinMax GetPmcLevelRangeForMap(string location)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _pmcConfig.LocationSpecificPmcLevelOverride.GetValueOrDefault(location.ToLower(), null);
|
||||
}
|
||||
|
||||
public BotGenerationDetails GetBotGenerationDetailsForWave(Condition condition, PmcData pmcProfile, bool AllPmcsHaveSameNameAsPlayer,
|
||||
GetRaidConfigurationRequestData raidSettings, int botCountToGenerate, bool generateAsPmc)
|
||||
public BotGenerationDetails GetBotGenerationDetailsForWave(
|
||||
GenerateCondition condition,
|
||||
PmcData pmcProfile,
|
||||
bool allPmcsHaveSameNameAsPlayer,
|
||||
GetRaidConfigurationRequestData raidSettings,
|
||||
int botCountToGenerate,
|
||||
bool generateAsPmc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int GetPlayerLevelFromProfile()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new BotGenerationDetails{
|
||||
IsPmc = generateAsPmc,
|
||||
Side = generateAsPmc ? _botHelper.GetPmcSideByRole(condition.Role) : "Savage",
|
||||
Role = condition.Role,
|
||||
PlayerLevel = pmcProfile.Info.Level.Value,
|
||||
PlayerName = pmcProfile.Info.Nickname,
|
||||
BotRelativeLevelDeltaMax = _pmcConfig.BotRelativeLevelDeltaMax,
|
||||
BotRelativeLevelDeltaMin = _pmcConfig.BotRelativeLevelDeltaMin,
|
||||
BotCountToGenerate = botCountToGenerate,
|
||||
BotDifficulty = condition.Difficulty,
|
||||
LocationSpecificPmcLevelOverride = this.GetPmcLevelRangeForMap(raidSettings?.Location), // Min/max levels for PMCs to generate within
|
||||
IsPlayerScav = false,
|
||||
AllPmcsHaveSameNameAsPlayer = allPmcsHaveSameNameAsPlayer,
|
||||
};
|
||||
}
|
||||
|
||||
public int GetBotLimit(string type)
|
||||
@@ -181,11 +414,23 @@ public class BotController
|
||||
|
||||
public int GetBotCap(string location)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botCap = _botConfig.MaxBotCap[location.ToLower()];
|
||||
if (location == "default")
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("bot-no_bot_cap_found_for_location", location.ToLower()));
|
||||
}
|
||||
|
||||
return botCap;
|
||||
}
|
||||
|
||||
public object GetAiBotBrainTypes() // TODO: Returns `any` in the node server
|
||||
public object GetAiBotBrainTypes()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// TODO: Returns `any` in the node server
|
||||
return new {
|
||||
pmc = _pmcConfig.PmcType,
|
||||
assault = _botConfig.AssaultBrainType,
|
||||
playerScav = _botConfig.PlayerScavBrainType,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Core.Models.Eft.Dialog;
|
||||
using Core.Models.Eft.HttpResponse;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
|
||||
namespace Core.Controllers;
|
||||
@@ -14,17 +15,23 @@ public class DialogueController
|
||||
{
|
||||
private readonly DialogueHelper _dialogueHelper;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly SaveServer _saveServer;
|
||||
private readonly List<IDialogueChatBot> _dialogueChatBots;
|
||||
private readonly CoreConfig _coreConfig;
|
||||
|
||||
public DialogueController(
|
||||
DialogueHelper dialogueHelper,
|
||||
ProfileHelper profileHelper,
|
||||
ConfigServer configServer,
|
||||
SaveServer saveServer)
|
||||
{
|
||||
_dialogueHelper = dialogueHelper;
|
||||
_profileHelper = profileHelper;
|
||||
_configServer = configServer;
|
||||
_saveServer = saveServer;
|
||||
|
||||
_coreConfig = _configServer.GetConfig<CoreConfig>(ConfigTypes.CORE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +61,7 @@ public class DialogueController
|
||||
public GetFriendListDataResponse GetFriendList(string sessionId)
|
||||
{
|
||||
// Add all chatbots to the friends list
|
||||
var friends = _dialogueChatBots.Select((bot) => bot.GetChatBot()).ToList();
|
||||
var friends = GetActiveChatBots();
|
||||
|
||||
// Add any friends the user has after the chatbots
|
||||
var profile = _profileHelper.GetFullProfile(sessionId);
|
||||
@@ -82,6 +89,22 @@ public class DialogueController
|
||||
};
|
||||
}
|
||||
|
||||
private List<UserDialogInfo> GetActiveChatBots()
|
||||
{
|
||||
var activeBots = new List<UserDialogInfo>();
|
||||
|
||||
var chatBotConfig = _coreConfig.Features.ChatbotFeatures;
|
||||
foreach (var bot in _dialogueChatBots)
|
||||
{
|
||||
var botData = bot.GetChatBot();
|
||||
if (chatBotConfig.EnabledBots.ContainsKey(botData.Id)) {
|
||||
activeBots.Add(botData);
|
||||
}
|
||||
}
|
||||
|
||||
return activeBots;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/list
|
||||
/// Create array holding trader dialogs and mail interactions with player
|
||||
|
||||
@@ -1,9 +1,192 @@
|
||||
using Core.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Routers;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
[Injectable]
|
||||
public class HideoutController
|
||||
{
|
||||
// TODO
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly TimeUtil _timeUtil;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly InventoryHelper _inventoryHelper;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly SaveServer _saveServer;
|
||||
private readonly PlayerService _playerService;
|
||||
private readonly PresetHelper _presetHelper;
|
||||
private readonly PaymentHelper _paymentHelper;
|
||||
private readonly EventOutputHolder _eventOutputHolder;
|
||||
private readonly HttpResponseUtil _httpResponseUtil;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly HideoutHelper _hideoutHelper;
|
||||
private readonly ScavCaseRewardGenerator _scavCaseRewardGenerator;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly ProfileActivityService _profileActivityService;
|
||||
private readonly FenceService _fenceService;
|
||||
private readonly CircleOfCultistService _circleOfCultistService;
|
||||
private readonly ICloner _cloner;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly HideoutConfig _hideoutConfig;
|
||||
|
||||
public HideoutController(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
TimeUtil timeUtil,
|
||||
DatabaseService databaseService,
|
||||
RandomUtil randomUtil,
|
||||
InventoryHelper inventoryHelper,
|
||||
ItemHelper itemHelper,
|
||||
SaveServer saveServer,
|
||||
PlayerService playerService,
|
||||
PresetHelper presetHelper,
|
||||
PaymentHelper paymentHelper,
|
||||
EventOutputHolder eventOutputHolder,
|
||||
HttpResponseUtil httpResponseUtil,
|
||||
ProfileHelper profileHelper,
|
||||
HideoutHelper hideoutHelper,
|
||||
ScavCaseRewardGenerator scavCaseRewardGenerator,
|
||||
LocalisationService localisationService,
|
||||
ProfileActivityService profileActivityService,
|
||||
FenceService fenceService,
|
||||
CircleOfCultistService circleOfCultistService,
|
||||
ICloner cloner,
|
||||
ConfigServer configServer)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_timeUtil = timeUtil;
|
||||
_databaseService = databaseService;
|
||||
_randomUtil = randomUtil;
|
||||
_inventoryHelper = inventoryHelper;
|
||||
_itemHelper = itemHelper;
|
||||
_saveServer = saveServer;
|
||||
_playerService = playerService;
|
||||
_presetHelper = presetHelper;
|
||||
_paymentHelper = paymentHelper;
|
||||
_eventOutputHolder = eventOutputHolder;
|
||||
_httpResponseUtil = httpResponseUtil;
|
||||
_profileHelper = profileHelper;
|
||||
_hideoutHelper = hideoutHelper;
|
||||
_scavCaseRewardGenerator = scavCaseRewardGenerator;
|
||||
_localisationService = localisationService;
|
||||
_profileActivityService = profileActivityService;
|
||||
_fenceService = fenceService;
|
||||
_circleOfCultistService = circleOfCultistService;
|
||||
_cloner = cloner;
|
||||
_configServer = configServer;
|
||||
|
||||
_hideoutConfig = _configServer.GetConfig<HideoutConfig>(ConfigTypes.HIDEOUT);
|
||||
}
|
||||
|
||||
public void StartUpgrade(PmcData pmcData, HideoutUpgradeRequestData info, string sessionId, ItemEventRouterResponse output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData request, string sessionId, ItemEventRouterResponse output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void HandleQTEEventOutcome(string sessionId, PmcData pmcData, HandleQTEEventRequestData request, ItemEventRouterResponse output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RecordShootingRangePoints(string sessionId, PmcData pmcData, RecordShootingRangePoints request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse ImproveArea(string sessionId, PmcData pmcData, HideoutImproveAreaRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse CancelProduction(string sessionId, PmcData pmcData, HideoutImproveAreaRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse CicleOfCultistProductionStart(string sessionId, PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse HideoutDeleteProductionCommand(string sessionId, PmcData pmcData, HideoutDeleteProductionRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse HideoutCustomizationApply(string sessionId, PmcData pmcData, HideoutCustomizationApplyRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutCustomizationSetMannequinPose event
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
/// <param name="request">Client request</param>
|
||||
/// <returns></returns>
|
||||
public ItemEventRouterResponse HideoutCustomizationSetMannequinPose(string sessionId, PmcData pmcData, HideoutCustomizationSetMannequinPoseRequest request)
|
||||
{
|
||||
foreach (var poseKvP in request.Poses)
|
||||
{
|
||||
pmcData.Hideout.MannequinPoses[poseKvP.Key] = poseKvP.Value;
|
||||
}
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public class TraderController
|
||||
_traderHelper.LevelUp(trader.Key, pmcData);
|
||||
}
|
||||
|
||||
// traders.Sort((a, b) => SortByTraderId(a, b));
|
||||
traders.Sort((a, b) => SortByTraderId(a, b));
|
||||
return traders;
|
||||
}
|
||||
|
||||
@@ -185,11 +185,9 @@ public class TraderController
|
||||
/// <param name="traderA">First trader to compare</param>
|
||||
/// <param name="traderB">Second trader to compare</param>
|
||||
/// <returns>1,-1 or 0</returns>
|
||||
private int SortByTraderId(
|
||||
TraderBase traderA,
|
||||
TraderBase traderB)
|
||||
private int SortByTraderId(TraderBase traderA, TraderBase traderB)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return string.Compare(traderA.Id, traderB.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+546
-37
@@ -1,4 +1,5 @@
|
||||
using Core.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -6,7 +7,9 @@ using Core.Models.Enums;
|
||||
using Core.Models.Enums.RaidSettings;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using BodyPart = Core.Models.Eft.Common.Tables.BodyPart;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
@@ -17,20 +20,65 @@ namespace Core.Generators;
|
||||
public class BotGenerator
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly TimeUtil _timeUtil;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly BotInventoryGenerator _botInventoryGenerator;
|
||||
private readonly BotLevelGenerator _botLevelGenerator;
|
||||
private readonly BotEquipmentFilterService _botEquipmentFilterService;
|
||||
private readonly WeightedRandomHelper _weightedRandomHelper;
|
||||
private readonly BotHelper _botHelper;
|
||||
private readonly BotGeneratorHelper _botGeneratorHelper;
|
||||
private readonly SeasonalEventService _seasonalEventService;
|
||||
private readonly ItemFilterService _itemFilterService;
|
||||
private readonly BotNameService _botNameService;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly ICloner _cloner;
|
||||
private BotConfig _botConfig;
|
||||
private PmcConfig _pmcConfig;
|
||||
|
||||
public BotGenerator(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
RandomUtil randomUtil,
|
||||
TimeUtil timeUtil,
|
||||
ProfileHelper profileHelper,
|
||||
DatabaseService databaseService,
|
||||
BotInventoryGenerator botInventoryGenerator,
|
||||
BotLevelGenerator botLevelGenerator,
|
||||
BotEquipmentFilterService botEquipmentFilterService,
|
||||
WeightedRandomHelper weightedRandomHelper,
|
||||
BotHelper botHelper,
|
||||
BotGeneratorHelper botGeneratorHelper,
|
||||
SeasonalEventService seasonalEventService,
|
||||
ItemFilterService itemFilterService,
|
||||
BotNameService botNameService,
|
||||
ConfigServer configServer,
|
||||
ICloner cloner
|
||||
)
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_randomUtil = randomUtil;
|
||||
_timeUtil = timeUtil;
|
||||
_profileHelper = profileHelper;
|
||||
_databaseService = databaseService;
|
||||
_botInventoryGenerator = botInventoryGenerator;
|
||||
_botLevelGenerator = botLevelGenerator;
|
||||
_botEquipmentFilterService = botEquipmentFilterService;
|
||||
_weightedRandomHelper = weightedRandomHelper;
|
||||
_botHelper = botHelper;
|
||||
_botGeneratorHelper = botGeneratorHelper;
|
||||
_seasonalEventService = seasonalEventService;
|
||||
_itemFilterService = itemFilterService;
|
||||
_botNameService = botNameService;
|
||||
_configServer = configServer;
|
||||
_cloner = cloner;
|
||||
|
||||
_botConfig = _configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
|
||||
_pmcConfig = _configServer.GetConfig<PmcConfig>(ConfigTypes.PMC);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,7 +97,8 @@ public class BotGenerator
|
||||
bot.Info.Settings.Role = role;
|
||||
bot.Info.Side = "Savage";
|
||||
|
||||
var botGenDetails = new BotGenerationDetails{
|
||||
var botGenDetails = new BotGenerationDetails
|
||||
{
|
||||
IsPmc = false,
|
||||
Side = "Savage",
|
||||
Role = role,
|
||||
@@ -108,7 +157,23 @@ public class BotGenerator
|
||||
/// <returns>constructed bot</returns>
|
||||
public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var preparedBotBase = GetPreparedBotBase(
|
||||
botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided,
|
||||
botGenerationDetails.Side,
|
||||
botGenerationDetails.BotDifficulty
|
||||
);
|
||||
|
||||
// Get raw json data for bot (Cloned)
|
||||
var botRole = botGenerationDetails.IsPmc ?? false
|
||||
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC
|
||||
: botGenerationDetails.Role;
|
||||
var botJsonTemplateClone = _cloner.Clone(_botHelper.GetBotTemplate(botRole));
|
||||
if (botJsonTemplateClone is not null)
|
||||
{
|
||||
_logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
|
||||
}
|
||||
|
||||
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +185,12 @@ public class BotGenerator
|
||||
/// <returns>Cloned bot base</returns>
|
||||
public BotBase GetPreparedBotBase(string botRole, string botSide, string difficulty)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botBaseClone = GetCloneOfBotBase();
|
||||
botBaseClone.Info.Settings.Role = botRole;
|
||||
botBaseClone.Info.Side = botSide;
|
||||
botBaseClone.Info.Settings.BotDifficulty = difficulty;
|
||||
|
||||
return botBaseClone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,35 +210,154 @@ public class BotGenerator
|
||||
/// <param name="botJsonTemplate">Bot template from db/bots/x.json</param>
|
||||
/// <param name="botGenerationDetails">details on how to generate the bot</param>
|
||||
/// <returns>BotBase object</returns>
|
||||
public BotBase GenerateBot(string sessionId, BotBase bot, BotType botJsonTemplate, BotGenerationDetails botGenerationDetails)
|
||||
public BotBase GenerateBot(
|
||||
string sessionId,
|
||||
BotBase bot,
|
||||
BotType botJsonTemplate,
|
||||
BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
_logger.Error("NOT IMPLEMENTED BotGenerator.GenerateBot");
|
||||
|
||||
bot.Inventory.Items = [];
|
||||
var botRoleLowercase = botGenerationDetails.Role.ToLower();
|
||||
var botLevel = _botLevelGenerator.GenerateBotLevel(
|
||||
botJsonTemplate.BotExperience.Level,
|
||||
botGenerationDetails,
|
||||
bot);
|
||||
|
||||
// Only filter bot equipment, never players
|
||||
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false))
|
||||
{
|
||||
_botEquipmentFilterService.FilterBotEquipment(
|
||||
sessionId,
|
||||
botJsonTemplate,
|
||||
botLevel.Level.Value,
|
||||
botGenerationDetails);
|
||||
}
|
||||
|
||||
bot.Info.Nickname = _botNameService.GenerateUniqueBotNickname(
|
||||
botJsonTemplate,
|
||||
botGenerationDetails,
|
||||
botRoleLowercase,
|
||||
_botConfig.BotRolesThatMustHaveUniqueName);
|
||||
bot.Info.LowerNickname = bot.Info.Nickname.ToLower();
|
||||
|
||||
// Only run when generating a 'fake' playerscav, not actual player scav
|
||||
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false) && ShouldSimulatePlayerScav(botRoleLowercase))
|
||||
{
|
||||
_botNameService.AddRandomPmcNameToBotMainProfileNicknameProperty(bot);
|
||||
SetRandomisedGameVersionAndCategory(bot.Info);
|
||||
}
|
||||
|
||||
if (!_seasonalEventService.ChristmasEventEnabled())
|
||||
{
|
||||
// Process all bots EXCEPT gifter, he needs christmas items
|
||||
if (botGenerationDetails.Role != "gifter")
|
||||
{
|
||||
_seasonalEventService.RemoveChristmasItemsFromBotInventory(
|
||||
botJsonTemplate.BotInventory,
|
||||
botGenerationDetails.Role);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveBlacklistedLootFromBotTemplate(botJsonTemplate.BotInventory);
|
||||
|
||||
// Remove hideout data if bot is not a PMC or pscav - match what live sends
|
||||
if (!(botGenerationDetails.IsPmc.GetValueOrDefault(false) || botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)))
|
||||
{
|
||||
bot.Hideout = null;
|
||||
}
|
||||
|
||||
bot.Info.Experience = botLevel.Exp;
|
||||
bot.Info.Level = botLevel.Level;
|
||||
bot.Info.Settings.Experience = GetExperienceRewardForKillByDifficulty(
|
||||
botJsonTemplate.BotExperience.Reward,
|
||||
botGenerationDetails.BotDifficulty,
|
||||
botGenerationDetails.Role);
|
||||
bot.Info.Settings.StandingForKill = GetStandingChangeForKillByDifficulty(
|
||||
botJsonTemplate.BotExperience.StandingForKill,
|
||||
botGenerationDetails.BotDifficulty,
|
||||
botGenerationDetails.Role);
|
||||
bot.Info.Settings.AggressorBonus = GetAgressorBonusByDifficulty(
|
||||
botJsonTemplate.BotExperience.StandingForKill,
|
||||
botGenerationDetails.BotDifficulty,
|
||||
botGenerationDetails.Role);
|
||||
bot.Info.Settings.UseSimpleAnimator = botJsonTemplate.BotExperience.UseSimpleAnimator ?? false;
|
||||
bot.Info.Voice = _weightedRandomHelper.GetWeightedValue(botJsonTemplate.BotAppearance.Voice);
|
||||
bot.Health = GenerateHealth(botJsonTemplate.BotHealth, botGenerationDetails.IsPlayerScav.GetValueOrDefault(false));
|
||||
bot.Skills = GenerateSkills(botJsonTemplate.BotSkills); // TODO: fix bad type, bot jsons store skills in dict, output needs to be array
|
||||
|
||||
if (botGenerationDetails.IsPmc.GetValueOrDefault(false))
|
||||
{
|
||||
bot.Info.IsStreamerModeAvailable = true; // Set to true so client patches can pick it up later - client sometimes alters botrole to assaultGroup
|
||||
SetRandomisedGameVersionAndCategory(bot.Info);
|
||||
if (bot.Info.GameVersion == GameEditions.UNHEARD)
|
||||
{
|
||||
AddAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
// Add drip
|
||||
SetBotAppearance(bot, botJsonTemplate.BotAppearance, botGenerationDetails);
|
||||
|
||||
// Filter out blacklisted gear from the base template
|
||||
FilterBlacklistedGear(botJsonTemplate, botGenerationDetails);
|
||||
|
||||
bot.Inventory = _botInventoryGenerator.GenerateInventory(
|
||||
sessionId,
|
||||
botJsonTemplate,
|
||||
botRoleLowercase,
|
||||
botGenerationDetails.IsPmc.GetValueOrDefault(false),
|
||||
botLevel.Level.Value,
|
||||
bot.Info.GameVersion);
|
||||
|
||||
if (_botConfig.BotRolesWithDogTags.Contains(botRoleLowercase))
|
||||
{
|
||||
AddDogtagToBot(bot);
|
||||
}
|
||||
|
||||
// Generate new bot ID
|
||||
AddIdsToBot(bot);
|
||||
|
||||
// Generate new inventory ID
|
||||
GenerateInventoryId(bot);
|
||||
|
||||
// Set role back to originally requested now its been generated
|
||||
if (botGenerationDetails.EventRole is not null)
|
||||
{
|
||||
bot.Info.Settings.Role = botGenerationDetails.EventRole;
|
||||
}
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should this bot have a name like "name (Pmc Name)" and be alterd by client patch to be hostile to player
|
||||
/// Should this bot have a name like "name (Pmc Name)" and be altered by client patch to be hostile to player
|
||||
/// </summary>
|
||||
/// <param name="botRole">Role bot has</param>
|
||||
/// <returns>True if name should be simulated pscav</returns>
|
||||
public bool ShouldSimulatePlayerScav(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return botRole == "assault" && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get exp for kill by bot difficulty
|
||||
/// </summary>
|
||||
/// <param name="experience">Dict of difficulties and experience</param>
|
||||
/// <param name="experiences">Dict of difficulties and experience</param>
|
||||
/// <param name="botDifficulty">the killed bots difficulty</param>
|
||||
/// <param name="role">Role of bot (optional, used for error logging)</param>
|
||||
/// <returns>Experience for kill</returns>
|
||||
public int GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax> experience, string botDifficulty, string role)
|
||||
public double GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax> experiences, string botDifficulty, string role)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var result = experiences[botDifficulty.ToLower()];
|
||||
if (result is null)
|
||||
{
|
||||
_logger.Debug("Unable to find experience for kill value for: ${ role} ${ botDifficulty}, falling back to `normal`");
|
||||
|
||||
return _randomUtil.GetDouble(experiences["normal"].Min.Value, experiences["normal"].Max.Value);
|
||||
}
|
||||
|
||||
return _randomUtil.GetDouble(result.Min.Value, result.Max.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,9 +367,16 @@ public class BotGenerator
|
||||
/// <param name="botDifficulty">Difficulty of bot to look up</param>
|
||||
/// <param name="role">Role of bot (optional, used for error logging)</param>
|
||||
/// <returns>Standing change value</returns>
|
||||
public int GetStandingChangeForKillByDifficulty(Dictionary<string, int> standingForKill, string botDifficulty, string role)
|
||||
public double GetStandingChangeForKillByDifficulty(Dictionary<string, double> standingsForKill, string botDifficulty, string role)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
_logger.Warning($"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`");
|
||||
|
||||
return standingsForKill["normal"];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,9 +386,16 @@ public class BotGenerator
|
||||
/// <param name="botDifficulty">Difficulty of bot to look up</param>
|
||||
/// <param name="role">Role of bot (optional, used for error logging)</param>
|
||||
/// <returns>Standing change value</returns>
|
||||
public int GetAgressorBonusByDifficulty(Dictionary<string, int> aggressorBonus, string botDifficulty, string role)
|
||||
public double GetAgressorBonusByDifficulty(Dictionary<string, double> aggressorBonuses, string botDifficulty, string role)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
_logger.Warning($"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`");
|
||||
|
||||
return aggressorBonuses["normal"];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,7 +405,26 @@ public class BotGenerator
|
||||
/// <param name="botGenerationDetails">Generation details of bot</param>
|
||||
public void FilterBlacklistedGear(BotType botJsonTemplate, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var blacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
|
||||
_botGeneratorHelper.GetBotEquipmentRole(botGenerationDetails.Role),
|
||||
botGenerationDetails.PlayerLevel.GetValueOrDefault(1));
|
||||
|
||||
if (blacklist?.Gear is null)
|
||||
{
|
||||
// Nothing to filter by
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var equipmentKvP in blacklist.Gear)
|
||||
{
|
||||
var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentKvP.Key];
|
||||
|
||||
foreach (var blacklistedTpl in equipmentKvP.Value)
|
||||
{
|
||||
// Set weighting to 0, will never be picked
|
||||
equipmentDict[blacklistedTpl] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,7 +433,10 @@ public class BotGenerator
|
||||
/// <param name="botJsonTemplate">Bot data to adjust</param>
|
||||
public void AddAdditionalPocketLootWeightsForUnheardBot(BotType botJsonTemplate)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Adjust pocket loot weights to allow for 5 or 6 items
|
||||
var pocketWeights = botJsonTemplate.BotGeneration.Items["pocketLoot"].Weights;
|
||||
pocketWeights["5"] = 1;
|
||||
pocketWeights["6"] = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -220,7 +445,37 @@ public class BotGenerator
|
||||
/// <param name="botInventory">Bot to filter</param>
|
||||
public void RemoveBlacklistedLootFromBotTemplate(BotTypeInventory botInventory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
List<string> lootContainersToFilter = ["Backpack", "Pockets", "TacticalVest"];
|
||||
var props = botInventory.Items.GetType().GetProperties();
|
||||
|
||||
// Remove blacklisted loot from loot containers
|
||||
foreach (var lootContainerKey in lootContainersToFilter)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => x.Name.ToLower() == lootContainerKey.ToLower());
|
||||
var propValue = (Dictionary<string, double>)prop.GetValue(botInventory.Items);
|
||||
|
||||
// No container, skip
|
||||
if (propValue?.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<string> tplsToRemove = [];
|
||||
foreach (var item in propValue)
|
||||
{
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(item.Key))
|
||||
{
|
||||
tplsToRemove.Add(item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var blacklistedTplToRemove in tplsToRemove)
|
||||
{
|
||||
propValue.Remove(blacklistedTplToRemove);
|
||||
}
|
||||
|
||||
prop.SetValue(botInventory.Items, propValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,7 +486,19 @@ public class BotGenerator
|
||||
/// <param name="botGenerationDetails">Generation details</param>
|
||||
public void SetBotAppearance(BotBase bot, Appearance appearance, BotGenerationDetails botGenerationDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Choose random values by weight
|
||||
bot.Customization.Head = _weightedRandomHelper.GetWeightedValue<string>(appearance.Head);
|
||||
bot.Customization.Feet = _weightedRandomHelper.GetWeightedValue<string>(appearance.Feet);
|
||||
bot.Customization.Body = _weightedRandomHelper.GetWeightedValue<string>(appearance.Body);
|
||||
|
||||
var bodyGlobalDictDb = _databaseService.GetGlobals().Configuration.Customization.Body;
|
||||
var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body];
|
||||
|
||||
// Some bodies have matching hands, look up body to see if this is the case
|
||||
var chosenBody = bodyGlobalDictDb[chosenBodyTemplate?.Name.Trim()];
|
||||
bot.Customization.Hands = chosenBody?.IsNotRandom ?? false
|
||||
? chosenBody.Hands // Has fixed hands for chosen body, update to match
|
||||
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -240,7 +507,8 @@ public class BotGenerator
|
||||
/// <param name="output">Generated bot array, ready to send to client</param>
|
||||
public void LogPmcGeneratedCount(List<BotBase> output)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var pmcCount = output.Aggregate(0, (acc, cur) => { return cur.Info.Side == "Bear" || cur.Info.Side == "Usec" ? acc + 1 : acc; });
|
||||
_logger.Debug($"Generated {output.Count} total bots. Replaced ${pmcCount} with PMCs");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,9 +517,107 @@ public class BotGenerator
|
||||
/// <param name="healthObj">health object from bot json</param>
|
||||
/// <param name="playerScav">Is a pscav bot being generated</param>
|
||||
/// <returns>Health object</returns>
|
||||
public Health GenerateHealth(Health healthObj, bool playerScav = false)
|
||||
public BotBaseHealth GenerateHealth(BotTypeHealth healthObj, bool playerScav = false)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var bodyParts = playerScav
|
||||
? GetLowestHpBody(healthObj.BodyParts)
|
||||
: _randomUtil.GetArrayValue(healthObj.BodyParts);
|
||||
|
||||
BotBaseHealth health = new()
|
||||
{
|
||||
Hydration = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Hydration.Min, (int)healthObj.Hydration.Max),
|
||||
Maximum = healthObj.Hydration.Max
|
||||
},
|
||||
Energy = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Energy.Min, (int)healthObj.Energy.Max),
|
||||
Maximum = healthObj.Energy.Max
|
||||
},
|
||||
Temperature = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)healthObj.Temperature.Min, (int)healthObj.Temperature.Max),
|
||||
Maximum = healthObj.Temperature.Max
|
||||
},
|
||||
BodyParts = new Dictionary<string, BodyPartHealth>()
|
||||
{
|
||||
{
|
||||
"Head", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Head.Min, (int)bodyParts.Head.Max),
|
||||
Maximum = Math.Round(bodyParts.Head.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Chest", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Chest.Min, (int)bodyParts.Chest.Max),
|
||||
Maximum = Math.Round(bodyParts.Chest.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Stomach", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.Stomach.Min, (int)bodyParts.Stomach.Max),
|
||||
Maximum = Math.Round(bodyParts.Stomach.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LeftArm", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.LeftArm.Min, (int)bodyParts.LeftArm.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftArm.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RightArm", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.RightArm.Min, (int)bodyParts.RightArm.Max),
|
||||
Maximum = Math.Round(bodyParts.RightArm.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LeftLeg", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.LeftLeg.Min, (int)bodyParts.LeftLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.LeftLeg.Max ?? 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RightLeg", new BodyPartHealth
|
||||
{
|
||||
Health = new()
|
||||
{
|
||||
Current = _randomUtil.GetInt((int)bodyParts.RightLeg.Min, (int)bodyParts.RightLeg.Max),
|
||||
Maximum = Math.Round(bodyParts.RightLeg.Max ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
UpdateTime = _timeUtil.GetTimeStamp(),
|
||||
Immortal = false
|
||||
};
|
||||
|
||||
return health;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,9 +625,33 @@ public class BotGenerator
|
||||
/// </summary>
|
||||
/// <param name="bodies">Body parts to sum up</param>
|
||||
/// <returns>Lowest hp collection</returns>
|
||||
public BodyPart? GetLowestHpBody(List<BodyPart> bodies) // TODO: there are two types of body parts
|
||||
public BodyPart? GetLowestHpBody(List<BodyPart> bodies)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (bodies.Count == 0)
|
||||
return null;
|
||||
|
||||
BodyPart result = new();
|
||||
var props = result.GetType().GetProperties();
|
||||
double? currentHighest = double.MaxValue;
|
||||
foreach (var bodyPart in bodies)
|
||||
{
|
||||
double? hpTotal = 0;
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var value = (MinMax)prop.GetValue(bodyPart);
|
||||
hpTotal += value.Max;
|
||||
}
|
||||
|
||||
if (hpTotal < currentHighest)
|
||||
{
|
||||
// Found collection with lower value that previous, use it
|
||||
currentHighest = hpTotal;
|
||||
result = bodyPart;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -269,9 +659,16 @@ public class BotGenerator
|
||||
/// </summary>
|
||||
/// <param name="botSkills">Skills that should have their progress value randomised</param>
|
||||
/// <returns>Skills</returns>
|
||||
public Skills GenerateSkills(BaseJsonSkills botSkills)
|
||||
public Skills GenerateSkills(BotDbSkills botSkills)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var skillsToReturn = new Skills
|
||||
{
|
||||
Common = GetSkillsWithRandomisedProgressValue(botSkills.Common, true),
|
||||
Mastering = GetSkillsWithRandomisedProgressValue(botSkills.Mastering, false),
|
||||
Points = 0
|
||||
};
|
||||
|
||||
return skillsToReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,19 +677,49 @@ public class BotGenerator
|
||||
/// <param name="skills">Skills to randomise</param>
|
||||
/// <param name="isCommonSkills">Are the skills 'common' skills</param>
|
||||
/// <returns>Skills with randomised progress values as an array</returns>
|
||||
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, BaseSkill> skills, bool isCommonSkills)
|
||||
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, MinMax>? skills, bool isCommonSkills)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (skills is null)
|
||||
return [];
|
||||
|
||||
return skills.Select(kvp =>
|
||||
{
|
||||
// Get skill from dict, skip if not found
|
||||
var skill = kvp.Value;
|
||||
if (skill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All skills have id and progress props
|
||||
var skillToAdd = new BaseSkill
|
||||
{
|
||||
Id = kvp.Key,
|
||||
Progress = _randomUtil.GetInt((int)skill.Min, (int)skill.Max)
|
||||
};
|
||||
|
||||
// Common skills have additional props
|
||||
if (isCommonSkills)
|
||||
{
|
||||
((Common)skillToAdd).PointsEarnedDuringSession = 0;
|
||||
((Common)skillToAdd).LastAccess = 0;
|
||||
}
|
||||
|
||||
return skillToAdd;
|
||||
}).Where(baseSkill => baseSkill != null).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an id+aid for a bot and apply
|
||||
/// </summary>
|
||||
/// <param name="bot">bot to update</param>
|
||||
/// <returns>updated IBotBase object</returns> // TODO: Node server claims this in summary but is void
|
||||
/// <returns></returns>
|
||||
public void AddIdsToBot(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var botId = _hashUtil.Generate();
|
||||
|
||||
bot.Id = botId;
|
||||
bot.Aid = _hashUtil.GenerateAccountId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -302,7 +729,30 @@ public class BotGenerator
|
||||
/// <param name="profile">Profile to update</param>
|
||||
public void GenerateInventoryId(BotBase profile)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var newInventoryItemId = _hashUtil.Generate();
|
||||
|
||||
foreach (var item in profile.Inventory.Items) {
|
||||
// Root item found, update its _id value to newly generated id
|
||||
if (item.Template == ItemTpl.INVENTORY_DEFAULT) {
|
||||
item.Id = newInventoryItemId;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Optimisation - skip items without a parentId
|
||||
// They are never linked to root inventory item + we already handled root item above
|
||||
if (item.ParentId is null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item is a child of root inventory item, update its parentId value to newly generated id
|
||||
if (item.ParentId == profile.Inventory.Equipment) {
|
||||
item.ParentId = newInventoryItemId;
|
||||
}
|
||||
}
|
||||
|
||||
// Update inventory equipment id to new one we generated
|
||||
profile.Inventory.Equipment = newInventoryItemId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -312,19 +762,57 @@ public class BotGenerator
|
||||
/// </summary>
|
||||
/// <param name="botInfo">bot info object to update</param>
|
||||
/// <returns>Chosen game version</returns>
|
||||
public string SetRandomisedGameVersionAndCategory(Info botInfo) // TODO: there are two types of Info
|
||||
public string SetRandomisedGameVersionAndCategory(Info botInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Special case
|
||||
if (botInfo.Nickname?.ToLower() == "nikita") {
|
||||
botInfo.GameVersion = GameEditions.UNHEARD;
|
||||
botInfo.MemberCategory = MemberCategory.DEVELOPER;
|
||||
|
||||
return botInfo.GameVersion;
|
||||
}
|
||||
|
||||
// Choose random weighted game version for bot
|
||||
botInfo.GameVersion = _weightedRandomHelper.GetWeightedValue(_pmcConfig.GameVersionWeight);
|
||||
|
||||
// Choose appropriate member category value
|
||||
switch (botInfo.GameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
botInfo.MemberCategory = MemberCategory.UNIQUE_ID;
|
||||
break;
|
||||
case GameEditions.UNHEARD:
|
||||
botInfo.MemberCategory = MemberCategory.UNHEARD;
|
||||
break;
|
||||
default:
|
||||
// Everyone else gets a weighted randomised category
|
||||
botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue<MemberCategory>(_pmcConfig.AccountTypeWeight);
|
||||
break;
|
||||
}
|
||||
|
||||
// Ensure selected category matches
|
||||
botInfo.SelectedMemberCategory = botInfo.MemberCategory;
|
||||
|
||||
return botInfo.GameVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a side-specific (usec/bear) dogtag item to a bots inventory
|
||||
/// </summary>
|
||||
/// <param name="bot">bot to add dogtag to</param>
|
||||
/// <returns>Bot with dogtag added</returns> // TODO: Node server claims this in summary but is void
|
||||
/// <returns></returns>
|
||||
public void AddDogtagToBot(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Item inventoryItem = new () {
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion),
|
||||
ParentId = bot.Inventory.Equipment,
|
||||
SlotId = "Dogtag",
|
||||
Upd = new () {
|
||||
SpawnedInSession = true,
|
||||
},
|
||||
};
|
||||
|
||||
bot.Inventory.Items.Add(inventoryItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -335,7 +823,25 @@ public class BotGenerator
|
||||
/// <returns>item tpl</returns>
|
||||
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (side.ToLower() == "usec") {
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC_TUE;
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC;
|
||||
}
|
||||
}
|
||||
|
||||
switch (gameVersion) {
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_EOD;
|
||||
case GameEditions.UNHEARD:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR_TUE;
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_BEAR;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -344,6 +850,9 @@ public class BotGenerator
|
||||
/// <param name="bot">Pmc object to adjust</param>
|
||||
public void SetPmcPocketsByGameVersion(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (bot.Info.GameVersion == GameEditions.UNHEARD) {
|
||||
var pockets = bot.Inventory.Items.FirstOrDefault((item) => item.SlotId == "Pockets");
|
||||
pockets.Template = ItemTpl.POCKETS_1X4_TUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,55 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Match;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using Equipment = Core.Models.Eft.Common.Tables.Equipment;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class BotInventoryGenerator
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly BotLootGenerator _botLootGenerator;
|
||||
private readonly BotHelper _botHelper;
|
||||
private readonly BotGeneratorHelper _botGeneratorHelper;
|
||||
private readonly WeatherHelper _weatherHelper;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly ApplicationContext _applicationContext;
|
||||
private BotConfig _botConfig;
|
||||
|
||||
public BotInventoryGenerator()
|
||||
public BotInventoryGenerator(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
BotLootGenerator botLootGenerator,
|
||||
BotHelper botHelper,
|
||||
BotGeneratorHelper botGeneratorHelper,
|
||||
WeatherHelper weatherHelper,
|
||||
ProfileHelper profileHelper,
|
||||
ConfigServer configServer,
|
||||
ApplicationContext applicationContext
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_botLootGenerator = botLootGenerator;
|
||||
_botHelper = botHelper;
|
||||
_botGeneratorHelper = botGeneratorHelper;
|
||||
_weatherHelper = weatherHelper;
|
||||
_profileHelper = profileHelper;
|
||||
_configServer = configServer;
|
||||
_applicationContext = applicationContext;
|
||||
|
||||
_botConfig = _configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -26,9 +62,45 @@ public class BotInventoryGenerator
|
||||
/// <param name="botLevel">Level of bot being generated</param>
|
||||
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
|
||||
/// <returns>PmcInventory object with equipment/weapons/loot</returns>
|
||||
public BotBaseInventory generateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion)
|
||||
public BotBaseInventory GenerateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var templateInventory = botJsonTemplate.BotInventory;
|
||||
var wornItemChances = botJsonTemplate.BotChances;
|
||||
var itemGenerationLimitsMinMax = botJsonTemplate.BotGeneration;
|
||||
|
||||
// Generate base inventory with no items
|
||||
var botInventory = GenerateInventoryBase();
|
||||
|
||||
// Get generated raid details bot will be spawned in
|
||||
var raidConfig = _applicationContext
|
||||
.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.GetValue<GetRaidConfigurationRequestData>();
|
||||
|
||||
GenerateAndAddEquipmentToBot(
|
||||
sessionId,
|
||||
templateInventory,
|
||||
wornItemChances,
|
||||
botRole,
|
||||
botInventory,
|
||||
botLevel,
|
||||
chosenGameVersion,
|
||||
raidConfig);
|
||||
|
||||
// Roll weapon spawns (primary/secondary/holster) and generate a weapon for each roll that passed
|
||||
GenerateAndAddWeaponsToBot(
|
||||
templateInventory,
|
||||
wornItemChances,
|
||||
sessionId,
|
||||
botInventory,
|
||||
botRole,
|
||||
isPmc,
|
||||
itemGenerationLimitsMinMax,
|
||||
botLevel);
|
||||
|
||||
// Pick loot and add to bots containers (rig/backpack/pockets/secure)
|
||||
_botLootGenerator.GenerateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel);
|
||||
|
||||
return botInventory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +109,31 @@ public class BotInventoryGenerator
|
||||
/// <returns>PmcInventory object</returns>
|
||||
public BotBaseInventory GenerateInventoryBase()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var equipmentId = _hashUtil.Generate();
|
||||
var stashId = _hashUtil.Generate();
|
||||
var questRaidItemsId = _hashUtil.Generate();
|
||||
var questStashItemsId = _hashUtil.Generate();
|
||||
var sortingTableId = _hashUtil.Generate();
|
||||
|
||||
return new BotBaseInventory{
|
||||
Items =
|
||||
[
|
||||
new() { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
|
||||
new() { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 },
|
||||
new() { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID },
|
||||
new() { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE },
|
||||
new() { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE }
|
||||
],
|
||||
Equipment = equipmentId,
|
||||
Stash = stashId,
|
||||
QuestRaidItems = questRaidItemsId,
|
||||
QuestStashItems = questStashItemsId,
|
||||
SortingTable = sortingTableId,
|
||||
HideoutAreaStashes = { },
|
||||
FastPanel = { },
|
||||
FavoriteItems = [],
|
||||
HideoutCustomizationStashId = "",
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,10 +147,168 @@ public class BotInventoryGenerator
|
||||
/// <param name="botLevel">Level of bot</param>
|
||||
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
|
||||
/// <param name="raidConfig">RadiConfig</param>
|
||||
public void GenerateAndAddEquipmentToBot(string sessionId, BotBaseInventory templateInventory, Chances wornItemChances, string botRole,
|
||||
public void GenerateAndAddEquipmentToBot(string sessionId, BotTypeInventory templateInventory, Chances wornItemChances, string botRole,
|
||||
BotBaseInventory botInventory, int botLevel, string chosenGameVersion, GetRaidConfigurationRequestData raidConfig)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// These will be handled later
|
||||
var excludedSlots = new List<EquipmentSlots>(){
|
||||
EquipmentSlots.Pockets,
|
||||
EquipmentSlots.FirstPrimaryWeapon,
|
||||
EquipmentSlots.SecondPrimaryWeapon,
|
||||
EquipmentSlots.Holster,
|
||||
EquipmentSlots.ArmorVest,
|
||||
EquipmentSlots.TacticalVest,
|
||||
EquipmentSlots.FaceCover,
|
||||
EquipmentSlots.Headwear,
|
||||
EquipmentSlots.Earpiece
|
||||
};
|
||||
|
||||
_botConfig.Equipment.TryGetValue(_botGeneratorHelper.GetBotEquipmentRole(botRole), out var botEquipConfig);
|
||||
var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig);
|
||||
|
||||
// Apply nighttime changes if its nighttime + there's changes to make
|
||||
if (
|
||||
randomistionDetails?.NighttimeChanges is not null &&
|
||||
raidConfig is not null &&
|
||||
_weatherHelper.IsNightTime(raidConfig.TimeVariant)
|
||||
)
|
||||
{
|
||||
foreach (var equipmentSlotKvP in (randomistionDetails.NighttimeChanges.EquipmentModsModifiers)) {
|
||||
// Never let mod chance go outside of 0 - 100
|
||||
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min(
|
||||
Math.Max( randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Get profile of player generating bots, we use their level later on
|
||||
var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
|
||||
var botEquipmentRole = _botGeneratorHelper.GetBotEquipmentRole(botRole);
|
||||
|
||||
|
||||
// Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts
|
||||
// e.g. ArmorVest should be generated after TactivalVest
|
||||
// or FACE_COVER before HEADWEAR
|
||||
foreach (var equipmentSlotKvP in templateInventory.Equipment) {
|
||||
// Skip some slots as they need to be done in a specific order + with specific parameter values
|
||||
// e.g. Weapons
|
||||
if (excludedSlots.Contains(equipmentSlotKvP.Key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = equipmentSlotKvP.Key,
|
||||
RootEquipmentPool = equipmentSlotKvP.Value,
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate below in specific order
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Pockets,
|
||||
// Unheard profiles have unique sized pockets, TODO - handle this somewhere else in a better way
|
||||
RootEquipmentPool =
|
||||
chosenGameVersion == GameEditions.UNHEARD
|
||||
? new Dictionary<string, double>{ [ItemTpl.POCKETS_1X4_TUE] = 1 }
|
||||
: templateInventory.Equipment[EquipmentSlots.Pockets],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData{ Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE],
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.FaceCover,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.FaceCover],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Headwear,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Headwear],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
GenerateEquipment(new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Earpiece,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
var hasArmorVest = GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.ArmorVest,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.ArmorVest],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
|
||||
// Bot has no armor vest and flagged to be forced to wear armored rig in this event
|
||||
if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest) {
|
||||
// Filter rigs down to only those with armor
|
||||
FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
|
||||
// Optimisation - Remove armored rigs from pool
|
||||
if (hasArmorVest) {
|
||||
// Filter rigs down to only those with armor
|
||||
FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole);
|
||||
}
|
||||
|
||||
// Bot is flagged as always needing a vest
|
||||
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest) {
|
||||
wornItemChances.EquipmentChances["TacticalVest"] = 100;
|
||||
}
|
||||
|
||||
GenerateEquipment( new GenerateEquipmentProperties
|
||||
{
|
||||
RootEquipmentSlot = EquipmentSlots.Earpiece,
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
GeneratingPlayerLevel = pmcProfile.Info.Level,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,7 +316,12 @@ public class BotInventoryGenerator
|
||||
/// </summary>
|
||||
/// <param name="templateEquipment">Equpiment to filter TacticalVest of</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
public void FilterRigsToThoseWithProtection(Equipment templateEquipment, string botRole)
|
||||
public void FilterRigsToThoseWithProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole, bool allowEmptyResult = true)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -85,7 +344,8 @@ public class BotInventoryGenerator
|
||||
/// <returns>true when item added</returns>
|
||||
public bool GenerateEquipment(GenerateEquipmentProperties settings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_logger.Error("NOT IMPLEMENTED - GenerateEquipment");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +370,7 @@ public class BotInventoryGenerator
|
||||
/// <param name="isPmc">Is the bot being generated as a pmc</param>
|
||||
/// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param>
|
||||
/// <param name="botLevel">level of bot having weapon generated</param>
|
||||
public void GenerateAndAddWeaponsToBot(BotBaseInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory,
|
||||
public void GenerateAndAddWeaponsToBot(BotTypeInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory,
|
||||
string botRole,
|
||||
bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel)
|
||||
{
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Bot;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class BotLevelGenerator
|
||||
{
|
||||
public BotLevelGenerator()
|
||||
private readonly ILogger _logger;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly MathUtil _mathUtil;
|
||||
private readonly DatabaseService _databaseService;
|
||||
|
||||
public BotLevelGenerator(
|
||||
ILogger logger,
|
||||
RandomUtil randomUtil,
|
||||
MathUtil mathUtil,
|
||||
DatabaseService databaseService)
|
||||
{
|
||||
_logger = logger;
|
||||
_randomUtil = randomUtil;
|
||||
_mathUtil = mathUtil;
|
||||
_databaseService = databaseService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -22,12 +38,30 @@ public class BotLevelGenerator
|
||||
/// <returns>IRandomisedBotLevelResult object</returns>
|
||||
public RandomisedBotLevelResult GenerateBotLevel(MinMax levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
|
||||
var botLevelRange = GetRelativeBotLevelRange(botGenerationDetails, levelDetails, expTable.Length);
|
||||
|
||||
// Get random level based on the exp table.
|
||||
int exp = 0;
|
||||
var level = int.Parse(ChooseBotLevel(botLevelRange.Min.Value, botLevelRange.Max.Value, 1, 1.15)
|
||||
.ToString()); // TODO - nasty double to string to int conversion
|
||||
for (var i = 0; i < level; i++)
|
||||
{
|
||||
exp += expTable[i].Experience.Value;
|
||||
}
|
||||
|
||||
// Sprinkle in some random exp within the level, unless we are at max level.
|
||||
if (level < expTable.Length - 1)
|
||||
{
|
||||
exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
|
||||
}
|
||||
|
||||
return new RandomisedBotLevelResult { Level = level, Exp = exp };
|
||||
}
|
||||
|
||||
public int ChooseBotLevel(int min, int max, int shift, int number)
|
||||
public double ChooseBotLevel(double min, double max, int shift, double number)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _randomUtil.GetBiasedRandomNumber(min, max, shift, number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,6 +73,35 @@ public class BotLevelGenerator
|
||||
/// <returns>A MinMax of the lowest and highest level to generate the bots</returns>
|
||||
public MinMax GetRelativeBotLevelRange(BotGenerationDetails botGenerationDetails, MinMax levelDetails, int maxAvailableLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var isPmc = botGenerationDetails.IsPmc.GetValueOrDefault(false);
|
||||
var pmcOverride = botGenerationDetails.LocationSpecificPmcLevelOverride;
|
||||
|
||||
var minPossibleLevel = isPmc && pmcOverride is not null
|
||||
? Math.Min(
|
||||
Math.Max(levelDetails.Min.Value, pmcOverride.Min.Value), // Biggest between json min and the botgen min
|
||||
maxAvailableLevel // Fallback if value above is crazy (default is 79)
|
||||
)
|
||||
: Math.Min(levelDetails.Min.Value, maxAvailableLevel); // Not pmc with override or non-pmc
|
||||
|
||||
var maxPossibleLevel = isPmc && pmcOverride is not null
|
||||
? Math.Min(pmcOverride.Max.Value, maxAvailableLevel) // Was a PMC and they have a level override
|
||||
: Math.Min(levelDetails.Max.Value, maxAvailableLevel); // Not pmc with override or non-pmc
|
||||
|
||||
var minLevel = botGenerationDetails.PlayerLevel.HasValue
|
||||
? botGenerationDetails.PlayerLevel.Value
|
||||
: 0 - botGenerationDetails.BotRelativeLevelDeltaMin.Value;
|
||||
var maxLevel = botGenerationDetails.PlayerLevel.HasValue
|
||||
? botGenerationDetails.PlayerLevel.Value
|
||||
: 0 + botGenerationDetails.BotRelativeLevelDeltaMin.Value;
|
||||
|
||||
// Bound the level to the min/max possible
|
||||
maxLevel = Math.Min(Math.Max(maxLevel, minPossibleLevel), maxPossibleLevel);
|
||||
minLevel = Math.Min(Math.Max(minLevel, minPossibleLevel), maxPossibleLevel);
|
||||
|
||||
return new MinMax
|
||||
{
|
||||
Min = minLevel,
|
||||
Max = maxLevel,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,25 @@ public class WeatherGenerator
|
||||
private readonly SeasonalEventService _seasonalEventService;
|
||||
private readonly WeatherHelper _weatherHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private readonly WeightedRandomHelper _weightedRandomHelper;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly WeatherConfig _weatherConfig;
|
||||
|
||||
public WeatherGenerator(
|
||||
TimeUtil timeUtil,
|
||||
SeasonalEventService seasonalEventService,
|
||||
WeatherHelper weatherHelper,
|
||||
ConfigServer configServer)
|
||||
ConfigServer configServer,
|
||||
WeightedRandomHelper weightedRandomHelper,
|
||||
RandomUtil randomUtil
|
||||
)
|
||||
{
|
||||
_timeUtil = timeUtil;
|
||||
_seasonalEventService = seasonalEventService;
|
||||
_weatherHelper = weatherHelper;
|
||||
_configServer = configServer;
|
||||
_weightedRandomHelper = weightedRandomHelper;
|
||||
_randomUtil = randomUtil;
|
||||
|
||||
_weatherConfig = _configServer.GetConfig<WeatherConfig>();
|
||||
}
|
||||
@@ -81,7 +87,36 @@ public class WeatherGenerator
|
||||
*/
|
||||
public Weather GenerateWeather(Season currentSeason, long? timestamp = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var weatherValues = GetWeatherValuesBySeason(currentSeason);
|
||||
var clouds = GetWeightedClouds(weatherValues);
|
||||
|
||||
// Force rain to off if no clouds
|
||||
var rain = clouds <= 0.6 ? 0 : GetWeightedRain(weatherValues);
|
||||
|
||||
// TODO: Ensure Weather settings match Ts Server GetRandomDouble produces a decimal value way higher than ts server
|
||||
var result = new Weather
|
||||
{
|
||||
Pressure = GetRandomDouble(weatherValues.Pressure.Min ?? 0, weatherValues.Pressure.Max ?? 0),
|
||||
Temperature = 0,
|
||||
Fog = GetWeightedFog(weatherValues),
|
||||
RainIntensity =
|
||||
rain > 1 ? GetRandomDouble(weatherValues.RainIntensity.Min ?? 0, weatherValues.RainIntensity.Max ?? 0) : 0,
|
||||
Rain = rain,
|
||||
WindGustiness = GetRandomDouble(weatherValues.WindGustiness.Min ?? 0, weatherValues.WindGustiness.Max ?? 0, 2),
|
||||
WindDirection = GetWeightedWindDirection(weatherValues),
|
||||
WindSpeed = GetWeightedWindSpeed(weatherValues),
|
||||
Cloud = clouds,
|
||||
Time = "",
|
||||
Date = "",
|
||||
Timestamp = 0,
|
||||
SptInRaidTimestamp = 0
|
||||
};
|
||||
|
||||
SetCurrentDateTime(result, timestamp);
|
||||
|
||||
result.Temperature = GetRaidTemperature(weatherValues, result.SptInRaidTimestamp ?? 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason)
|
||||
@@ -92,7 +127,7 @@ public class WeatherGenerator
|
||||
return this._weatherConfig.Weather.SeasonValues["default"];
|
||||
}
|
||||
|
||||
return value;
|
||||
return value!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,9 +136,15 @@ public class WeatherGenerator
|
||||
* @param inRaidTimestamp What time is the raid running at
|
||||
* @returns Timestamp
|
||||
*/
|
||||
protected double GetRaidTemperature(SeasonalValues weather, int inRaidTimestamp)
|
||||
protected double GetRaidTemperature(SeasonalValues weather, long inRaidTimestamp)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Convert timestamp to date so we can get current hour and check if its day or night
|
||||
var currentRaidTime = new DateTime(inRaidTimestamp);
|
||||
var minMax = _weatherHelper.IsHourAtNightTime(currentRaidTime.Hour)
|
||||
? weather.Temp.Night
|
||||
: weather.Temp.Day;
|
||||
|
||||
return Math.Round(_randomUtil.GetDouble(minMax.Min ?? 0, minMax.Max ?? 0), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,38 +152,46 @@ public class WeatherGenerator
|
||||
* @param weather Object to update
|
||||
* @param timestamp OPTIONAL, define timestamp used
|
||||
*/
|
||||
protected void SetCurrentDateTime(Weather weather, int? timestamp = null)
|
||||
protected void SetCurrentDateTime(Weather weather, long? timestamp = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var inRaidTime = _weatherHelper.GetInRaidTime(timestamp);
|
||||
var normalTime = GetBsgFormattedTime(inRaidTime);
|
||||
var formattedDate = _timeUtil.FormatDate(timestamp.HasValue ? _timeUtil.GetDateTimeFromTimeStamp(timestamp.Value) : DateTime.UtcNow);
|
||||
var datetimeBsgFormat = $"{formattedDate} {normalTime}";
|
||||
|
||||
weather.Timestamp = timestamp ?? _timeUtil.GetTimeStampFromEpoch(inRaidTime); // matches weather.date We use to divide by 1000
|
||||
weather.Date = formattedDate; // matches weather.timestamp
|
||||
weather.Time = datetimeBsgFormat; // matches weather.timestamp
|
||||
weather.SptInRaidTimestamp = _timeUtil.GetTimeStampFromEpoch(inRaidTime);
|
||||
}
|
||||
|
||||
protected WindDirection GetWeightedWindDirection(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.WindDirection.Values, weather.WindDirection.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedClouds(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Clouds.Values, weather.Clouds.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedWindSpeed(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.WindSpeed.Values, weather.WindSpeed.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedFog(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Fog.Values, weather.Fog.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetWeightedRain(SeasonalValues weather)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _weightedRandomHelper.WeightedRandom(weather.Rain.Values, weather.Rain.Weights).Item;
|
||||
}
|
||||
|
||||
protected double GetRandomFloat(double min, double max, int precision = 3)
|
||||
protected double GetRandomDouble(double min, double max, int precision = 3)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Math.Round(_randomUtil.GetDouble(min, max), precision);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
|
||||
namespace Core.Helpers;
|
||||
|
||||
[Injectable]
|
||||
public class BotGeneratorHelper
|
||||
{
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly PmcConfig _pmcConfig;
|
||||
|
||||
public BotGeneratorHelper(
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_configServer = configServer;
|
||||
_pmcConfig = _configServer.GetConfig<PmcConfig>(ConfigTypes.PMC);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds properties to an item
|
||||
/// e.g. Repairable / HasHinge / Foldable / MaxDurability
|
||||
@@ -84,7 +96,11 @@ public class BotGeneratorHelper
|
||||
/// <returns>Equipment role (e.g. pmc / assault / bossTagilla)</returns>
|
||||
public string GetBotEquipmentRole(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
string[] pmcs = [_pmcConfig.UsecType.ToLower(), _pmcConfig.BearType.ToLower()];
|
||||
return pmcs.Contains(
|
||||
botRole.ToLower())
|
||||
? "pmc"
|
||||
: botRole;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+97
-16
@@ -1,8 +1,12 @@
|
||||
using Core.Annotations;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Match;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Helpers;
|
||||
@@ -12,14 +16,30 @@ public class BotHelper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
public BotHelper(
|
||||
private readonly BotConfig _botConfig;
|
||||
private readonly PmcConfig _pmcConfig;
|
||||
|
||||
private readonly List<string> _pmcNames = ["usec", "bear", "pmc", "pmcbear", "pmcusec"];
|
||||
|
||||
public BotHelper
|
||||
(
|
||||
ILogger logger,
|
||||
DatabaseService databaseService)
|
||||
DatabaseService databaseService,
|
||||
RandomUtil randomUtil,
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_databaseService = databaseService;
|
||||
_randomUtil = randomUtil;
|
||||
_configServer = configServer;
|
||||
_botConfig = configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
|
||||
_pmcConfig = configServer.GetConfig<PmcConfig>(ConfigTypes.PMC);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a template object for the specified botRole from bots.types db
|
||||
/// </summary>
|
||||
@@ -44,17 +64,17 @@ public class BotHelper
|
||||
/// <returns>true if is pmc</returns>
|
||||
public bool IsBotPmc(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _pmcNames.Contains(botRole?.ToLower());
|
||||
}
|
||||
|
||||
public bool IsBotBoss(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _botConfig.Bosses.Any(x => x.ToLower() == botRole.ToLower());
|
||||
}
|
||||
|
||||
public bool IsBotFollower(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return botRole?.ToLower().StartsWith("follower") ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +84,15 @@ public class BotHelper
|
||||
/// <param name="typeToAdd">bot type to add to friendly list</param>
|
||||
public void AddBotToFriendlyList(DifficultyCategories difficultySettings, string typeToAdd)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var friendlyBotTypesKey = "FRIENDLY_BOT_TYPES";
|
||||
|
||||
// Null guard
|
||||
if (difficultySettings.Mind[friendlyBotTypesKey] is null)
|
||||
{
|
||||
difficultySettings.Mind[friendlyBotTypesKey] = new List<string>();
|
||||
}
|
||||
|
||||
((List<string>)difficultySettings.Mind[friendlyBotTypesKey]).Add(typeToAdd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,17 +102,44 @@ public class BotHelper
|
||||
/// <param name="typesToAdd">bot type to add to revenge list</param>
|
||||
public void AddBotToRevengeList(DifficultyCategories difficultySettings, string[] typesToAdd)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var revengePropKey = "REVENGE_BOT_TYPES";
|
||||
|
||||
// Nothing to add
|
||||
if (typesToAdd is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Null guard
|
||||
if (difficultySettings.Mind[revengePropKey] is null)
|
||||
{
|
||||
difficultySettings.Mind[revengePropKey] = new List<string>();
|
||||
}
|
||||
|
||||
var revengeArray = (List<string>)difficultySettings.Mind[revengePropKey];
|
||||
foreach (var botTypeToAdd in typesToAdd)
|
||||
{
|
||||
if (!revengeArray.Contains(botTypeToAdd))
|
||||
{
|
||||
revengeArray.Add(botTypeToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RollChanceToBePmc(MinMax botConvertMinMax)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _randomUtil.GetChance100(_randomUtil.GetInt((int)botConvertMinMax.Min, (int)botConvertMinMax.Max));
|
||||
}
|
||||
|
||||
protected void GetPmcConversionValuesForLocation(string location)
|
||||
protected Dictionary<string, MinMax> GetPmcConversionValuesForLocation(string location)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var result = _pmcConfig.ConvertIntoPmcChance[location.ToLower()];
|
||||
if (result is null)
|
||||
{
|
||||
_pmcConfig.ConvertIntoPmcChance = new();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +149,10 @@ public class BotHelper
|
||||
/// <returns>True if role is PMC</returns>
|
||||
public bool BotRoleIsPmc(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
List<string> ListToCheck = [_pmcConfig.UsecType.ToLower(), _pmcConfig.BearType.ToLower()];
|
||||
return ListToCheck.Contains(
|
||||
botRole.ToLower()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,7 +163,15 @@ public class BotHelper
|
||||
/// <returns>RandomisationDetails</returns>
|
||||
public RandomisationDetails GetBotRandomizationDetails(int botLevel, EquipmentFilters botEquipConfig)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// No randomisation details found, skip
|
||||
if (botEquipConfig is null || botEquipConfig.Randomisation is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return botEquipConfig.Randomisation.FirstOrDefault(
|
||||
(randDetails) => botLevel >= randDetails.LevelRange.Min && botLevel <= randDetails.LevelRange.Max
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -114,7 +180,7 @@ public class BotHelper
|
||||
/// <returns>pmc role</returns>
|
||||
public string GetRandomizedPmcRole()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? _pmcConfig.UsecType : _pmcConfig.BearType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +190,13 @@ public class BotHelper
|
||||
/// <returns>side (usec/bear)</returns>
|
||||
public string GetPmcSideByRole(string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (_pmcConfig.BearType.ToLower() == botRole.ToLower())
|
||||
return "Bear";
|
||||
|
||||
if (_pmcConfig.UsecType.ToLower() == botRole.ToLower())
|
||||
return "Usec";
|
||||
|
||||
return GetRandomizedPmcSide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -133,7 +205,7 @@ public class BotHelper
|
||||
/// <returns>pmc side as string</returns>
|
||||
protected string GetRandomizedPmcSide()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _randomUtil.GetChance100(_pmcConfig.IsUsec) ? "Usec" : "Bear";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,6 +216,15 @@ public class BotHelper
|
||||
/// <returns>name of PMC</returns>
|
||||
public string GetPmcNicknameOfMaxLength(int maxLength, string side = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var randomType = (side is not null) ? side : (_randomUtil.GetInt(0, 1) == 0) ? "usec" : "bear";
|
||||
var allNames = _databaseService.GetBots().Types[randomType.ToLower()].FirstNames;
|
||||
var filteredNames = allNames.Where((name) => name.Length <= maxLength);
|
||||
if (filteredNames.Count() == 0) {
|
||||
_logger.Warning($"Unable to filter: {randomType} PMC names to only those under: {maxLength}, none found that match that criteria, selecting from entire name pool instead`,\n");
|
||||
|
||||
return _randomUtil.GetStringCollectionValue(allNames);
|
||||
}
|
||||
|
||||
return _randomUtil.GetStringCollectionValue(filteredNames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,46 @@
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Helpers;
|
||||
|
||||
[Injectable]
|
||||
public class DialogueHelper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly SaveServer _saveServer;
|
||||
private readonly DatabaseServer _databaseServer;
|
||||
private readonly NotifierHelper _notifierHelper;
|
||||
private readonly NotificationSendHelper _notificationSendHelper;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
|
||||
public DialogueHelper
|
||||
(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
SaveServer saveServer,
|
||||
DatabaseServer databaseServer,
|
||||
NotifierHelper notifierHelper,
|
||||
NotificationSendHelper notificationSendHelper,
|
||||
LocalisationService localisationService,
|
||||
ItemHelper itemHelper
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_saveServer = saveServer;
|
||||
_databaseServer = databaseServer;
|
||||
_notifierHelper = notifierHelper;
|
||||
_localisationService = localisationService;
|
||||
_itemHelper = itemHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the preview contents of the last message in a dialogue.
|
||||
/// </summary>
|
||||
@@ -14,7 +48,23 @@ public class DialogueHelper
|
||||
/// <returns>MessagePreview</returns>
|
||||
public MessagePreview GetMessagePreview(Models.Eft.Profile.Dialogue dialogue)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// The last message of the dialogue should be shown on the preview.
|
||||
var message = dialogue.Messages[dialogue.Messages.Count - 1];
|
||||
MessagePreview result = new()
|
||||
{
|
||||
DateTime = message?.DateTime,
|
||||
MessageType = message?.MessageType,
|
||||
TemplateId = message?.TemplateId,
|
||||
UserId = dialogue?.Id
|
||||
};
|
||||
|
||||
if (message?.Text is not null)
|
||||
result.Text = message.Text;
|
||||
|
||||
if (message?.SystemData is not null)
|
||||
result.SystemData = message?.SystemData;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +76,36 @@ public class DialogueHelper
|
||||
/// <returns></returns>
|
||||
public List<Item> GetMessageItemContents(string messageID, string sessionID, string itemId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var dialogueData = _saveServer.GetProfile(sessionID).DialogueRecords;
|
||||
foreach (var dialogue in dialogueData)
|
||||
{
|
||||
var message = dialogueData[dialogue.Key].Messages.FirstOrDefault(x => x.Id == messageID);
|
||||
if (message is null)
|
||||
continue;
|
||||
|
||||
if (message.Id == messageID)
|
||||
{
|
||||
var attachmentsNew = _saveServer.GetProfile(sessionID).DialogueRecords[dialogue.Key].AttachmentsNew;
|
||||
if (attachmentsNew > 0)
|
||||
_saveServer.GetProfile(sessionID).DialogueRecords[dialogue.Key].AttachmentsNew = attachmentsNew - 1;
|
||||
|
||||
// Check reward count when item being moved isn't in reward list
|
||||
// If count is 0, it means after this move occurs the reward array will be empty and all rewards collected
|
||||
if (message.Items.Data is null)
|
||||
message.Items.Data = new();
|
||||
|
||||
var rewardItems = message.Items.Data?.Where(x => x.Id != itemId);
|
||||
if (rewardItems.Count() == 0)
|
||||
{
|
||||
message.RewardCollected = true;
|
||||
message.HasRewards = false;
|
||||
}
|
||||
|
||||
return message.Items.Data;
|
||||
}
|
||||
}
|
||||
|
||||
return new List<Item>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +115,26 @@ public class DialogueHelper
|
||||
/// <returns>Dialog dictionary</returns>
|
||||
public Dictionary<string, Models.Eft.Profile.Dialogue> GetDialogsForProfile(string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var profile = _saveServer.GetProfile(sessionId);
|
||||
if (profile.DialogueRecords is null)
|
||||
profile.DialogueRecords = new();
|
||||
|
||||
return profile.DialogueRecords;
|
||||
}
|
||||
|
||||
public Models.Eft.Profile.Dialogue? GetDialogueFromProfile(string profileId, string dialogueId)
|
||||
{
|
||||
Models.Eft.Profile.Dialogue? returnDialogue = null;
|
||||
var dialogues = GetDialogsForProfile(profileId);
|
||||
|
||||
foreach (var dialogue in dialogues.Values)
|
||||
{
|
||||
if (dialogue.Id == dialogueId)
|
||||
returnDialogue = dialogue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return returnDialogue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1044,9 +1044,9 @@ public class ItemHelper
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsOfBaseclass(string valueEncyclopedia, string weapon)
|
||||
public bool IsOfBaseclass(string tpl, List<string> baseClassTpls)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _itemBaseClassService.ItemHasBaseClass(tpl, baseClassTpls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ public class PresetHelper
|
||||
var tempPresets = _databaseService.GetGlobals().ItemPresets;
|
||||
tempPresets = tempPresets.Where(p =>
|
||||
p.Value.Encyclopedia != null &&
|
||||
_itemHelper.IsOfBaseclass(p.Value.Encyclopedia, BaseClasses.WEAPON)).ToDictionary();
|
||||
_itemHelper.IsOfBaseclass(p.Value.Encyclopedia, [BaseClasses.WEAPON])).ToDictionary();
|
||||
}
|
||||
|
||||
return _defaultWeaponPresets;
|
||||
|
||||
@@ -506,7 +506,7 @@ public class ProfileHelper
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
/// <param name="skill">Skill to look up and return value from</param>
|
||||
/// <returns>Common skill object from desired profile</returns>
|
||||
public Common? GetSkillFromProfile(PmcData pmcData, SkillTypes skill)
|
||||
public BaseSkill? GetSkillFromProfile(PmcData pmcData, SkillTypes skill)
|
||||
{
|
||||
var skillToReturn = pmcData?.Skills?.Common.FirstOrDefault(s => s.Id == skill.ToString());
|
||||
if (skillToReturn == null)
|
||||
@@ -662,9 +662,9 @@ public class ProfileHelper
|
||||
}
|
||||
|
||||
var customisationTemplateDb = _databaseService.GetTemplates().Customization;
|
||||
var matchingCustomisation = customisationTemplateDb[reward.Target];
|
||||
var matchingCustomisation = customisationTemplateDb.GetValueOrDefault(reward.Target, null);
|
||||
|
||||
if (matchingCustomisation != null)
|
||||
if (matchingCustomisation is null)
|
||||
{
|
||||
var rewardToStore = new CustomisationStorage
|
||||
{
|
||||
|
||||
@@ -306,9 +306,11 @@ public class TraderHelper
|
||||
/// </summary>
|
||||
/// <param name="traderEnumValue">The trader enum value to validate</param>
|
||||
/// <returns>The validated trader enum value as a string, or an empty string if invalid</returns>
|
||||
public string GetValidTraderIdByEnumValue(object traderEnumValue) // TODO: param was Traders
|
||||
public string GetValidTraderIdByEnumValue(string traderEnumValue) // TODO: param was Traders
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var traderId = _databaseService.GetTraders();
|
||||
var id = traderId.FirstOrDefault(x => x.Value.Base.Nickname.ToLower() == traderEnumValue.ToLower()).Key;
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -35,16 +35,18 @@ public class WeatherHelper
|
||||
/// </summary>
|
||||
/// <param name="currentDate">(new Date())</param>
|
||||
/// <returns>Date object of current in-raid time</returns>
|
||||
public DateTime GetInRaidTime(double? timestamp = null)
|
||||
public DateTime GetInRaidTime(long? timestamp = null)
|
||||
{
|
||||
// tarkov time = (real time * 7 % 24 hr) + 3 hour
|
||||
var russiaOffsetMilliseconds = _timeUtil.GetHoursAsSeconds(3) * 1000;
|
||||
var twentyFourHoursMilliseconds = _timeUtil.GetHoursAsSeconds(24) * 1000;
|
||||
var currentTimestampMilliSeconds = (timestamp is not null) ? timestamp : _timeUtil.GetTimeStamp();
|
||||
var currentTimestampMilliSeconds = timestamp.HasValue
|
||||
? timestamp ?? 0
|
||||
: (DateTime.UtcNow - DateTime.UnixEpoch).TotalMilliseconds;
|
||||
|
||||
return new DateTime().AddMilliseconds(
|
||||
(russiaOffsetMilliseconds + russiaOffsetMilliseconds * _weatherConfig.Acceleration) %
|
||||
twentyFourHoursMilliseconds);
|
||||
return _timeUtil.GetDateTimeFromTimeStamp((long)
|
||||
(russiaOffsetMilliseconds + currentTimestampMilliSeconds * _weatherConfig.Acceleration) %
|
||||
twentyFourHoursMilliseconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Spt.Helper;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
@@ -16,13 +16,19 @@ public class WeightedRandomHelper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choos an item from the passed in array based on the weightings of each
|
||||
/// Choose an item from the passed in array based on the weightings of each
|
||||
/// </summary>
|
||||
/// <param name="itemArray">Items and weights to use</param>
|
||||
/// <param name="values">Items and weights to use</param>
|
||||
/// <returns>Chosen item from array</returns>
|
||||
public T GetWeightedValue<T>(Dictionary<string, object> itemArray)
|
||||
public T GetWeightedValue<T>(Dictionary<T, int> values) where T : notnull
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var itemKeys = values.Keys.ToList();
|
||||
var weights = values.Values.ToList();
|
||||
|
||||
var chosenItem = WeightedRandom<T>(itemKeys, weights);
|
||||
|
||||
return chosenItem.Item;
|
||||
// SORRY IF THIS BLEW UP, I DONT SEE A REASON ITS GENERIC - CWX
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ public class MinMax
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("max")]
|
||||
public double? Max { get; set; }
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ public class RandomisedBotLevelResult
|
||||
|
||||
[JsonPropertyName("exp")]
|
||||
public int? Exp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Hideout;
|
||||
|
||||
namespace Core.Models.Eft.Common;
|
||||
@@ -540,7 +540,7 @@ public class Config
|
||||
public BTRSettings? BTRSettings { get; set; }
|
||||
|
||||
[JsonPropertyName("EventType")]
|
||||
public string[] EventType { get; set; }
|
||||
public List<string> EventType { get; set; }
|
||||
|
||||
[JsonPropertyName("WalkSpeed")]
|
||||
public XYZ? WalkSpeed { get; set; }
|
||||
@@ -1294,13 +1294,13 @@ public class Mastering
|
||||
public class Customization
|
||||
{
|
||||
[JsonPropertyName("SavageHead")]
|
||||
public Dictionary<string, Dictionary<string, object>>? Head { get; set; }
|
||||
public Dictionary<string, WildHead>? Head { get; set; }
|
||||
|
||||
[JsonPropertyName("SavageBody")]
|
||||
public Dictionary<string, Dictionary<string, object>>? Body { get; set; }
|
||||
public Dictionary<string, WildBody>? Body { get; set; }
|
||||
|
||||
[JsonPropertyName("SavageFeet")]
|
||||
public Dictionary<string, Dictionary<string, object>>? Feet { get; set; }
|
||||
public Dictionary<string, WildFeet>? Feet { get; set; }
|
||||
|
||||
[JsonPropertyName("CustomizationVoice")]
|
||||
public List<CustomizationVoice>? VoiceOptions { get; set; }
|
||||
@@ -1309,6 +1309,41 @@ public class Customization
|
||||
public BodyParts? BodyParts { get; set; }
|
||||
}
|
||||
|
||||
public class WildHead
|
||||
{
|
||||
[JsonPropertyName("head")]
|
||||
public string? Head { get; set; }
|
||||
|
||||
[JsonPropertyName("isNotRandom")]
|
||||
public bool? IsNotRandom { get; set; }
|
||||
|
||||
[JsonPropertyName("NotRandom")]
|
||||
public bool? NotRandom { get; set; }
|
||||
}
|
||||
|
||||
public class WildBody
|
||||
{
|
||||
[JsonPropertyName("body")]
|
||||
public string? Body { get; set; }
|
||||
|
||||
[JsonPropertyName("hands")]
|
||||
public string? Hands { get; set; }
|
||||
|
||||
[JsonPropertyName("isNotRandom")]
|
||||
public bool? IsNotRandom { get; set; }
|
||||
}
|
||||
public class WildFeet
|
||||
{
|
||||
[JsonPropertyName("feet")]
|
||||
public string? Feet { get; set; }
|
||||
|
||||
[JsonPropertyName("isNotRandom")]
|
||||
public bool? IsNotRandom { get; set; }
|
||||
|
||||
[JsonPropertyName("NotRandom")]
|
||||
public bool? NotRandom { get; set; }
|
||||
}
|
||||
|
||||
public class CustomizationVoice
|
||||
{
|
||||
[JsonPropertyName("voice")]
|
||||
|
||||
@@ -521,7 +521,7 @@ public class BotLocationModifier
|
||||
public double? AccuracySpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("AdditionalHostilitySettings")]
|
||||
public AdditionalHostilitySettings[] AdditionalHostilitySettings { get; set; }
|
||||
public List<AdditionalHostilitySettings> AdditionalHostilitySettings { get; set; }
|
||||
|
||||
[JsonPropertyName("DistToActivate")]
|
||||
public double? DistanceToActivate { get; set; }
|
||||
@@ -939,4 +939,4 @@ public enum WildSpawnType
|
||||
pmcbot,
|
||||
bosskilla,
|
||||
bossknight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,15 +300,17 @@ public class BaseJsonSkills
|
||||
|
||||
public class Skills
|
||||
{
|
||||
public List<Common>? Common { get; set; }
|
||||
public List<BaseSkill>? Common { get; set; }
|
||||
|
||||
public List<Mastering>? Mastering { get; set; }
|
||||
public List<BaseSkill>? Mastering { get; set; }
|
||||
|
||||
public double? Points { get; set; }
|
||||
}
|
||||
|
||||
public class BaseSkill
|
||||
{
|
||||
public int? PointsEarnedDuringSession { get; set; }
|
||||
public long? LastAccess { get; set; }
|
||||
public string? Id { get; set; }
|
||||
public double? Progress { get; set; }
|
||||
|
||||
@@ -321,8 +323,7 @@ public class BaseSkill
|
||||
|
||||
public class Common : BaseSkill
|
||||
{
|
||||
public int? PointsEarnedDuringSession { get; set; }
|
||||
public long? LastAccess { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class Mastering : BaseSkill
|
||||
@@ -511,7 +512,7 @@ public class Hideout
|
||||
public Dictionary<string, HideoutImprovement>? Improvements { get; set; }
|
||||
public HideoutCounters? HideoutCounters { get; set; }
|
||||
public double? Seed { get; set; }
|
||||
public List<string>? MannequinPoses { get; set; }
|
||||
public Dictionary<string, string>? MannequinPoses { get; set; }
|
||||
|
||||
[JsonPropertyName("sptUpdateLastRunTimestamp")]
|
||||
public long? SptUpdateLastRunTimestamp { get; set; }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Enums;
|
||||
using Core.Utils.Json.Converters;
|
||||
|
||||
namespace Core.Models.Eft.Common.Tables;
|
||||
@@ -332,7 +333,7 @@ public class BodyPart
|
||||
public class BotTypeInventory
|
||||
{
|
||||
[JsonPropertyName("equipment")]
|
||||
public Dictionary<string, Dictionary<string, double>>? Equipment { get; set; }
|
||||
public Dictionary<EquipmentSlots, Dictionary<string, double>>? Equipment { get; set; }
|
||||
|
||||
public GlobalAmmo? Ammo { get; set; }
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ public class Item
|
||||
public string? Template { get; set; }
|
||||
|
||||
[JsonPropertyName("parentId")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string? ParentId { get; set; }
|
||||
|
||||
[JsonPropertyName("slotId")]
|
||||
@@ -108,11 +109,17 @@ public class UpdMap
|
||||
|
||||
public class MapMarker
|
||||
{
|
||||
[JsonPropertyName("Type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("X")]
|
||||
public int? X { get; set; }
|
||||
public double? X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
public int? Y { get; set; }
|
||||
public double? Y { get; set; }
|
||||
|
||||
[JsonPropertyName("Note")]
|
||||
public string? Note { get; set; }
|
||||
}
|
||||
|
||||
public class UpdTag
|
||||
|
||||
@@ -119,7 +119,7 @@ public class ChangeRequirement
|
||||
public List<ChangeCost?>? ChangeCost { get; set; }
|
||||
|
||||
[JsonPropertyName("changeStandingCost")]
|
||||
public int? ChangeStandingCost { get; set; }
|
||||
public double? ChangeStandingCost { get; set; }
|
||||
}
|
||||
|
||||
public class ChangeCost
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Models.Eft.Hideout
|
||||
{
|
||||
public class HideoutCustomizationSetMannequinPoseRequest
|
||||
{
|
||||
[JsonPropertyName("Action")]
|
||||
public string? Action { get; set; } = "HideoutCustomizationSetMannequinPose";
|
||||
|
||||
[JsonPropertyName("poses")]
|
||||
public Dictionary<string, string> Poses { get; set; }
|
||||
|
||||
[JsonPropertyName("timestamp")]
|
||||
public double Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
|
||||
namespace Core.Models.Eft.Inventory;
|
||||
|
||||
@@ -13,18 +14,3 @@ public class InventoryCreateMarkerRequestData : InventoryBaseActionRequestData
|
||||
[JsonPropertyName("mapMarker")]
|
||||
public MapMarker? MapMarker { get; set; }
|
||||
}
|
||||
|
||||
public class MapMarker
|
||||
{
|
||||
[JsonPropertyName("Type")]
|
||||
public string? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("X")]
|
||||
public double? X { get; set; }
|
||||
|
||||
[JsonPropertyName("Y")]
|
||||
public double? Y { get; set; }
|
||||
|
||||
[JsonPropertyName("Note")]
|
||||
public string? Note { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
|
||||
namespace Core.Models.Eft.Inventory;
|
||||
|
||||
@@ -18,4 +19,4 @@ public class InventoryEditMarkerRequestData : InventoryBaseActionRequestData
|
||||
|
||||
[JsonPropertyName("mapMarker")]
|
||||
public MapMarker? MapMarker { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Enums.RaidSettings;
|
||||
using Core.Models.Enums.RaidSettings.TimeAndWeather;
|
||||
@@ -17,7 +17,7 @@ public class RaidSettings
|
||||
public bool? IsLocationTransition { get; set; }
|
||||
|
||||
[JsonPropertyName("timeVariant")]
|
||||
public DateTime? TimeVariant { get; set; }
|
||||
public DateTimeEnum TimeVariant { get; set; }
|
||||
|
||||
[JsonPropertyName("metabolismDisabled")]
|
||||
public bool? MetabolismDisabled { get; set; }
|
||||
@@ -93,4 +93,4 @@ public class WavesSettings
|
||||
|
||||
[JsonPropertyName("isTaggedAndCursed")]
|
||||
public bool? IsTaggedAndCursed { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
namespace Core.Models.Enums;
|
||||
namespace Core.Models.Enums;
|
||||
|
||||
public enum EquipmentSlots
|
||||
{
|
||||
HEADWEAR,
|
||||
EARPIECE,
|
||||
FACE_COVER,
|
||||
ARMOR_VEST,
|
||||
EYEWEAR,
|
||||
ARM_BAND,
|
||||
TACTICAL_VEST,
|
||||
POCKETS,
|
||||
BACKPACK,
|
||||
SECURED_CONTAINER,
|
||||
FIRST_PRIMARY_WEAPON,
|
||||
SECOND_PRIMARY_WEAPON,
|
||||
HOLSTER,
|
||||
SCABBARD
|
||||
}
|
||||
Headwear,
|
||||
Earpiece,
|
||||
FaceCover,
|
||||
ArmorVest,
|
||||
Eyewear,
|
||||
ArmBand,
|
||||
TacticalVest,
|
||||
Pockets,
|
||||
Backpack,
|
||||
SecuredContainer,
|
||||
FirstPrimaryWeapon,
|
||||
SecondPrimaryWeapon,
|
||||
Holster,
|
||||
Scabbard
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Core.Models.Enums;
|
||||
namespace Core.Models.Enums;
|
||||
|
||||
public class HideoutEventActions
|
||||
{
|
||||
@@ -17,4 +17,5 @@ public class HideoutEventActions
|
||||
public const string HIDEOUT_CIRCLE_OF_CULTIST_PRODUCTION_START = "HideoutCircleOfCultistProductionStart";
|
||||
public const string HIDEOUT_DELETE_PRODUCTION_COMMAND = "HideoutDeleteProductionCommand";
|
||||
public const string HIDEOUT_CUSTOMIZATION_APPLY_COMMAND = "HideoutCustomizationApply";
|
||||
public const string HIDEOUT_CUSTOMIZATION_SET_MANNEQUIN_POSE = "HideoutCustomizationSetMannequinPose";
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ public class BotGenerationDetails
|
||||
/// Delta of highest level of bot e.g. 50 means 50 levels above player
|
||||
/// </summary>
|
||||
[JsonPropertyName("botRelativeLevelDeltaMax")]
|
||||
public int? BotRelativeLevelDeltaMax { get; set; }
|
||||
public double? BotRelativeLevelDeltaMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delta of lowest level of bot e.g. 50 means 50 levels below player
|
||||
/// </summary>
|
||||
[JsonPropertyName("botRelativeLevelDeltaMin")]
|
||||
public int? BotRelativeLevelDeltaMin { get; set; }
|
||||
public double? BotRelativeLevelDeltaMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How many to create and store
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
|
||||
namespace Core.Models.Spt.Bots;
|
||||
@@ -10,13 +11,13 @@ public class GenerateEquipmentProperties
|
||||
/// Root Slot being generated
|
||||
/// </summary>
|
||||
[JsonPropertyName("rootEquipmentSlot")]
|
||||
public string? RootEquipmentSlot { get; set; }
|
||||
public EquipmentSlots RootEquipmentSlot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Equipment pool for root slot being generated
|
||||
/// </summary>
|
||||
[JsonPropertyName("rootEquipmentPool")]
|
||||
public Dictionary<string, int>? RootEquipmentPool { get; set; }
|
||||
public Dictionary<string, double>? RootEquipmentPool { get; set; }
|
||||
|
||||
[JsonPropertyName("modPool")]
|
||||
public GlobalMods? ModPool { get; set; }
|
||||
@@ -53,4 +54,4 @@ public class GenerateEquipmentProperties
|
||||
|
||||
[JsonPropertyName("generatingPlayerLevel")]
|
||||
public int? GeneratingPlayerLevel { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
|
||||
namespace Core.Models.Spt.Config;
|
||||
|
||||
@@ -479,7 +480,7 @@ public class EquipmentFilterDetails
|
||||
/// Key: equipment slot name e.g. FirstPrimaryWeapon, value: item tpls
|
||||
/// </summary>
|
||||
[JsonPropertyName("gear")]
|
||||
public Dictionary<string, List<string>>? Gear { get; set; }
|
||||
public Dictionary<EquipmentSlots, List<string>>? Gear { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key: cartridge type e.g. Caliber23x75, value: item tpls
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Eft.Game;
|
||||
|
||||
namespace Core.Models.Spt.Config;
|
||||
@@ -172,15 +172,9 @@ public class ServerFeatures
|
||||
|
||||
public class ChatbotFeatures
|
||||
{
|
||||
[JsonPropertyName("sptFriendEnabled")]
|
||||
public bool SptFriendEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("sptFriendGiftsEnabled")]
|
||||
public bool SptFriendGiftsEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("commandoEnabled")]
|
||||
public bool CommandoEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("commandoFeatures")]
|
||||
public CommandoFeatures CommandoFeatures { get; set; }
|
||||
|
||||
@@ -189,10 +183,13 @@ public class ChatbotFeatures
|
||||
|
||||
[JsonPropertyName("ids")]
|
||||
public Dictionary<string, string> Ids { get; set; }
|
||||
|
||||
[JsonPropertyName("enabledBots")]
|
||||
public Dictionary<string, bool> EnabledBots { get; set; }
|
||||
}
|
||||
|
||||
public class CommandoFeatures
|
||||
{
|
||||
[JsonPropertyName("giveCommandEnabled")]
|
||||
public bool GiveCommandEnabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
|
||||
@@ -132,6 +132,12 @@ public class LocationConfig : BaseConfig
|
||||
[JsonPropertyName("reserveRaiderSpawnChanceOverrides")]
|
||||
public ReserveRaiderSpawnChanceOverrides ReserveRaiderSpawnChanceOverrides { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Containers to remove all children from when generating static/loose loot
|
||||
/// </summary>
|
||||
[JsonPropertyName("tplsToStripChildItemsFrom")]
|
||||
public List<string> TplsToStripChildItemsFrom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Map ids players cannot visit
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
|
||||
namespace Core.Models.Spt.Config;
|
||||
|
||||
@@ -24,7 +25,7 @@ public class KarmaLevel
|
||||
public Dictionary<string, GenerationData> ItemLimits { get; set; }
|
||||
|
||||
[JsonPropertyName("equipmentBlacklist")]
|
||||
public Dictionary<string, string[]> EquipmentBlacklist { get; set; }
|
||||
public Dictionary<EquipmentSlots, string[]> EquipmentBlacklist { get; set; }
|
||||
|
||||
[JsonPropertyName("labsAccessCardChancePercent")]
|
||||
public double? LabsAccessCardChancePercent { get; set; }
|
||||
|
||||
@@ -12,11 +12,11 @@ public class PmcConfig : BaseConfig
|
||||
|
||||
/** What game version should the PMC have */
|
||||
[JsonPropertyName("gameVersionWeight")]
|
||||
public Dictionary<string, double> GameVersionWeight { get; set; }
|
||||
public Dictionary<string, int> GameVersionWeight { get; set; }
|
||||
|
||||
/** What account type should the PMC have */
|
||||
[JsonPropertyName("accountTypeWeight")]
|
||||
public Dictionary<MemberCategory, double> AccountTypeWeight { get; set; }
|
||||
public Dictionary<MemberCategory, int> AccountTypeWeight { get; set; }
|
||||
|
||||
/** Global whitelist/blacklist of vest loot for PMCs */
|
||||
[JsonPropertyName("vestLoot")]
|
||||
@@ -76,7 +76,7 @@ public class PmcConfig : BaseConfig
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType { get; set; }
|
||||
|
||||
[JsonPropertyName("maxBackpackLootTotalRub")]
|
||||
public List<IMinMaxLootValue> MaxBackpackLootTotalRub { get; set; }
|
||||
public List<MinMaxLootValue> MaxBackpackLootTotalRub { get; set; }
|
||||
|
||||
[JsonPropertyName("maxPocketLootTotalRub")]
|
||||
public double MaxPocketLootTotalRub { get; set; }
|
||||
@@ -115,6 +115,9 @@ public class PmcConfig : BaseConfig
|
||||
|
||||
[JsonPropertyName("addPrefixToSameNamePMCAsPlayerChance")]
|
||||
public int? AddPrefixToSameNamePMCAsPlayerChance { get; set; }
|
||||
|
||||
[JsonPropertyName("lootItemLimitsRub")]
|
||||
public List<MinMaxLootValue>? LootItemLimitsRub { get; set; }
|
||||
}
|
||||
|
||||
public class HostilitySettings
|
||||
@@ -164,8 +167,17 @@ public class SlotLootSettings
|
||||
public List<string> Blacklist { get; set; }
|
||||
}
|
||||
|
||||
public class IMinMaxLootValue : MinMax
|
||||
public class MinMaxLootValue : MinMax
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public double Value { get; set; }
|
||||
|
||||
[JsonPropertyName("backpack")]
|
||||
public MinMax Backpack { get; set; }
|
||||
|
||||
[JsonPropertyName("pocket")]
|
||||
public MinMax Pocket { get; set; }
|
||||
|
||||
[JsonPropertyName("vest")]
|
||||
public MinMax Vest { get; set; }
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ public class SeasonalEventConfig : BaseConfig
|
||||
|
||||
/** event / botType / equipSlot / itemid */
|
||||
[JsonPropertyName("eventGear")]
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, int>>>> EventGear { get; set; }
|
||||
public Dictionary<SeasonalEventType, Dictionary<string, Dictionary<string, Dictionary<string, int>>>> EventGear { get; set; }
|
||||
|
||||
/** event / bot type / equipSlot / itemid */
|
||||
[JsonPropertyName("eventLoot")]
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, int>>>> EventLoot { get; set; }
|
||||
public Dictionary<SeasonalEventType, Dictionary<string, Dictionary<string, Dictionary<string, int>>>> EventLoot { get; set; }
|
||||
|
||||
[JsonPropertyName("events")]
|
||||
public List<SeasonalEvent> Events { get; set; }
|
||||
@@ -77,38 +77,78 @@ public class SeasonalEvent
|
||||
public int EndMonth { get; set; }
|
||||
|
||||
[JsonPropertyName("settings")]
|
||||
public Dictionary<string, object> Settings { get; set; } // TODO: Type was Record<string, ISeasonalEventSettings | IZombieSettings>
|
||||
public SeasonalEventSettings? Settings { get; set; }
|
||||
|
||||
[JsonPropertyName("setting")]
|
||||
public Dictionary<string, object> SettingsDoNOTUse { set => Settings = value; }
|
||||
public SeasonalEventSettings? SettingsDoNOTUse { set => Settings = value; }
|
||||
}
|
||||
|
||||
public class SeasonalEventSettings
|
||||
{
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; set; }
|
||||
[JsonPropertyName("enableSummoning")]
|
||||
public bool? EnableSummoning { get; set; }
|
||||
|
||||
[JsonPropertyName("enableHalloweenHideout")]
|
||||
public bool? EnableHalloweenHideout { get; set; }
|
||||
|
||||
[JsonPropertyName("enableChristmasHideout")]
|
||||
public bool? EnableChristmasHideout { get; set; }
|
||||
|
||||
[JsonPropertyName("enableSanta")]
|
||||
public bool? EnableSanta { get; set; }
|
||||
|
||||
[JsonPropertyName("adjustBotAppearances")]
|
||||
public bool? AdjustBotAppearances { get; set; }
|
||||
|
||||
[JsonPropertyName("addEventGearToBots")]
|
||||
public bool? AddEventGearToBots { get; set; }
|
||||
|
||||
[JsonPropertyName("addEventLootToBots")]
|
||||
public bool? AddEventLootToBots { get; set; }
|
||||
|
||||
[JsonPropertyName("removeEntryRequirement")]
|
||||
public List<string>? RemoveEntryRequirement { get; set; }
|
||||
|
||||
[JsonPropertyName("replaceBotHostility")]
|
||||
public bool? ReplaceBotHostility { get; set; }
|
||||
|
||||
[JsonPropertyName("forceSeason")]
|
||||
public Season? ForceSeason { get; set; }
|
||||
|
||||
[JsonPropertyName("zombieSettings")]
|
||||
public ZombieSettings? ZombieSettings { get; set; }
|
||||
|
||||
[JsonPropertyName("disableBosses")]
|
||||
public List<string>? DisableBosses { get; set; }
|
||||
|
||||
[JsonPropertyName("disableWaves")]
|
||||
public List<string>? DisableWaves { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class ZombieSettings : SeasonalEventSettings
|
||||
public class ZombieSettings
|
||||
{
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool? Enabled { get; set; }
|
||||
|
||||
[JsonPropertyName("mapInfectionAmount")]
|
||||
public Dictionary<string, int> MapInfectionAmount { get; set; }
|
||||
public Dictionary<string, int>? MapInfectionAmount { get; set; }
|
||||
|
||||
[JsonPropertyName("disableBosses")]
|
||||
public List<string> DisableBosses { get; set; }
|
||||
public List<string>? DisableBosses { get; set; }
|
||||
|
||||
[JsonPropertyName("disableWaves")]
|
||||
public List<string> DisableWaves { get; set; }
|
||||
public List<string>? DisableWaves { get; set; }
|
||||
}
|
||||
|
||||
public class GifterSetting
|
||||
{
|
||||
[JsonPropertyName("map")]
|
||||
public string Map { get; set; }
|
||||
public string? Map { get; set; }
|
||||
|
||||
[JsonPropertyName("zones")]
|
||||
public string Zones { get; set; }
|
||||
public string? Zones { get; set; }
|
||||
|
||||
[JsonPropertyName("spawnChance")]
|
||||
public int SpawnChance { get; set; }
|
||||
public int? SpawnChance { get; set; }
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class SendMessageDetails
|
||||
/// Optional - How long items will be stored in mail before expiry
|
||||
/// </summary>
|
||||
[JsonPropertyName("itemsMaxStorageLifetimeSeconds")]
|
||||
public int? ItemsMaxStorageLifetimeSeconds { get; set; }
|
||||
public long? ItemsMaxStorageLifetimeSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional - Used when sending messages from traders who send text from locale json
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Callbacks;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -11,7 +11,7 @@ namespace Core.Routers.ItemEvents;
|
||||
[Injectable(InjectableTypeOverride = typeof(ItemEventRouterDefinition))]
|
||||
public class HideoutItemEventRouter : ItemEventRouterDefinition
|
||||
{
|
||||
protected HideoutCallbacks _hideoutCallbacks;
|
||||
private readonly HideoutCallbacks _hideoutCallbacks;
|
||||
|
||||
public HideoutItemEventRouter
|
||||
(
|
||||
@@ -38,7 +38,8 @@ public class HideoutItemEventRouter : ItemEventRouterDefinition
|
||||
new HandledRoute(HideoutEventActions.HIDEOUT_CANCEL_PRODUCTION_COMMAND, false),
|
||||
new HandledRoute(HideoutEventActions.HIDEOUT_CIRCLE_OF_CULTIST_PRODUCTION_START, false),
|
||||
new HandledRoute(HideoutEventActions.HIDEOUT_DELETE_PRODUCTION_COMMAND, false),
|
||||
new HandledRoute(HideoutEventActions.HIDEOUT_CUSTOMIZATION_APPLY_COMMAND, false)
|
||||
new HandledRoute(HideoutEventActions.HIDEOUT_CUSTOMIZATION_APPLY_COMMAND, false),
|
||||
new HandledRoute(HideoutEventActions.HIDEOUT_CUSTOMIZATION_SET_MANNEQUIN_POSE, false)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +76,8 @@ public class HideoutItemEventRouter : ItemEventRouterDefinition
|
||||
return _hideoutCallbacks.HideoutDeleteProductionCommand(pmcData, body as HideoutDeleteProductionRequestData, sessionID);
|
||||
case HideoutEventActions.HIDEOUT_CUSTOMIZATION_APPLY_COMMAND:
|
||||
return _hideoutCallbacks.HideoutCustomizationApplyCommand(pmcData, body as HideoutCustomizationApplyRequestData, sessionID);
|
||||
case HideoutEventActions.HIDEOUT_CUSTOMIZATION_SET_MANNEQUIN_POSE:
|
||||
return _hideoutCallbacks.HideoutCustomizationSetMannequinPose(pmcData, body as HideoutCustomizationSetMannequinPoseRequest, sessionID);
|
||||
default:
|
||||
throw new Exception($"HideoutItemEventRouter being used when it cant handle route {url}");
|
||||
}
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class BotEquipmentFilterService
|
||||
{
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly BotConfig _botConfig;
|
||||
|
||||
public BotEquipmentFilterService(
|
||||
ConfigServer configServer)
|
||||
{
|
||||
_configServer = configServer;
|
||||
|
||||
_botConfig = _configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter a bots data to exclude equipment and cartridges defines in the botConfig
|
||||
/// </summary>
|
||||
@@ -74,9 +87,12 @@ public class BotEquipmentFilterService
|
||||
/// <param name="botRole">Role of the bot we want the blacklist for</param>
|
||||
/// <param name="playerLevel">Level of the player</param>
|
||||
/// <returns>EquipmentBlacklistDetails object</returns>
|
||||
public EquipmentFilterDetails GetBotEquipmentBlacklist(string botRole, int playerLevel)
|
||||
public EquipmentFilterDetails? GetBotEquipmentBlacklist(string botRole, double playerLevel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var blacklistDetailsForBot = _botConfig.Equipment.GetValueOrDefault(botRole, null);
|
||||
|
||||
return blacklistDetailsForBot?.Blacklist?.FirstOrDefault(
|
||||
(equipmentFilter) => playerLevel >= equipmentFilter.LevelRange.Min && playerLevel <= equipmentFilter.LevelRange.Max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,47 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
using Core.Utils;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class BotNameService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly BotHelper _botHelper;
|
||||
private readonly RandomUtil _randomUtil;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly BotConfig _botConfig;
|
||||
|
||||
private readonly HashSet<string> _usedNameCache;
|
||||
|
||||
public BotNameService(
|
||||
ILogger logger,
|
||||
BotHelper botHelper,
|
||||
RandomUtil randomUtil,
|
||||
LocalisationService localisationService,
|
||||
DatabaseService databaseService,
|
||||
ConfigServer configServer)
|
||||
{
|
||||
_logger = logger;
|
||||
_botHelper = botHelper;
|
||||
_randomUtil = randomUtil;
|
||||
_localisationService = localisationService;
|
||||
_databaseService = databaseService;
|
||||
_configServer = configServer;
|
||||
|
||||
_botConfig = _configServer.GetConfig<BotConfig>(ConfigTypes.BOT);
|
||||
_usedNameCache = new HashSet<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear out any entries in Name Set
|
||||
/// </summary>
|
||||
@@ -22,7 +57,6 @@ public class BotNameService
|
||||
/// <param name="botGenerationDetails"></param>
|
||||
/// <param name="botRole">role of bot e.g. assault</param>
|
||||
/// <param name="uniqueRoles">Lowercase roles to always make unique</param>
|
||||
/// <param name="sessionId">OPTIONAL: profile session id</param>
|
||||
/// <returns>Nickname for bot</returns>
|
||||
public string GenerateUniqueBotNickname(
|
||||
BotType botJsonTemplate,
|
||||
@@ -30,7 +64,68 @@ public class BotNameService
|
||||
string botRole,
|
||||
List<string> uniqueRoles = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var isPmc = botGenerationDetails.IsPmc;
|
||||
|
||||
// Never show for players
|
||||
var showTypeInNickname = !botGenerationDetails.IsPlayerScav.GetValueOrDefault(false) && _botConfig.ShowTypeInNickname;
|
||||
var roleShouldBeUnique = uniqueRoles?.Contains(botRole.ToLower());
|
||||
|
||||
var isUnique = true;
|
||||
var attempts = 0;
|
||||
while (attempts <= 5)
|
||||
{
|
||||
// Get bot name with leading/trailing whitespace removed
|
||||
var name = (isPmc.GetValueOrDefault(false)) // Explicit handling of PMCs, all other bots will get "first_name last_name"
|
||||
? _botHelper.GetPmcNicknameOfMaxLength(_botConfig.BotNameLengthLimit, botGenerationDetails.Side)
|
||||
: $"{ _randomUtil.GetArrayValue(botJsonTemplate.FirstNames)} {_randomUtil.GetArrayValue(botJsonTemplate.LastNames)}";
|
||||
|
||||
name = name.Trim();
|
||||
|
||||
// Config is set to add role to end of bot name
|
||||
if (showTypeInNickname)
|
||||
{
|
||||
name += $" { botRole}";
|
||||
}
|
||||
|
||||
// Replace pmc bot names with player name + prefix
|
||||
if (botGenerationDetails.IsPmc.GetValueOrDefault(false) && botGenerationDetails.AllPmcsHaveSameNameAsPlayer.GetValueOrDefault(false))
|
||||
{
|
||||
var prefix = _localisationService.GetRandomTextThatMatchesPartialKey("pmc-name_prefix_");
|
||||
name = $"{prefix} { name}";
|
||||
}
|
||||
|
||||
// Is this a role that must be unique
|
||||
if (roleShouldBeUnique.GetValueOrDefault(false))
|
||||
{
|
||||
// Check name in cache
|
||||
isUnique = _usedNameCache.Contains(name);
|
||||
if (!isUnique)
|
||||
{
|
||||
// Not unique
|
||||
if (attempts >= 5)
|
||||
{
|
||||
// 5 attempts to generate a name, pool probably isn't big enough
|
||||
var genericName = $"{ botGenerationDetails.Side} { _randomUtil.GetInt(100000, 999999)}";
|
||||
_logger.Debug($"Failed to find unique name for: { botRole} ${ botGenerationDetails.Side} after 5 attempts, using: ${ genericName}");
|
||||
|
||||
return genericName;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
|
||||
// Try again
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add bot name to cache to prevent being used again
|
||||
_usedNameCache.Add(name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
// Should never reach here
|
||||
return $"BOT {botRole} {botGenerationDetails.BotDifficulty}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,15 +134,23 @@ public class BotNameService
|
||||
/// <param name="bot">Bot to update</param>
|
||||
public void AddRandomPmcNameToBotMainProfileNicknameProperty(BotBase bot)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Simulate bot looking like a player scav with the PMC name in brackets.
|
||||
// E.g. "ScavName (PMC Name)"
|
||||
bot.Info.MainProfileNickname = GetRandomPmcName();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose a random PMC name from bear or usec bot jsons
|
||||
/// </summary>
|
||||
/// <returns>PMC name as string</returns>
|
||||
protected string GetRandomPMCName()
|
||||
protected string GetRandomPmcName()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var bots = _databaseService.GetBots().Types;
|
||||
|
||||
var pmcNames = new List<string>();
|
||||
pmcNames.AddRange(bots["usec"].FirstNames);
|
||||
pmcNames.AddRange(bots["bear"].FirstNames);
|
||||
|
||||
return _randomUtil.GetArrayValue(pmcNames);
|
||||
}
|
||||
}
|
||||
|
||||
+140
-12
@@ -2,7 +2,9 @@ using Core.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Spt.Dialog;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services;
|
||||
@@ -11,18 +13,34 @@ namespace Core.Services;
|
||||
public class GiftService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private readonly MailSendService _mailSendService;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly TimeUtil _timeUtil;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private readonly GiftsConfig _giftConfig;
|
||||
|
||||
public GiftService(
|
||||
public GiftService
|
||||
(
|
||||
ILogger logger,
|
||||
ConfigServer configServer,
|
||||
ProfileHelper profileHelper)
|
||||
MailSendService mailSendService,
|
||||
LocalisationService localisationService,
|
||||
HashUtil hashUtil,
|
||||
TimeUtil timeUtil,
|
||||
ProfileHelper profileHelper,
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_configServer = configServer;
|
||||
_mailSendService = mailSendService;
|
||||
_localisationService = localisationService;
|
||||
_hashUtil = hashUtil;
|
||||
_timeUtil = timeUtil;
|
||||
_profileHelper = profileHelper;
|
||||
_configServer = configServer;
|
||||
|
||||
_giftConfig = _configServer.GetConfig<GiftsConfig>();
|
||||
}
|
||||
@@ -34,7 +52,7 @@ public class GiftService
|
||||
*/
|
||||
public bool GiftExists(string giftId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _giftConfig.Gifts[giftId] is not null;
|
||||
}
|
||||
|
||||
public Gift GetGiftById(string giftId)
|
||||
@@ -50,7 +68,7 @@ public class GiftService
|
||||
*/
|
||||
public Dictionary<string, Gift> GetGifts()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _giftConfig.Gifts;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +77,7 @@ public class GiftService
|
||||
*/
|
||||
public List<string> GetGiftIds()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _giftConfig.Gifts.Keys.ToList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +108,96 @@ public class GiftService
|
||||
_logger.Warning($"Gift {giftId} has items but no collection time limit, defaulting to 48 hours");
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
// Handle system messsages
|
||||
if (giftData.Sender == GiftSenderType.System)
|
||||
{
|
||||
// Has a localisable text id to send to player
|
||||
if (giftData.LocaleTextId is not null)
|
||||
{
|
||||
_mailSendService.SendLocalisedSystemMessageToPlayer(
|
||||
playerId,
|
||||
giftData.LocaleTextId,
|
||||
giftData.Items,
|
||||
giftData.ProfileChangeEvents,
|
||||
_timeUtil.GetHoursAsSeconds(giftData.CollectionTimeHours ?? 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
_mailSendService.SendSystemMessageToPlayer(
|
||||
playerId,
|
||||
giftData.MessageText,
|
||||
giftData.Items,
|
||||
_timeUtil.GetHoursAsSeconds(giftData.CollectionTimeHours ?? 1),
|
||||
giftData.ProfileChangeEvents);
|
||||
}
|
||||
}
|
||||
// Handle user messages
|
||||
else if (giftData.Sender == GiftSenderType.User)
|
||||
{
|
||||
_mailSendService.SendUserMessageToPlayer(
|
||||
playerId,
|
||||
giftData.SenderDetails,
|
||||
giftData.MessageText,
|
||||
giftData.Items,
|
||||
_timeUtil.GetHoursAsSeconds(giftData.CollectionTimeHours ?? 1));
|
||||
}
|
||||
else if (giftData.Sender == GiftSenderType.Trader)
|
||||
{
|
||||
if (giftData.LocaleTextId is not null)
|
||||
{
|
||||
_mailSendService.SendLocalisedNpcMessageToPlayer(
|
||||
playerId,
|
||||
giftData.Trader,
|
||||
MessageType.MESSAGE_WITH_ITEMS,
|
||||
giftData.LocaleTextId,
|
||||
giftData.Items,
|
||||
_timeUtil.GetHoursAsSeconds(giftData.CollectionTimeHours ?? 1),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mailSendService.SendLocalisedNpcMessageToPlayer(
|
||||
playerId,
|
||||
giftData.Trader,
|
||||
MessageType.MESSAGE_WITH_ITEMS,
|
||||
giftData.MessageText,
|
||||
giftData.Items,
|
||||
_timeUtil.GetHoursAsSeconds(giftData.CollectionTimeHours ?? 1),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: further split out into different message systems like above SYSTEM method
|
||||
// Trader / ragfair
|
||||
SendMessageDetails details = new () {
|
||||
RecipientId = playerId,
|
||||
Sender = GetMessageType(giftData),
|
||||
SenderDetails = new ()
|
||||
{
|
||||
Id = GetSenderId(giftData),
|
||||
Aid = 1234567, // TODO - pass proper aid value
|
||||
Info = null,
|
||||
},
|
||||
MessageText = giftData.MessageText,
|
||||
Items = giftData.Items,
|
||||
ItemsMaxStorageLifetimeSeconds = _timeUtil.GetHoursAsSeconds(giftData.CollectionTimeHours ?? 0),
|
||||
};
|
||||
|
||||
if (giftData.Trader is not null) {
|
||||
details.Trader = giftData.Trader;
|
||||
}
|
||||
|
||||
_mailSendService.SendMessageToPlayer(details);
|
||||
}
|
||||
|
||||
_profileHelper.FlagGiftReceivedInProfile(playerId, giftId, maxGiftsToSendCount);
|
||||
|
||||
return GiftSentResult.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,9 +205,19 @@ public class GiftService
|
||||
* @param giftData Gift to send player
|
||||
* @returns trader/user/system id
|
||||
*/
|
||||
protected string? GetSenderId(Gift giftData)
|
||||
private string? GetSenderId(Gift giftData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (giftData.Sender == GiftSenderType.Trader)
|
||||
{
|
||||
return Enum.GetName(typeof(GiftSenderType), giftData.Sender);
|
||||
}
|
||||
|
||||
if (giftData.Sender == GiftSenderType.User)
|
||||
{
|
||||
return giftData.Sender.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +227,18 @@ public class GiftService
|
||||
*/
|
||||
protected MessageType? GetMessageType(Gift giftData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
switch (giftData.Sender)
|
||||
{
|
||||
case GiftSenderType.System:
|
||||
return MessageType.SYSTEM_MESSAGE;
|
||||
case GiftSenderType.Trader:
|
||||
return MessageType.NPC_TRADER;
|
||||
case GiftSenderType.User:
|
||||
return MessageType.USER_MESSAGE;
|
||||
default:
|
||||
_logger.Error(_localisationService.GetText("gift-unable_to_handle_message_type_command", giftData.Sender));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,38 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Utils.Cloners;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class ItemFilterService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICloner _cloner;
|
||||
private readonly DatabaseServer _databaseServer;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private readonly HashSet<string> _lootableItemBlacklistCache = [];
|
||||
private readonly ItemConfig _itemConfig;
|
||||
|
||||
public ItemFilterService(
|
||||
ILogger logger,
|
||||
ICloner cloner,
|
||||
DatabaseServer databaseServer,
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_cloner = cloner;
|
||||
_databaseServer = databaseServer;
|
||||
_configServer = configServer;
|
||||
|
||||
_itemConfig = _configServer.GetConfig<ItemConfig>(ConfigTypes.ITEM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided template id is blacklisted in config/item.json/blacklist
|
||||
* @param tpl template id
|
||||
@@ -89,4 +117,16 @@ public class ItemFilterService
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsLootableItemBlacklisted(string itemKey)
|
||||
{
|
||||
if (_lootableItemBlacklistCache.Count == 0)
|
||||
{
|
||||
foreach (var item in _itemConfig.LootableItemBlacklist) {
|
||||
_lootableItemBlacklistCache.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return _lootableItemBlacklistCache.Contains(itemKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
using Core.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Dialog;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable]
|
||||
public class MailSendService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly TimeUtil _timeUtil;
|
||||
private readonly SaveServer _saveServer;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly NotifierHelper _notifierHelper;
|
||||
private readonly DialogueHelper _dialogueHelper;
|
||||
private readonly NotificationSendHelper _notificationSendHelper;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly TraderHelper _traderHelper;
|
||||
|
||||
private const string _systemSenderId = "59e7125688a45068a6249071";
|
||||
private readonly List<MessageType> _messageTypes = [MessageType.NPC_TRADER, MessageType.FLEAMARKET_MESSAGE];
|
||||
private readonly List<string> _slotNames = ["hideout", "main"];
|
||||
|
||||
public MailSendService
|
||||
(
|
||||
ILogger logger,
|
||||
HashUtil hashUtil,
|
||||
TimeUtil timeUtil,
|
||||
SaveServer saveServer,
|
||||
DatabaseService databaseService,
|
||||
NotifierHelper notifierHelper,
|
||||
DialogueHelper dialogueHelper,
|
||||
NotificationSendHelper notificationSendHelper,
|
||||
LocalisationService localisationService,
|
||||
ItemHelper itemHelper,
|
||||
TraderHelper traderHelper
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_hashUtil = hashUtil;
|
||||
_timeUtil = timeUtil;
|
||||
_saveServer = saveServer;
|
||||
_databaseService = databaseService;
|
||||
_notifierHelper = notifierHelper;
|
||||
_dialogueHelper = dialogueHelper;
|
||||
_localisationService = localisationService;
|
||||
_itemHelper = itemHelper;
|
||||
_traderHelper = traderHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message from an NPC (e.g. prapor) to the player with or without items using direct message text, do not look up any locale
|
||||
* @param sessionId The session ID to send the message to
|
||||
* @param trader The trader sending the message
|
||||
* @param messageType What type the message will assume (e.g. QUEST_SUCCESS)
|
||||
* @param message Text to send to the player
|
||||
* @param items Optional items to send to player
|
||||
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||
*/
|
||||
public void SendDirectNpcMessageToPlayer(
|
||||
string sessionId,
|
||||
string trader,
|
||||
MessageType messageType,
|
||||
string message,
|
||||
List<Item>? items,
|
||||
long? maxStorageTimeSeconds,
|
||||
SystemData? systemData,
|
||||
MessageContentRagfair? ragfair
|
||||
)
|
||||
{
|
||||
if (trader is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("mailsend-missing_trader", new
|
||||
{
|
||||
MessageType = messageType,
|
||||
SessionId = sessionId,
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageDetails details = new()
|
||||
{
|
||||
RecipientId = sessionId,
|
||||
Sender = messageType,
|
||||
DialogType = MessageType.NPC_TRADER,
|
||||
Trader = trader,
|
||||
MessageText = message,
|
||||
Items = new ()
|
||||
};
|
||||
|
||||
// Add items to message
|
||||
if (items?.Count > 0)
|
||||
{
|
||||
details.Items.AddRange(items);
|
||||
details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800;
|
||||
}
|
||||
|
||||
if (systemData is not null)
|
||||
details.SystemData = systemData;
|
||||
|
||||
if (ragfair is not null)
|
||||
details.RagfairDetails = ragfair;
|
||||
|
||||
SendMessageToPlayer(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message from an NPC (e.g. prapor) to the player with or without items
|
||||
* @param sessionId The session ID to send the message to
|
||||
* @param trader The trader sending the message
|
||||
* @param messageType What type the message will assume (e.g. QUEST_SUCCESS)
|
||||
* @param messageLocaleId The localised text to send to player
|
||||
* @param items Optional items to send to player
|
||||
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||
*/
|
||||
public void SendLocalisedNpcMessageToPlayer(
|
||||
string sessionId,
|
||||
string trader,
|
||||
MessageType messageType,
|
||||
string messageLocaleId,
|
||||
List<Item>? items,
|
||||
long? maxStorageTimeSeconds,
|
||||
SystemData? systemData,
|
||||
MessageContentRagfair? ragfair
|
||||
)
|
||||
{
|
||||
if (trader is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("mailsend-missing_trader", new
|
||||
{
|
||||
MessageType = messageType,
|
||||
SessionId = sessionId,
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessageDetails details = new()
|
||||
{
|
||||
RecipientId = sessionId,
|
||||
Sender = messageType,
|
||||
DialogType = MessageType.NPC_TRADER,
|
||||
Trader = trader,
|
||||
TemplateId = messageLocaleId,
|
||||
Items = new()
|
||||
};
|
||||
|
||||
// add items to message
|
||||
|
||||
if (items?.Count > 0)
|
||||
{
|
||||
details.Items.AddRange(items);
|
||||
details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800;
|
||||
}
|
||||
|
||||
if (systemData is not null)
|
||||
details.SystemData = systemData;
|
||||
|
||||
if (ragfair is not null)
|
||||
details.RagfairDetails = ragfair;
|
||||
|
||||
SendMessageToPlayer(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message from SYSTEM to the player with or without items
|
||||
* @param sessionId The session ID to send the message to
|
||||
* @param message The text to send to player
|
||||
* @param items Optional items to send to player
|
||||
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||
*/
|
||||
public void SendSystemMessageToPlayer(
|
||||
string sessionId,
|
||||
string message,
|
||||
List<Item>? items,
|
||||
long? maxStorageTimeSeconds,
|
||||
List<ProfileChangeEvent>? profileChangeEvents)
|
||||
{
|
||||
SendMessageDetails details = new()
|
||||
{
|
||||
RecipientId = sessionId,
|
||||
Sender = MessageType.SYSTEM_MESSAGE,
|
||||
MessageText = message,
|
||||
Items = new()
|
||||
};
|
||||
|
||||
// add items to message
|
||||
if (items?.Count > 0)
|
||||
{
|
||||
var rootItemParentId = _hashUtil.Generate();
|
||||
|
||||
details.Items.AddRange(_itemHelper.AdoptOrphanedItems(rootItemParentId, items));
|
||||
details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800;
|
||||
}
|
||||
|
||||
if ((profileChangeEvents?.Count ?? 0) > 0)
|
||||
details.ProfileChangeEvents = profileChangeEvents;
|
||||
|
||||
SendMessageToPlayer(details);
|
||||
}
|
||||
|
||||
public void SendLocalisedSystemMessageToPlayer(
|
||||
string sessionId,
|
||||
string messageLocaleId,
|
||||
List<Item>? items,
|
||||
List<ProfileChangeEvent>? profileChangeEvents,
|
||||
long? maxStorageTimeSeconds
|
||||
)
|
||||
{
|
||||
SendMessageDetails details = new()
|
||||
{
|
||||
RecipientId = sessionId,
|
||||
Sender = MessageType.SYSTEM_MESSAGE,
|
||||
TemplateId = messageLocaleId,
|
||||
Items = new()
|
||||
};
|
||||
|
||||
// add items to message
|
||||
if (items?.Count > 0)
|
||||
{
|
||||
details.Items.AddRange(items);
|
||||
details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800;
|
||||
}
|
||||
|
||||
if ((profileChangeEvents?.Count ?? 0) > 0)
|
||||
details.ProfileChangeEvents = profileChangeEvents;
|
||||
|
||||
SendMessageToPlayer(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a USER message to a player with or without items
|
||||
* @param sessionId The session ID to send the message to
|
||||
* @param senderId Who is sending the message
|
||||
* @param message The text to send to player
|
||||
* @param items Optional items to send to player
|
||||
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||
*/
|
||||
public void SendUserMessageToPlayer(
|
||||
string sessionId,
|
||||
UserDialogInfo senderDetails,
|
||||
string message,
|
||||
List<Item>? items,
|
||||
long? maxStorageTimeSeconds
|
||||
)
|
||||
{
|
||||
SendMessageDetails details = new()
|
||||
{
|
||||
RecipientId = sessionId,
|
||||
Sender = MessageType.USER_MESSAGE,
|
||||
SenderDetails = senderDetails,
|
||||
MessageText = message,
|
||||
Items = new ()
|
||||
};
|
||||
|
||||
// add items to message
|
||||
if (items?.Count > 0)
|
||||
{
|
||||
details.Items.AddRange(items);
|
||||
details.ItemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800;
|
||||
}
|
||||
|
||||
SendMessageToPlayer(details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Large function to send messages to players from a variety of sources (SYSTEM/NPC/USER)
|
||||
* Helper functions in this class are available to simplify common actions
|
||||
* @param messageDetails Details needed to send a message to the player
|
||||
*/
|
||||
public void SendMessageToPlayer(SendMessageDetails messageDetails)
|
||||
{
|
||||
// Get dialog, create if doesn't exist
|
||||
var senderDialog = GetDialog(messageDetails);
|
||||
|
||||
// Flag dialog as containing a new message to player
|
||||
senderDialog.New++;
|
||||
|
||||
// Craft message
|
||||
var message = CreateDialogMessage(senderDialog.Id, messageDetails);
|
||||
|
||||
// Create items array
|
||||
// Generate item stash if we have rewards.
|
||||
var itemsToSendToPlayer = ProcessItemsBeforeAddingToMail(senderDialog.Type, messageDetails);
|
||||
|
||||
// If there's items to send to player, flag dialog as containing attachments
|
||||
if ((itemsToSendToPlayer.Data?.Count ?? 0) > 0)
|
||||
{
|
||||
senderDialog.AttachmentsNew += 1;
|
||||
}
|
||||
|
||||
// Store reward items inside message and set appropriate flags inside message
|
||||
AddRewardItemsToMessage(message, itemsToSendToPlayer, messageDetails.ItemsMaxStorageLifetimeSeconds);
|
||||
|
||||
if (messageDetails.ProfileChangeEvents is not null)
|
||||
message.ProfileChangeEvents = messageDetails.ProfileChangeEvents;
|
||||
|
||||
// Add message to dialog
|
||||
senderDialog.Messages.Add(message);
|
||||
|
||||
// TODO: clean up old code here
|
||||
// Offer Sold notifications are now separate from the main notification
|
||||
if (
|
||||
_messageTypes.Contains(senderDialog.Type ?? MessageType.SYSTEM_MESSAGE) &&
|
||||
messageDetails?.RagfairDetails is not null
|
||||
)
|
||||
{
|
||||
var offerSoldMessage = _notifierHelper.CreateRagfairOfferSoldNotification(
|
||||
message,
|
||||
messageDetails.RagfairDetails
|
||||
);
|
||||
_notificationSendHelper.SendMessage(messageDetails.RecipientId, offerSoldMessage);
|
||||
message.MessageType = MessageType.MESSAGE_WITH_ITEMS; // Should prevent getting the same notification popup twice
|
||||
}
|
||||
|
||||
// Send message off to player so they get it in client
|
||||
var notificationMessage = _notifierHelper.CreateNewMessageNotification(message);
|
||||
_notificationSendHelper.SendMessage(messageDetails.RecipientId, notificationMessage);
|
||||
}
|
||||
|
||||
public void SendPlayerMessageToNpc(string sessionId, string targetNpcId, string message)
|
||||
{
|
||||
var playerProfile = _saveServer.GetProfile(sessionId);
|
||||
var dialogWithNpc = playerProfile.DialogueRecords[targetNpcId];
|
||||
if (dialogWithNpc is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("mailsend-missing_npc_dialog", targetNpcId));
|
||||
return;
|
||||
}
|
||||
|
||||
dialogWithNpc.Messages.Add(new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
DateTime = _timeUtil.GetTimeStamp(),
|
||||
HasRewards = false,
|
||||
UserId = playerProfile.CharacterData.PmcData.Id,
|
||||
MessageType = MessageType.USER_MESSAGE,
|
||||
RewardCollected = false,
|
||||
Text = message
|
||||
});
|
||||
}
|
||||
|
||||
private Message CreateDialogMessage(string dialogId, SendMessageDetails messageDetails)
|
||||
{
|
||||
Message message = new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
UserId = dialogId,
|
||||
MessageType = messageDetails.DialogType,
|
||||
DateTime = _timeUtil.GetTimeStamp(),
|
||||
Text = (messageDetails.TemplateId is not null) ? "" : messageDetails.MessageText,
|
||||
TemplateId = messageDetails.TemplateId,
|
||||
HasRewards = false,
|
||||
RewardCollected = false,
|
||||
SystemData = messageDetails.SystemData is not null ? messageDetails.SystemData : null,
|
||||
ProfileChangeEvents = (messageDetails.ProfileChangeEvents?.Count == 0) ? messageDetails.ProfileChangeEvents : null
|
||||
};
|
||||
|
||||
// Clean up empty system data
|
||||
// if (message.SystemData is null) {
|
||||
// delete message.SystemData;
|
||||
// }
|
||||
|
||||
// Clean up empty template id
|
||||
// if (message.TemplateId is null)
|
||||
// delete message.templateId;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add items to message and adjust various properties to reflect the items being added
|
||||
* @param message Message to add items to
|
||||
* @param itemsToSendToPlayer Items to add to message
|
||||
* @param maxStorageTimeSeconds total time items are stored in mail before being deleted
|
||||
*/
|
||||
private void AddRewardItemsToMessage(Message message, MessageItems? itemsToSendToPlayer, long? maxStorageTimeSeconds)
|
||||
{
|
||||
if ((itemsToSendToPlayer?.Data?.Count ?? 0) > 0)
|
||||
{
|
||||
message.Items = itemsToSendToPlayer;
|
||||
message.HasRewards = true;
|
||||
message.MaxStorageTime = maxStorageTimeSeconds;
|
||||
message.RewardCollected = false;
|
||||
}
|
||||
}
|
||||
|
||||
private MessageItems ProcessItemsBeforeAddingToMail(MessageType? dialogType, SendMessageDetails messageDetails)
|
||||
{
|
||||
var items = _databaseService.GetItems();
|
||||
|
||||
MessageItems itemsToSendToPlayer = new();
|
||||
if ((messageDetails.Items?.Count ?? 0) > 0)
|
||||
{
|
||||
// Find base item that should be the 'primary' + have its parent id be used as the dialogs 'stash' value
|
||||
var parentItem = GetBaseItemFromRewards(messageDetails.Items);
|
||||
if (parentItem is null)
|
||||
{
|
||||
_localisationService.GetText("mailsend-missing_parent", new
|
||||
{
|
||||
TraderId = messageDetails.Trader,
|
||||
Sender = messageDetails.Sender,
|
||||
});
|
||||
|
||||
return itemsToSendToPlayer;
|
||||
}
|
||||
|
||||
// No parent id, generate random id and add (doesn't need to be actual parentId from db, only unique)
|
||||
if (parentItem?.ParentId is null)
|
||||
parentItem.ParentId = _hashUtil.Generate();
|
||||
|
||||
itemsToSendToPlayer = new()
|
||||
{
|
||||
Stash = parentItem.ParentId, Data = new()
|
||||
};
|
||||
|
||||
// Ensure Ids are unique and cont collide with items in player inventory later
|
||||
messageDetails.Items = _itemHelper.ReplaceIDs(messageDetails.Items);
|
||||
|
||||
foreach (var reward in messageDetails.Items)
|
||||
{
|
||||
// Ensure item exists in items db
|
||||
var itemTemplate = items[reward.Template];
|
||||
if (itemTemplate is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("dialog-missing_item_template", new
|
||||
{
|
||||
Tpl = reward.Template,
|
||||
Type = dialogType,
|
||||
}));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure every 'base/root' item has the same parentId + has a slotid of 'main'
|
||||
if (!(reward.SlotId is not null) || reward.SlotId == "hideout" || reward.ParentId == parentItem.ParentId)
|
||||
{
|
||||
// Reward items NEED a parent id + slotid
|
||||
reward.ParentId = parentItem.ParentId;
|
||||
reward.SlotId = "main";
|
||||
}
|
||||
|
||||
// Boxes can contain sub-items
|
||||
if (_itemHelper.IsOfBaseclass(itemTemplate.Id, [BaseClasses.AMMO_BOX]))
|
||||
{
|
||||
var boxAndCartridges = new List<Item>();
|
||||
boxAndCartridges.Add(reward);
|
||||
_itemHelper.AddCartridgesToAmmoBox(boxAndCartridges, itemTemplate);
|
||||
|
||||
// Push box + cartridge children into array
|
||||
itemsToSendToPlayer.Data.AddRange(boxAndCartridges);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (itemTemplate.Properties.StackSlots is not null)
|
||||
_logger.Error(_localisationService.GetText("mail-unable_to_give_gift_not_handled", itemTemplate.Id));
|
||||
|
||||
// Item is sanitised and ready to be pushed into holding array
|
||||
itemsToSendToPlayer.Data.Add(reward);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty data property if no rewards
|
||||
// if (itemsToSendToPlayer.Data.Count == 0)
|
||||
// delete itemsToSendToPlayer.data;
|
||||
}
|
||||
|
||||
return itemsToSendToPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find the most correct item to be the 'primary' item in a reward mail
|
||||
* @param items Possible items to choose from
|
||||
* @returns Chosen 'primary' item
|
||||
*/
|
||||
private Item GetBaseItemFromRewards(List<Item>? items)
|
||||
{
|
||||
// Only one item in reward, return it
|
||||
if (items?.Count == 1)
|
||||
return items[0];
|
||||
|
||||
// Find first item with slotId that indicates its a 'base' item
|
||||
var item = items.FirstOrDefault(x => _slotNames.Contains(x.SlotId ?? ""));
|
||||
if (item is not null)
|
||||
return item;
|
||||
|
||||
// Not a singlular item + no items have a hideout/main slotid
|
||||
// Look for first item without parent id
|
||||
item = items.FirstOrDefault(x => x.ParentId is null);
|
||||
if (item is not null)
|
||||
return item;
|
||||
|
||||
// Just return first item in array
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dialog with a specified entity (user/trader)
|
||||
* Create and store empty dialog if none exists in profile
|
||||
* @param messageDetails Data on what message should do
|
||||
* @returns Relevant Dialogue
|
||||
*/
|
||||
private Dialogue GetDialog(SendMessageDetails messageDetails)
|
||||
{
|
||||
var dialogsInProfile = _dialogueHelper.GetDialogsForProfile(messageDetails.RecipientId);
|
||||
var senderId = GetMessageSenderIdByType(messageDetails);
|
||||
if (senderId is null)
|
||||
throw new Exception(_localisationService.GetText("mail-unable_to_find_message_sender_by_id", messageDetails.Sender));
|
||||
|
||||
// Does dialog exist
|
||||
var senderDialog = dialogsInProfile.FirstOrDefault(x => x.Key == senderId).Value;
|
||||
if (senderDialog is null)
|
||||
{
|
||||
// create if doesnt
|
||||
dialogsInProfile[senderId] = senderDialog = new Dialogue()
|
||||
{
|
||||
Id = senderId,
|
||||
Type = (messageDetails.DialogType is not null) ? messageDetails.DialogType : messageDetails.Sender,
|
||||
Messages = new(),
|
||||
Pinned = false,
|
||||
New = 0,
|
||||
AttachmentsNew = 0
|
||||
};
|
||||
|
||||
senderDialog = dialogsInProfile[senderId];
|
||||
}
|
||||
|
||||
return senderDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate sender id by the sender enum type
|
||||
* @param messageDetails
|
||||
* @returns gets an id of the individual sending it
|
||||
*/
|
||||
private string? GetMessageSenderIdByType(SendMessageDetails messageDetails)
|
||||
{
|
||||
if (messageDetails.Sender == MessageType.SYSTEM_MESSAGE)
|
||||
return _systemSenderId;
|
||||
|
||||
if (messageDetails.Sender == MessageType.NPC_TRADER || messageDetails.DialogType == MessageType.NPC_TRADER)
|
||||
return (messageDetails.Trader is not null) ? _traderHelper.GetValidTraderIdByEnumValue(messageDetails.Trader) : null;
|
||||
|
||||
if (messageDetails.Sender == MessageType.USER_MESSAGE)
|
||||
return messageDetails.SenderDetails?.Id;
|
||||
|
||||
if (messageDetails.SenderDetails?.Id is not null)
|
||||
return messageDetails.SenderDetails.Id;
|
||||
|
||||
if (messageDetails.Trader is not null)
|
||||
return _traderHelper.GetValidTraderIdByEnumValue(messageDetails.Trader);
|
||||
|
||||
_logger.Warning($"Unable to handle message of type: {messageDetails.Sender}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,29 @@
|
||||
using Core.Annotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Inventory;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Dialog;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
using MapMarker = Core.Models.Eft.Common.Tables.MapMarker;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
[Injectable]
|
||||
public class MapMarkerService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MapMarkerService
|
||||
(
|
||||
ILogger logger
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add note to a map item in player inventory
|
||||
/// </summary>
|
||||
@@ -19,7 +32,17 @@ public class MapMarkerService
|
||||
/// <returns>Item</returns>
|
||||
public Item CreateMarkerOnMap(PmcData pmcData, InventoryCreateMarkerRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Get map from inventory
|
||||
var mapItem = pmcData?.Inventory?.Items?.FirstOrDefault((i) => i?.Id == request?.Item);
|
||||
|
||||
// add marker to map item
|
||||
mapItem.Upd.Map = mapItem?.Upd?.Map ?? new() { Markers = new() };
|
||||
|
||||
// Update request note with text, then add to maps upd
|
||||
request.MapMarker.Note = SanitiseMapMarkerText(request.MapMarker.Note);
|
||||
mapItem?.Upd?.Map?.Markers?.Add(request.MapMarker);
|
||||
|
||||
return mapItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +53,16 @@ public class MapMarkerService
|
||||
/// <returns>Item</returns>
|
||||
public Item DeleteMarkerFromMap(PmcData pmcData, InventoryDeleteMarkerRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Get map from inventory
|
||||
var mapItem = pmcData.Inventory.Items.FirstOrDefault((item) => item.Id == request.Item);
|
||||
|
||||
// remove marker
|
||||
var markers = mapItem.Upd.Map.Markers.Where((marker) => {
|
||||
return marker.X != request.X && marker.Y != request.Y;
|
||||
}).ToList();
|
||||
mapItem.Upd.Map.Markers = markers;
|
||||
|
||||
return mapItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +73,16 @@ public class MapMarkerService
|
||||
/// <returns>Item</returns>
|
||||
public Item EditMarkerOnMap(PmcData pmcData, InventoryEditMarkerRequestData request)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Get map from inventory
|
||||
var mapItem = pmcData.Inventory.Items.FirstOrDefault((item) => item.Id == request.Item);
|
||||
|
||||
// edit marker
|
||||
var indexOfExistingNote = mapItem.Upd.Map.Markers.IndexOf(request.MapMarker);
|
||||
request.MapMarker.Note = SanitiseMapMarkerText(request.MapMarker.Note);
|
||||
mapItem.Upd.Map.Markers.RemoveAt(indexOfExistingNote);
|
||||
mapItem.Upd.Map.Markers.Add(request.MapMarker);
|
||||
|
||||
return mapItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,192 +92,6 @@ public class MapMarkerService
|
||||
/// <returns>Sanitised map marker text</returns>
|
||||
protected string SanitiseMapMarkerText(string mapNoteText)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message from an NPC (e.g. prapor) to the player with or without items using direct message text, do not look up any locale
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session ID to send the message to</param>
|
||||
/// <param name="trader">The trader sending the message</param>
|
||||
/// <param name="messageType">What type the message will assume (e.g. QUEST_SUCCESS)</param>
|
||||
/// <param name="message">Text to send to the player</param>
|
||||
/// <param name="items">Optional items to send to player</param>
|
||||
/// <param name="maxStorageTimeSeconds">Optional time to collect items before they expire</param>
|
||||
public void SendDirectNpcMessageToPlayer(
|
||||
string sessionId,
|
||||
object trader,
|
||||
MessageType messageType,
|
||||
string message,
|
||||
List<Item> items = null,
|
||||
int? maxStorageTimeSeconds = null,
|
||||
SystemData systemData = null,
|
||||
MessageContentRagfair ragfair = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message from an NPC (e.g. prapor) to the player with or without items
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session ID to send the message to</param>
|
||||
/// <param name="trader">The trader sending the message</param>
|
||||
/// <param name="messageType">What type the message will assume (e.g. QUEST_SUCCESS)</param>
|
||||
/// <param name="messageLocaleId">The localised text to send to player</param>
|
||||
/// <param name="items">Optional items to send to player</param>
|
||||
/// <param name="maxStorageTimeSeconds">Optional time to collect items before they expire</param>
|
||||
public void SendLocalisedNpcMessageToPlayer(
|
||||
string sessionId,
|
||||
object trader,
|
||||
MessageType messageType,
|
||||
string messageLocaleId,
|
||||
List<Item> items = null,
|
||||
int? maxStorageTimeSeconds = null,
|
||||
SystemData systemData = null,
|
||||
MessageContentRagfair ragfair = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message from SYSTEM to the player with or without items
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session ID to send the message to</param>
|
||||
/// <param name="message">The text to send to player</param>
|
||||
/// <param name="items">Optional items to send to player</param>
|
||||
/// <param name="maxStorageTimeSeconds">Optional time to collect items before they expire</param>
|
||||
public void SendSystemMessageToPlayer(
|
||||
string sessionId,
|
||||
string message,
|
||||
List<Item> items = null,
|
||||
int? maxStorageTimeSeconds = null,
|
||||
List<ProfileChangeEvent> profileChangeEvents = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message from SYSTEM to the player with or without items with localised text
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session ID to send the message to</param>
|
||||
/// <param name="messageLocaleId">Id of key from locale file to send to player</param>
|
||||
/// <param name="items">Optional items to send to player</param>
|
||||
/// <param name="maxStorageTimeSeconds">Optional time to collect items before they expire</param>
|
||||
public void SendLocalisedSystemMessageToPlayer(
|
||||
string sessionId,
|
||||
string messageLocaleId,
|
||||
List<Item> items = null,
|
||||
List<ProfileChangeEvent> profileChangeEvents = null,
|
||||
int? maxStorageTimeSeconds = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a USER message to a player with or without items
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session ID to send the message to</param>
|
||||
/// <param name="senderDetails">Who is sending the message</param>
|
||||
/// <param name="message">The text to send to player</param>
|
||||
/// <param name="items">Optional items to send to player</param>
|
||||
/// <param name="maxStorageTimeSeconds">Optional time to collect items before they expire</param>
|
||||
public void SendUserMessageToPlayer(
|
||||
string sessionId,
|
||||
UserDialogInfo senderDetails,
|
||||
string message,
|
||||
List<Item> items = null,
|
||||
int? maxStorageTimeSeconds = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Large function to send messages to players from a variety of sources (SYSTEM/NPC/USER)
|
||||
/// Helper functions in this class are available to simplify common actions
|
||||
/// </summary>
|
||||
/// <param name="messageDetails">Details needed to send a message to the player</param>
|
||||
public void SendMessageToPlayer(SendMessageDetails messageDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message from the player to an NPC
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Player id</param>
|
||||
/// <param name="targetNpcId">NPC message is sent to</param>
|
||||
/// <param name="message">Text to send to NPC</param>
|
||||
public void SendPlayerMessageToNpc(string sessionId, string targetNpcId, string message)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a message for storage inside a dialog in the player profile
|
||||
/// </summary>
|
||||
/// <param name="senderDialog">Id of dialog that will hold the message</param>
|
||||
/// <param name="messageDetails">Various details on what the message must contain/do</param>
|
||||
/// <returns>Message</returns>
|
||||
protected Message CreateDialogMessage(string dialogId, SendMessageDetails messageDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add items to message and adjust various properties to reflect the items being added
|
||||
/// </summary>
|
||||
/// <param name="message">Message to add items to</param>
|
||||
/// <param name="itemsToSendToPlayer">Items to add to message</param>
|
||||
/// <param name="maxStorageTimeSeconds">total time items are stored in mail before being deleted</param>
|
||||
protected void AddRewardItemsToMessage(
|
||||
Message message,
|
||||
List<MessageItems> itemsToSendToPlayer,
|
||||
int? maxStorageTimeSeconds)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// perform various sanitising actions on the items before they're considered ready for insertion into message
|
||||
/// </summary>
|
||||
/// <param name="dialogType">The type of the dialog that will hold the reward items being processed</param>
|
||||
/// <param name="messageDetails"></param>
|
||||
/// <returns>Sanitised items</returns>
|
||||
protected List<MessageItems> ProcessItemsBeforeAddingToMail(
|
||||
MessageType dialogType,
|
||||
SendMessageDetails messageDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find the most correct item to be the 'primary' item in a reward mail
|
||||
/// </summary>
|
||||
/// <param name="items">Possible items to choose from</param>
|
||||
/// <returns>Chosen 'primary' item</returns>
|
||||
protected Item GetBaseItemFromRewards(List<Item> items)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a dialog with a specified entity (user/trader)
|
||||
/// Create and store empty dialog if none exists in profile
|
||||
/// </summary>
|
||||
/// <param name="messageDetails">Data on what message should do</param>
|
||||
/// <returns>Relevant Dialogue</returns>
|
||||
protected Dialogue GetDialog(SendMessageDetails messageDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the appropriate sender id by the sender enum type
|
||||
/// </summary>
|
||||
/// <param name="messageDetails"></param>
|
||||
/// <returns>gets an id of the individual sending it</returns>
|
||||
protected string GetMessageSenderIdByType(SendMessageDetails messageDetails)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Regex.Replace(mapNoteText, @"[^\p{L}\d\s]", "", RegexOptions.CultureInvariant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using Core.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Spt.Bots;
|
||||
using System.Text.RegularExpressions;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services
|
||||
{
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class MatchBotDetailsCacheService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly LocalisationService _localisationService;
|
||||
|
||||
private readonly Dictionary<string, BotBase> _botDetailsCache;
|
||||
|
||||
public MatchBotDetailsCacheService(
|
||||
ILogger logger,
|
||||
LocalisationService localisationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_localisationService = localisationService;
|
||||
|
||||
_botDetailsCache = new();
|
||||
}
|
||||
|
||||
public void CacheBot(BotBase botToCache)
|
||||
{
|
||||
if (botToCache.Info.Nickname is null)
|
||||
{
|
||||
_logger.Warning($"Unable to cache: { botToCache.Info.Settings.Role} bot with id: ${ botToCache.Id} as it lacks a nickname");
|
||||
return;
|
||||
}
|
||||
|
||||
var key = $"{botToCache.Info.Nickname.Trim()}{botToCache.Info.Side}";
|
||||
_botDetailsCache.TryAdd(key, botToCache);
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
_botDetailsCache.Clear();
|
||||
}
|
||||
|
||||
public BotBase GetBotByNameAndSide(string botName, string botSide)
|
||||
{
|
||||
var botInCache = _botDetailsCache.GetValueOrDefault($"{botName}{botSide}`", null);
|
||||
if (botInCache is null)
|
||||
{
|
||||
_logger.Warning($"Bot not found in match bot cache: {botName.ToLower()} { botSide}");
|
||||
}
|
||||
|
||||
return botInCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,122 @@
|
||||
using Core.Annotations;
|
||||
using Core.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Services;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class SeasonalEventService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly DatabaseService _databaseService;
|
||||
|
||||
//private readonly DatabaseImporter _databaseImporter;
|
||||
private readonly GiftService _giftService;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly BotHelper _botHelper;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private bool _christmasEventActive = false;
|
||||
private bool _halloweenEventActive = false;
|
||||
|
||||
private readonly SeasonalEventConfig _seasonalEventConfig;
|
||||
private readonly QuestConfig _questConfig;
|
||||
private readonly HttpConfig _httpConfig;
|
||||
private readonly WeatherConfig _weatherConfig;
|
||||
private readonly LocationConfig _locationConfig;
|
||||
|
||||
private List<SeasonalEvent> _currentlyActiveEvents = [];
|
||||
|
||||
private readonly IReadOnlyList<string> _christmasEventItems =
|
||||
[
|
||||
ItemTpl.FACECOVER_FAKE_WHITE_BEARD,
|
||||
ItemTpl.BARTER_CHRISTMAS_TREE_ORNAMENT_RED,
|
||||
ItemTpl.BARTER_CHRISTMAS_TREE_ORNAMENT_VIOLET,
|
||||
ItemTpl.BARTER_CHRISTMAS_TREE_ORNAMENT_SILVER,
|
||||
ItemTpl.HEADWEAR_DED_MOROZ_HAT,
|
||||
ItemTpl.HEADWEAR_SANTA_HAT,
|
||||
ItemTpl.BACKPACK_SANTAS_BAG,
|
||||
ItemTpl.RANDOMLOOTCONTAINER_NEW_YEAR_GIFT_BIG,
|
||||
ItemTpl.RANDOMLOOTCONTAINER_NEW_YEAR_GIFT_MEDIUM,
|
||||
ItemTpl.RANDOMLOOTCONTAINER_NEW_YEAR_GIFT_SMALL
|
||||
];
|
||||
|
||||
private readonly IReadOnlyList<string> _halloweenEventItems =
|
||||
[
|
||||
ItemTpl.FACECOVER_SPOOKY_SKULL_MASK,
|
||||
ItemTpl.RANDOMLOOTCONTAINER_PUMPKIN_RAND_LOOT_CONTAINER,
|
||||
ItemTpl.HEADWEAR_JACKOLANTERN_TACTICAL_PUMPKIN_HELMET,
|
||||
ItemTpl.FACECOVER_FACELESS_MASK,
|
||||
ItemTpl.FACECOVER_JASON_MASK,
|
||||
ItemTpl.FACECOVER_MISHA_MAYOROV_MASK,
|
||||
ItemTpl.FACECOVER_SLENDER_MASK,
|
||||
ItemTpl.FACECOVER_GHOUL_MASK,
|
||||
ItemTpl.FACECOVER_HOCKEY_PLAYER_MASK_CAPTAIN,
|
||||
ItemTpl.FACECOVER_HOCKEY_PLAYER_MASK_BRAWLER,
|
||||
ItemTpl.FACECOVER_HOCKEY_PLAYER_MASK_QUIET
|
||||
];
|
||||
|
||||
public SeasonalEventService
|
||||
(
|
||||
ILogger logger,
|
||||
DatabaseService databaseService,
|
||||
//DatabaseImporter databaseImporter,
|
||||
GiftService giftService,
|
||||
LocalisationService localisationService,
|
||||
BotHelper botHelper,
|
||||
ProfileHelper profileHelper,
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_databaseService = databaseService;
|
||||
//_databaseImporter = databaseImporter;
|
||||
_giftService = giftService;
|
||||
_localisationService = localisationService;
|
||||
_botHelper = botHelper;
|
||||
_profileHelper = profileHelper;
|
||||
_configServer = configServer;
|
||||
|
||||
_seasonalEventConfig = _configServer.GetConfig<SeasonalEventConfig>(ConfigTypes.SEASONAL_EVENT);
|
||||
_questConfig = _configServer.GetConfig<QuestConfig>(ConfigTypes.QUEST);
|
||||
_httpConfig = _configServer.GetConfig<HttpConfig>(ConfigTypes.HTTP);
|
||||
_weatherConfig = _configServer.GetConfig<WeatherConfig>(ConfigTypes.WEATHER);
|
||||
_locationConfig = _configServer.GetConfig<LocationConfig>(ConfigTypes.LOCATION);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an array of christmas items found in bots inventories as loot
|
||||
/// </summary>
|
||||
/// <returns>array</returns>
|
||||
public string[] GetChristmasEventItems()
|
||||
public IEnumerable<string> GetChristmasEventItems()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _christmasEventItems;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an array of halloween items found in bots inventories as loot
|
||||
/// </summary>
|
||||
/// <returns>array</returns>
|
||||
public string[] GetHalloweenEventItems()
|
||||
public IEnumerable<string> GetHalloweenEventItems()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _halloweenEventItems;
|
||||
}
|
||||
|
||||
public bool ItemIsChristmasRelated(string itemTpl)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _christmasEventItems.Contains(itemTpl);
|
||||
}
|
||||
|
||||
public bool ItemIsHalloweenRelated(string itemTpl)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _halloweenEventItems.Contains(itemTpl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +126,7 @@ public class SeasonalEventService
|
||||
/// <returns></returns>
|
||||
public bool ItemIsSeasonalRelated(string itemTpl)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _christmasEventItems.Contains(itemTpl) || _halloweenEventItems.Contains(itemTpl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,7 +135,7 @@ public class SeasonalEventService
|
||||
/// <returns>Array of active events</returns>
|
||||
public List<SeasonalEvent> GetActiveEvents()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _currentlyActiveEvents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,9 +144,20 @@ public class SeasonalEventService
|
||||
/// or, if halloween and christmas are inactive, return both sets of items
|
||||
/// </summary>
|
||||
/// <returns>array of tpl strings</returns>
|
||||
public string[] GetInactiveSeasonalEventItems()
|
||||
public List<string> GetInactiveSeasonalEventItems()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var items = new List<string>();
|
||||
if (!ChristmasEventEnabled())
|
||||
{
|
||||
items.AddRange(_christmasEventItems);
|
||||
}
|
||||
|
||||
if (!HalloweenEventEnabled())
|
||||
{
|
||||
items.AddRange(_halloweenEventItems);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -73,7 +166,7 @@ public class SeasonalEventService
|
||||
/// <returns>true if event is active</returns>
|
||||
public bool SeasonalEventEnabled()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _christmasEventActive || _halloweenEventActive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +175,7 @@ public class SeasonalEventService
|
||||
/// <returns>true if active</returns>
|
||||
public bool ChristmasEventEnabled()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _christmasEventActive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,7 +184,7 @@ public class SeasonalEventService
|
||||
/// <returns>true if active</returns>
|
||||
public bool HalloweenEventEnabled()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _halloweenEventActive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +193,7 @@ public class SeasonalEventService
|
||||
/// <returns>true if seasonal events should be checked for</returns>
|
||||
public bool IsAutomaticEventDetectionEnabled()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _seasonalEventConfig.EnableSeasonalEventDetection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +203,7 @@ public class SeasonalEventService
|
||||
/// <returns>bots with equipment changes</returns>
|
||||
protected Dictionary<string, Dictionary<string, Dictionary<string, int>>> GetEventBotGear(SeasonalEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _seasonalEventConfig.EventGear.GetValueOrDefault(eventType, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,7 +213,7 @@ public class SeasonalEventService
|
||||
/// <returns>bots with loot changes</returns>
|
||||
protected Dictionary<string, Dictionary<string, Dictionary<string, int>>> GetEventBotLoot(SeasonalEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _seasonalEventConfig.EventLoot.GetValueOrDefault(eventType, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,7 +222,7 @@ public class SeasonalEventService
|
||||
/// <returns>Record with event name + start/end date</returns>
|
||||
public List<SeasonalEvent> GetEventDetails()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _seasonalEventConfig.Events;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -140,7 +233,13 @@ public class SeasonalEventService
|
||||
/// <returns>true if related</returns>
|
||||
public bool IsQuestRelatedToEvent(string questId, SeasonalEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var eventQuestData = _questConfig.EventQuests.GetValueOrDefault(questId, null);
|
||||
if (eventQuestData?.Season == eventType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -148,7 +247,14 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
public void EnableSeasonalEvents()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (_currentlyActiveEvents.Count > 0)
|
||||
{
|
||||
var globalConfig = _databaseService.GetGlobals().Configuration;
|
||||
foreach (var activeEvent in _currentlyActiveEvents)
|
||||
{
|
||||
UpdateGlobalEvents(globalConfig, activeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -158,7 +264,17 @@ public class SeasonalEventService
|
||||
/// <returns>True if event was successfully force enabled</returns>
|
||||
public bool ForceSeasonalEvent(SeasonalEventType eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var globalConfig = _databaseService.GetGlobals().Configuration;
|
||||
var seasonEvent = _seasonalEventConfig.Events.FirstOrDefault((e) => e.Type == eventType);
|
||||
if (seasonEvent is null)
|
||||
{
|
||||
_logger.Warning($"Unable to force event: {eventType} as it cannot be found in events config");
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateGlobalEvents(globalConfig, seasonEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,7 +282,25 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
public void CacheActiveEvents()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var currentDate = DateTimeOffset.UtcNow.DateTime;
|
||||
var seasonalEvents = GetEventDetails();
|
||||
|
||||
// reset existing data
|
||||
_currentlyActiveEvents = new();
|
||||
|
||||
// Add active events to array
|
||||
foreach (var events in seasonalEvents)
|
||||
{
|
||||
if (!events.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DateIsBetweenTwoDates(currentDate, events.StartMonth, events.StartDay, events.EndMonth, events.EndDay))
|
||||
{
|
||||
_currentlyActiveEvents.Add(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -175,7 +309,30 @@ public class SeasonalEventService
|
||||
/// <returns>Season enum value</returns>
|
||||
public Season GetActiveWeatherSeason()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (_weatherConfig.OverrideSeason.HasValue)
|
||||
{
|
||||
return _weatherConfig.OverrideSeason.Value;
|
||||
}
|
||||
|
||||
var currentDate = new DateTime();
|
||||
foreach (var seasonRange in _weatherConfig.SeasonDates)
|
||||
{
|
||||
if (
|
||||
DateIsBetweenTwoDates(
|
||||
currentDate,
|
||||
seasonRange.StartMonth,
|
||||
seasonRange.StartDay,
|
||||
seasonRange.EndMonth,
|
||||
seasonRange.EndDay)
|
||||
)
|
||||
{
|
||||
return seasonRange.SeasonType;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Warning(_localisationService.GetText("season-no_matching_season_found_for_date"));
|
||||
|
||||
return Season.SUMMER;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -189,9 +346,12 @@ public class SeasonalEventService
|
||||
/// <param name="endMonth">Upper bound for month</param>
|
||||
/// <param name="endDay">Upper bound for day</param>
|
||||
/// <returns>True when inside date range</returns>
|
||||
protected bool DateIsBetweenTwoDates(DateTime dateToCheck, int startMonth, int startDay, int endMonth, int endDay)
|
||||
private bool DateIsBetweenTwoDates(DateTime dateToCheck, int startMonth, int startDay, int endMonth, int endDay)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var eventStartDate = new DateTime(dateToCheck.Year, startMonth, startDay);
|
||||
var eventEndDate = new DateTime(dateToCheck.Year, endMonth, endDay, 23, 59, 0);
|
||||
|
||||
return dateToCheck >= eventStartDate && dateToCheck <= eventEndDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,9 +359,78 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
/// <param name="botInventory">Bots inventory to iterate over</param>
|
||||
/// <param name="botRole">the role of the bot being processed</param>
|
||||
public void RemoveChristmasItemsFromBotInventory(BotBaseInventory botInventory, string botRole)
|
||||
public void RemoveChristmasItemsFromBotInventory(BotTypeInventory botInventory, string botRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var christmasItems = GetChristmasEventItems();
|
||||
List<EquipmentSlots> equipmentSlotsToFilter = [EquipmentSlots.FaceCover, EquipmentSlots.Headwear, EquipmentSlots.Backpack, EquipmentSlots.TacticalVest];
|
||||
List<string> lootContainersToFilter = ["Backpack", "Pockets", "TacticalVest"];
|
||||
|
||||
// Remove christmas related equipment
|
||||
foreach (var equipmentSlotKey in equipmentSlotsToFilter)
|
||||
{
|
||||
if (botInventory.Equipment[equipmentSlotKey] is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("seasonal-missing_equipment_slot_on_bot", new
|
||||
{
|
||||
EquipmentSlot = equipmentSlotKey,
|
||||
BotRole = botRole,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Dictionary<string, double> equipment = botInventory.Equipment[equipmentSlotKey];
|
||||
botInventory.Equipment[equipmentSlotKey] = equipment.Where(i => !_christmasEventItems.Contains(i.Key)).ToDictionary();
|
||||
}
|
||||
|
||||
// Remove christmas related loot from loot containers
|
||||
var props = botInventory.Items.GetType().GetProperties();
|
||||
foreach (var lootContainerKey in lootContainersToFilter)
|
||||
{
|
||||
var prop = (Dictionary<string, double>?)props.FirstOrDefault(p => p.Name.ToLower() == lootContainerKey.ToLower()).GetValue(botInventory.Items);
|
||||
|
||||
if (prop is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("seasonal-missing_loot_container_slot_on_bot", new
|
||||
{
|
||||
LootContainer = lootContainerKey,
|
||||
BotRole = botRole,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
List<string> tplsToRemove = [];
|
||||
foreach (var tplKey in prop)
|
||||
{
|
||||
if (christmasItems.Contains(tplKey.Key))
|
||||
{
|
||||
tplsToRemove.Add(tplKey.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tplToRemove in tplsToRemove)
|
||||
{
|
||||
prop.Remove(tplToRemove);
|
||||
}
|
||||
|
||||
// Get non-christmas items
|
||||
var nonChristmasTpls = prop.Where(tpl => !christmasItems.Contains(tpl.Key));
|
||||
if (nonChristmasTpls.Count() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Dictionary<string, double> intermediaryDict = new();
|
||||
|
||||
foreach (var tpl in nonChristmasTpls)
|
||||
{
|
||||
intermediaryDict[tpl.Key] = prop[tpl.Key];
|
||||
}
|
||||
|
||||
// Replace the original containerItems with the updated one
|
||||
prop = intermediaryDict;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -209,44 +438,229 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
/// <param name="globalConfig">globals.json</param>
|
||||
/// <param name="event">Name of the event to enable. e.g. Christmas</param>
|
||||
protected void UpdateGlobalEvents(Config globalConfig, SeasonalEvent eventType)
|
||||
private void UpdateGlobalEvents(Config globalConfig, SeasonalEvent eventType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_logger.Success(_localisationService.GetText("season-event_is_active", eventType.Type));
|
||||
_christmasEventActive = false;
|
||||
_halloweenEventActive = false;
|
||||
|
||||
switch (eventType.Type)
|
||||
{
|
||||
case SeasonalEventType.Halloween:
|
||||
ApplyHalloweenEvent(eventType, globalConfig);
|
||||
break;
|
||||
case SeasonalEventType.Christmas:
|
||||
ApplyChristmasEvent(eventType, globalConfig);
|
||||
break;
|
||||
case SeasonalEventType.NewYears:
|
||||
ApplyNewYearsEvent(eventType, globalConfig);
|
||||
|
||||
break;
|
||||
case SeasonalEventType.AprilFools:
|
||||
AddGifterBotToMaps();
|
||||
AddLootItemsToGifterDropItemsList();
|
||||
AddEventGearToBots(SeasonalEventType.Halloween);
|
||||
AddEventGearToBots(SeasonalEventType.Christmas);
|
||||
AddEventLootToBots(SeasonalEventType.Christmas);
|
||||
AddEventBossesToMaps(SeasonalEventType.Halloween.ToString());
|
||||
EnableHalloweenSummonEvent();
|
||||
AddPumpkinsToScavBackpacks();
|
||||
RenameBitcoin();
|
||||
EnableSnow();
|
||||
break;
|
||||
default:
|
||||
// Likely a mod event
|
||||
HandleModEvent(eventType, globalConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ApplyHalloweenEvent(SeasonalEvent eventType, Config globalConfig)
|
||||
private void ApplyHalloweenEvent(SeasonalEvent eventType, Config globalConfig)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_halloweenEventActive = true;
|
||||
|
||||
globalConfig.EventType = globalConfig.EventType.Where((x) => x != "None").ToList();
|
||||
globalConfig.EventType.Add("Halloween");
|
||||
globalConfig.EventType.Add("HalloweenIllumination");
|
||||
globalConfig.Health.ProfileHealthSettings.DefaultStimulatorBuff = "Buffs_Halloween";
|
||||
AddEventGearToBots(eventType.Type);
|
||||
AdjustZryachiyMeleeChance();
|
||||
if (eventType.Settings?.EnableSummoning ?? false)
|
||||
{
|
||||
EnableHalloweenSummonEvent();
|
||||
AddEventBossesToMaps("halloweensummon");
|
||||
}
|
||||
|
||||
if (eventType.Settings?.ZombieSettings?.Enabled ?? false)
|
||||
{
|
||||
ConfigureZombies(eventType.Settings.ZombieSettings);
|
||||
}
|
||||
|
||||
if (eventType.Settings?.RemoveEntryRequirement is not null)
|
||||
{
|
||||
RemoveEntryRequirement(eventType.Settings.RemoveEntryRequirement);
|
||||
}
|
||||
|
||||
if (eventType.Settings?.ReplaceBotHostility ?? false)
|
||||
{
|
||||
ReplaceBotHostility(_seasonalEventConfig.HostilitySettingsForEvent.FirstOrDefault(x => x.Key == "zombies").Value);
|
||||
}
|
||||
|
||||
if (eventType.Settings?.AdjustBotAppearances ?? false)
|
||||
{
|
||||
AdjustBotAppearanceValues(eventType.Type);
|
||||
}
|
||||
|
||||
AddPumpkinsToScavBackpacks();
|
||||
AdjustTraderIcons(eventType.Type);
|
||||
}
|
||||
|
||||
protected void ApplyChristmasEvent(SeasonalEvent eventType, Config globalConfig)
|
||||
private void ApplyChristmasEvent(SeasonalEvent eventType, Config globalConfig)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_christmasEventActive = true;
|
||||
|
||||
if (eventType.Settings?.EnableChristmasHideout ?? false)
|
||||
{
|
||||
globalConfig.EventType = globalConfig.EventType.Where((x) => x != "None").ToList();
|
||||
globalConfig.EventType.Add("Christmas");
|
||||
}
|
||||
|
||||
AddEventGearToBots(eventType.Type);
|
||||
AddEventLootToBots(eventType.Type);
|
||||
|
||||
if (eventType.Settings?.EnableSanta ?? false)
|
||||
{
|
||||
AddGifterBotToMaps();
|
||||
AddLootItemsToGifterDropItemsList();
|
||||
}
|
||||
|
||||
EnableDancingTree();
|
||||
if (eventType.Settings?.AdjustBotAppearances ?? false)
|
||||
{
|
||||
AdjustBotAppearanceValues(eventType.Type);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ApplyNewYearsEvent(SeasonalEvent eventType, Config globalConfig)
|
||||
private void ApplyNewYearsEvent(SeasonalEvent eventType, Config globalConfig)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_christmasEventActive = true;
|
||||
|
||||
if (eventType.Settings?.EnableChristmasHideout ?? false)
|
||||
{
|
||||
globalConfig.EventType = globalConfig.EventType.Where((x) => x != "None").ToList();
|
||||
globalConfig.EventType.Add("Christmas");
|
||||
}
|
||||
|
||||
AddEventGearToBots(SeasonalEventType.Christmas);
|
||||
AddEventLootToBots(SeasonalEventType.Christmas);
|
||||
|
||||
if (eventType.Settings?.EnableSanta ?? false)
|
||||
{
|
||||
AddGifterBotToMaps();
|
||||
AddLootItemsToGifterDropItemsList();
|
||||
}
|
||||
|
||||
EnableDancingTree();
|
||||
|
||||
if (eventType.Settings?.AdjustBotAppearances ?? false)
|
||||
{
|
||||
AdjustBotAppearanceValues(SeasonalEventType.Christmas);
|
||||
}
|
||||
}
|
||||
|
||||
protected void AdjustBotAppearanceValues(SeasonalEventType season)
|
||||
private void AdjustBotAppearanceValues(SeasonalEventType season)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var adjustments = _seasonalEventConfig.BotAppearanceChanges[season];
|
||||
if (adjustments is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var botTypeKey in adjustments)
|
||||
{
|
||||
var botDb = _databaseService.GetBots().Types[botTypeKey.Key];
|
||||
if (botDb is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var botAppearanceAdjustments = botTypeKey.Value;
|
||||
foreach (var appearanceKey in botAppearanceAdjustments)
|
||||
{
|
||||
var weightAdjustments = appearanceKey.Value;
|
||||
var props = botDb.BotAppearance.GetType().GetProperties();
|
||||
foreach (var itemKey in weightAdjustments)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => x.Name.ToLower() == appearanceKey.Key.ToLower());
|
||||
var propValue = (Dictionary<string, int>)prop.GetValue(botDb.BotAppearance);
|
||||
propValue[itemKey.Key] = weightAdjustments[itemKey.Key];
|
||||
prop.SetValue(botDb.BotAppearance, propValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ReplaceBotHostility(Dictionary<string, AdditionalHostilitySettings[]> hostilitySettings)
|
||||
private void ReplaceBotHostility(Dictionary<string, List<AdditionalHostilitySettings>> hostilitySettings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var locations = _databaseService.GetLocations();
|
||||
var ignoreList = _locationConfig.NonMaps;
|
||||
var useDefault = hostilitySettings is null;
|
||||
|
||||
var props = locations.GetType().GetProperties();
|
||||
|
||||
foreach (var locationProp in props)
|
||||
{
|
||||
if (ignoreList.Contains(locationProp.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Location location = (Location)locationProp.GetValue(locations);
|
||||
if (location?.Base?.BotLocationModifier?.AdditionalHostilitySettings is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<AdditionalHostilitySettings> newHostilitySettings = useDefault ? new() : hostilitySettings[locationProp.Name];
|
||||
if (newHostilitySettings is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
location.Base.BotLocationModifier.AdditionalHostilitySettings = new();
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveEntryRequirement(List<string> locationIds)
|
||||
private void RemoveEntryRequirement(List<string> locationIds)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
foreach (var locationId in locationIds)
|
||||
{
|
||||
var location = _databaseService.GetLocation(locationId);
|
||||
location.Base.AccessKeys = [];
|
||||
location.Base.AccessKeysPvE = [];
|
||||
}
|
||||
}
|
||||
|
||||
public void GivePlayerSeasonalGifts(string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (_currentlyActiveEvents is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var seasonEvent in _currentlyActiveEvents)
|
||||
{
|
||||
switch (seasonEvent.Type)
|
||||
{
|
||||
case SeasonalEventType.Christmas:
|
||||
GiveGift(sessionId, "Christmas2022");
|
||||
break;
|
||||
case SeasonalEventType.NewYears:
|
||||
GiveGift(sessionId, "NewYear2023");
|
||||
GiveGift(sessionId, "NewYear2024");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -254,7 +668,21 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
protected void AdjustZryachiyMeleeChance()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var zyrach = _databaseService.GetBots().Types.FirstOrDefault(x => x.Key.ToLower() == "bosszryachiy");
|
||||
var value = new Dictionary<string, double>();
|
||||
|
||||
foreach (var chance in zyrach.Value.BotChances.EquipmentChances)
|
||||
{
|
||||
if (chance.Key.ToLower() == "Scabbard")
|
||||
{
|
||||
value.Add(chance.Key, 100);
|
||||
continue;
|
||||
}
|
||||
|
||||
value.Add(chance.Key, chance.Value);
|
||||
}
|
||||
|
||||
zyrach.Value.BotChances.EquipmentChances = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -262,7 +690,7 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
protected void EnableHalloweenSummonEvent()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_databaseService.GetGlobals().Configuration.EventSettings.EventActive = true;
|
||||
}
|
||||
|
||||
protected void ConfigureZombies(ZombieSettings zombieSettings)
|
||||
@@ -319,7 +747,12 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
protected void AddLootItemsToGifterDropItemsList()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var gifterBot = _databaseService.GetBots().Types["gifter"];
|
||||
var items = gifterBot.BotInventory.Items.Backpack.Keys.ToList();
|
||||
gifterBot.BotDifficulty.Easy.Patrol["ITEMS_TO_DROP"] = items;
|
||||
gifterBot.BotDifficulty.Normal.Patrol["ITEMS_TO_DROP"] = items;
|
||||
gifterBot.BotDifficulty.Hard.Patrol["ITEMS_TO_DROP"] = items;
|
||||
gifterBot.BotDifficulty.Impossible.Patrol["ITEMS_TO_DROP"] = items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -345,12 +778,16 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
protected void AddPumpkinsToScavBackpacks()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_databaseService.GetBots().Types["assault"].BotInventory.Items.Backpack[
|
||||
ItemTpl.RANDOMLOOTCONTAINER_PUMPKIN_RAND_LOOT_CONTAINER
|
||||
] = 400;
|
||||
}
|
||||
|
||||
protected void RenameBitcoin()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var enLocale = _databaseService.GetLocales().Global["en"];
|
||||
enLocale[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name"] = "Physical SPT Coin";
|
||||
enLocale[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName"] = "0.2SPT";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -381,7 +818,11 @@ public class SeasonalEventService
|
||||
/// <param name="giftKey">Key of gift to give</param>
|
||||
protected void GiveGift(string playerId, string giftKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var gitftData = _giftService.GetGiftById(giftKey);
|
||||
if (!_profileHelper.PlayerHasRecievedMaxNumberOfGift(playerId, giftKey, gitftData.MaxToSendPlayer ?? 5))
|
||||
{
|
||||
_giftService.SendGiftToPlayer(playerId, giftKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -391,7 +832,7 @@ public class SeasonalEventService
|
||||
/// <returns>Bot role as string</returns>
|
||||
public string GetBaseRoleForEventBot(string eventBotRole)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return _seasonalEventConfig.EventBotMapping.GetValueOrDefault(eventBotRole, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -399,6 +840,6 @@ public class SeasonalEventService
|
||||
/// </summary>
|
||||
public void EnableSnow()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_weatherConfig.OverrideSeason = Season.WINTER;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-25
@@ -1,4 +1,3 @@
|
||||
using System.Diagnostics;
|
||||
using Core.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Models.Enums;
|
||||
@@ -55,31 +54,21 @@ public class App
|
||||
// execute onLoad callbacks
|
||||
_logger.Info(_localisationService.GetText("executing_startup_callbacks"));
|
||||
|
||||
/*
|
||||
_logger.Debug($"OS: {os.arch()} | {os.version()} | {process.platform}");
|
||||
_logger.Debug($"CPU: {os.cpus()[0]?.model} cores: {os.cpus().length}");
|
||||
_logger.Debug($"RAM: {(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB");
|
||||
_logger.Debug($"PATH: {this.encodingUtil.toBase64(process.argv[0])}");
|
||||
_logger.Debug($"PATH: {this.encodingUtil.toBase64(process.execPath)}");
|
||||
_logger.Debug($"Server: {ProgramStatics.SPT_VERSION || this.coreConfig.sptVersion}");
|
||||
_logger.Debug($"OS: {Environment.OSVersion.Version} | {Environment.OSVersion.Platform}");
|
||||
//_logger.Debug($"CPU: {ApplicationId.ProcessorArchitecture} cores: {os.cpus().length}");
|
||||
//_logger.Debug($"RAM: {(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB");
|
||||
//_logger.Debug($"PATH: {this.encodingUtil.toBase64(process.argv[0])}");
|
||||
//_logger.Debug($"PATH: {this.encodingUtil.toBase64(process.execPath)}");
|
||||
//_logger.Debug($"Server: {ProgramStatics.SPT_VERSION || _coreConfig.SptVersion}");
|
||||
|
||||
const nodeVersion = process.version.replace(/^v/, "");
|
||||
if (ProgramStatics.EXPECTED_NODE && nodeVersion !== ProgramStatics.EXPECTED_NODE) {
|
||||
this.logger.error(
|
||||
"Node version mismatch. Required: ${ProgramStatics.EXPECTED_NODE} | Current: ${nodeVersion}",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
_logger.Debug("Node: ${nodeVersion}");
|
||||
|
||||
if (ProgramStatics.BUILD_TIME) {
|
||||
_logger.Debug("Date: ${ProgramStatics.BUILD_TIME}");
|
||||
}
|
||||
|
||||
if (ProgramStatics.COMMIT) {
|
||||
_logger.Debug("Commit: ${ProgramStatics.COMMIT}");
|
||||
}
|
||||
*/
|
||||
//if (ProgramStatics.BUILD_TIME) {
|
||||
// _logger.Debug($"Date: {ProgramStatics.BUILD_TIME}");
|
||||
//}
|
||||
|
||||
//if (ProgramStatics.COMMIT) {
|
||||
// _logger.Debug($"Commit: {ProgramStatics.COMMIT}");
|
||||
//}
|
||||
|
||||
foreach (var onLoad in _onLoad)
|
||||
await onLoad.OnLoad();
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ using Core.Models.Spt.Server;
|
||||
using Core.Routers;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils.Cloners;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Core.Utils;
|
||||
|
||||
@@ -18,19 +18,19 @@ public class DatabaseImporter : OnLoad
|
||||
private object hashedFile;
|
||||
private ValidationResult valid = ValidationResult.UNDEFINED;
|
||||
private string filepath;
|
||||
protected HttpConfig httpConfig;
|
||||
private HttpConfig httpConfig;
|
||||
|
||||
protected readonly ILogger _logger;
|
||||
protected readonly LocalisationService _localisationService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly LocalisationService _localisationService;
|
||||
|
||||
protected readonly DatabaseServer _databaseServer;
|
||||
private readonly DatabaseServer _databaseServer;
|
||||
|
||||
protected readonly ImageRouter _imageRouter;
|
||||
protected readonly EncodingUtil _encodingUtil;
|
||||
protected readonly HashUtil _hashUtil;
|
||||
protected readonly ImporterUtil _importerUtil;
|
||||
protected readonly ConfigServer _configServer;
|
||||
protected readonly FileUtil _fileUtil;
|
||||
private readonly ImageRouter _imageRouter;
|
||||
private readonly EncodingUtil _encodingUtil;
|
||||
private readonly HashUtil _hashUtil;
|
||||
private readonly ImporterUtil _importerUtil;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly FileUtil _fileUtil;
|
||||
|
||||
public DatabaseImporter(
|
||||
ILogger logger,
|
||||
@@ -95,29 +95,43 @@ public class DatabaseImporter : OnLoad
|
||||
await HydrateDatabase(filepath);
|
||||
|
||||
var imageFilePath = $"{filepath}images/";
|
||||
//var directories = this.vfs.getDirs(imageFilePath);
|
||||
LoadImages(imageFilePath, _fileUtil.GetDirectories(imageFilePath), [
|
||||
"/files/achievement/",
|
||||
"/files/CONTENT/banners/",
|
||||
"/files/handbook/",
|
||||
"/files/Hideout/",
|
||||
"/files/launcher/",
|
||||
"/files/prestige/",
|
||||
"/files/quest/icon/",
|
||||
"/files/trader/avatar/",
|
||||
]);
|
||||
CreateRouteMapping(imageFilePath, "files");
|
||||
}
|
||||
|
||||
private void CreateRouteMapping(string directory, string newBasePath)
|
||||
{
|
||||
var directoryContent = GetAllFilesInDirectory(directory);
|
||||
|
||||
foreach (var fileNameWithPath in directoryContent) {
|
||||
var bsgPath = $"/{newBasePath}/{_fileUtil.StripExtension(fileNameWithPath)}";
|
||||
var sptPath = $"{directory}{ fileNameWithPath}";
|
||||
_imageRouter.AddRoute(bsgPath, sptPath);
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> GetAllFilesInDirectory(string directoryPath)
|
||||
{
|
||||
List<string> result = [];
|
||||
result.AddRange(Directory.GetFiles(directoryPath));
|
||||
|
||||
foreach (var subdirectory in Directory.GetDirectories(directoryPath))
|
||||
{
|
||||
result.AddRange(GetAllFilesInDirectory(subdirectory));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all json files in database folder and map into a json object
|
||||
* @param filepath path to database folder
|
||||
*/
|
||||
protected async Task HydrateDatabase(string filepath)
|
||||
protected async Task HydrateDatabase(string filePath)
|
||||
{
|
||||
_logger.Info(_localisationService.GetText("importing_database"));
|
||||
|
||||
var dataToImport = (DatabaseTables) await _importerUtil.LoadRecursiveAsync(
|
||||
$"{filepath}database/",
|
||||
$"{filePath}database/",
|
||||
typeof(DatabaseTables),
|
||||
OnReadValidate
|
||||
);
|
||||
@@ -183,9 +197,11 @@ public class DatabaseImporter : OnLoad
|
||||
}
|
||||
|
||||
/**
|
||||
* absolute dogshit, do not use
|
||||
* Find and map files with image router inside a designated path
|
||||
* @param filepath Path to find files in
|
||||
*/
|
||||
[Obsolete]
|
||||
public void LoadImages(string filepath, string[] directories, List<string> routes)
|
||||
{
|
||||
for (var i = 0; i < directories.Length; i++)
|
||||
|
||||
@@ -106,7 +106,7 @@ public class HashUtil
|
||||
|
||||
var random = new Random();
|
||||
|
||||
return random.Next() * (max - min + 1) + min;
|
||||
return random.Next(min, max + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ public class ImporterUtil
|
||||
)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
var dictionaryLock = new object();
|
||||
var result = Activator.CreateInstance(loadedType);
|
||||
|
||||
// get all filepaths
|
||||
@@ -66,8 +67,11 @@ public class ImporterUtil
|
||||
if (onObjectDeserialized != null)
|
||||
onObjectDeserialized(file, fileDeserialized);
|
||||
|
||||
setMethod.Invoke(result,
|
||||
isDictionary ? [_fileUtil.StripExtension(file), fileDeserialized] : [fileDeserialized]);
|
||||
lock (dictionaryLock)
|
||||
{
|
||||
setMethod.Invoke(result,
|
||||
isDictionary ? [_fileUtil.StripExtension(file), fileDeserialized] : [fileDeserialized]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -80,7 +84,6 @@ public class ImporterUtil
|
||||
// deep tree search
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
var dictionaryLock = new object();
|
||||
tasks.Add(
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
@@ -125,7 +128,8 @@ public class ImporterUtil
|
||||
{
|
||||
var matchedProperty = type.GetProperties()
|
||||
.FirstOrDefault(prop =>
|
||||
prop.Name.ToLower() == _fileUtil.StripExtension(propertyName).ToLower());
|
||||
string.Equals(prop.Name.ToLower(), _fileUtil.StripExtension(propertyName).ToLower(),
|
||||
StringComparison.Ordinal));
|
||||
if (matchedProperty == null)
|
||||
throw new Exception(
|
||||
$"Unable to find property '{_fileUtil.StripExtension(propertyName)}' for type '{type.Name}'");
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Unicode;
|
||||
using Core.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Dialog;
|
||||
@@ -15,6 +17,7 @@ public class JsonUtil
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters =
|
||||
{
|
||||
new ListOrTConverterFactory(),
|
||||
@@ -25,7 +28,8 @@ public class JsonUtil
|
||||
new EftEnumConverter<ProfileChangeEventType>(),
|
||||
new EftEnumConverter<QuestStatusEnum>(),
|
||||
new EftEnumConverter<QuestRewardType>(),
|
||||
new EftEnumConverter<SideType>()
|
||||
new EftEnumConverter<SideType>(),
|
||||
new EftEnumConverter<BonusSkillType>()
|
||||
}
|
||||
};
|
||||
private static readonly JsonSerializerOptions jsonSerializerOptionsIndented = new(jsonSerializerOptionsNoIndent)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Core.Utils
|
||||
{
|
||||
public class ProgressWriter
|
||||
{
|
||||
private readonly int _total;
|
||||
private readonly int? _maxBarLength;
|
||||
private readonly string? _barFillChar;
|
||||
private readonly string? _barEmptyChar;
|
||||
|
||||
public ProgressWriter(int total, int maxBarLength, string barFillChar, string barEmptyChar)
|
||||
{
|
||||
_total = total;
|
||||
_maxBarLength = maxBarLength;
|
||||
_barFillChar = barFillChar;
|
||||
_barEmptyChar = barEmptyChar;
|
||||
}
|
||||
|
||||
public ProgressWriter(int total)
|
||||
{
|
||||
_total = total;
|
||||
_maxBarLength = 25;
|
||||
_barFillChar = "\u2593";
|
||||
_barEmptyChar = "\u2591";
|
||||
}
|
||||
|
||||
public void Increment()
|
||||
{
|
||||
// TODO - implement
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography;
|
||||
using Core.Annotations;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Utils;
|
||||
|
||||
@@ -7,6 +8,16 @@ namespace Core.Utils;
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class RandomUtil
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RandomUtil
|
||||
(
|
||||
ILogger logger
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public readonly Random Random = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -300,7 +311,7 @@ public class RandomUtil
|
||||
/// <returns>A biased random number within the specified range.</returns>
|
||||
public double GetBiasedRandomNumber(double min, double max, double shift, double n)
|
||||
{
|
||||
/***
|
||||
/**
|
||||
* This function generates a random number based on a gaussian distribution with an option to add a bias via shifting.
|
||||
*
|
||||
* Here's an example graph of how the probabilities can be distributed:
|
||||
@@ -314,8 +325,63 @@ public class RandomUtil
|
||||
* Here's a place where you can play around with the 'n' and 'shift' values to see how the distribution changes:
|
||||
* http://jsfiddle.net/e08cumyx/
|
||||
*/
|
||||
if (max < min)
|
||||
{
|
||||
_logger.Error($"Invalid argument, Bounded random number generation max is smaller than min({max} < {min}");
|
||||
return -1;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("This honestly went over my head...");
|
||||
if (n < 1)
|
||||
{
|
||||
_logger.Error($"Invalid argument, 'n' must be 1 or greater(received {n})");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (min == max)
|
||||
{
|
||||
return min;
|
||||
}
|
||||
|
||||
if (shift > max - min)
|
||||
{
|
||||
/**
|
||||
* If a rolled number is out of bounds (due to bias being applied), we roll it again.
|
||||
* As the shifting increases, the chance of rolling a number within bounds decreases.
|
||||
* A shift that is equal to the available range only has a 50% chance of rolling correctly, theoretically halving performance.
|
||||
* Shifting even further drops the success chance very rapidly - so we want to warn against that
|
||||
**/
|
||||
_logger.Warning(
|
||||
"Bias shift for random number generation is greater than the range of available numbers. This will have a severe performance impact");
|
||||
_logger.Warning($"min-> {min}; max-> {max}; shift-> {shift}");
|
||||
}
|
||||
|
||||
|
||||
var biasedMin = shift >= 0 ? min - shift : min;
|
||||
var biasedMax = shift < 0 ? max + shift : max;
|
||||
|
||||
double num;
|
||||
do
|
||||
{
|
||||
num = GetBoundedGaussian(biasedMin, biasedMax, n);
|
||||
} while (num < min || num > max);
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
private double GetBoundedGaussian(double start, double end, double n)
|
||||
{
|
||||
return Math.Round(start + GetGaussianRandom(n) * (end - start + 1));
|
||||
}
|
||||
|
||||
private double GetGaussianRandom(double n)
|
||||
{
|
||||
var rand = 0d;
|
||||
for (var i = 0; i < n; i += 1)
|
||||
{
|
||||
rand += GetSecureRandomNumber();
|
||||
}
|
||||
|
||||
return rand / n;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -378,4 +444,10 @@ public class RandomUtil
|
||||
? parts[1].Length
|
||||
: 0;
|
||||
}
|
||||
|
||||
public T GetArrayValue<T>(IEnumerable<T> list)
|
||||
{
|
||||
var rand = new Random();
|
||||
return list.ElementAt(rand.Next(0, list.Count()));
|
||||
}
|
||||
}
|
||||
|
||||
+59
-38
@@ -12,7 +12,7 @@ public class TimeUtil
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The date to format in UTC.</param>
|
||||
/// <returns>The formatted time as 'HH-MM-SS'.</returns>
|
||||
public string FormatTime(DateTime dateTime)
|
||||
public string FormatTime(DateTimeOffset dateTime)
|
||||
{
|
||||
var hour = Pad(dateTime.ToUniversalTime().Hour);
|
||||
var minute = Pad(dateTime.ToUniversalTime().Minute);
|
||||
@@ -26,7 +26,7 @@ public class TimeUtil
|
||||
/// </summary>
|
||||
/// <param name="dateTime">The date to format in UTC.</param>
|
||||
/// <returns>The formatted date as 'YYYY-MM-DD'.</returns>
|
||||
public string FormatDate(DateTime dateTime)
|
||||
public string FormatDate(DateTimeOffset dateTime)
|
||||
{
|
||||
var day = Pad(dateTime.ToUniversalTime().Day);
|
||||
var month = Pad(dateTime.ToUniversalTime().Month);
|
||||
@@ -41,7 +41,7 @@ public class TimeUtil
|
||||
/// <returns>The current date as 'YYYY-MM-DD'.</returns>
|
||||
public string GetDate()
|
||||
{
|
||||
return FormatDate(DateTime.Now);
|
||||
return FormatDate(DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -50,7 +50,7 @@ public class TimeUtil
|
||||
/// <returns>The current time as 'HH-MM-SS'.</returns>
|
||||
public string GetTime()
|
||||
{
|
||||
return FormatTime(DateTime.Now);
|
||||
return FormatTime(DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,12 +67,14 @@ public class TimeUtil
|
||||
/// </summary>
|
||||
/// <param name="dateTime">datetime to get the time stamp for, if null it uses current date.</param>
|
||||
/// <returns>Unix epoch for the start of day of the calculated date</returns>
|
||||
public long GetStartOfDayTimeStamp(DateTime? dateTime)
|
||||
public long GetStartOfDayTimeStamp(long? timestamp)
|
||||
{
|
||||
var now = dateTime ?? DateTime.Now;
|
||||
|
||||
return new DateTimeOffset(new DateTime(now.Year, now.Month, now.Day, 0, 0, 0))
|
||||
.ToUnixTimeSeconds();
|
||||
DateTime now = timestamp.HasValue
|
||||
? DateTimeOffset.FromUnixTimeMilliseconds(timestamp.Value).DateTime
|
||||
: DateTime.Now;
|
||||
|
||||
DateTime startOfDay = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0);
|
||||
return ((DateTimeOffset)startOfDay).ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +84,7 @@ public class TimeUtil
|
||||
/// <returns></returns>
|
||||
public long GetTimeStampFromNowDays(int daysFromNow)
|
||||
{
|
||||
return DateTimeOffset.Now.AddDays(daysFromNow).ToUnixTimeSeconds();
|
||||
return DateTimeOffset.UtcNow.AddDays(daysFromNow).ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,16 +94,17 @@ public class TimeUtil
|
||||
/// <returns></returns>
|
||||
public long GetTimeStampFromNowHours(int hoursFromNow)
|
||||
{
|
||||
return DateTimeOffset.Now.AddHours(hoursFromNow).ToUnixTimeSeconds();
|
||||
return DateTimeOffset.UtcNow.AddHours(hoursFromNow).ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current time in UTC in a format suitable for mail in EFT.
|
||||
/// </summary>
|
||||
/// <returns>The current time as 'HH:MM' in UTC.</returns>
|
||||
/// GetTimeMailFormat
|
||||
public string GetTimeMailFormat()
|
||||
{
|
||||
return DateTime.UtcNow.ToString("HH:mm");
|
||||
return DateTimeOffset.UtcNow.ToString("HH:mm");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +113,7 @@ public class TimeUtil
|
||||
/// <returns>The current date as 'DD.MM.YYYY' in UTC.</returns>
|
||||
public string GetDateMailFormat()
|
||||
{
|
||||
return DateTime.UtcNow.ToString("dd.MM.yyyy");
|
||||
return DateTimeOffset.UtcNow.ToString("dd.MM.yyyy");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,19 +132,14 @@ public class TimeUtil
|
||||
/// <returns>Time stamp of the next hour in unix time seconds</returns>
|
||||
public long GetTimeStampOfNextHour()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
DateTime now = DateTime.Now;
|
||||
TimeSpan timeUntilNextHour = TimeSpan.FromMinutes(60 - now.Minute)
|
||||
.Subtract(TimeSpan.FromSeconds(now.Second))
|
||||
.Subtract(TimeSpan.FromMilliseconds(now.Millisecond));
|
||||
|
||||
var time = ((DateTimeOffset)now.Add(timeUntilNextHour)).ToUnixTimeSeconds();
|
||||
|
||||
var nextHour = new DateTime(
|
||||
now.Year,
|
||||
now.Month,
|
||||
now.Day,
|
||||
now.Hour,
|
||||
0,
|
||||
0,
|
||||
DateTimeKind.Utc
|
||||
).AddHours(1);
|
||||
|
||||
return new DateTimeOffset(nextHour).ToUnixTimeSeconds();
|
||||
return time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,19 +149,20 @@ public class TimeUtil
|
||||
/// <returns>Timestamp</returns>
|
||||
public long GetTodayMidnightTimeStamp()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var midNight = new DateTime(
|
||||
now.Year,
|
||||
now.Month,
|
||||
now.Day,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
DateTimeKind.Utc
|
||||
);
|
||||
|
||||
return new DateTimeOffset(midNight).ToUnixTimeSeconds();
|
||||
DateTime now = DateTime.Now;
|
||||
int hours = now.Hour;
|
||||
int minutes = now.Minute;
|
||||
|
||||
// If minutes greater than 0, subtract 1 hour
|
||||
if (minutes > 0)
|
||||
{
|
||||
hours--;
|
||||
}
|
||||
|
||||
// Create a new DateTime with the last full hour, 0 minutes, and 0 seconds
|
||||
DateTime lastFullHour = new DateTime(now.Year, now.Month, now.Day, hours, 0, 0);
|
||||
|
||||
return ((DateTimeOffset)lastFullHour).ToUnixTimeMilliseconds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -175,4 +174,26 @@ public class TimeUtil
|
||||
{
|
||||
return number.ToString().PadLeft(2, '0');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a timestamp and converts to its date with Epoch
|
||||
/// </summary>
|
||||
/// <param name="timeStamp"></param>
|
||||
/// <returns></returns>
|
||||
public DateTime GetDateTimeFromTimeStamp(long timeStamp)
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeMilliseconds(timeStamp).DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a timestamp and gets difference between Epoch time and time provided resulting in a unixtimestamp (date defaults to utcnow)
|
||||
/// </summary>
|
||||
/// <param name="date"></param>
|
||||
/// <returns></returns>
|
||||
public long GetTimeStampFromEpoch(DateTime? date = null)
|
||||
{
|
||||
var dateToCompare = date ?? DateTime.UtcNow;
|
||||
return (long)(dateToCompare - DateTime.UnixEpoch).TotalSeconds;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Diagnostics;
|
||||
using Core.Annotations;
|
||||
|
||||
namespace Core.Utils
|
||||
{
|
||||
[Injectable]
|
||||
public class TimerUtil
|
||||
{
|
||||
private readonly Stopwatch _stopwatch;
|
||||
|
||||
public TimerUtil()
|
||||
{
|
||||
_stopwatch = new Stopwatch();
|
||||
_stopwatch.Start();
|
||||
}
|
||||
|
||||
public int Stop(string unit = "sec")
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
var timePassed = _stopwatch.Elapsed;
|
||||
|
||||
return unit switch
|
||||
{
|
||||
"ns" => timePassed.Nanoseconds,
|
||||
"ms" => timePassed.Milliseconds,
|
||||
_ => timePassed.Seconds
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1149,7 +1149,7 @@
|
||||
"backpackLoot": {
|
||||
"weights": {
|
||||
"0": 3,
|
||||
"1": 5,
|
||||
"1": 7,
|
||||
"2": 12,
|
||||
"3": 20,
|
||||
"4": 8,
|
||||
|
||||
@@ -179,12 +179,9 @@
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"autoInstallModDependencies": false,
|
||||
"compressProfile": false,
|
||||
"chatbotFeatures": {
|
||||
"sptFriendEnabled": true,
|
||||
"sptFriendGiftsEnabled": true,
|
||||
"commandoEnabled": true,
|
||||
"commandoFeatures": {
|
||||
"giveCommandEnabled": true
|
||||
},
|
||||
@@ -194,7 +191,11 @@
|
||||
"ids": {
|
||||
"commando": "6723fd51c5924c57ce0ca01e",
|
||||
"spt": "6723fd51c5924c57ce0ca01f"
|
||||
}
|
||||
},
|
||||
"enabledBots": {
|
||||
"6723fd51c5924c57ce0ca01e": true,
|
||||
"6723fd51c5924c57ce0ca01f": true
|
||||
}
|
||||
},
|
||||
"createNewProfileTypesBlacklist": []
|
||||
},
|
||||
|
||||
+229
-199
@@ -8522,209 +8522,239 @@
|
||||
"associatedEvent": "Promo",
|
||||
"maxToSendPlayer": 5
|
||||
},
|
||||
"NewYear2024": {
|
||||
"items": [{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"_tpl": "67124dcfa3541f2a1f0e788b",
|
||||
"upd": {
|
||||
"FireMode": {
|
||||
"FireMode": "single"
|
||||
},
|
||||
"Repairable": {
|
||||
"Durability": 100,
|
||||
"MaxDurability": 100
|
||||
}
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec4",
|
||||
"_tpl": "6719023b612cc94b9008e78c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_stock"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec5",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_magazine"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec6",
|
||||
"_tpl": "670fd0a8d8d4eae4790c8187",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_barrel"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec7",
|
||||
"_tpl": "6710cea62bb09af72f0e6bf8",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec6",
|
||||
"slotId": "mod_mount"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec8",
|
||||
"_tpl": "57d17c5e2459775a5c57d17d",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec7",
|
||||
"slotId": "mod_tactical_003"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec9",
|
||||
"_tpl": "6165ac8c290d254f5e6b2f6c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_scope"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ecb",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ecd",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ecf",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed1",
|
||||
"_tpl": "5c0d591486f7744c505b416f",
|
||||
"upd": {
|
||||
"StackObjectsCount": 20
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed3",
|
||||
"_tpl": "5c0d591486f7744c505b416f",
|
||||
"upd": {
|
||||
"StackObjectsCount": 20
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed5",
|
||||
"_tpl": "5c0d591486f7744c505b416f",
|
||||
"upd": {
|
||||
"StackObjectsCount": 20
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"_tpl": "5ab8e79e86f7742d8b372e78",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed7",
|
||||
"_tpl": "65732688d9d89ff7ac0d9c4c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Soft_armor_front"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed8",
|
||||
"_tpl": "657326978c1cc6dcd9098b56",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Soft_armor_back"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed9",
|
||||
"_tpl": "657326a28c1cc6dcd9098b5a",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Soft_armor_left"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7eda",
|
||||
"_tpl": "657326b08c1cc6dcd9098b5e",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "soft_armor_right"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7edb",
|
||||
"_tpl": "657326bc5d3a3129fb05f36b",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Collar"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7edc",
|
||||
"_tpl": "656f611f94b480b8a500c0db",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Front_plate"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7edd",
|
||||
"_tpl": "65573fa5655447403702a816",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Back_plate"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ede",
|
||||
"_tpl": "675956062f6ddfe8ff0e2806",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7edf",
|
||||
"_tpl": "676307c004856a0b3c0dfffd",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ede",
|
||||
"slotId": "Helmet_top"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee0",
|
||||
"_tpl": "676307b4d9ec0af3d9001fa8",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ede",
|
||||
"slotId": "Helmet_back"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee2",
|
||||
"_tpl": "674589d98dd67746010329e6",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee4",
|
||||
"_tpl": "67458794e21e5d724e066976",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee6",
|
||||
"_tpl": "5d40407c86f774318526545a",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee8",
|
||||
"_tpl": "5d40407c86f774318526545a",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7eea",
|
||||
"_tpl": "5d403f9186f7743cac3f229b",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}, {
|
||||
"_id": "6773b39ccf4c2fe4e50a7eec",
|
||||
"_tpl": "5d403f9186f7743cac3f229b",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
"NewYear2024": {
|
||||
"items": [
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"_tpl": "67124dcfa3541f2a1f0e788b",
|
||||
"upd": {
|
||||
"FireMode": {
|
||||
"FireMode": "single"
|
||||
},
|
||||
"Repairable": {
|
||||
"Durability": 100,
|
||||
"MaxDurability": 100
|
||||
}
|
||||
],
|
||||
"sender": "System",
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec4",
|
||||
"_tpl": "6719023b612cc94b9008e78c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_stock"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec5",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_magazine"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec6",
|
||||
"_tpl": "670fd0a8d8d4eae4790c8187",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_barrel"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec7",
|
||||
"_tpl": "6710cea62bb09af72f0e6bf8",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec6",
|
||||
"slotId": "mod_mount"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec8",
|
||||
"_tpl": "57d17c5e2459775a5c57d17d",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec7",
|
||||
"slotId": "mod_tactical_003"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ec9",
|
||||
"_tpl": "6165ac8c290d254f5e6b2f6c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec3",
|
||||
"slotId": "mod_scope"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ecb",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ecd",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ecf",
|
||||
"_tpl": "6709133fa532466d5403fb7c",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed1",
|
||||
"_tpl": "5c0d591486f7744c505b416f",
|
||||
"upd": {
|
||||
"StackObjectsCount": 20
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed3",
|
||||
"_tpl": "5c0d591486f7744c505b416f",
|
||||
"upd": {
|
||||
"StackObjectsCount": 20
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed5",
|
||||
"_tpl": "5c0d591486f7744c505b416f",
|
||||
"upd": {
|
||||
"StackObjectsCount": 20
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"_tpl": "5ab8e79e86f7742d8b372e78",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed7",
|
||||
"_tpl": "65732688d9d89ff7ac0d9c4c",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Soft_armor_front"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed8",
|
||||
"_tpl": "657326978c1cc6dcd9098b56",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Soft_armor_back"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ed9",
|
||||
"_tpl": "657326a28c1cc6dcd9098b5a",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Soft_armor_left"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7eda",
|
||||
"_tpl": "657326b08c1cc6dcd9098b5e",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "soft_armor_right"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7edb",
|
||||
"_tpl": "657326bc5d3a3129fb05f36b",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Collar"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7edc",
|
||||
"_tpl": "656f611f94b480b8a500c0db",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Front_plate"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7edd",
|
||||
"_tpl": "65573fa5655447403702a816",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ed6",
|
||||
"slotId": "Back_plate"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ede",
|
||||
"_tpl": "675956062f6ddfe8ff0e2806",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7edf",
|
||||
"_tpl": "676307c004856a0b3c0dfffd",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ede",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee0",
|
||||
"_tpl": "676307b4d9ec0af3d9001fa8",
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ede",
|
||||
"slotId": "Helmet_back"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee2",
|
||||
"_tpl": "674589d98dd67746010329e6",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee4",
|
||||
"_tpl": "67458794e21e5d724e066976",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee6",
|
||||
"_tpl": "5d40407c86f774318526545a",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7ee8",
|
||||
"_tpl": "5d40407c86f774318526545a",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7eea",
|
||||
"_tpl": "5d403f9186f7743cac3f229b",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
},
|
||||
{
|
||||
"_id": "6773b39ccf4c2fe4e50a7eec",
|
||||
"_tpl": "5d403f9186f7743cac3f229b",
|
||||
"upd": {
|
||||
"StackObjectsCount": 1
|
||||
},
|
||||
"parentId": "6773b39ccf4c2fe4e50a7ec2",
|
||||
"slotId": "main"
|
||||
}
|
||||
],
|
||||
"sender": "System",
|
||||
"messageText": "New year present!",
|
||||
"collectionTimeHours": 72,
|
||||
"associatedEvent": "Promo",
|
||||
"maxToSendPlayer": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1411,7 +1411,7 @@
|
||||
"_type": "opened case - walter event quest",
|
||||
"rewardCount": 4,
|
||||
"foundInRaid": true,
|
||||
"rewardTplPool": {"67409848d0b2f8eb9b034db9": 1}
|
||||
"rewardTplPool": { "67409848d0b2f8eb9b034db9": 1 }
|
||||
},
|
||||
"674098588466ebb03408b210": {
|
||||
"_type": "opened box - walter event quest",
|
||||
@@ -1596,5 +1596,5 @@
|
||||
"skillGainMultiplers": {
|
||||
"Strength": 1
|
||||
},
|
||||
"deprioritisedMoneyContainers": ["590c60fc86f77412b13fddcf", "5d235bb686f77443f4331278"]
|
||||
"deprioritisedMoneyContainers": ["590c60fc86f77412b13fddcf", "5d235bb686f77443f4331278"]
|
||||
}
|
||||
|
||||
+264
-189
@@ -52,12 +52,12 @@
|
||||
"66da1b546916142b3b022777",
|
||||
"670ad7f1ad195290cd00da7a",
|
||||
"66ec2aa6daf127599c0c31f1",
|
||||
"67654a6759116d347b0bfb86",
|
||||
"5751916f24597720a27126df",
|
||||
"57518f7724597720a31c09ab",
|
||||
"57518fd424597720c85dbaaa",
|
||||
"5a043f2c86f7741aa57b5145",
|
||||
"5a0448bc86f774736f14efa8"
|
||||
"67654a6759116d347b0bfb86",
|
||||
"5751916f24597720a27126df",
|
||||
"57518f7724597720a31c09ab",
|
||||
"57518fd424597720c85dbaaa",
|
||||
"5a043f2c86f7741aa57b5145",
|
||||
"5a0448bc86f774736f14efa8"
|
||||
],
|
||||
"rewardItemTypeBlacklist": ["65649eb40bf0ed77b8044453"],
|
||||
"lootableItemBlacklist": ["660bbc47c38b837877075e47", "660bc341c38b837877075e4c"],
|
||||
@@ -122,21 +122,26 @@
|
||||
"671d8b38b769f0d88c0950f8",
|
||||
"671d8b8c0959c721a50ca838",
|
||||
"660bc341c38b837877075e4c",
|
||||
"67409848d0b2f8eb9b034db9",
|
||||
"67449b6c89d5e1ddc603f504",
|
||||
"675aab0d6b6addc02a08f097",
|
||||
"675aaae1dcf102478202c537",
|
||||
"675aaa9a3107dac100063331",
|
||||
"675aaae75a3ab8372d0b02a7",
|
||||
"675aaab74bca0b001d02f356",
|
||||
"675aaa8f7f3c962069072b27",
|
||||
"675aaaf674a7619a5304c233",
|
||||
"675aaa003107dac10006332f",
|
||||
"6764207f2fa5e32733055c4a",
|
||||
"6764202ae307804338014c1a",
|
||||
"6707d13e4e617ec94f0e5631",
|
||||
"675dc9d37ae1a8792107ca96",
|
||||
"675dcb0545b1a2d108011b2b"
|
||||
"67409848d0b2f8eb9b034db9",
|
||||
"67449b6c89d5e1ddc603f504",
|
||||
"675aab0d6b6addc02a08f097",
|
||||
"675aaae1dcf102478202c537",
|
||||
"675aaa9a3107dac100063331",
|
||||
"675aaae75a3ab8372d0b02a7",
|
||||
"675aaab74bca0b001d02f356",
|
||||
"675aaa8f7f3c962069072b27",
|
||||
"675aaaf674a7619a5304c233",
|
||||
"675aaa003107dac10006332f",
|
||||
"6764207f2fa5e32733055c4a",
|
||||
"6764202ae307804338014c1a",
|
||||
"6707d13e4e617ec94f0e5631",
|
||||
"675dc9d37ae1a8792107ca96",
|
||||
"675dcb0545b1a2d108011b2b",
|
||||
"66d9f8744827a77e870ecaf1",
|
||||
"6707d0804e617ec94f0e562f",
|
||||
"67449b6c89d5e1ddc603f504",
|
||||
"6740987b89d5e1ddc603f4f0",
|
||||
"6707d0bdaab679420007e01a"
|
||||
],
|
||||
"bossItems": [
|
||||
"6275303a9f372d6ea97f9ec7",
|
||||
@@ -183,172 +188,242 @@
|
||||
"63a898a328e385334e0640a5": { "price": 20000, "parentId": "5b5f6fa186f77409407a7eb7" },
|
||||
"63a897c6b1ff6e29734fcc95": { "price": 40000, "parentId": "5b5f6fa186f77409407a7eb7" }
|
||||
},
|
||||
"customItemGlobalPresets": [
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "675956062f6ddfe8ff0e2806",
|
||||
"_id": "6777b37393a9a6f10ea57501",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777bee3324b2e0cf7b7cfe1",
|
||||
"_tpl": "675956062f6ddfe8ff0e2806"
|
||||
},
|
||||
{
|
||||
"_id": "6777c029e3703d1f9dc47d15",
|
||||
"_tpl": "676307c004856a0b3c0dfffd",
|
||||
"parentId": "6777bee3324b2e0cf7b7cfe1",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c030386bec3cd3c41b6e",
|
||||
"_tpl": "676307b4d9ec0af3d9001fa8",
|
||||
"parentId": "6777bee3324b2e0cf7b7cfe1",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (MultiCam Alpine) Default",
|
||||
"_parent": "6777bee3324b2e0cf7b7cfe1",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "6759655674aa5e0825040d62",
|
||||
"_id": "6777bf37e10ffdb431ff9508",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777bf03ad549e66e7ad06b2",
|
||||
"_tpl": "6759655674aa5e0825040d62"
|
||||
},
|
||||
{
|
||||
"_id": "6777c02180ddaee0b54493da",
|
||||
"_tpl": "676307c004856a0b3c0dfffd",
|
||||
"parentId": "6777bf03ad549e66e7ad06b2",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c00f41b022243abdac99",
|
||||
"_tpl": "676307b4d9ec0af3d9001fa8",
|
||||
"parentId": "6777bf03ad549e66e7ad06b2",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (Olive Drab) Default",
|
||||
"_parent": "6777bf03ad549e66e7ad06b2",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "67597ceea35600b4c10cea86",
|
||||
"_id": "6777bfee0658691ef27d9993",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777bfaaaf1999ce0701bc00",
|
||||
"_tpl": "67597ceea35600b4c10cea86"
|
||||
},
|
||||
{
|
||||
"_id": "6777c01b07b5d347d6b99404",
|
||||
"_tpl": "676307ded8b241b4f703a3e8",
|
||||
"parentId": "6777bfaaaf1999ce0701bc00",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c0033f374912b965ae5a",
|
||||
"_tpl": "676307d3d9ec0af3d9001fac",
|
||||
"parentId": "6777bfaaaf1999ce0701bc00",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (Coyote) Default",
|
||||
"_parent": "6777bfaaaf1999ce0701bc00",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "67597d241d5a44f2f605df06",
|
||||
"_id": "6777c1244557b9b1474da362",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777c10f4989fc544063b5a1",
|
||||
"_tpl": "67597d241d5a44f2f605df06"
|
||||
},
|
||||
{
|
||||
"_id": "6777c1293c00b35cd6960536",
|
||||
"_tpl": "676307ded8b241b4f703a3e8",
|
||||
"parentId": "6777c10f4989fc544063b5a1",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c12f8fc234973af0cf16",
|
||||
"_tpl": "676307d3d9ec0af3d9001fac",
|
||||
"parentId": "6777c10f4989fc544063b5a1",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (Coyote/MultiCam Arid) Default",
|
||||
"_parent": "6777c10f4989fc544063b5a1",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "674d91ce6e862d5a95059ed6",
|
||||
"_id": "67784df15058057382b28f87",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "67784dfaf57296ecbdbfc87f",
|
||||
"_tpl": "674d91ce6e862d5a95059ed6"
|
||||
},
|
||||
{
|
||||
"_id": "67784e27ee352de60dc982a3",
|
||||
"_tpl": "6575ea3060703324250610da",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Soft_armor_front"
|
||||
},
|
||||
{
|
||||
"_id": "67784e32d75f52daffe36de5",
|
||||
"_tpl": "6575ea4cf6a13a7b7100adc4",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Soft_armor_back"
|
||||
},
|
||||
{
|
||||
"_id": "67784e36e778f8aace662e1d",
|
||||
"_tpl": "6575ea5cf6a13a7b7100adc8",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Soft_armor_left"
|
||||
},
|
||||
{
|
||||
"_id": "67784e3c0e4afac4ebdab52d",
|
||||
"_tpl": "6575ea6760703324250610de",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "soft_armor_right"
|
||||
},
|
||||
{
|
||||
"_id": "67784e40832d547cb2c986bf",
|
||||
"_tpl": "6575ea719c7cad336508e418",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Collar"
|
||||
},
|
||||
{
|
||||
"_id": "67784e446c92fcb0e0d8fea8",
|
||||
"_tpl": "6575ea7c60703324250610e2",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Groin"
|
||||
},
|
||||
{
|
||||
"_id": "67784e473abf2a39a05dba5a",
|
||||
"_tpl": "656f611f94b480b8a500c0db",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Front_plate"
|
||||
},
|
||||
{
|
||||
"_id": "67784e4b934ace8451681bee",
|
||||
"_tpl": "656efaf54772930db4031ff5",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Back_plate"
|
||||
}
|
||||
],
|
||||
"_name": "Body armor 6B13 M Killa Christmas Standard",
|
||||
"_parent": "67784dfaf57296ecbdbfc87f",
|
||||
"_type": "Preset"
|
||||
}
|
||||
]
|
||||
"customItemGlobalPresets": [
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "675956062f6ddfe8ff0e2806",
|
||||
"_id": "6777b37393a9a6f10ea57501",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777bee3324b2e0cf7b7cfe1",
|
||||
"_tpl": "675956062f6ddfe8ff0e2806"
|
||||
},
|
||||
{
|
||||
"_id": "6777c029e3703d1f9dc47d15",
|
||||
"_tpl": "676307c004856a0b3c0dfffd",
|
||||
"parentId": "6777bee3324b2e0cf7b7cfe1",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c030386bec3cd3c41b6e",
|
||||
"_tpl": "676307b4d9ec0af3d9001fa8",
|
||||
"parentId": "6777bee3324b2e0cf7b7cfe1",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (MultiCam Alpine) Default",
|
||||
"_parent": "6777bee3324b2e0cf7b7cfe1",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "6759655674aa5e0825040d62",
|
||||
"_id": "6777bf37e10ffdb431ff9508",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777bf03ad549e66e7ad06b2",
|
||||
"_tpl": "6759655674aa5e0825040d62"
|
||||
},
|
||||
{
|
||||
"_id": "6777c02180ddaee0b54493da",
|
||||
"_tpl": "676307c004856a0b3c0dfffd",
|
||||
"parentId": "6777bf03ad549e66e7ad06b2",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c00f41b022243abdac99",
|
||||
"_tpl": "676307b4d9ec0af3d9001fa8",
|
||||
"parentId": "6777bf03ad549e66e7ad06b2",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (Olive Drab) Default",
|
||||
"_parent": "6777bf03ad549e66e7ad06b2",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "67597ceea35600b4c10cea86",
|
||||
"_id": "6777bfee0658691ef27d9993",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777bfaaaf1999ce0701bc00",
|
||||
"_tpl": "67597ceea35600b4c10cea86"
|
||||
},
|
||||
{
|
||||
"_id": "6777c01b07b5d347d6b99404",
|
||||
"_tpl": "676307ded8b241b4f703a3e8",
|
||||
"parentId": "6777bfaaaf1999ce0701bc00",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c0033f374912b965ae5a",
|
||||
"_tpl": "676307d3d9ec0af3d9001fac",
|
||||
"parentId": "6777bfaaaf1999ce0701bc00",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (Coyote) Default",
|
||||
"_parent": "6777bfaaaf1999ce0701bc00",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "67597d241d5a44f2f605df06",
|
||||
"_id": "6777c1244557b9b1474da362",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "6777c10f4989fc544063b5a1",
|
||||
"_tpl": "67597d241d5a44f2f605df06"
|
||||
},
|
||||
{
|
||||
"_id": "6777c1293c00b35cd6960536",
|
||||
"_tpl": "676307ded8b241b4f703a3e8",
|
||||
"parentId": "6777c10f4989fc544063b5a1",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "6777c12f8fc234973af0cf16",
|
||||
"_tpl": "676307d3d9ec0af3d9001fac",
|
||||
"parentId": "6777c10f4989fc544063b5a1",
|
||||
"slotId": "Helmet_back"
|
||||
}
|
||||
],
|
||||
"_name": "MTEK FLUX Ballistic helmet (Coyote/MultiCam Arid) Default",
|
||||
"_parent": "6777c10f4989fc544063b5a1",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "674d91ce6e862d5a95059ed6",
|
||||
"_id": "67784df15058057382b28f87",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "67784dfaf57296ecbdbfc87f",
|
||||
"_tpl": "674d91ce6e862d5a95059ed6"
|
||||
},
|
||||
{
|
||||
"_id": "67784e27ee352de60dc982a3",
|
||||
"_tpl": "6575ea3060703324250610da",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Soft_armor_front"
|
||||
},
|
||||
{
|
||||
"_id": "67784e32d75f52daffe36de5",
|
||||
"_tpl": "6575ea4cf6a13a7b7100adc4",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Soft_armor_back"
|
||||
},
|
||||
{
|
||||
"_id": "67784e36e778f8aace662e1d",
|
||||
"_tpl": "6575ea5cf6a13a7b7100adc8",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Soft_armor_left"
|
||||
},
|
||||
{
|
||||
"_id": "67784e3c0e4afac4ebdab52d",
|
||||
"_tpl": "6575ea6760703324250610de",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "soft_armor_right"
|
||||
},
|
||||
{
|
||||
"_id": "67784e40832d547cb2c986bf",
|
||||
"_tpl": "6575ea719c7cad336508e418",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Collar"
|
||||
},
|
||||
{
|
||||
"_id": "67784e446c92fcb0e0d8fea8",
|
||||
"_tpl": "6575ea7c60703324250610e2",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Groin"
|
||||
},
|
||||
{
|
||||
"_id": "67784e473abf2a39a05dba5a",
|
||||
"_tpl": "656f611f94b480b8a500c0db",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Front_plate"
|
||||
},
|
||||
{
|
||||
"_id": "67784e4b934ace8451681bee",
|
||||
"_tpl": "656efaf54772930db4031ff5",
|
||||
"parentId": "67784dfaf57296ecbdbfc87f",
|
||||
"slotId": "Back_plate"
|
||||
}
|
||||
],
|
||||
"_name": "Body armor 6B13 M Killa Christmas Standard",
|
||||
"_parent": "67784dfaf57296ecbdbfc87f",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "6745895717824b1ec20570a6",
|
||||
"_id": "677d14927f8ee8353d85918d",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "677d14a27757dcc54a3054fb",
|
||||
"_tpl": "6745895717824b1ec20570a6"
|
||||
},
|
||||
{
|
||||
"_id": "677d14abef2285ed18ee3e62",
|
||||
"_tpl": "657baaf0b7e9ca9a02045c02",
|
||||
"parentId": "677d14a27757dcc54a3054fb",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "677d14b0024eda59d1544794",
|
||||
"_tpl": "657bab6ec6f689d3a205b85f",
|
||||
"parentId": "677d14a27757dcc54a3054fb",
|
||||
"slotId": "Helmet_back"
|
||||
},
|
||||
{
|
||||
"_id": "677d14bb1dfea5b33fa31337",
|
||||
"_tpl": "657babc6f58ba5a6250107a2",
|
||||
"parentId": "677d14a27757dcc54a3054fb",
|
||||
"slotId": "Helmet_ears"
|
||||
}
|
||||
],
|
||||
"_name": "6B47 Ratnik-BSh helmet (EMR Arctic cover) default",
|
||||
"_parent": "677d14a27757dcc54a3054fb",
|
||||
"_type": "Preset"
|
||||
},
|
||||
{
|
||||
"_changeWeaponName": false,
|
||||
"_encyclopedia": "6759af0f9c8a538dd70bfae6",
|
||||
"_id": "677e90e191de7ae4136e3967",
|
||||
"_items": [
|
||||
{
|
||||
"_id": "677e90d1fc28426ede1448bd",
|
||||
"_tpl": "6759af0f9c8a538dd70bfae6"
|
||||
},
|
||||
{
|
||||
"_id": "677e90e71b6c92662b1b5cce",
|
||||
"_tpl": "6571133d22996eaf11088200",
|
||||
"parentId": "677e90d1fc28426ede1448bd",
|
||||
"slotId": "Helmet_top"
|
||||
},
|
||||
{
|
||||
"_id": "677e90ef6b6b559c36d31485",
|
||||
"_tpl": "6571138e818110db4600aa71",
|
||||
"parentId": "677e90d1fc28426ede1448bd",
|
||||
"slotId": "Helmet_back"
|
||||
},
|
||||
{
|
||||
"_id": "677e90f2e2de53f5b48dd35d",
|
||||
"_tpl": "657112fa818110db4600aa6b",
|
||||
"parentId": "677e90d1fc28426ede1448bd",
|
||||
"slotId": "Helmet_ears"
|
||||
},
|
||||
{
|
||||
"_id": "677e90f61cc7ed9f89331cac",
|
||||
"_tpl": "5c0e842486f77443a74d2976",
|
||||
"parentId": "677e90d1fc28426ede1448bd",
|
||||
"slotId": "mod_equipment"
|
||||
}
|
||||
],
|
||||
"_name": "Maska-1SCh bulletproof helmet (Christmas Edition) default",
|
||||
"_parent": "677e90d1fc28426ede1448bd",
|
||||
"_type": "Preset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -539,5 +539,11 @@
|
||||
"nonTriggered": 80,
|
||||
"triggered": 90
|
||||
},
|
||||
"tplsToStripChildItemsFrom": [
|
||||
"63a8970d7108f713591149f5",
|
||||
"63a897c6b1ff6e29734fcc95",
|
||||
"63a898a328e385334e0640a5",
|
||||
"634959225289190e5e773b3b"
|
||||
],
|
||||
"nonMaps": ["base", "develop", "hideout", "privatearea", "suburbs", "terminal", "town"]
|
||||
}
|
||||
|
||||
@@ -123,20 +123,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -146,21 +146,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -208,20 +208,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -231,21 +231,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -293,20 +293,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -316,21 +316,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -378,20 +378,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -401,21 +401,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -463,20 +463,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -486,21 +486,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -548,20 +548,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -571,21 +571,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 2,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -633,20 +633,20 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -656,21 +656,21 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {}
|
||||
@@ -718,7 +718,7 @@
|
||||
"0": 2,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
@@ -726,13 +726,13 @@
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -741,7 +741,7 @@
|
||||
"3": 1,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
@@ -749,7 +749,7 @@
|
||||
"2": 2,
|
||||
"3": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
@@ -757,7 +757,7 @@
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {
|
||||
@@ -808,42 +808,42 @@
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"0": 1,
|
||||
"1": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
"2": 2,
|
||||
"3": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"2": 2,
|
||||
"3": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {
|
||||
@@ -895,21 +895,21 @@
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"1": 5,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -918,7 +918,7 @@
|
||||
"4": 1,
|
||||
"5": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
@@ -926,14 +926,14 @@
|
||||
"3": 2,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {
|
||||
@@ -984,21 +984,21 @@
|
||||
"weights": {
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"2": 1,
|
||||
"3": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -1007,7 +1007,7 @@
|
||||
"5": 1,
|
||||
"6": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
@@ -1015,14 +1015,14 @@
|
||||
"3": 2,
|
||||
"4": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"2": 1,
|
||||
"3": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {
|
||||
@@ -1074,21 +1074,21 @@
|
||||
"2": 5,
|
||||
"3": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"2": 1,
|
||||
"3": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -1097,21 +1097,21 @@
|
||||
"5": 2,
|
||||
"6": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"3": 2,
|
||||
"4": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"2": 2,
|
||||
"3": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {
|
||||
@@ -1180,21 +1180,21 @@
|
||||
"2": 1,
|
||||
"3": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"drugs": {
|
||||
"weights": {
|
||||
"3": 1,
|
||||
"4": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"stims": {
|
||||
"weights": {
|
||||
"2": 1,
|
||||
"3": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"looseLoot": {
|
||||
"weights": {
|
||||
@@ -1202,21 +1202,21 @@
|
||||
"6": 2,
|
||||
"7": 1
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"magazines": {
|
||||
"weights": {
|
||||
"3": 1,
|
||||
"4": 4
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
},
|
||||
"grenades": {
|
||||
"weights": {
|
||||
"3": 1,
|
||||
"4": 2
|
||||
},
|
||||
"whitelist": {}
|
||||
"whitelist": []
|
||||
}
|
||||
},
|
||||
"lootItemsToAddChancePercent": {
|
||||
|
||||
@@ -151,9 +151,15 @@
|
||||
"675dc9d37ae1a8792107ca96",
|
||||
"675dcb0545b1a2d108011b2b",
|
||||
"6707d13e4e617ec94f0e5631",
|
||||
"67408903268737ef6908d432",
|
||||
"67499b9b909d2013670a5029",
|
||||
"6638a5474e92f038531e210e"
|
||||
"67408903268737ef6908d432",
|
||||
"67499b9b909d2013670a5029",
|
||||
"6638a5474e92f038531e210e",
|
||||
"66d9f8744827a77e870ecaf1",
|
||||
"6707d0804e617ec94f0e562f",
|
||||
"67449b6c89d5e1ddc603f504",
|
||||
"6740987b89d5e1ddc603f4f0",
|
||||
"6707d0bdaab679420007e01a",
|
||||
"66d9f7e7099cf6adcc07a369"
|
||||
],
|
||||
"useDifficultyOverride": false,
|
||||
"difficulty": "AsOnline",
|
||||
@@ -717,6 +723,40 @@
|
||||
"value": 2500000
|
||||
}
|
||||
],
|
||||
"lootItemLimitsRub": [
|
||||
{
|
||||
"min": 46,
|
||||
"max": 64,
|
||||
"backpack": {
|
||||
"min": 5000,
|
||||
"max": 0
|
||||
},
|
||||
"pocket": {
|
||||
"min": 5000,
|
||||
"max": 0
|
||||
},
|
||||
"vest": {
|
||||
"min": 5000,
|
||||
"max": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"min": 65,
|
||||
"max": 100,
|
||||
"backpack": {
|
||||
"min": 10000,
|
||||
"max": 0
|
||||
},
|
||||
"pocket": {
|
||||
"min": 10000,
|
||||
"max": 0
|
||||
},
|
||||
"vest": {
|
||||
"min": 10000,
|
||||
"max": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"maxPocketLootTotalRub": 50000,
|
||||
"maxVestLootTotalRub": 50000,
|
||||
"convertIntoPmcChance": {
|
||||
@@ -726,8 +766,8 @@
|
||||
"max": 35
|
||||
},
|
||||
"cursedassault": {
|
||||
"min": 15,
|
||||
"max": 30
|
||||
"min": 0,
|
||||
"max": 0
|
||||
},
|
||||
"pmcbot": {
|
||||
"min": 5,
|
||||
@@ -772,8 +812,8 @@
|
||||
"hostilitySettings": {
|
||||
"pmcusec": {
|
||||
"additionalEnemyTypes": [
|
||||
"arenaFighterEvent",
|
||||
"marksman",
|
||||
"arenaFighterEvent",
|
||||
"marksman",
|
||||
"peacemaker",
|
||||
"skier",
|
||||
"ravangeZryachiyEvent",
|
||||
@@ -796,14 +836,16 @@
|
||||
"followerBigPipe",
|
||||
"followerBirdEye",
|
||||
"followerBully",
|
||||
"followerBoarClose1",
|
||||
"followerBoarClose2"
|
||||
"followerBoarClose1",
|
||||
"followerBoarClose2"
|
||||
],
|
||||
"additionalFriendlyTypes": ["gifter", "shooterBTR", "sectactPriestEvent", "peacefullZryachiyEvent"],
|
||||
"chancedEnemies": [{
|
||||
"chancedEnemies": [
|
||||
{
|
||||
"EnemyChance": 85,
|
||||
"Role": "pmcUSEC"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"EnemyChance": 100,
|
||||
"Role": "pmcBEAR"
|
||||
}
|
||||
@@ -815,8 +857,8 @@
|
||||
},
|
||||
"pmcbear": {
|
||||
"additionalEnemyTypes": [
|
||||
"arenaFighterEvent",
|
||||
"marksman",
|
||||
"arenaFighterEvent",
|
||||
"marksman",
|
||||
"peacemaker",
|
||||
"skier",
|
||||
"ravangeZryachiyEvent",
|
||||
@@ -839,14 +881,16 @@
|
||||
"followerBigPipe",
|
||||
"followerBirdEye",
|
||||
"followerBully",
|
||||
"followerBoarClose1",
|
||||
"followerBoarClose2"
|
||||
"followerBoarClose1",
|
||||
"followerBoarClose2"
|
||||
],
|
||||
"additionalFriendlyTypes": ["gifter", "shooterBTR", "sectactPriestEvent", "peacefullZryachiyEvent"],
|
||||
"chancedEnemies": [{
|
||||
"chancedEnemies": [
|
||||
{
|
||||
"EnemyChance": 85,
|
||||
"Role": "pmcBEAR"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"EnemyChance": 100,
|
||||
"Role": "pmcUSEC"
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"5e381b0286f77420e3417a74",
|
||||
"5e4d4ac186f774264f758336",
|
||||
"639136d68ba6894d155e77cf",
|
||||
"6613f3007f6666d56807c929",
|
||||
"6613f307fca4f2f386029409"
|
||||
"6613f3007f6666d56807c929",
|
||||
"6613f307fca4f2f386029409"
|
||||
],
|
||||
"profileBlacklist": {
|
||||
"unheard_edition": ["666314a50aa5c7436c00908a"]
|
||||
|
||||
@@ -147,6 +147,18 @@
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
"5c164d2286f774194c5e69fa": {
|
||||
"_name": "KEYCARD",
|
||||
"conditionChance": 0.04,
|
||||
"current": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"max": {
|
||||
"min": 0.25,
|
||||
"max": 1
|
||||
}
|
||||
},
|
||||
"5448e5284bdc2dcb718b4567": {
|
||||
"_name": "VEST",
|
||||
"conditionChance": 0.2,
|
||||
@@ -337,13 +349,13 @@
|
||||
"newPriceHandbookMultiplier": 11
|
||||
}
|
||||
},
|
||||
"itemPriceOverrideRouble":{
|
||||
"66bc98a01a47be227a5e956e": 500000,
|
||||
"63a8970d7108f713591149f5": 50000,
|
||||
"63a898a328e385334e0640a5": 100000,
|
||||
"63a897c6b1ff6e29734fcc95": 200000,
|
||||
"674d91ce6e862d5a95059ed6": 250000
|
||||
}
|
||||
"itemPriceOverrideRouble": {
|
||||
"66bc98a01a47be227a5e956e": 500000,
|
||||
"63a8970d7108f713591149f5": 50000,
|
||||
"63a898a328e385334e0640a5": 100000,
|
||||
"63a897c6b1ff6e29734fcc95": 200000,
|
||||
"674d91ce6e862d5a95059ed6": 250000
|
||||
}
|
||||
},
|
||||
"tieredFlea": {
|
||||
"enabled": false,
|
||||
|
||||
@@ -98,13 +98,13 @@
|
||||
"5a43957686f7742a2c2f11b0": 150
|
||||
}
|
||||
},
|
||||
"bosskilla": {
|
||||
"bosskilla": {
|
||||
"ArmorVest": {
|
||||
"674d91ce6e862d5a95059ed6": 200
|
||||
},
|
||||
"Headwear": {
|
||||
"6759af0f9c8a538dd70bfae6": 200
|
||||
}
|
||||
"Headwear": {
|
||||
"6759af0f9c8a538dd70bfae6": 200
|
||||
}
|
||||
},
|
||||
"bossknight": {
|
||||
"FaceCover": {
|
||||
@@ -242,7 +242,7 @@
|
||||
"bear": {
|
||||
"FaceCover": {
|
||||
"5c1a1e3f2e221602b66cc4c2": 30,
|
||||
"675ac888803644528007b3f6": 30
|
||||
"675ac888803644528007b3f6": 30
|
||||
},
|
||||
"Headwear": {
|
||||
"5a43957686f7742a2c2f11b0": 80,
|
||||
@@ -252,7 +252,7 @@
|
||||
"usec": {
|
||||
"FaceCover": {
|
||||
"5c1a1e3f2e221602b66cc4c2": 30,
|
||||
"675ac888803644528007b3f6": 30
|
||||
"675ac888803644528007b3f6": 30
|
||||
},
|
||||
"Headwear": {
|
||||
"5a43957686f7742a2c2f11b0": 80,
|
||||
@@ -261,28 +261,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eventLoot": {
|
||||
"christmas": {
|
||||
"assault": {
|
||||
"Pockets": {
|
||||
"5df8a6a186f77412640e2e80": 2200,
|
||||
"5df8a72c86f77412640e2e83": 2200,
|
||||
"5df8a77486f77412672a1e3f": 2200
|
||||
},
|
||||
"Backpack": {
|
||||
"5df8a6a186f77412640e2e80": 2200,
|
||||
"5df8a72c86f77412640e2e83": 2200,
|
||||
"5df8a77486f77412672a1e3f": 2200,
|
||||
"63a8970d7108f713591149f5": 250
|
||||
},
|
||||
"TacticalVest": {
|
||||
"5df8a6a186f77412640e2e80": 2200,
|
||||
"5df8a72c86f77412640e2e83": 2200,
|
||||
"5df8a77486f77412672a1e3f": 2200
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"eventLoot": {
|
||||
"christmas": {
|
||||
"assault": {
|
||||
"Pockets": {
|
||||
"5df8a6a186f77412640e2e80": 2200,
|
||||
"5df8a72c86f77412640e2e83": 2200,
|
||||
"5df8a77486f77412672a1e3f": 2200
|
||||
},
|
||||
"Backpack": {
|
||||
"5df8a6a186f77412640e2e80": 2200,
|
||||
"5df8a72c86f77412640e2e83": 2200,
|
||||
"5df8a77486f77412672a1e3f": 2200,
|
||||
"63a8970d7108f713591149f5": 250
|
||||
},
|
||||
"TacticalVest": {
|
||||
"5df8a6a186f77412640e2e80": 2200,
|
||||
"5df8a72c86f77412640e2e83": 2200,
|
||||
"5df8a77486f77412672a1e3f": 2200
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"eventBotMapping": {
|
||||
"peacefullZryachiyEvent": "bossZryachiy",
|
||||
"sectactPriestEvent": "sectantPriest",
|
||||
@@ -8363,9 +8363,9 @@
|
||||
"endDay": "31",
|
||||
"endMonth": "12",
|
||||
"settings": {
|
||||
"enableChristmasHideout": true,
|
||||
"enableChristmasHideout": true,
|
||||
"enableSanta": true,
|
||||
"adjustBotAppearances": true
|
||||
"adjustBotAppearances": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -8376,24 +8376,24 @@
|
||||
"startMonth": "1",
|
||||
"endDay": "1",
|
||||
"endMonth": "1",
|
||||
"settings": {
|
||||
"enableChristmasHideout": true,
|
||||
"settings": {
|
||||
"enableChristmasHideout": true,
|
||||
"enableSanta": true,
|
||||
"adjustBotAppearances": true
|
||||
"adjustBotAppearances": true
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "christmas January",
|
||||
"type": "CHRISTMAS",
|
||||
"startDay": "2",
|
||||
"startMonth": "1",
|
||||
"endDay": "7",
|
||||
"endDay": "14",
|
||||
"endMonth": "1",
|
||||
"settings": {
|
||||
"enableChristmasHideout": true,
|
||||
"enableChristmasHideout": true,
|
||||
"enableSanta": true,
|
||||
"adjustBotAppearances": true
|
||||
"adjustBotAppearances": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -12314,11 +12314,11 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"christmasContainerIds": [
|
||||
"container_custom_DesignStuff_00427",
|
||||
"container_Shopping_Mall_DesignStuff_00808",
|
||||
"container_Lighthouse_DesignStuff_00001",
|
||||
"container_shoreline_DesignStuff_00418",
|
||||
"container_woods_design_stuff_00328"
|
||||
]
|
||||
"christmasContainerIds": [
|
||||
"container_custom_DesignStuff_00427",
|
||||
"container_Shopping_Mall_DesignStuff_00808",
|
||||
"container_Lighthouse_DesignStuff_00001",
|
||||
"container_shoreline_DesignStuff_00418",
|
||||
"container_woods_design_stuff_00328"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
"65ddcc9cfa85b9f17d0dfb07",
|
||||
"660312cc4d6cdfa6f500c703",
|
||||
"6655e35b6bc645cb7b059912",
|
||||
"6759673c76e93d8eb20b2080"
|
||||
"6759673c76e93d8eb20b2080"
|
||||
],
|
||||
"coopExtractGift": {
|
||||
"sendGift": true,
|
||||
|
||||
@@ -97,56 +97,64 @@
|
||||
"weights": [1, 2]
|
||||
}
|
||||
},
|
||||
"seasonDates": [{
|
||||
"seasonDates": [
|
||||
{
|
||||
"seasonType": 0,
|
||||
"name": "SUMMER",
|
||||
"startDay": "2",
|
||||
"startMonth": "6",
|
||||
"endDay": "15",
|
||||
"endMonth": "10"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 1,
|
||||
"name": "AUTUMN",
|
||||
"startDay": "15",
|
||||
"startMonth": "10",
|
||||
"endDay": "1",
|
||||
"endMonth": "11"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 4,
|
||||
"name": "AUTUMN_LATE",
|
||||
"startDay": "1",
|
||||
"startMonth": "11",
|
||||
"endDay": "21",
|
||||
"endMonth": "12"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 2,
|
||||
"name": "WINTER_START",
|
||||
"startDay": "21",
|
||||
"startMonth": "12",
|
||||
"endDay": "31",
|
||||
"endMonth": "12"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 2,
|
||||
"name": "WINTER_END",
|
||||
"startDay": "1",
|
||||
"startMonth": "1",
|
||||
"endDay": "9",
|
||||
"endMonth": "1"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 5,
|
||||
"name": "SPRING_EARLY",
|
||||
"startDay": "9",
|
||||
"startMonth": "1",
|
||||
"endDay": "25",
|
||||
"endMonth": "3"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 3,
|
||||
"name": "SPRING",
|
||||
"startDay": "25",
|
||||
"startMonth": "3",
|
||||
"endDay": "2",
|
||||
"endMonth": "6"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"seasonType": 4,
|
||||
"name": "STORM",
|
||||
"startDay": "24",
|
||||
|
||||
@@ -76,9 +76,7 @@
|
||||
"DamageHistory": {
|
||||
"LethalDamagePart": "Head",
|
||||
"LethalDamage": null,
|
||||
"BodyParts": {
|
||||
"Head": []
|
||||
}
|
||||
"BodyParts": []
|
||||
},
|
||||
"LastPlayerState": null,
|
||||
"TotalInGameTime": 0,
|
||||
|
||||
@@ -34670,6 +34670,41 @@
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 0,
|
||||
"TemplateId": "676aa450fe1fc45172014df2"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "5b7c710788a4506dec015957"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "5c093db286f7740a1b2617e3"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "5c0a840b86f7742ffa4f2482"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "5aafbcd986f7745e590fff23"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "59fb016586f7746d0d4b423a"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "59fb042886f7746c5005a7b2"
|
||||
},
|
||||
{
|
||||
"MaxInLobby": 0,
|
||||
"MaxInRaid": 5,
|
||||
"TemplateId": "5b6d9ce188a4501afc1b2b25"
|
||||
}
|
||||
],
|
||||
"RunddansSettings": {
|
||||
|
||||
@@ -3355,7 +3355,7 @@
|
||||
"count": 1,
|
||||
"isEncoded": false,
|
||||
"isFunctional": false,
|
||||
"isSpawnedInSession": true,
|
||||
"isSpawnedInSession": false,
|
||||
"templateId": "6389c7f115805221fb410466",
|
||||
"type": "Item"
|
||||
}
|
||||
@@ -4626,7 +4626,7 @@
|
||||
"count": 1,
|
||||
"isEncoded": false,
|
||||
"isFunctional": false,
|
||||
"isSpawnedInSession": true,
|
||||
"isSpawnedInSession": false,
|
||||
"templateId": "6389c85357baa773a825b356",
|
||||
"type": "Item"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Награда за прохождение опроса",
|
||||
"676ae11cf444b79e7306745f 0": "Предметы сохраненные при переходе на новый уровень престижа",
|
||||
"676bc75c4859905179061aff 0": "Письмо с наградами за престиж",
|
||||
"6776e324810eb26b880fb4a5 0": "Говорят, что с инструментами сейчас совсем туго стало, даже OLI не спасает. Правильно я сделал, что оптом тогда эти рулетки заказал. Держи, сейчас я тебе помогу, а сочтёмся как-нибудь потом.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "Если вы покинете матч, то вы подставляете своих союзников в невыгодное положение./nВы потеряете награду и рейтинг, а также можете получить временный бан.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Внимание! Вы покидаете матч.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
@@ -27978,7 +27979,7 @@
|
||||
"675c044e3691199fe911a641": "Найти и пометить четвертый особый груз TerraGroup Маркером MS2000 на локации Таможня",
|
||||
"675c3fbeb402d4fa5589516f": "Найти и пометить пятый особый груз TerraGroup Маркером MS2000 на локации Таможня",
|
||||
"675c3fd3a2c0bad5f70af01c": "Найти и пометить шестой особый груз TerraGroup Маркером MS2000 на локации Таможня",
|
||||
"675c3fdd5af984e99db7b4e1": "Найти и пометить седьмрй особый груз TerraGroup Маркером MS2000 на локации Таможня",
|
||||
"675c3fdd5af984e99db7b4e1": "Найти и пометить седьмой особый груз TerraGroup Маркером MS2000 на локации Таможня",
|
||||
"675c03d1f7da9792a405549a acceptPlayerMessage": "",
|
||||
"675c03d1f7da9792a405549a declinePlayerMessage": "",
|
||||
"675c03d1f7da9792a405549a completePlayerMessage": "",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -20273,6 +20273,7 @@
|
||||
"672dd5b83804f9bc8e0a610e 0": "Survey reward",
|
||||
"676ae11cf444b79e7306745f 0": "Items that were saved when achieving the new level of prestige",
|
||||
"676bc75c4859905179061aff 0": "Prestige rewards",
|
||||
"6776e324810eb26b880fb4a5 0": "They say tools are in short supply these days, even OLI can't save the day. Good thing I ordered those tape measures in bulk back then. Here, take this — I’ll help you out now, and we’ll settle up later, one way or another.",
|
||||
"Arena/UI/Match_leaving_warning_body 0": "If you leave the match, you'll put your allies at disadvantage./nYou'll lose your reward and rating and could receive a temporary ban.",
|
||||
"Arena/UI/Match_leaving_warning_header 0": "Warning! You are leaving the match.",
|
||||
"5fc615710b735e7b024c76ed Name": "Boss sanitar",
|
||||
|
||||
@@ -131,8 +131,6 @@
|
||||
"modloader-error_parsing_mod_load_order": "حدث خطأ أثناء تحليل ترتيب المودات.\n\n\n\n\n\n\n",
|
||||
"modloader-incompatibilities_not_string_array": "يجب أن يكون Mod %s package.json الخاصية 'incompatibilities' مصفوفة سلاسل",
|
||||
"modloader-incompatible_mod_found": "المود {{author}}-{{name}} غير متوافق مع {{incompatibleModName}}.\n\n\n\n\n\n\n",
|
||||
"modloader-installing_external_dependencies": "تثبيت الاعتمادات للإضافة: {{name}} بواسطة: {{author}}",
|
||||
"modloader-installing_external_dependencies_disabled": "الإضافة: {{name}}: {{author}} تتطلب اعتمادات خارجية ولكن الميزة معطلة حاليًا، اذهب إلى \"{{configPath}}\", اضبط \"{{configOption}}\" إلى \"صحيح\" و أعد تشغيل الخادم.\nبتمكينك هذا فإنك تتحمل كل المسؤولية عن ما سيتم تحميله من قبل {{name}} إلى جهازك.",
|
||||
"modloader-invalid_version_property": "ملف package.json للمود %s يحتوي على سلسلة إصدار غير صالحة.\n\n\n\n\n\n\n",
|
||||
"modloader-is_client_mod": "المود (%s) هو مود خاص بالعميل ويجب وضعه في المجلد التالي: /spt/bepinex/plugins\n\n\n\n\n\n\n",
|
||||
"modloader-load_order_conflict": "{{modOneName}} و {{modTwoName}} لديهما متطلبات تعارض في ترتيب التحميل، لا يمكن للخادم أن يبدأ حتى يتم حل هذا وسيتم إيقاف التشغيل.\n\n\n\n\n\n\n",
|
||||
|
||||
@@ -143,8 +143,6 @@
|
||||
"modloader-error_parsing_mod_load_order": "Chyba parsování načítání pořadí módů",
|
||||
"modloader-incompatibilities_not_string_array": "Vlastnost módu %s package.json 'incompatibility' by měla být pole pro řetězec",
|
||||
"modloader-incompatible_mod_found": "Modifikace {{author}}-{{name}} je nekompatibilní s {{incompatibleModName}}",
|
||||
"modloader-installing_external_dependencies": "Instalace závislostí pro mód: {{name}} od autora: {{author}}",
|
||||
"modloader-installing_external_dependencies_disabled": "Mód: {{name}} od: {{author}} vyžaduje externí závislosti, ale funkce je momentálně zakázána, přejděte na \"{{configPath}}\", nastavte \"{{configOption}}\" na true a restartujte server.\nPovolením této funkce přijímáte veškerou odpovědnost za to, co {{name}} stáhne do vašeho počítače.",
|
||||
"modloader-invalid_version_property": "Mód %s package.json obsahuje neplatnou verzi řetězce",
|
||||
"modloader-is_client_mod": "Mód (%s) je klientský mód a měl by být umístěn v následující složce: /spt/bepinex/plugins",
|
||||
"modloader-load_order_conflict": "`{{modOneName}}` a `{{modTwoName}}` mají protichůdné požadavky na pořadí načítání, server není schopen spustit, dokud nebude opraven a nebude vypnut",
|
||||
@@ -591,7 +589,7 @@
|
||||
"watermark-discord_url": "https://discord.sp-tarkov.com",
|
||||
"watermark-do_not_report": "NENAHLAŠUJ TO",
|
||||
"watermark-free_of_charge": "Tento produkt je zdarma",
|
||||
"watermark-issue_tracker_url": "https://dev.sp-tarkov.com/SPT/Server/issues",
|
||||
"watermark-issue_tracker_url": "https://github.com/sp-tarkov/server/",
|
||||
"watermark-modding_disabled": "TATO VERZE MÁ ZÁKÁZÁNO MÓDOVÁNÍ SERVERU",
|
||||
"watermark-no_support": "ŽÁDNÁ PODPORA NEBUDE POSKYTNUTA",
|
||||
"watermark-not_an_issue": "TOTO NENÍ CHYBA",
|
||||
|
||||
@@ -199,8 +199,6 @@
|
||||
"modloader-error_parsing_mod_load_order": "Fehler beim Parsen der Mod-Ladereihenfolge",
|
||||
"modloader-incompatibilities_not_string_array": "Die Mod %s package.json Eigenschaft 'Inkompatibilitäten' sollte ein String-Array sein",
|
||||
"modloader-incompatible_mod_found": "Mod {{author}}-{{name}} ist nicht kompatibel mit {{incompatibleModName}}",
|
||||
"modloader-installing_external_dependencies": "Abhängigkeiten für Mod: {{name}} von: {{author}} werden installieren",
|
||||
"modloader-installing_external_dependencies_disabled": "Mod: {{name}} von: {{author}} erfordert externe Abhängigkeiten, aber die Funktion ist derzeit deaktiviert. Gehe zu '{{configPath}}', setze '{{configOption}}' auf 'true' und starte den Server neu.\nWenn du dies aktivierst, übernimmst du die volle Verantwortung dafür, was {{name}} auf deinen Computer herunterlädt.",
|
||||
"modloader-invalid_sptVersion_field": "Mod: %s enthält eine ungültige ´semver´ Zeichenkette im ´sptversions´ Feld. Beispiele für gültige Werte: https://github.com/npm/node-semver#versions",
|
||||
"modloader-invalid_version_property": "Mod %s package.json enthält eine ungültige Versionszeichenfolge",
|
||||
"modloader-is_client_mod": "Mod (%s) ist ein Client-Mod und sollte im folgenden Ordner abgelegt werden: /spt/bepinex/plugins",
|
||||
@@ -708,7 +706,7 @@
|
||||
"watermark-discord_url": "https://discord.sp-tarkov.com",
|
||||
"watermark-do_not_report": "MELDEN UNTERSAGT",
|
||||
"watermark-free_of_charge": "Diese Arbeit ist kostenfrei",
|
||||
"watermark-issue_tracker_url": "https://dev.sp-tarkov.com/SPT/Server/issues",
|
||||
"watermark-issue_tracker_url": "https://github.com/sp-tarkov/server/",
|
||||
"watermark-modding_disabled": "BEI DIESEM BUILD IST DAS SERVER-MODDING DEAKTIVIERT",
|
||||
"watermark-no_support": "ES WIRD KEIN SUPPORT GEWÄHRT",
|
||||
"watermark-not_an_issue": "DAS IST KEIN PROBLEM",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user