BIG BOI FORMATTING
This commit is contained in:
@@ -1,29 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Libraries\Core\Core.csproj">
|
||||
<Private>false</Private>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Libraries\SptDependencyInjection\SptDependencyInjection.csproj">
|
||||
<Private>false</Private>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SptCommon\SptCommon.csproj">
|
||||
<Private>false</Private>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Libraries\Core\Core.csproj">
|
||||
<Private>false</Private>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Libraries\SptDependencyInjection\SptDependencyInjection.csproj">
|
||||
<Private>false</Private>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SptCommon\SptCommon.csproj">
|
||||
<Private>false</Private>
|
||||
<CopyLocal>false</CopyLocal>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.External;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace ExampleMods.Mods;
|
||||
|
||||
[Injectable]
|
||||
public class EditConfigs : IPostDBLoadMod
|
||||
{
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly BotConfig _botConfig;
|
||||
private readonly HideoutConfig _hideoutConfig;
|
||||
private readonly WeatherConfig _weatherConfig;
|
||||
private readonly AirdropConfig _airdropConfig;
|
||||
private readonly PmcChatResponse _pmcChatResponseConfig;
|
||||
private readonly QuestConfig _questConfig;
|
||||
private readonly PmcConfig _pmcConfig;
|
||||
private readonly BotConfig _botConfig;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly HideoutConfig _hideoutConfig;
|
||||
|
||||
private readonly ISptLogger<EditConfigs> _logger;
|
||||
private readonly PmcChatResponse _pmcChatResponseConfig;
|
||||
private readonly PmcConfig _pmcConfig;
|
||||
private readonly QuestConfig _questConfig;
|
||||
private readonly WeatherConfig _weatherConfig;
|
||||
|
||||
// We access configs via ConfigServer
|
||||
public EditConfigs(
|
||||
@@ -39,7 +39,7 @@ public class EditConfigs : IPostDBLoadMod
|
||||
_questConfig = _configServer.GetConfig<QuestConfig>();
|
||||
_pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
}
|
||||
|
||||
|
||||
public void PostDBLoad()
|
||||
{
|
||||
// Let's edit the weather config to make the season winter
|
||||
@@ -76,7 +76,7 @@ public class EditConfigs : IPostDBLoadMod
|
||||
var assaultConversionSettings = factory4DayConversionSettings["assault"];
|
||||
assaultConversionSettings.Min = 100;
|
||||
assaultConversionSettings.Max = 100;
|
||||
|
||||
|
||||
_logger.Success("Finished Editing Configs");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.External;
|
||||
using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace ExampleMods.Mods;
|
||||
|
||||
@@ -12,7 +11,7 @@ public class EditDatabaseValues : IPostDBLoadMod
|
||||
{
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly ISptLogger<EditDatabaseValues> _logger;
|
||||
|
||||
|
||||
public EditDatabaseValues(
|
||||
DatabaseService databaseService,
|
||||
ISptLogger<EditDatabaseValues> logger
|
||||
@@ -41,7 +40,7 @@ public class EditDatabaseValues : IPostDBLoadMod
|
||||
|
||||
// Lets edit Customs
|
||||
EditCustoms();
|
||||
|
||||
|
||||
_logger.Success("Finished Editing Database");
|
||||
}
|
||||
|
||||
@@ -64,7 +63,10 @@ public class EditDatabaseValues : IPostDBLoadMod
|
||||
// The max is stored in a list, different flea ratings give different offer amounts
|
||||
|
||||
// We loop over all the settings, setting all of them to be 20
|
||||
foreach (var offerCountSettings in ragfairSettings.MaxActiveOfferCount) offerCountSettings.Count = 20;
|
||||
foreach (var offerCountSettings in ragfairSettings.MaxActiveOfferCount)
|
||||
{
|
||||
offerCountSettings.Count = 20;
|
||||
}
|
||||
}
|
||||
|
||||
private void EditBtr()
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Core.Models.External;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Logging;
|
||||
using Core.Models.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace ExampleMods.Mods;
|
||||
|
||||
@@ -23,7 +18,7 @@ public class Logging : IPostSptLoadMod
|
||||
// Save the logger we're injecting into a private variable that is scoped to this class (only this class has access to it)
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public void PostSptLoad()
|
||||
{
|
||||
// We can access the logger to assigned in the constructor here
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace ExampleMods.Mods.Override;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class AchievementCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/achievement/list
|
||||
/// Handle client/achievement/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -24,7 +24,7 @@ public class AchievementCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/achievement/statistic
|
||||
/// Handle client/achievement/statistic
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -16,8 +16,8 @@ public class BotCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/bot/limit
|
||||
/// Is called by client to define each bot roles wave limit
|
||||
/// Handle singleplayer/settings/bot/limit
|
||||
/// Is called by client to define each bot roles wave limit
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -31,7 +31,7 @@ public class BotCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/bot/difficulty
|
||||
/// Handle singleplayer/settings/bot/difficulty
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -43,7 +43,9 @@ public class BotCallbacks(
|
||||
var type = splitUrl[^2].ToLower();
|
||||
var difficulty = splitUrl[^1];
|
||||
if (difficulty == "core")
|
||||
{
|
||||
return _httpResponseUtil.NoBody(_botController.GetBotCoreDifficulty());
|
||||
}
|
||||
|
||||
var raidConfig = _applicationContext.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.GetValue<GetRaidConfigurationRequestData>();
|
||||
@@ -52,7 +54,7 @@ public class BotCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/bot/difficulties
|
||||
/// Handle singleplayer/settings/bot/difficulties
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -64,7 +66,7 @@ public class BotCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/bot/generate
|
||||
/// Handle client/game/bot/generate
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -76,7 +78,7 @@ public class BotCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/bot/maxCap
|
||||
/// Handle singleplayer/settings/bot/maxCap
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetBotCap(string url, EmptyRequestData info, string sessionID)
|
||||
@@ -87,7 +89,7 @@ public class BotCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/bot/getBotBehaviours
|
||||
/// Handle singleplayer/settings/bot/getBotBehaviours
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetBotBehaviours()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Builds;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.PresetBuild;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -14,7 +14,7 @@ public class BuildsCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/builds/list
|
||||
/// Handle client/builds/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -26,7 +26,7 @@ public class BuildsCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/magazine/save
|
||||
/// Handle client/builds/magazine/save
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -39,7 +39,7 @@ public class BuildsCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/weapon/save
|
||||
/// Handle client/builds/weapon/save
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -52,7 +52,7 @@ public class BuildsCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/equipment/save
|
||||
/// Handle client/builds/equipment/save
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -65,7 +65,7 @@ public class BuildsCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/delete
|
||||
/// Handle client/builds/delete
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -11,7 +11,7 @@ public class BundleCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle singleplayer/bundles
|
||||
/// Handle singleplayer/bundles
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Spt.Logging;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Server;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -19,7 +19,7 @@ public class ClientLogCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle /singleplayer/log
|
||||
/// Handle /singleplayer/log
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -32,7 +32,7 @@ public class ClientLogCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /singleplayer/release
|
||||
/// Handle /singleplayer/release
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ReleaseNotes()
|
||||
@@ -59,7 +59,7 @@ public class ClientLogCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /singleplayer/enableBSGlogging
|
||||
/// Handle /singleplayer/enableBSGlogging
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string BsgLogging()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Customization;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class CustomizationCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/trading/customization/storage
|
||||
/// Handle client/trading/customization/storage
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -28,7 +28,7 @@ public class CustomizationCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/customization
|
||||
/// Handle client/trading/customization
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -43,7 +43,7 @@ public class CustomizationCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle CustomizationBuy event
|
||||
/// Handle CustomizationBuy event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -55,7 +55,7 @@ public class CustomizationCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/hideout/customization/offer/list
|
||||
/// Handle client/hideout/customization/offer/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -67,7 +67,7 @@ public class CustomizationCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/customization/storage
|
||||
/// Handle client/customization/storage
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -79,7 +79,7 @@ public class CustomizationCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle CustomizationSet
|
||||
/// Handle CustomizationSet
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -15,7 +15,7 @@ public class DataCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/settings
|
||||
/// Handle client/settings
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -28,7 +28,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/globals
|
||||
/// Handle client/globals
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -43,7 +43,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/items
|
||||
/// Handle client/items
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -55,7 +55,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/handbook/templates
|
||||
/// Handle client/handbook/templates
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -67,7 +67,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/customization
|
||||
/// Handle client/customization
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -79,7 +79,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/account/customization
|
||||
/// Handle client/account/customization
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -91,7 +91,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/hideout/settings
|
||||
/// Handle client/hideout/settings
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -103,7 +103,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/hideout/areas
|
||||
/// Handle client/hideout/areas
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -115,7 +115,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/hideout/production/recipes
|
||||
/// Handle client/hideout/production/recipes
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -127,7 +127,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/languages
|
||||
/// Handle client/languages
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -139,7 +139,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/menu/locale
|
||||
/// Handle client/menu/locale
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -151,13 +151,16 @@ public class DataCallbacks(
|
||||
var locales = _databaseService.GetLocales();
|
||||
var result = locales.Menu?[localeId] ?? locales.Menu?.FirstOrDefault(m => m.Key == "en").Value;
|
||||
|
||||
if (result == null) throw new Exception($"Unable to determine locale for request with {localeId}");
|
||||
if (result == null)
|
||||
{
|
||||
throw new Exception($"Unable to determine locale for request with {localeId}");
|
||||
}
|
||||
|
||||
return _httpResponseUtil.GetBody(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/locale
|
||||
/// Handle client/locale
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -173,7 +176,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/hideout/qte/list
|
||||
/// Handle client/hideout/qte/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -185,7 +188,7 @@ public class DataCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/items/prices/
|
||||
/// Handle client/items/prices/
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Request;
|
||||
using Core.Models.Eft.Dialog;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -18,8 +18,19 @@ public class DialogueCallbacks(
|
||||
)
|
||||
: OnUpdate
|
||||
{
|
||||
public bool OnUpdate(long timeSinceLastRun)
|
||||
{
|
||||
_dialogueController.Update();
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-dialogue";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/list
|
||||
/// Handle client/friend/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -31,7 +42,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/chatServer/list
|
||||
/// Handle client/chatServer/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -51,7 +62,14 @@ public class DialogueCallbacks(
|
||||
VersionId = "bgkidft87ddd",
|
||||
Ip = "",
|
||||
Port = 0,
|
||||
Chats = [new Chat { Id = "0", Members = 0 }]
|
||||
Chats =
|
||||
[
|
||||
new Chat
|
||||
{
|
||||
Id = "0",
|
||||
Members = 0
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -59,7 +77,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/list
|
||||
/// Handle client/mail/dialog/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -71,7 +89,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/view
|
||||
/// Handle client/mail/dialog/view
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -83,7 +101,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/info
|
||||
/// Handle client/mail/dialog/info
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -95,7 +113,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/remove
|
||||
/// Handle client/mail/dialog/remove
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -108,7 +126,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/pin
|
||||
/// Handle client/mail/dialog/pin
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -121,7 +139,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/unpin
|
||||
/// Handle client/mail/dialog/unpin
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -134,7 +152,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/read
|
||||
/// Handle client/mail/dialog/read
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -147,7 +165,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/getAllAttachments
|
||||
/// Handle client/mail/dialog/getAllAttachments
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -159,7 +177,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/msg/send
|
||||
/// Handle client/mail/msg/send
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -171,7 +189,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/list/outbox
|
||||
/// Handle client/friend/request/list/outbox
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -183,7 +201,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/list/inbox
|
||||
/// Handle client/friend/request/list/inbox
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -195,7 +213,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/send
|
||||
/// Handle client/friend/request/send
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -207,7 +225,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/accept-all
|
||||
/// Handle client/friend/request/accept-all
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -219,7 +237,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/accept
|
||||
/// Handle client/friend/request/accept
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -231,7 +249,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/decline
|
||||
/// Handle client/friend/request/decline
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -243,7 +261,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/request/cancel
|
||||
/// Handle client/friend/request/cancel
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -255,7 +273,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/delete
|
||||
/// Handle client/friend/delete
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -268,7 +286,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/ignore/set
|
||||
/// Handle client/friend/ignore/set
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -280,7 +298,7 @@ public class DialogueCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/ignore/remove
|
||||
/// Handle client/friend/ignore/remove
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -315,15 +333,4 @@ public class DialogueCallbacks(
|
||||
{
|
||||
return "Not Implemented!"; // Not implemented in Node
|
||||
}
|
||||
|
||||
public bool OnUpdate(long timeSinceLastRun)
|
||||
{
|
||||
_dialogueController.Update();
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-dialogue";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Request;
|
||||
using Core.Models.Eft.Game;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/version/validate
|
||||
/// Handle client/game/version/validate
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -43,7 +43,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/start
|
||||
/// Handle client/game/start
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -53,12 +53,17 @@ public class GameCallbacks(
|
||||
{
|
||||
var startTimestampSec = _timeUtil.GetTimeStamp();
|
||||
_gameController.GameStart(url, info, sessionID, startTimestampSec);
|
||||
return _httpResponseUtil.GetBody(new GameStartResponse() { UtcTime = startTimestampSec });
|
||||
return _httpResponseUtil.GetBody(
|
||||
new GameStartResponse
|
||||
{
|
||||
UtcTime = startTimestampSec
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/logout
|
||||
/// Save profiles on game close
|
||||
/// Handle client/game/logout
|
||||
/// Save profiles on game close
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -67,11 +72,16 @@ public class GameCallbacks(
|
||||
public string GameLogout(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
_saveServer.Save();
|
||||
return _httpResponseUtil.GetBody(new GameLogoutResponseData() { Status = "ok" });
|
||||
return _httpResponseUtil.GetBody(
|
||||
new GameLogoutResponseData
|
||||
{
|
||||
Status = "ok"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/config
|
||||
/// Handle client/game/config
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -83,7 +93,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/mode
|
||||
/// Handle client/game/mode
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -95,7 +105,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/server/list
|
||||
/// Handle client/server/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -107,7 +117,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/current
|
||||
/// Handle client/match/group/current
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -119,7 +129,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/checkVersion
|
||||
/// Handle client/checkVersion
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -131,7 +141,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/keepalive
|
||||
/// Handle client/game/keepalive
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -143,7 +153,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/version
|
||||
/// Handle singleplayer/settings/version
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -152,11 +162,16 @@ public class GameCallbacks(
|
||||
public string GetVersion(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
// change to be a proper type
|
||||
return _httpResponseUtil.NoBody(new { Version = _watermark.GetInGameVersionLabel() });
|
||||
return _httpResponseUtil.NoBody(
|
||||
new
|
||||
{
|
||||
Version = _watermark.GetInGameVersionLabel()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /client/report/send & /client/reports/lobby/send
|
||||
/// Handle /client/report/send & /client/reports/lobby/send
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -168,7 +183,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/getRaidTime
|
||||
/// Handle singleplayer/settings/getRaidTime
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -180,7 +195,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /client/survey
|
||||
/// Handle /client/survey
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -192,7 +207,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/survey/view
|
||||
/// Handle client/survey/view
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -204,7 +219,7 @@ public class GameCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/survey/opinion
|
||||
/// Handle client/survey/opinion
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Health;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class HealthCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom spt server request found in modules/QTEPatch.cs
|
||||
/// Custom spt server request found in modules/QTEPatch.cs
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info">HealthListener.Instance.CurrentHealth class</param>
|
||||
@@ -29,7 +29,7 @@ public class HealthCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle Eat
|
||||
/// Handle Eat
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -41,7 +41,7 @@ public class HealthCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle Heal
|
||||
/// Handle Heal
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -53,7 +53,7 @@ public class HealthCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RestoreHealth
|
||||
/// Handle RestoreHealth
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -6,6 +5,7 @@ using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -18,151 +18,6 @@ public class HideoutCallbacks(
|
||||
{
|
||||
private readonly HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutUpgrade event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse Upgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.StartUpgrade(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutUpgradeComplete event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.UpgradeComplete(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutPutItemsInAreaSlots
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutTakeItemsFromAreaSlots event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutToggleArea event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ToggleArea(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutSingleProductionStart event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.SingleProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutScavCaseProductionStart event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutContinuousProductionStart
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutTakeProduction event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.TakeProduction(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutQuickTimeEvent
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse HandleQTEEvent(PmcData pmcData, HandleQTEEventRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints request, string sessionID,
|
||||
ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.RecordShootingRangePoints(sessionID, pmcData, request);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ImproveArea(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ImproveArea(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCancelProductionCommand
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutCancelProductionRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.CancelProduction(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCircleOfCultistProductionStart
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse CicleOfCultistProductionStart(PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutDeleteProductionCommand
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse HideoutDeleteProductionCommand(PmcData pmcData, HideoutDeleteProductionRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCustomizationApply
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse HideoutCustomizationApplyCommand(PmcData pmcData, HideoutCustomizationApplyRequestData request, string sessionID)
|
||||
{
|
||||
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 bool OnUpdate(long timeSinceLastRun)
|
||||
{
|
||||
if (timeSinceLastRun > _hideoutConfig.RunIntervalSeconds)
|
||||
@@ -178,4 +33,149 @@ public class HideoutCallbacks(
|
||||
{
|
||||
return "spt-hideout";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutUpgrade event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse Upgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.StartUpgrade(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutUpgradeComplete event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.UpgradeComplete(pmcData, request, sessionID, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutPutItemsInAreaSlots
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutTakeItemsFromAreaSlots event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutToggleArea event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ToggleArea(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutSingleProductionStart event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.SingleProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutScavCaseProductionStart event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutContinuousProductionStart
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutTakeProduction event
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.TakeProduction(pmcData, request, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutQuickTimeEvent
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse HandleQTEEvent(PmcData pmcData, HandleQTEEventRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints request, string sessionID,
|
||||
ItemEventRouterResponse output)
|
||||
{
|
||||
_hideoutController.RecordShootingRangePoints(sessionID, pmcData, request);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - RecordShootingRangePoints
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse ImproveArea(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.ImproveArea(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCancelProductionCommand
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutCancelProductionRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.CancelProduction(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCircleOfCultistProductionStart
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse CicleOfCultistProductionStart(PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutDeleteProductionCommand
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse HideoutDeleteProductionCommand(PmcData pmcData, HideoutDeleteProductionRequestData request, string sessionID)
|
||||
{
|
||||
return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving - HideoutCustomizationApply
|
||||
/// </summary>
|
||||
public ItemEventRouterResponse HideoutCustomizationApplyCommand(PmcData pmcData, HideoutCustomizationApplyRequestData request, string sessionID)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Context;
|
||||
using Core.DI;
|
||||
using Core.Servers;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.InRaid;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -13,8 +13,8 @@ public class InraidCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/location/getLocalloot
|
||||
/// Store active map in profile + applicationContext
|
||||
/// Handle client/location/getLocalloot
|
||||
/// Store active map in profile + applicationContext
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info">register player request</param>
|
||||
@@ -27,7 +27,7 @@ public class InraidCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle raid/profile/scavsave
|
||||
/// Handle raid/profile/scavsave
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info">Save progress request</param>
|
||||
@@ -40,7 +40,7 @@ public class InraidCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/raid/menu
|
||||
/// Handle singleplayer/settings/raid/menu
|
||||
/// </summary>
|
||||
/// <returns>JSON as string</returns>
|
||||
public string GetRaidMenuSettings()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Insurance;
|
||||
@@ -8,6 +7,7 @@ using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -21,10 +21,26 @@ public class InsuranceCallbacks(
|
||||
)
|
||||
: OnUpdate
|
||||
{
|
||||
private InsuranceConfig _insuranceConfig = _configServer.GetConfig<InsuranceConfig>();
|
||||
private readonly InsuranceConfig _insuranceConfig = _configServer.GetConfig<InsuranceConfig>();
|
||||
|
||||
public bool OnUpdate(long timeSinceLastRun)
|
||||
{
|
||||
if (timeSinceLastRun > Math.Max(_insuranceConfig.RunIntervalSeconds, 1))
|
||||
{
|
||||
_insuranceController.ProcessReturn();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-insurance";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/insurance/items/list/cost
|
||||
/// Handle client/insurance/items/list/cost
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -36,7 +52,7 @@ public class InsuranceCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle Insure event
|
||||
/// Handle Insure event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -46,18 +62,4 @@ public class InsuranceCallbacks(
|
||||
{
|
||||
return _insuranceController.Insure(pmcData, info, sessionID);
|
||||
}
|
||||
|
||||
public bool OnUpdate(long timeSinceLastRun)
|
||||
{
|
||||
if (timeSinceLastRun > Math.Max(_insuranceConfig.RunIntervalSeconds, 1))
|
||||
_insuranceController.ProcessReturn();
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-insurance";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Core.Controllers;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Inventory;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Enums;
|
||||
using Core.Routers;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -20,21 +20,29 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the passed in list of warnings contains critical issues
|
||||
/// Return true if the passed in list of warnings contains critical issues
|
||||
/// </summary>
|
||||
/// <param name="warnings">The list of warnings to check for critical errors</param>
|
||||
/// <returns></returns>
|
||||
public bool IsCriticalError(List<Warning>? warnings)
|
||||
{
|
||||
if (warnings is null) return false;
|
||||
if (warnings is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// List of non-critical error codes, we return true if any error NOT included is passed in
|
||||
var nonCriticalErrorCodes = new List<BackendErrorCodes> { BackendErrorCodes.NotEnoughSpace };
|
||||
var nonCriticalErrorCodes = new List<BackendErrorCodes>
|
||||
{
|
||||
BackendErrorCodes.NotEnoughSpace
|
||||
};
|
||||
|
||||
foreach (var warning in warnings)
|
||||
{
|
||||
if (!nonCriticalErrorCodes.Contains(warning.Code ?? BackendErrorCodes.None))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -44,6 +52,5 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
|
||||
{
|
||||
// Cast int to string to get the error code of 220 for Unknown Error.
|
||||
return warnings.FirstOrDefault()?.Code is null ? BackendErrorCodes.UnknownError : warnings.FirstOrDefault()?.Code ?? BackendErrorCodes.UnknownError;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Launcher;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Launcher;
|
||||
using Core.Models.Spt.Launcher;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Location;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -13,7 +13,7 @@ public class LocationCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/locations
|
||||
/// Handle client/locations
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -25,7 +25,7 @@ public class LocationCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/airdrop/loot
|
||||
/// Handle client/airdrop/loot
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Match;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using static Core.Services.MatchLocationService;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
@@ -16,7 +16,7 @@ public class MatchCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/match/updatePing
|
||||
/// Handle client/match/updatePing
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -28,7 +28,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/exit
|
||||
/// Handle client/match/exit
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -40,7 +40,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/exit_from_menu
|
||||
/// Handle client/match/group/exit_from_menu
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -52,7 +52,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/current
|
||||
/// Handle client/match/group/current
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -60,11 +60,16 @@ public class MatchCallbacks(
|
||||
/// <returns></returns>
|
||||
public string GroupCurrent(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
return _httpResponseUtil.GetBody(new MatchGroupCurrentResponse { Squad = [] });
|
||||
return _httpResponseUtil.GetBody(
|
||||
new MatchGroupCurrentResponse
|
||||
{
|
||||
Squad = []
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/looking/start
|
||||
/// Handle client/match/group/looking/start
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -76,7 +81,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/looking/stop
|
||||
/// Handle client/match/group/looking/stop
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -88,7 +93,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/invite/send
|
||||
/// Handle client/match/group/invite/send
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -100,7 +105,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/invite/accept
|
||||
/// Handle client/match/group/invite/accept
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -108,11 +113,16 @@ public class MatchCallbacks(
|
||||
/// <returns></returns>
|
||||
public string AcceptGroupInvite(string url, RequestIdRequest info, string sessionID)
|
||||
{
|
||||
return _httpResponseUtil.GetBody(new List<GroupCharacter>() { new() });
|
||||
return _httpResponseUtil.GetBody(
|
||||
new List<GroupCharacter>
|
||||
{
|
||||
new()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/invite/decline
|
||||
/// Handle client/match/group/invite/decline
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -124,7 +134,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/invite/cancel
|
||||
/// Handle client/match/group/invite/cancel
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -136,7 +146,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/transfer
|
||||
/// Handle client/match/group/transfer
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -148,7 +158,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/invite/cancel-all
|
||||
/// Handle client/match/group/invite/cancel-all
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -160,7 +170,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/putMetrics
|
||||
/// Handle client/putMetrics
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -172,7 +182,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/analytics/event-disconnect
|
||||
/// Handle client/analytics/event-disconnect
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -184,7 +194,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/available
|
||||
/// Handle client/match/available
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -196,7 +206,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle match/group/start_game
|
||||
/// Handle match/group/start_game
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -208,7 +218,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/getMetricsConfig
|
||||
/// Handle client/getMetricsConfig
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -220,8 +230,8 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called periodically while in a group
|
||||
/// Handle client/match/group/status
|
||||
/// Called periodically while in a group
|
||||
/// Handle client/match/group/status
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -233,7 +243,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/delete
|
||||
/// Handle client/match/group/delete
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -246,7 +256,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/leave
|
||||
/// Handle client/match/group/leave
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -258,7 +268,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/player/remove
|
||||
/// Handle client/match/group/player/remove
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -270,7 +280,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/local/start
|
||||
/// Handle client/match/local/start
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -282,7 +292,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/local/end
|
||||
/// Handle client/match/local/end
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -295,7 +305,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/raid/configuration
|
||||
/// Handle client/raid/configuration
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -308,7 +318,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/raid/configuration-by-profile
|
||||
/// Handle client/raid/configuration-by-profile
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -320,7 +330,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/raid/ready
|
||||
/// Handle client/match/group/raid/ready
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -332,7 +342,7 @@ public class MatchCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/raid/not-ready
|
||||
/// Handle client/match/group/raid/not-ready
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Notes;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Core.Callbacks;
|
||||
public class NoteCallbacks(NoteController _noteController)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle AddNote event
|
||||
/// Handle AddNote event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -22,7 +22,7 @@ public class NoteCallbacks(NoteController _noteController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle EditNote event
|
||||
/// Handle EditNote event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -34,7 +34,7 @@ public class NoteCallbacks(NoteController _noteController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle DeleteNote event
|
||||
/// Handle DeleteNote event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Request;
|
||||
using Core.Models.Eft.Notifier;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -14,7 +14,7 @@ public class NotifierCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/notifier/channel/create
|
||||
/// Handle client/notifier/channel/create
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -26,7 +26,7 @@ public class NotifierCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/select
|
||||
/// Handle client/game/profile/select
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -34,11 +34,15 @@ public class NotifierCallbacks(
|
||||
/// <returns></returns>
|
||||
public string SelectProfile(string url, UIDRequestData info, string sessionID)
|
||||
{
|
||||
return _httpResponseUtil.GetBody(new SelectProfileResponse { Status = "ok" });
|
||||
return _httpResponseUtil.GetBody(
|
||||
new SelectProfileResponse
|
||||
{
|
||||
Status = "ok"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Prestige;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -13,7 +13,7 @@ public class PrestigeCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/prestige/list
|
||||
/// Handle client/prestige/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -25,7 +25,7 @@ public class PrestigeCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/prestige/obtain
|
||||
/// Handle client/prestige/obtain
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -6,6 +5,7 @@ using Core.Models.Eft.Launcher;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -23,7 +23,12 @@ public class ProfileCallbacks(
|
||||
public string CreateProfile(string url, ProfileCreateRequestData info, string sessionID)
|
||||
{
|
||||
var id = _profileController.CreateProfile(info, sessionID);
|
||||
return _httpResponse.GetBody(new CreateProfileResponse { UserId = id });
|
||||
return _httpResponse.GetBody(
|
||||
new CreateProfileResponse
|
||||
{
|
||||
UserId = id
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +51,12 @@ public class ProfileCallbacks(
|
||||
*/
|
||||
public string RegenerateScav(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
return _httpResponse.GetBody(new List<PmcData> { _profileController.GeneratePlayerScav(sessionID) });
|
||||
return _httpResponse.GetBody(
|
||||
new List<PmcData>
|
||||
{
|
||||
_profileController.GeneratePlayerScav(sessionID)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +80,13 @@ public class ProfileCallbacks(
|
||||
{
|
||||
"taken" => _httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotUnique, "The nickname is already in use"),
|
||||
"tooshort" => _httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotValid, "The nickname is too short"),
|
||||
_ => _httpResponse.GetBody<object>(new { status = 0, nicknamechangedate = _timeUtil.GetTimeStamp() })
|
||||
_ => _httpResponse.GetBody<object>(
|
||||
new
|
||||
{
|
||||
status = 0,
|
||||
nicknamechangedate = _timeUtil.GetTimeStamp()
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,7 +101,12 @@ public class ProfileCallbacks(
|
||||
{
|
||||
"taken" => _httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotUnique, "The nickname is already in use"),
|
||||
"tooshort" => _httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotValid, "The nickname is too short"),
|
||||
_ => _httpResponse.GetBody(new { status = "ok" })
|
||||
_ => _httpResponse.GetBody(
|
||||
new
|
||||
{
|
||||
status = "ok"
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,7 +116,10 @@ public class ProfileCallbacks(
|
||||
public string GetReservedNickname(string url, EmptyRequestData info, string sessionID)
|
||||
{
|
||||
var fullProfile = _profileHelper.GetFullProfile(sessionID);
|
||||
if (fullProfile?.ProfileInfo?.Username is not null) return _httpResponse.GetBody(fullProfile?.ProfileInfo?.Username);
|
||||
if (fullProfile?.ProfileInfo?.Username is not null)
|
||||
{
|
||||
return _httpResponse.GetBody(fullProfile?.ProfileInfo?.Username);
|
||||
}
|
||||
|
||||
return _httpResponse.GetBody("SPTarkov");
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Quests;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -15,7 +15,7 @@ public class QuestCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle RepeatableQuestChange event
|
||||
/// Handle RepeatableQuestChange event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -27,7 +27,7 @@ public class QuestCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle QuestAccept event
|
||||
/// Handle QuestAccept event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -36,13 +36,15 @@ public class QuestCallbacks(
|
||||
public ItemEventRouterResponse AcceptQuest(PmcData pmcData, AcceptQuestRequestData info, string sessionID)
|
||||
{
|
||||
if (info.Type == "repeatable")
|
||||
{
|
||||
return _questController.AcceptRepeatableQuest(pmcData, info, sessionID);
|
||||
}
|
||||
|
||||
return _questController.AcceptQuest(pmcData, info, sessionID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle QuestComplete event
|
||||
/// Handle QuestComplete event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -54,7 +56,7 @@ public class QuestCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle QuestHandover event
|
||||
/// Handle QuestHandover event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -66,7 +68,7 @@ public class QuestCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/quest/list
|
||||
/// Handle client/quest/list
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -78,7 +80,7 @@ public class QuestCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/repeatalbeQuests/activityPeriods
|
||||
/// Handle client/repeatalbeQuests/activityPeriods
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -8,6 +7,7 @@ using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -23,7 +23,7 @@ public class RagfairCallbacks(
|
||||
ConfigServer _configServer
|
||||
) : OnLoad, OnUpdate
|
||||
{
|
||||
private RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
|
||||
private readonly RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
|
||||
|
||||
public Task OnLoad()
|
||||
{
|
||||
@@ -57,8 +57,8 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/ragfair/search
|
||||
/// Handle client/ragfair/find
|
||||
/// Handle client/ragfair/search
|
||||
/// Handle client/ragfair/find
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -70,7 +70,7 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/ragfair/itemMarketPrice
|
||||
/// Handle client/ragfair/itemMarketPrice
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -82,7 +82,7 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RagFairAddOffer event
|
||||
/// Handle RagFairAddOffer event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -94,7 +94,7 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RagFairRemoveOffer event
|
||||
/// Handle RagFairRemoveOffer event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -106,7 +106,7 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RagFairRenewOffer event
|
||||
/// Handle RagFairRenewOffer event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -118,8 +118,8 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /client/items/prices
|
||||
/// Called when clicking an item to list on flea
|
||||
/// Handle /client/items/prices
|
||||
/// Called when clicking an item to list on flea
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -131,7 +131,7 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/reports/ragfair/send
|
||||
/// Handle client/reports/ragfair/send
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -149,7 +149,7 @@ public class RagfairCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/ragfair/offer/findbyid
|
||||
/// Handle client/ragfair/offer/findbyid
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Repair;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace Core.Callbacks;
|
||||
public class RepairCallbacks(RepairController _repairController)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle TraderRepair event
|
||||
/// use trader to repair item
|
||||
/// Handle TraderRepair event
|
||||
/// use trader to repair item
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -23,8 +23,8 @@ public class RepairCallbacks(RepairController _repairController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle Repair event
|
||||
/// Use repair kit to repair item
|
||||
/// Handle Repair event
|
||||
/// Use repair kit to repair item
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -23,6 +23,11 @@ public class SaveCallbacks(
|
||||
_saveServer.Load();
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-save";
|
||||
}
|
||||
|
||||
public bool OnUpdate(long secondsSinceLastRun)
|
||||
{
|
||||
if (secondsSinceLastRun > _coreConfig.ProfileSaveIntervalInSeconds)
|
||||
@@ -33,9 +38,4 @@ public class SaveCallbacks(
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-save";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Trade;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Core.Callbacks;
|
||||
public class TradeCallbacks(TradeController _tradeController)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/game/profile/items/moving TradingConfirm event
|
||||
/// Handle client/game/profile/items/moving TradingConfirm event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -22,7 +22,7 @@ public class TradeCallbacks(TradeController _tradeController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RagFairBuyOffer event
|
||||
/// Handle RagFairBuyOffer event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -34,7 +34,7 @@ public class TradeCallbacks(TradeController _tradeController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle SellAllFromSavage event
|
||||
/// Handle SellAllFromSavage event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.DI;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -25,18 +25,18 @@ public class TraderCallbacks(
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool OnUpdate(long _)
|
||||
{
|
||||
return _traderController.Update();
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-traders";
|
||||
}
|
||||
|
||||
public bool OnUpdate(long _)
|
||||
{
|
||||
return _traderController.Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/api/traderSettings
|
||||
/// Handle client/trading/api/traderSettings
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -48,7 +48,7 @@ public class TraderCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/api/getTrader
|
||||
/// Handle client/trading/api/getTrader
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -61,7 +61,7 @@ public class TraderCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/api/getTraderAssort
|
||||
/// Handle client/trading/api/getTraderAssort
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -74,7 +74,7 @@ public class TraderCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /singleplayer/moddedTraders
|
||||
/// Handle /singleplayer/moddedTraders
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class WeatherCallbacks(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/weather
|
||||
/// Handle client/weather
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -24,7 +24,7 @@ public class WeatherCallbacks(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/localGame/weather
|
||||
/// Handle client/localGame/weather
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Controllers;
|
||||
using Core.Controllers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Wishlist;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Core.Callbacks;
|
||||
public class WishlistCallbacks(WishlistController _wishlistController)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle AddToWishList event
|
||||
/// Handle AddToWishList event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -22,7 +22,7 @@ public class WishlistCallbacks(WishlistController _wishlistController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RemoveFromWishList event
|
||||
/// Handle RemoveFromWishList event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -34,7 +34,7 @@ public class WishlistCallbacks(WishlistController _wishlistController)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle ChangeWishlistItemCategory
|
||||
/// Handle ChangeWishlistItemCategory
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="info"></param>
|
||||
|
||||
@@ -14,7 +14,9 @@ public class ApplicationContext
|
||||
lock (variablesLock)
|
||||
{
|
||||
if (variables.TryGetValue(type, out var savedValues))
|
||||
{
|
||||
return savedValues.Last!.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -25,7 +27,10 @@ public class ApplicationContext
|
||||
lock (variablesLock)
|
||||
{
|
||||
var values = new List<ContextVariable>();
|
||||
if (variables.TryGetValue(type, out var savedValues)) values.AddRange(savedValues);
|
||||
if (variables.TryGetValue(type, out var savedValues))
|
||||
{
|
||||
values.AddRange(savedValues);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
@@ -42,7 +47,9 @@ public class ApplicationContext
|
||||
}
|
||||
|
||||
if (savedValues.Count >= MaxSavedValues)
|
||||
{
|
||||
savedValues.RemoveFirst();
|
||||
}
|
||||
|
||||
savedValues.AddLast(new ContextVariable(value, type));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ public class ContextVariable(object value, ContextVariableType contextVariableIn
|
||||
|
||||
public T GetValue<T>()
|
||||
{
|
||||
return (T)value;
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
public DateTime GetTimestamp()
|
||||
|
||||
@@ -2,20 +2,30 @@
|
||||
|
||||
public enum ContextVariableType
|
||||
{
|
||||
/** Logged in users session id */
|
||||
/**
|
||||
* Logged in users session id
|
||||
*/
|
||||
SESSION_ID = 0,
|
||||
|
||||
/** Currently acive raid information */
|
||||
/**
|
||||
* Currently acive raid information
|
||||
*/
|
||||
RAID_CONFIGURATION = 1,
|
||||
|
||||
/** SessionID + Timestamp when client first connected, has _ between values */
|
||||
/**
|
||||
* SessionID + Timestamp when client first connected, has _ between values
|
||||
*/
|
||||
CLIENT_START_TIMESTAMP = 2,
|
||||
|
||||
/** When player is loading into map and loot is requested */
|
||||
/**
|
||||
* When player is loading into map and loot is requested
|
||||
*/
|
||||
REGISTER_PLAYER_REQUEST = 3,
|
||||
RAID_ADJUSTMENTS = 4,
|
||||
|
||||
/** Data returned from client request object from endLocalRaid() */
|
||||
/**
|
||||
* Data returned from client request object from endLocalRaid()
|
||||
*/
|
||||
TRANSIT_INFO = 5,
|
||||
APP_BUILDER = 6,
|
||||
LOADED_MOD_ASSEMBLIES = 7,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Services;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -23,8 +23,12 @@ public class AchievementController(
|
||||
var stats = new Dictionary<string, int>();
|
||||
|
||||
foreach (var achievement in achievements)
|
||||
{
|
||||
if (achievement.Id != null)
|
||||
{
|
||||
stats.Add(achievement.Id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return new CompletedAchievementsResponse
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using SptCommon.Annotations;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Context;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
@@ -14,11 +15,9 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -48,11 +47,14 @@ public class BotController(
|
||||
public int? GetBotPresetGenerationLimit(string type)
|
||||
{
|
||||
var typeInLower = type.ToLower();
|
||||
var value = (int?)typeof(PresetBatch).GetProperties()
|
||||
var value = (int?) typeof(PresetBatch).GetProperties()
|
||||
.First(p => p.Name.ToLower() == (typeInLower == "assaultgroup" ? "assault" : typeInLower))
|
||||
.GetValue(_botConfig.PresetBatch);
|
||||
|
||||
if (value != null) return value;
|
||||
if (value != null)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
_logger.Warning(_localisationService.GetText("bot-bot_preset_count_value_missing", type));
|
||||
return 30;
|
||||
@@ -67,12 +69,18 @@ public class BotController(
|
||||
{
|
||||
var difficulty = diffLevel.ToLower();
|
||||
|
||||
if (!(raidConfig != null || ignoreRaidSettings)) _logger.Error(_localisationService.GetText("bot-missing_application_context", "RAID_CONFIGURATION"));
|
||||
if (!(raidConfig != null || ignoreRaidSettings))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-missing_application_context", "RAID_CONFIGURATION"));
|
||||
}
|
||||
|
||||
// Check value chosen in pre-raid difficulty dropdown
|
||||
// If value is not 'asonline', change requested difficulty to be what was chosen in dropdown
|
||||
var botDifficultyDropDownValue = raidConfig?.WavesSettings?.BotDifficulty?.ToString().ToLower() ?? "asonline";
|
||||
if (botDifficultyDropDownValue != "asonline") difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue);
|
||||
if (botDifficultyDropDownValue != "asonline")
|
||||
{
|
||||
difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue);
|
||||
}
|
||||
|
||||
var botDb = _databaseService.GetBots();
|
||||
return _botDifficultyHelper.GetBotDifficultySettings(type, difficulty, botDb);
|
||||
@@ -87,7 +95,10 @@ public class BotController(
|
||||
var botTypes = Enum.GetValues<WildSpawnType>().Select(item => item.ToString()).ToList();
|
||||
foreach (var botType in botTypes)
|
||||
{
|
||||
if (botTypesDb is null) continue;
|
||||
if (botTypesDb is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If bot is usec/bear, swap to different name
|
||||
var botTypeLower = _botHelper.IsBotPmc(botType)
|
||||
@@ -101,7 +112,11 @@ public class BotController(
|
||||
{
|
||||
// No bot of this type found, copy details from assault
|
||||
result[botTypeLower] = result["assault"];
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to find bot: {botTypeLower} in db, copying 'assault'");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to find bot: {botTypeLower} in db, copying 'assault'");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -116,7 +131,10 @@ public class BotController(
|
||||
foreach (var (difficultyName, _) in botDetails.BotDifficulty)
|
||||
{
|
||||
// Bot doesn't exist in result, add
|
||||
if (!result.ContainsKey(botNameKey)) result.TryAdd(botNameKey, new Dictionary<string, DifficultyCategories>());
|
||||
if (!result.ContainsKey(botNameKey))
|
||||
{
|
||||
result.TryAdd(botNameKey, new Dictionary<string, DifficultyCategories>());
|
||||
}
|
||||
|
||||
// Store all difficulty values in dict keyed by difficulty type e.g. easy/normal/impossible
|
||||
result[botNameKey].Add(difficultyName, GetBotDifficulty(botNameKey, difficultyName, null, true));
|
||||
@@ -148,6 +166,7 @@ public class BotController(
|
||||
var tasks = new List<Task>();
|
||||
// Map conditions to promises for bot generation
|
||||
foreach (var condition in request.Conditions ?? [])
|
||||
{
|
||||
tasks.Add(
|
||||
Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -166,10 +185,14 @@ public class BotController(
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
stopwatch.Stop();
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache");
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
@@ -197,7 +220,11 @@ public class BotController(
|
||||
|
||||
if (botCacheCount >= botGenerationDetails.BotCountToGenerate)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Cache already has sufficient {cacheKey} bots: {botCacheCount}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Cache already has sufficient {cacheKey} bots: {botCacheCount}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -205,9 +232,13 @@ public class BotController(
|
||||
var botsToGenerate = botGenerationDetails.BotCountToGenerate - botCacheCount;
|
||||
var progressWriter = new ProgressWriter(botGenerationDetails.BotCountToGenerate.GetValueOrDefault(30));
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generating {botsToGenerate} bots for cacheKey: {cacheKey}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Generating {botsToGenerate} bots for cacheKey: {cacheKey}");
|
||||
}
|
||||
|
||||
for (var i = 0; i < botsToGenerate; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var detailsClone = _cloner.Clone(botGenerationDetails);
|
||||
@@ -218,12 +249,15 @@ public class BotController(
|
||||
{
|
||||
_logger.Error($"Failed to generate bot: {botGenerationDetails.Role} #{i + 1}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Generated {botGenerationDetails.BotCountToGenerate} {botGenerationDetails.Role}" +
|
||||
$"({botGenerationDetails.EventRole ?? botGenerationDetails.Role ?? ""}) {botGenerationDetails.BotDifficulty}bots"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private List<BotBase> ReturnSingleBotFromCache(string sessionId, GenerateBotsRequestData request)
|
||||
@@ -289,8 +323,12 @@ public class BotController(
|
||||
var bossConvertPercent = bossConvertMinMax.GetByJsonProp<MinMax>(requestedBot?.Role?.ToLower() ?? string.Empty);
|
||||
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
|
||||
@@ -306,10 +344,12 @@ public class BotController(
|
||||
GenerateSingleBotAndStoreInCache(botGenerationDetails, sessionId, cacheKey);
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Generated {botGenerationDetails.BotCountToGenerate} " +
|
||||
$"{botGenerationDetails.Role} ({botGenerationDetails.EventRole ?? ""}) {botGenerationDetails.BotDifficulty} bots"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var desiredBot = _botGenerationCacheService.GetBot(cacheKey);
|
||||
@@ -361,7 +401,10 @@ public class BotController(
|
||||
.GetLatestValue(ContextVariableType.RAID_CONFIGURATION)
|
||||
?.GetValue<GetRaidConfigurationRequestData>();
|
||||
|
||||
if (raidSettings is null) _logger.Warning(_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext"));
|
||||
if (raidSettings is null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext"));
|
||||
}
|
||||
|
||||
return raidSettings;
|
||||
}
|
||||
@@ -400,9 +443,11 @@ public class BotController(
|
||||
{
|
||||
var botCap = _botConfig.MaxBotCap.FirstOrDefault(x => x.Key.ToLower() == location.ToLower());
|
||||
if (location == "default")
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("bot-no_bot_cap_found_for_location", location.ToLower())
|
||||
);
|
||||
}
|
||||
|
||||
return botCap.Value;
|
||||
}
|
||||
@@ -421,11 +466,23 @@ public class BotController(
|
||||
public record AiBotBrainTypes
|
||||
{
|
||||
[JsonPropertyName("pmc")]
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType { get; set; }
|
||||
public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("assault")]
|
||||
public Dictionary<string, Dictionary<string, int>> Assault { get; set; }
|
||||
public Dictionary<string, Dictionary<string, int>> Assault
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("playerScav")]
|
||||
public Dictionary<string, Dictionary<string, int>> PlayerScav { get; set; }
|
||||
public Dictionary<string, Dictionary<string, int>> PlayerScav
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Builds;
|
||||
using Core.Models.Eft.PresetBuild;
|
||||
@@ -10,6 +9,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -27,7 +27,7 @@ public class BuildController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/handbook/builds/my/list
|
||||
/// Handle client/handbook/builds/my/list
|
||||
/// </summary>
|
||||
/// <param name="sessionID"></param>
|
||||
/// <returns></returns>
|
||||
@@ -37,7 +37,14 @@ public class BuildController(
|
||||
|
||||
var profile = _profileHelper.GetFullProfile(sessionID);
|
||||
if (profile?.UserBuildData is null)
|
||||
profile.UserBuildData = new UserBuilds { EquipmentBuilds = [], WeaponBuilds = [], MagazineBuilds = [] };
|
||||
{
|
||||
profile.UserBuildData = new UserBuilds
|
||||
{
|
||||
EquipmentBuilds = [],
|
||||
WeaponBuilds = [],
|
||||
MagazineBuilds = []
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure the secure container in the default presets match what the player has equipped
|
||||
var defaultEquipmentPresetsClone = _cloner.Clone(_databaseService.GetTemplates().DefaultEquipmentPresets)
|
||||
@@ -49,17 +56,23 @@ public class BuildController(
|
||||
);
|
||||
|
||||
var firstDefaultItemsSecureContainer = defaultEquipmentPresetsClone?
|
||||
.FirstOrDefault()?.Items?
|
||||
.FirstOrDefault()
|
||||
?.Items?
|
||||
.FirstOrDefault(x => x.SlotId == secureContainerSlotId);
|
||||
|
||||
if (playerSecureContainer is not null && playerSecureContainer.Template != firstDefaultItemsSecureContainer?.Template)
|
||||
// Default equipment presets' secure container tpl doesn't match players secure container tpl
|
||||
{
|
||||
foreach (var defaultPreset in defaultEquipmentPresetsClone)
|
||||
{
|
||||
// Find presets secure container
|
||||
var secureContainer = defaultPreset.Items?.FirstOrDefault(item => item.SlotId == secureContainerSlotId);
|
||||
if (secureContainer is not null) secureContainer.Template = playerSecureContainer.Template;
|
||||
if (secureContainer is not null)
|
||||
{
|
||||
secureContainer.Template = playerSecureContainer.Template;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clone player build data from profile and append the above defaults onto end
|
||||
var userBuildsClone = _cloner.Clone(profile?.UserBuildData);
|
||||
@@ -71,7 +84,7 @@ public class BuildController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/weapon/save
|
||||
/// Handle client/builds/weapon/save
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="body"></param>
|
||||
@@ -85,7 +98,13 @@ public class BuildController(
|
||||
body.Root = body.Items.FirstOrDefault().Id;
|
||||
|
||||
// Create new object ready to save into profile userbuilds.weaponBuilds
|
||||
var newBuild = new WeaponBuild { Id = body.Id, Name = body.Name, Root = body.Root, Items = body.Items };
|
||||
var newBuild = new WeaponBuild
|
||||
{
|
||||
Id = body.Id,
|
||||
Name = body.Name,
|
||||
Root = body.Root,
|
||||
Items = body.Items
|
||||
};
|
||||
|
||||
var profile = _profileHelper.GetFullProfile(sessionId);
|
||||
|
||||
@@ -105,7 +124,7 @@ public class BuildController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/equipment/save event
|
||||
/// Handle client/builds/equipment/save event
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -148,18 +167,20 @@ public class BuildController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/delete
|
||||
/// Handle client/builds/delete
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
public void RemoveBuild(string sessionId, RemoveBuildRequestData request)
|
||||
{
|
||||
if (request.Id is not null)
|
||||
{
|
||||
RemovePlayerBuild(request.Id, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/builds/magazine/save
|
||||
/// Handle client/builds/magazine/save
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -179,7 +200,7 @@ public class BuildController(
|
||||
|
||||
profile.UserBuildData.MagazineBuilds ??= [];
|
||||
|
||||
var existingArrayId = profile.UserBuildData.MagazineBuilds.FirstOrDefault((item) => item.Name == request.Name);
|
||||
var existingArrayId = profile.UserBuildData.MagazineBuilds.FirstOrDefault(item => item.Name == request.Name);
|
||||
if (existingArrayId is not null)
|
||||
{
|
||||
{
|
||||
@@ -192,7 +213,6 @@ public class BuildController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="idToRemove"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Spt.Logging;
|
||||
using Core.Models.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Controllers;
|
||||
@@ -11,7 +11,7 @@ public class ClientLogController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle /singleplayer/log
|
||||
/// Handle /singleplayer/log
|
||||
/// </summary>
|
||||
/// <param name="logRequest"></param>
|
||||
public void ClientLog(ClientLogRequest logRequest)
|
||||
|
||||
@@ -52,7 +52,10 @@ public class CustomizationController(
|
||||
)
|
||||
.ToList();
|
||||
|
||||
if (matchingSuits == null) throw new Exception(_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId));
|
||||
if (matchingSuits == null)
|
||||
{
|
||||
throw new Exception(_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId));
|
||||
}
|
||||
|
||||
return matchingSuits;
|
||||
}
|
||||
@@ -121,14 +124,21 @@ public class CustomizationController(
|
||||
{
|
||||
var suits = _saveServer.GetProfile(sessionId).Suits;
|
||||
|
||||
if (suits is null || suits.Count == 0) return false;
|
||||
if (suits is null || suits.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return suits.Contains(suitId);
|
||||
}
|
||||
|
||||
private Suit? GetTraderClothingOffer(string sessionId, string? offerId)
|
||||
{
|
||||
var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId);
|
||||
if (foundSuit is null) _logger.Error(_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId));
|
||||
if (foundSuit is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId));
|
||||
}
|
||||
|
||||
return foundSuit;
|
||||
}
|
||||
@@ -144,13 +154,23 @@ public class CustomizationController(
|
||||
List<PaymentItemForClothing>? itemsToPayForClothingWith,
|
||||
ItemEventRouterResponse output)
|
||||
{
|
||||
if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0) return;
|
||||
if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var inventoryItemToProcess in itemsToPayForClothingWith)
|
||||
{
|
||||
var options = new ProcessBuyTradeRequestData
|
||||
{
|
||||
SchemeItems = [new IdWithCount { Count = inventoryItemToProcess.Count.Value, Id = inventoryItemToProcess.Id }],
|
||||
SchemeItems =
|
||||
[
|
||||
new IdWithCount
|
||||
{
|
||||
Count = inventoryItemToProcess.Count.Value,
|
||||
Id = inventoryItemToProcess.Id
|
||||
}
|
||||
],
|
||||
TransactionId = Traders.RAGMAN,
|
||||
Action = "BuyCustomization",
|
||||
Type = "",
|
||||
@@ -173,8 +193,12 @@ public class CustomizationController(
|
||||
var result = new List<Suit>();
|
||||
|
||||
foreach (var trader in traders)
|
||||
{
|
||||
if (trader.Value.Base?.CustomizationSeller is not null && trader.Value.Base.CustomizationSeller.Value)
|
||||
{
|
||||
result.AddRange(GetTraderSuits(trader.Key, sessionId));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -203,7 +227,10 @@ public class CustomizationController(
|
||||
var customisationResultsClone = _cloner.Clone(_databaseService.GetTemplates().CustomisationStorage);
|
||||
|
||||
var profile = _profileHelper.GetFullProfile(sessionId);
|
||||
if (profile is null) return customisationResultsClone!;
|
||||
if (profile is null)
|
||||
{
|
||||
return customisationResultsClone!;
|
||||
}
|
||||
|
||||
customisationResultsClone!.AddRange(profile.CustomisationUnlocks ?? []);
|
||||
|
||||
@@ -220,6 +247,7 @@ public class CustomizationController(
|
||||
public ItemEventRouterResponse SetCustomisation(string sessionId, CustomizationSetRequest request, PmcData pmcData)
|
||||
{
|
||||
foreach (var customisation in request.Customizations)
|
||||
{
|
||||
switch (customisation.Type)
|
||||
{
|
||||
case "dogTag":
|
||||
@@ -232,6 +260,7 @@ public class CustomizationController(
|
||||
_logger.Error($"Unhandled customisation type: {customisation.Type}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
@@ -263,6 +292,9 @@ public class CustomizationController(
|
||||
}
|
||||
|
||||
// Feet
|
||||
if (dbSuit.Parent == _lowerParentClothingId) pmcData.Customization.Feet = dbSuit.Properties.Feet;
|
||||
if (dbSuit.Parent == _lowerParentClothingId)
|
||||
{
|
||||
pmcData.Customization.Feet = dbSuit.Properties.Feet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Helpers.Dialogue;
|
||||
using Core.Models.Eft.Dialog;
|
||||
@@ -10,6 +9,7 @@ using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -31,29 +31,33 @@ public class DialogueController(
|
||||
protected List<IDialogueChatBot> _dialogueChatBots = dialogueChatBots.ToList();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="chatBot"></param>
|
||||
public void RegisterChatBot(IDialogueChatBot chatBot) // TODO: this is in with the helper types
|
||||
{
|
||||
if (_dialogueChatBots.Any(cb => cb.GetChatBot().Id == chatBot.GetChatBot().Id))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("dialog-chatbot_id_already_exists", chatBot.GetChatBot().Id));
|
||||
}
|
||||
|
||||
_dialogueChatBots.Add(chatBot);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle onUpdate spt event
|
||||
/// Handle onUpdate spt event
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
var profiles = _saveServer.GetProfiles();
|
||||
foreach (var kvp in profiles) RemoveExpiredItemsFromMessages(kvp.Key);
|
||||
foreach (var kvp in profiles)
|
||||
{
|
||||
RemoveExpiredItemsFromMessages(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/friend/list
|
||||
/// Handle client/friend/list
|
||||
/// </summary>
|
||||
/// <param name="sessionId">session id</param>
|
||||
/// <returns>GetFriendListDataResponse</returns>
|
||||
@@ -65,10 +69,12 @@ public class DialogueController(
|
||||
// Add any friends the user has after the chatbots
|
||||
var profile = _profileHelper.GetFullProfile(sessionId);
|
||||
if (profile?.FriendProfileIds is not null)
|
||||
{
|
||||
foreach (var friendId in profile.FriendProfileIds)
|
||||
{
|
||||
var friendProfile = _profileHelper.GetChatRoomMemberFromSessionId(friendId);
|
||||
if (friendProfile is not null)
|
||||
{
|
||||
friends.Add(
|
||||
new UserDialogInfo
|
||||
{
|
||||
@@ -77,7 +83,9 @@ public class DialogueController(
|
||||
Info = friendProfile.Info
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new GetFriendListDataResponse
|
||||
{
|
||||
@@ -96,29 +104,35 @@ public class DialogueController(
|
||||
foreach (var bot in _dialogueChatBots)
|
||||
{
|
||||
var botData = bot.GetChatBot();
|
||||
if (chatBotConfig.EnabledBots.ContainsKey(botData.Id!)) activeBots.Add(botData);
|
||||
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
|
||||
/// Set the content of the dialogue on the list tab.
|
||||
/// Handle client/mail/dialog/list
|
||||
/// Create array holding trader dialogs and mail interactions with player
|
||||
/// Set the content of the dialogue on the list tab.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session Id</param>
|
||||
/// <returns>list of dialogs</returns>
|
||||
public List<DialogueInfo> GenerateDialogueList(string sessionId)
|
||||
{
|
||||
var data = new List<DialogueInfo>();
|
||||
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) data.Add(GetDialogueInfo(dialogueId.Key, sessionId));
|
||||
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId))
|
||||
{
|
||||
data.Add(GetDialogueInfo(dialogueId.Key, sessionId));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the content of a dialogue
|
||||
/// Get the content of a dialogue
|
||||
/// </summary>
|
||||
/// <param name="dialogueId">Dialog id</param>
|
||||
/// <param name="sessionId">Session Id</param>
|
||||
@@ -145,7 +159,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the users involved in a dialog (player + other party)
|
||||
/// Get the users involved in a dialog (player + other party)
|
||||
/// </summary>
|
||||
/// <param name="dialog">The dialog to check for users</param>
|
||||
/// <param name="messageType">What type of message is being sent</param>
|
||||
@@ -162,6 +176,7 @@ public class DialogueController(
|
||||
if (messageType == MessageType.USER_MESSAGE &&
|
||||
dialog?.Users is not null &&
|
||||
dialog.Users.All(userDialog => userDialog.Id != profile.CharacterData?.PmcData?.SessionId))
|
||||
{
|
||||
dialog.Users.Add(
|
||||
new UserDialogInfo
|
||||
{
|
||||
@@ -177,15 +192,16 @@ public class DialogueController(
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return dialog?.Users!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/view
|
||||
/// Handle player clicking 'messenger' and seeing all the messages they've received
|
||||
/// Set the content of the dialogue on the details panel, showing all the messages
|
||||
/// for the specified dialogue.
|
||||
/// Handle client/mail/dialog/view
|
||||
/// Handle player clicking 'messenger' and seeing all the messages they've received
|
||||
/// Set the content of the dialogue on the details panel, showing all the messages
|
||||
/// for the specified dialogue.
|
||||
/// </summary>
|
||||
/// <param name="request">Get dialog request</param>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
@@ -213,7 +229,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get dialog from player profile, create if doesn't exist
|
||||
/// Get dialog from player profile, create if doesn't exist
|
||||
/// </summary>
|
||||
/// <param name="profile">Player profile</param>
|
||||
/// <param name="request">get dialog request</param>
|
||||
@@ -223,7 +239,9 @@ public class DialogueController(
|
||||
GetMailDialogViewRequestData request)
|
||||
{
|
||||
if (profile.DialogueRecords is null || profile.DialogueRecords.ContainsKey(request.DialogId!))
|
||||
{
|
||||
return profile.DialogueRecords?[request.DialogId!] ?? throw new NullReferenceException();
|
||||
}
|
||||
|
||||
profile.DialogueRecords[request.DialogId!] = new Dialogue
|
||||
{
|
||||
@@ -235,13 +253,19 @@ public class DialogueController(
|
||||
Type = request.Type
|
||||
};
|
||||
|
||||
if (request.Type != MessageType.USER_MESSAGE) return profile.DialogueRecords[request.DialogId!];
|
||||
if (request.Type != MessageType.USER_MESSAGE)
|
||||
{
|
||||
return profile.DialogueRecords[request.DialogId!];
|
||||
}
|
||||
|
||||
var dialogue = profile.DialogueRecords[request.DialogId!];
|
||||
dialogue.Users = [];
|
||||
var chatBot = _dialogueChatBots.FirstOrDefault(cb => cb.GetChatBot().Id == request.DialogId);
|
||||
|
||||
if (chatBot is null) return profile.DialogueRecords[request.DialogId!];
|
||||
if (chatBot is null)
|
||||
{
|
||||
return profile.DialogueRecords[request.DialogId!];
|
||||
}
|
||||
|
||||
dialogue.Users ??= [];
|
||||
dialogue.Users.Add(chatBot.GetChatBot());
|
||||
@@ -250,7 +274,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the users involved in a mail between two entities
|
||||
/// Get the users involved in a mail between two entities
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Player profile</param>
|
||||
/// <param name="userDialogs">The participants of the mail</param>
|
||||
@@ -260,11 +284,16 @@ public class DialogueController(
|
||||
List<UserDialogInfo> result = [];
|
||||
if (userDialogs is null)
|
||||
// Nothing to add
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.AddRange(userDialogs);
|
||||
|
||||
if (result.Any(userDialog => userDialog.Id == fullProfile.ProfileInfo?.ProfileId)) return result;
|
||||
if (result.Any(userDialog => userDialog.Id == fullProfile.ProfileInfo?.ProfileId))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Player doesn't exist, add them in before returning
|
||||
var pmcProfile = fullProfile.CharacterData?.PmcData;
|
||||
@@ -288,7 +317,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a count of messages with attachments from a particular dialog
|
||||
/// Get a count of messages with attachments from a particular dialog
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="dialogueId">Dialog id</param>
|
||||
@@ -300,8 +329,12 @@ public class DialogueController(
|
||||
var newAttachmentCount = 0;
|
||||
var activeMessages = GetActiveMessagesFromDialog(sessionId, dialogueId);
|
||||
foreach (var message in activeMessages)
|
||||
{
|
||||
if (message.HasRewards.GetValueOrDefault(false) && !message.RewardCollected.GetValueOrDefault(false))
|
||||
{
|
||||
newAttachmentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return newAttachmentCount;
|
||||
}
|
||||
@@ -321,7 +354,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does list have messages with uncollected rewards (includes expired rewards)
|
||||
/// Does list have messages with uncollected rewards (includes expired rewards)
|
||||
/// </summary>
|
||||
/// <param name="messages">Messages to check</param>
|
||||
/// <returns>true if uncollected rewards found</returns>
|
||||
@@ -331,8 +364,8 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/remove
|
||||
/// Remove an entire dialog with an entity (trader/user)
|
||||
/// Handle client/mail/dialog/remove
|
||||
/// Remove an entire dialog with an entity (trader/user)
|
||||
/// </summary>
|
||||
/// <param name="dialogueId">id of the dialog to remove</param>
|
||||
/// <param name="sessionId">Player id</param>
|
||||
@@ -362,7 +395,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/pin && Handle client/mail/dialog/unpin
|
||||
/// Handle client/mail/dialog/pin && Handle client/mail/dialog/unpin
|
||||
/// </summary>
|
||||
/// <param name="dialogueId"></param>
|
||||
/// <param name="shouldPin"></param>
|
||||
@@ -390,8 +423,8 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/read
|
||||
/// Set a dialog to be read (no number alert/attachment alert)
|
||||
/// Handle client/mail/dialog/read
|
||||
/// Set a dialog to be read (no number alert/attachment alert)
|
||||
/// </summary>
|
||||
/// <param name="dialogueIds">Dialog ids to set as read</param>
|
||||
/// <param name="sessionId">Player profile id</param>
|
||||
@@ -421,8 +454,8 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/mail/dialog/getAllAttachments
|
||||
/// Get all uncollected items attached to mail in a particular dialog
|
||||
/// Handle client/mail/dialog/getAllAttachments
|
||||
/// Get all uncollected items attached to mail in a particular dialog
|
||||
/// </summary>
|
||||
/// <param name="dialogueId">Dialog to get mail attachments from</param>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
@@ -455,7 +488,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// handle client/mail/msg/send
|
||||
/// handle client/mail/msg/send
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -476,7 +509,7 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return list of messages with uncollected items (includes expired)
|
||||
/// Return list of messages with uncollected items (includes expired)
|
||||
/// </summary>
|
||||
/// <param name="messages">Messages to parse</param>
|
||||
/// <returns>messages with items to collect</returns>
|
||||
@@ -486,27 +519,37 @@ public class DialogueController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete expired items from all messages in player profile. triggers when updating traders.
|
||||
/// Delete expired items from all messages in player profile. triggers when updating traders.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
private void RemoveExpiredItemsFromMessages(string sessionId)
|
||||
{
|
||||
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) RemoveExpiredItemsFromMessage(sessionId, dialogueId.Key);
|
||||
foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId))
|
||||
{
|
||||
RemoveExpiredItemsFromMessage(sessionId, dialogueId.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes expired items from a message in player profile
|
||||
/// Removes expired items from a message in player profile
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="dialogueId">Dialog id</param>
|
||||
private void RemoveExpiredItemsFromMessage(string sessionId, string dialogueId)
|
||||
{
|
||||
var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId);
|
||||
if (!dialogs.TryGetValue(dialogueId, out var dialog)) return;
|
||||
if (!dialogs.TryGetValue(dialogueId, out var dialog))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var message in dialog.Messages ?? [])
|
||||
{
|
||||
if (MessageHasExpired(message))
|
||||
{
|
||||
message.Items = new MessageItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,16 +567,21 @@ public class DialogueController(
|
||||
// To avoid needing to jump between profiles, auto-accept all friend requests
|
||||
var friendProfile = _profileHelper.GetFullProfile(request.To);
|
||||
if (friendProfile?.CharacterData?.PmcData is null)
|
||||
{
|
||||
return new FriendRequestSendResponse
|
||||
{
|
||||
Status = BackendErrorCodes.PlayerProfileNotFound,
|
||||
RequestId = "", // Unused in an error state
|
||||
RetryAfter = 600
|
||||
};
|
||||
}
|
||||
|
||||
// Only add the profile to the friends list if it doesn't already exist
|
||||
var profile = _saveServer.GetProfile(sessionID);
|
||||
if (!profile.FriendProfileIds.Contains(request.To)) profile.FriendProfileIds.Add(request.To);
|
||||
if (!profile.FriendProfileIds.Contains(request.To))
|
||||
{
|
||||
profile.FriendProfileIds.Add(request.To);
|
||||
}
|
||||
|
||||
// We need to delay this so that the friend request gets properly added to the clientside list before we accept it
|
||||
_ = new Timer(
|
||||
@@ -563,6 +611,9 @@ public class DialogueController(
|
||||
{
|
||||
var profile = _saveServer.GetProfile(sessionID);
|
||||
var friendIndex = profile.FriendProfileIds.IndexOf(request.FriendId);
|
||||
if (friendIndex != -1) profile.FriendProfileIds.RemoveAt(friendIndex);
|
||||
if (friendIndex != -1)
|
||||
{
|
||||
profile.FriendProfileIds.RemoveAt(friendIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -13,6 +12,7 @@ using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using Core.Utils.Json;
|
||||
using Server;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
@@ -44,15 +44,15 @@ public class GameController(
|
||||
ICloner _cloner
|
||||
)
|
||||
{
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
|
||||
protected double _deviation = 0.0001;
|
||||
protected HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
|
||||
protected HttpConfig _httpConfig = _configServer.GetConfig<HttpConfig>();
|
||||
protected RagfairConfig _ragfairConfig = _configServer.GetConfig<RagfairConfig>();
|
||||
protected HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected double _deviation = 0.0001;
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/start
|
||||
/// Handle client/game/start
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -83,24 +83,42 @@ public class GameController(
|
||||
return;
|
||||
}
|
||||
|
||||
fullProfile.SptData ??= new Spt { Version = "Replace_me" };
|
||||
fullProfile.SptData ??= new Spt
|
||||
{
|
||||
Version = "Replace_me"
|
||||
};
|
||||
fullProfile.SptData.Migrations ??= new Dictionary<string, long>();
|
||||
fullProfile.FriendProfileIds ??= [];
|
||||
|
||||
if (fullProfile.ProfileInfo?.IsWiped is not null && fullProfile.ProfileInfo.IsWiped.Value) return;
|
||||
if (fullProfile.ProfileInfo?.IsWiped is not null && fullProfile.ProfileInfo.IsWiped.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fullProfile.CharacterData!.PmcData!.WishList ??= new DictionaryOrList<string, int>(new Dictionary<string, int>(), []);
|
||||
fullProfile.CharacterData.ScavData!.WishList ??= new DictionaryOrList<string, int>(new Dictionary<string, int>(), []);
|
||||
|
||||
if (fullProfile.DialogueRecords is not null) _profileFixerService.CheckForAndFixDialogueAttachments(fullProfile);
|
||||
if (fullProfile.DialogueRecords is not null)
|
||||
{
|
||||
_profileFixerService.CheckForAndFixDialogueAttachments(fullProfile);
|
||||
}
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Started game with session {sessionId} {fullProfile.ProfileInfo?.Username}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Started game with session {sessionId} {fullProfile.ProfileInfo?.Username}");
|
||||
}
|
||||
|
||||
var pmcProfile = fullProfile.CharacterData.PmcData;
|
||||
|
||||
if (_coreConfig.Fixes.FixProfileBreakingInventoryItemIssues) _profileFixerService.FixProfileBreakingInventoryItemIssues(pmcProfile);
|
||||
if (_coreConfig.Fixes.FixProfileBreakingInventoryItemIssues)
|
||||
{
|
||||
_profileFixerService.FixProfileBreakingInventoryItemIssues(pmcProfile);
|
||||
}
|
||||
|
||||
if (pmcProfile.Health is not null) UpdateProfileHealthValues(pmcProfile);
|
||||
if (pmcProfile.Health is not null)
|
||||
{
|
||||
UpdateProfileHealthValues(pmcProfile);
|
||||
}
|
||||
|
||||
if (pmcProfile.Inventory is not null)
|
||||
{
|
||||
@@ -128,13 +146,16 @@ public class GameController(
|
||||
CheckForAndRemoveUndefinedDialogues(fullProfile);
|
||||
}
|
||||
|
||||
if (pmcProfile.Skills?.Common is not null) WarnOnActiveBotReloadSkill(pmcProfile);
|
||||
if (pmcProfile.Skills?.Common is not null)
|
||||
{
|
||||
WarnOnActiveBotReloadSkill(pmcProfile);
|
||||
}
|
||||
|
||||
_seasonalEventService.GivePlayerSeasonalGifts(sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/config
|
||||
/// Handle client/game/config
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -180,7 +201,7 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/mode
|
||||
/// Handle client/game/mode
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="requestData"></param>
|
||||
@@ -197,7 +218,7 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/server/list
|
||||
/// Handle client/server/list
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -214,7 +235,7 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/current
|
||||
/// Handle client/match/group/current
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -228,7 +249,7 @@ public class GameController(
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/checkVersion
|
||||
/// Handle client/checkVersion
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -242,18 +263,22 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/game/keepalive
|
||||
/// Handle client/game/keepalive
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
public GameKeepAliveResponse GetKeepAlive(string sessionId)
|
||||
{
|
||||
_profileActivityService.SetActivityTimestamp(sessionId);
|
||||
return new GameKeepAliveResponse { Message = "OK", UtcTime = _timeUtil.GetTimeStamp() };
|
||||
return new GameKeepAliveResponse
|
||||
{
|
||||
Message = "OK",
|
||||
UtcTime = _timeUtil.GetTimeStamp()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle singleplayer/settings/getRaidTime
|
||||
/// Handle singleplayer/settings/getRaidTime
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -269,7 +294,6 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -279,17 +303,20 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Players set botReload to a high value and don't expect the crazy fast reload speeds, give them a warn about it
|
||||
/// Players set botReload to a high value and don't expect the crazy fast reload speeds, give them a warn about it
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">Player profile</param>
|
||||
private void WarnOnActiveBotReloadSkill(PmcData pmcProfile)
|
||||
{
|
||||
var botReloadSkill = _profileHelper.GetSkillFromProfile(pmcProfile, SkillTypes.BotReload);
|
||||
if (botReloadSkill?.Progress > 0) _logger.Warning(_localisationService.GetText("server_start_player_active_botreload_skill"));
|
||||
if (botReloadSkill?.Progress > 0)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("server_start_player_active_botreload_skill"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When player logs in, iterate over all active effects and reduce timer
|
||||
/// When player logs in, iterate over all active effects and reduce timer
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">Profile to adjust values for</param>
|
||||
private void UpdateProfileHealthValues(PmcData pmcProfile)
|
||||
@@ -351,7 +378,7 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for and update any timers on effect found on body parts
|
||||
/// Check for and update any timers on effect found on body parts
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">Player</param>
|
||||
/// <param name="hpRegenPerHour"></param>
|
||||
@@ -385,7 +412,10 @@ public class GameController(
|
||||
if (effectKvP.Value.Time < 1)
|
||||
{
|
||||
// More than 30 minutes has passed
|
||||
if (diffSeconds > 1800) bodyPart.Effects.Remove(effectKvP.Key);
|
||||
if (diffSeconds > 1800)
|
||||
{
|
||||
bodyPart.Effects.Remove(effectKvP.Key);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -394,13 +424,15 @@ public class GameController(
|
||||
effectKvP.Value.Time -= diffSeconds;
|
||||
if (effectKvP.Value.Time < 1)
|
||||
// Effect time was sub 1, set floor it can be
|
||||
{
|
||||
effectKvP.Value.Time = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send starting gifts to profile after x days
|
||||
/// Send starting gifts to profile after x days
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">Profile to add gifts to</param>
|
||||
private void SendPraporGiftsToNewProfiles(PmcData pmcProfile)
|
||||
@@ -410,10 +442,16 @@ public class GameController(
|
||||
var currentTimeStamp = _timeUtil.GetTimeStamp();
|
||||
|
||||
// One day post-profile creation
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds) _giftService.SendPraporStartingGift(pmcProfile.SessionId!, 1);
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds)
|
||||
{
|
||||
_giftService.SendPraporStartingGift(pmcProfile.SessionId!, 1);
|
||||
}
|
||||
|
||||
// Two day post-profile creation
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds * 2) _giftService.SendPraporStartingGift(pmcProfile.SessionId!, 2);
|
||||
if (currentTimeStamp > timeStampProfileCreated + oneDaySeconds * 2)
|
||||
{
|
||||
_giftService.SendPraporStartingGift(pmcProfile.SessionId!, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,7 +464,7 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of installed mods and save their details to the profile being used
|
||||
/// Get a list of installed mods and save their details to the profile being used
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Profile to add mod details to</param>
|
||||
private void SaveActiveModsToProfile(SptProfile fullProfile)
|
||||
@@ -467,7 +505,7 @@ public class GameController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the logged in players name to PMC name pool
|
||||
/// Add the logged in players name to PMC name pool
|
||||
/// </summary>
|
||||
/// <param name="pmcProfile">Profile of player to get name from</param>
|
||||
private void AddPlayerToPmcNames(PmcData pmcProfile)
|
||||
@@ -478,30 +516,43 @@ public class GameController(
|
||||
var bots = _databaseService.GetBots().Types;
|
||||
|
||||
// Official names can only be 15 chars in length
|
||||
if (playerName.Length > _botConfig.BotNameLengthLimit) return;
|
||||
if (playerName.Length > _botConfig.BotNameLengthLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if player name exists already
|
||||
if (bots!.TryGetValue("bear", out var bearBot))
|
||||
{
|
||||
if (bearBot is not null && bearBot.FirstNames!.Any(x => x == playerName))
|
||||
{
|
||||
bearBot.FirstNames!.Add(playerName);
|
||||
}
|
||||
}
|
||||
|
||||
if (bots.TryGetValue("bear", out var usecBot))
|
||||
{
|
||||
if (usecBot is not null && usecBot.FirstNames!.Any(x => x == playerName))
|
||||
{
|
||||
usecBot.FirstNames!.Add(playerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for a dialog with the key 'undefined', and remove it
|
||||
/// Check for a dialog with the key 'undefined', and remove it
|
||||
/// </summary>
|
||||
/// <param name="fullProfile">Profile to check for dialog in</param>
|
||||
private void CheckForAndRemoveUndefinedDialogues(SptProfile fullProfile)
|
||||
{
|
||||
if (fullProfile.DialogueRecords!.TryGetValue("undefined", out _)) fullProfile.DialogueRecords.Remove("undefined");
|
||||
if (fullProfile.DialogueRecords!.TryGetValue("undefined", out _))
|
||||
{
|
||||
fullProfile.DialogueRecords.Remove("undefined");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="fullProfile"></param>
|
||||
private void LogProfileDetails(SptProfile fullProfile)
|
||||
|
||||
@@ -8,6 +8,5 @@ public class HandBookController
|
||||
public void Load()
|
||||
{
|
||||
// leaving as this is how node is RN
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
using BodyPartHealth = Core.Models.Eft.Common.Tables.BodyPartHealth;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -63,18 +62,24 @@ public class HealthController(
|
||||
{
|
||||
// Get max healing from db
|
||||
var maxhp = _itemHelper.GetItem(healingItemToUse.Template).Value.Properties.MaxHpResource;
|
||||
healingItemToUse.Upd.MedKit = new UpdMedKit { HpResource = maxhp - request.Count }; // Subtract amout used from max
|
||||
healingItemToUse.Upd.MedKit = new UpdMedKit
|
||||
{
|
||||
HpResource = maxhp - request.Count
|
||||
}; // Subtract amout used from max
|
||||
// request.count appears to take into account healing effects removed, e.g. bleeds
|
||||
// Salewa heals limb for 20 and fixes light bleed = (20+45 = 65)
|
||||
}
|
||||
|
||||
// Resource in medkit is spent, delete it
|
||||
if (healingItemToUse.Upd.MedKit.HpResource <= 0) _inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
|
||||
if (healingItemToUse.Upd.MedKit.HpResource <= 0)
|
||||
{
|
||||
_inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
|
||||
}
|
||||
|
||||
var healingItemDbDetails = _itemHelper.GetItem(healingItemToUse.Template);
|
||||
|
||||
var healItemEffectDetails = healingItemDbDetails.Value.Properties.EffectsDamage;
|
||||
var bodyPartToHeal = pmcData.Health.BodyParts.GetValueOrDefault(request.Part.ToString());
|
||||
var bodyPartToHeal = pmcData.Health.BodyParts.GetValueOrDefault(request.Part);
|
||||
if (bodyPartToHeal is null)
|
||||
{
|
||||
_logger.Warning($"Player: {sessionID} Tried to heal a non-existent body part: {request.Part}");
|
||||
@@ -96,10 +101,12 @@ public class HealthController(
|
||||
// Check if healing item removes the effect on limb
|
||||
if (!healItemEffectDetails.TryGetValue(Enum.Parse<DamageEffectType>(effectKey), out var matchingEffectFromHealingItem))
|
||||
// Healing item doesn't have matching effect, it doesn't remove the effect
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Adjust limb heal amount based on if its fixing an effect (request.count is TOTAL cost of hp resource on heal item, NOT amount to heal limb)
|
||||
amountToHealLimb -= (int)(matchingEffectFromHealingItem.Cost ?? 0);
|
||||
amountToHealLimb -= (int) (matchingEffectFromHealingItem.Cost ?? 0);
|
||||
bodyPartToHeal.Effects.Remove(effectKey);
|
||||
}
|
||||
}
|
||||
@@ -108,7 +115,10 @@ public class HealthController(
|
||||
bodyPartToHeal.Health.Current += amountToHealLimb;
|
||||
|
||||
// Ensure we've not healed beyond the limbs max hp
|
||||
if (bodyPartToHeal.Health.Current > bodyPartToHeal.Health.Maximum) bodyPartToHeal.Health.Current = bodyPartToHeal.Health.Maximum;
|
||||
if (bodyPartToHeal.Health.Current > bodyPartToHeal.Health.Maximum)
|
||||
{
|
||||
bodyPartToHeal.Health.Current = bodyPartToHeal.Health.Maximum;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -132,10 +142,12 @@ public class HealthController(
|
||||
var itemToConsume = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
|
||||
if (itemToConsume is null)
|
||||
// Item not found, very bad
|
||||
{
|
||||
return _httpResponseUtil.AppendErrorToOutput(
|
||||
output,
|
||||
_localisationService.GetText("health-unable_to_find_item_to_consume", request.Item)
|
||||
);
|
||||
}
|
||||
|
||||
var consumedItemMaxResource = _itemHelper.GetItem(itemToConsume.Template).Value.Properties.MaxResource;
|
||||
if (consumedItemMaxResource > 1)
|
||||
@@ -144,15 +156,25 @@ public class HealthController(
|
||||
_itemHelper.AddUpdObjectToItem(itemToConsume);
|
||||
|
||||
if (itemToConsume.Upd.FoodDrink is null)
|
||||
itemToConsume.Upd.FoodDrink = new UpdFoodDrink { HpPercent = consumedItemMaxResource - request.Count };
|
||||
{
|
||||
itemToConsume.Upd.FoodDrink = new UpdFoodDrink
|
||||
{
|
||||
HpPercent = consumedItemMaxResource - request.Count
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
itemToConsume.Upd.FoodDrink.HpPercent -= request.Count;
|
||||
}
|
||||
|
||||
resourceLeft = itemToConsume.Upd.FoodDrink.HpPercent.Value;
|
||||
}
|
||||
|
||||
// Remove item from inventory if resource has dropped below threshold
|
||||
if (consumedItemMaxResource == 1 || resourceLeft < 1) _inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
|
||||
if (consumedItemMaxResource == 1 || resourceLeft < 1)
|
||||
{
|
||||
_inventoryHelper.RemoveItem(pmcData, request.Item, sessionID, output);
|
||||
}
|
||||
|
||||
// Check what effect eating item has and handle
|
||||
var foodItemDbDetails = _itemHelper.GetItem(itemToConsume.Template).Value;
|
||||
@@ -160,6 +182,7 @@ public class HealthController(
|
||||
var foodIsSingleUse = foodItemDbDetails.Properties.MaxResource == 1;
|
||||
|
||||
foreach (var (key, effectProps) in foodItemEffectDetails)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case HealthFactor.Hydration:
|
||||
@@ -173,6 +196,7 @@ public class HealthController(
|
||||
_logger.Warning($"Unhandled effect after consuming: {itemToConsume.Template}, {key}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -182,9 +206,13 @@ public class HealthController(
|
||||
{
|
||||
if (foodIsSingleUse)
|
||||
// Apply whole value from passed in parameter
|
||||
{
|
||||
bodyValue.Current += consumptionDetails.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyValue.Current += request.Count;
|
||||
}
|
||||
|
||||
// Ensure current never goes over max
|
||||
if (bodyValue.Current > bodyValue.Maximum)
|
||||
@@ -195,7 +223,10 @@ public class HealthController(
|
||||
}
|
||||
|
||||
// Same as above but for the lower bound
|
||||
if (bodyValue.Current < 0) bodyValue.Current = 0;
|
||||
if (bodyValue.Current < 0)
|
||||
{
|
||||
bodyValue.Current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,27 +255,38 @@ public class HealthController(
|
||||
};
|
||||
|
||||
_paymentService.PayMoney(pmcData, payMoneyRequest, sessionID, output);
|
||||
if (output.Warnings.Count > 0) return output;
|
||||
if (output.Warnings.Count > 0)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
foreach (var bodyPartKvP in healthTreatmentRequest.Difference.BodyParts.GetAllPropsAsDict())
|
||||
{
|
||||
// Get body part from request + from pmc profile
|
||||
var partRequest = (BodyPartEffects)bodyPartKvP.Value;
|
||||
var partRequest = (BodyPartEffects) bodyPartKvP.Value;
|
||||
var profilePart = pmcData.Health.BodyParts[bodyPartKvP.Key];
|
||||
|
||||
// Bodypart healing is chosen when part request hp is above 0
|
||||
if (partRequest.Health > 0)
|
||||
// Heal bodypart
|
||||
{
|
||||
profilePart.Health.Current = profilePart.Health.Maximum;
|
||||
}
|
||||
|
||||
// Check for effects to remove
|
||||
if (partRequest.Effects?.Count > 0)
|
||||
{
|
||||
// Found some, loop over them and remove from pmc profile
|
||||
foreach (var effect in partRequest.Effects) pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Remove(effect);
|
||||
foreach (var effect in partRequest.Effects)
|
||||
{
|
||||
pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Remove(effect);
|
||||
}
|
||||
|
||||
// Remove empty effect object
|
||||
if (pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Count == 0) pmcData.Health.BodyParts[bodyPartKvP.Key].Effects = null;
|
||||
if (pmcData.Health.BodyParts[bodyPartKvP.Key].Effects.Count == 0)
|
||||
{
|
||||
pmcData.Health.BodyParts[bodyPartKvP.Key].Effects = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Common;
|
||||
@@ -16,7 +15,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -46,7 +45,6 @@ public class HideoutController(
|
||||
ConfigServer _configServer
|
||||
)
|
||||
{
|
||||
protected HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
|
||||
public const string NameTaskConditionCountersCraftingId = "673f5d6fdd6ed700c703afdc";
|
||||
|
||||
protected List<HideoutAreas> _hideoutAreas =
|
||||
@@ -57,13 +55,19 @@ public class HideoutController(
|
||||
HideoutAreas.BITCOIN_FARM
|
||||
];
|
||||
|
||||
protected HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
|
||||
|
||||
public void StartUpgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output)
|
||||
{
|
||||
var items = request.Items.Select(
|
||||
reqItem =>
|
||||
{
|
||||
var item = pmcData.Inventory.Items.FirstOrDefault(invItem => invItem.Id == reqItem.Id);
|
||||
return new { inventoryItem = item, requestedItem = reqItem };
|
||||
return new
|
||||
{
|
||||
inventoryItem = item,
|
||||
requestedItem = reqItem
|
||||
};
|
||||
}
|
||||
)
|
||||
.ToList();
|
||||
@@ -87,9 +91,13 @@ public class HideoutController(
|
||||
item.inventoryItem.Upd.StackObjectsCount is not null &&
|
||||
item.inventoryItem.Upd.StackObjectsCount > item.requestedItem.Count
|
||||
)
|
||||
{
|
||||
item.inventoryItem.Upd.StackObjectsCount -= item.requestedItem.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
_inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionID, output);
|
||||
}
|
||||
}
|
||||
|
||||
// Construction time management
|
||||
@@ -118,11 +126,14 @@ public class HideoutController(
|
||||
var ctime = hideoutDataDb.Stages[(profileHideoutArea.Level + 1).ToString()].ConstructionTime;
|
||||
if (ctime > 0)
|
||||
{
|
||||
if (_profileHelper.IsDeveloperAccount(sessionID)) ctime = 40;
|
||||
if (_profileHelper.IsDeveloperAccount(sessionID))
|
||||
{
|
||||
ctime = 40;
|
||||
}
|
||||
|
||||
var timestamp = _timeUtil.GetTimeStamp();
|
||||
|
||||
profileHideoutArea.CompleteTime = Math.Round((double)(timestamp - ctime));
|
||||
profileHideoutArea.CompleteTime = Math.Round((double) (timestamp - ctime));
|
||||
profileHideoutArea.Constructing = true;
|
||||
}
|
||||
}
|
||||
@@ -161,11 +172,16 @@ public class HideoutController(
|
||||
var hideoutStage = hideoutData.Stages[profileHideoutArea.Level.ToString()];
|
||||
var bonuses = hideoutStage.Bonuses;
|
||||
if (bonuses?.Count > 0)
|
||||
{
|
||||
foreach (var bonus in bonuses)
|
||||
{
|
||||
_hideoutHelper.ApplyPlayerUpgradesBonuses(pmcData, bonus);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade includes a container improvement/addition
|
||||
if (!string.IsNullOrEmpty(hideoutStage?.Container))
|
||||
{
|
||||
AddContainerImprovementToProfile(
|
||||
output,
|
||||
sessionID,
|
||||
@@ -174,17 +190,22 @@ public class HideoutController(
|
||||
hideoutData,
|
||||
hideoutStage
|
||||
);
|
||||
}
|
||||
|
||||
// Upgrading water collector / med station
|
||||
if (
|
||||
profileHideoutArea.Type == HideoutAreas.WATER_COLLECTOR ||
|
||||
profileHideoutArea.Type == HideoutAreas.MEDSTATION
|
||||
)
|
||||
{
|
||||
SetWallVisibleIfPrereqsMet(pmcData);
|
||||
}
|
||||
|
||||
// Cleanup temporary buffs/debuffs from wall if complete
|
||||
if (profileHideoutArea.Type == HideoutAreas.EMERGENCY_WALL && profileHideoutArea.Level == 6)
|
||||
{
|
||||
_hideoutHelper.RemoveHideoutWallBuffsAndDebuffs(hideoutData, pmcData);
|
||||
}
|
||||
|
||||
// Add Skill Points Per Area Upgrade
|
||||
_profileHelper.AddSkillPointsToPlayer(
|
||||
@@ -196,12 +217,15 @@ public class HideoutController(
|
||||
|
||||
private void SetWallVisibleIfPrereqsMet(PmcData pmcData)
|
||||
{
|
||||
var medStation = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.MEDSTATION);
|
||||
var waterCollector = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.WATER_COLLECTOR);
|
||||
var medStation = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.MEDSTATION);
|
||||
var waterCollector = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.WATER_COLLECTOR);
|
||||
if (medStation?.Level >= 1 && waterCollector?.Level >= 1)
|
||||
{
|
||||
var wall = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == HideoutAreas.EMERGENCY_WALL);
|
||||
if (wall?.Level == 0) wall.Level = 3;
|
||||
var wall = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.EMERGENCY_WALL);
|
||||
if (wall?.Level == 0)
|
||||
{
|
||||
wall.Level = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +234,9 @@ public class HideoutController(
|
||||
{
|
||||
// Add key/value to `hideoutAreaStashes` dictionary - used to link hideout area to inventory stash by its id
|
||||
if (!pmcData.Inventory.HideoutAreaStashes.ContainsKey(dbHideoutArea.Type.ToString()))
|
||||
{
|
||||
pmcData.Inventory.HideoutAreaStashes[dbHideoutArea.Type.ToString()] = dbHideoutArea.Id;
|
||||
}
|
||||
|
||||
// Add/upgrade stash item in player inventory
|
||||
AddUpdateInventoryItemToProfile(sessionID, pmcData, dbHideoutArea, hideoutStage);
|
||||
@@ -218,13 +244,17 @@ public class HideoutController(
|
||||
// Edge case, add/update `stand1/stand2/stand3` children
|
||||
if (dbHideoutArea.Type == HideoutAreas.EQUIPMENT_PRESETS_STAND)
|
||||
// Can have multiple 'standx' children depending on upgrade level
|
||||
{
|
||||
AddMissingPresetStandItemsToProfile(sessionID, hideoutStage, pmcData, dbHideoutArea, output);
|
||||
}
|
||||
|
||||
// Dont inform client when upgraded area is hall of fame or equipment stand, BSG doesn't inform client this specifc upgrade has occurred
|
||||
// will break client if sent
|
||||
List<HideoutAreas> check = [HideoutAreas.PLACE_OF_FAME];
|
||||
if (!check.Contains(dbHideoutArea.Type ?? HideoutAreas.NOTSET))
|
||||
{
|
||||
AddContainerUpgradeToClientOutput(sessionID, dbHideoutArea.Type, dbHideoutArea, hideoutStage, output);
|
||||
}
|
||||
|
||||
// Some hideout areas (Gun stand) have child areas linked to it
|
||||
var childDbArea = _databaseService
|
||||
@@ -234,11 +264,13 @@ public class HideoutController(
|
||||
{
|
||||
// Add key/value to `hideoutAreaStashes` dictionary - used to link hideout area to inventory stash by its id
|
||||
if (pmcData.Inventory.HideoutAreaStashes.GetValueOrDefault(childDbArea.Type.ToString()) is null)
|
||||
{
|
||||
pmcData.Inventory.HideoutAreaStashes[childDbArea.Type.ToString()] = childDbArea.Id;
|
||||
}
|
||||
|
||||
// Set child area level to same as parent area
|
||||
pmcData.Hideout.Areas.FirstOrDefault((hideoutArea) => hideoutArea.Type == childDbArea.Type).Level =
|
||||
pmcData.Hideout.Areas.FirstOrDefault((x) => x.Type == profileParentHideoutArea.Type).Level;
|
||||
pmcData.Hideout.Areas.FirstOrDefault(hideoutArea => hideoutArea.Type == childDbArea.Type).Level =
|
||||
pmcData.Hideout.Areas.FirstOrDefault(x => x.Type == profileParentHideoutArea.Type).Level;
|
||||
|
||||
// Add/upgrade stash item in player inventory
|
||||
var childDbAreaStage = childDbArea.Stages[profileParentHideoutArea.Level.ToString()];
|
||||
@@ -261,7 +293,11 @@ public class HideoutController(
|
||||
}
|
||||
|
||||
// Add new item as none exists (don't inform client of newContainerItem, will be done in `profileChanges.changedHideoutStashes`)
|
||||
var newContainerItem = new Item { Id = dbHideoutArea.Id, Template = hideoutStage.Container };
|
||||
var newContainerItem = new Item
|
||||
{
|
||||
Id = dbHideoutArea.Id,
|
||||
Template = hideoutStage.Container
|
||||
};
|
||||
pmcData.Inventory.Items.Add(newContainerItem);
|
||||
}
|
||||
|
||||
@@ -269,7 +305,9 @@ public class HideoutController(
|
||||
ItemEventRouterResponse output)
|
||||
{
|
||||
if (output.ProfileChanges[sessionID].ChangedHideoutStashes is null)
|
||||
{
|
||||
output.ProfileChanges[sessionID].ChangedHideoutStashes = new Dictionary<string, HideoutStashItem>();
|
||||
}
|
||||
|
||||
// Inform client of changes
|
||||
output.ProfileChanges[sessionID].ChangedHideoutStashes[areaType.ToString()] = new HideoutStashItem
|
||||
@@ -286,8 +324,13 @@ public class HideoutController(
|
||||
var itemsToAdd = addItemToHideoutRequest.Items.Select(
|
||||
kvp =>
|
||||
{
|
||||
var item = pmcData.Inventory.Items.FirstOrDefault((invItem) => invItem.Id == kvp.Value.Id);
|
||||
return new { inventoryItem = item, requestedItem = kvp.Value, slot = kvp.Key };
|
||||
var item = pmcData.Inventory.Items.FirstOrDefault(invItem => invItem.Id == kvp.Value.Id);
|
||||
return new
|
||||
{
|
||||
inventoryItem = item,
|
||||
requestedItem = kvp.Value,
|
||||
slot = kvp.Key
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -323,7 +366,7 @@ public class HideoutController(
|
||||
// Add item to area.slots
|
||||
var destinationLocationIndex = int.Parse(item.slot);
|
||||
var hideoutSlotIndex = hideoutArea.Slots.FindIndex(
|
||||
(slot) => slot.LocationIndex == destinationLocationIndex
|
||||
slot => slot.LocationIndex == destinationLocationIndex
|
||||
);
|
||||
if (hideoutSlotIndex == -1)
|
||||
{
|
||||
@@ -356,7 +399,7 @@ public class HideoutController(
|
||||
{
|
||||
var output = _eventOutputHolder.GetOutput(sessionID);
|
||||
|
||||
var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == request.AreaType);
|
||||
var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == request.AreaType);
|
||||
if (hideoutArea is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("hideout-unable_to_find_area", request.AreaType));
|
||||
@@ -421,10 +464,12 @@ public class HideoutController(
|
||||
_inventoryHelper.AddItemToStash(sessionID, request, pmcData, output);
|
||||
if (output.Warnings?.Count > 0)
|
||||
// Adding to stash failed, drop out - don't remove item from hideout area slot
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// Remove items from slot, locationIndex remains
|
||||
var hideoutSlotIndex = hideoutArea.Slots.FindIndex((slot) => slot.LocationIndex == slotIndexToRemove);
|
||||
var hideoutSlotIndex = hideoutArea.Slots.FindIndex(slot => slot.LocationIndex == slotIndexToRemove);
|
||||
hideoutArea.Slots[hideoutSlotIndex].Items = null;
|
||||
|
||||
return output;
|
||||
@@ -437,7 +482,7 @@ public class HideoutController(
|
||||
// Force a production update (occur before area is toggled as it could be generator and doing it after generator enabled would cause incorrect calculaton of production progress)
|
||||
_hideoutHelper.UpdatePlayerHideout(sessionID);
|
||||
|
||||
var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault((area) => area.Type == request.AreaType);
|
||||
var hideoutArea = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == request.AreaType);
|
||||
if (hideoutArea is null)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("hideout-unable_to_find_area", request.AreaType));
|
||||
@@ -461,7 +506,7 @@ public class HideoutController(
|
||||
|
||||
// Find the actual amount of items we need to remove because body can send weird data
|
||||
var recipeRequirementsClone = _cloner.Clone(
|
||||
recipe.Requirements.Where((r) => r.Type == "Item" || r.Type == "Tool")
|
||||
recipe.Requirements.Where(r => r.Type == "Item" || r.Type == "Tool")
|
||||
);
|
||||
|
||||
List<IdWithCount> itemsToDelete = [];
|
||||
@@ -478,14 +523,17 @@ public class HideoutController(
|
||||
|
||||
// Handle tools not having a `count`, but always only requiring 1
|
||||
var requiredCount = requirement.Count ?? 1;
|
||||
if (requiredCount <= 0) continue;
|
||||
if (requiredCount <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_inventoryHelper.RemoveItemByCount(pmcData, itemToDelete.Id, requiredCount, sessionID, output);
|
||||
|
||||
// Tools don't have a count
|
||||
if (requirement.Type != "Tool")
|
||||
{
|
||||
requirement.Count -= (int)itemToDelete.Count;
|
||||
requirement.Count -= (int) itemToDelete.Count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,9 +559,13 @@ public class HideoutController(
|
||||
}
|
||||
|
||||
if (inventoryItem.Upd?.StackObjectsCount is not null && inventoryItem.Upd.StackObjectsCount > requestedItem.Count)
|
||||
{
|
||||
inventoryItem.Upd.StackObjectsCount -= requestedItem.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
_inventoryHelper.RemoveItem(pmcData, requestedItem.Id, sessionID, output);
|
||||
}
|
||||
}
|
||||
|
||||
var recipe = _databaseService.GetHideout().Production?.ScavRecipes?.FirstOrDefault(r => r.Id == body.RecipeId);
|
||||
@@ -542,7 +594,7 @@ public class HideoutController(
|
||||
|
||||
pmcData.Hideout.Production[body.RecipeId] = _hideoutHelper.InitProduction(
|
||||
body.RecipeId,
|
||||
(int)(_profileHelper.IsDeveloperAccount(sessionID) ? 40 : modifiedScavCaseTime),
|
||||
(int) (_profileHelper.IsDeveloperAccount(sessionID) ? 40 : modifiedScavCaseTime),
|
||||
false
|
||||
);
|
||||
pmcData.Hideout.Production[body.RecipeId].SptIsScavCase = true;
|
||||
@@ -553,14 +605,21 @@ public class HideoutController(
|
||||
private double? GetScavCaseTime(PmcData pmcData, double? productionTime)
|
||||
{
|
||||
var fenceLevel = _fenceService.GetFenceInfo(pmcData);
|
||||
if (fenceLevel is null) return productionTime;
|
||||
if (fenceLevel is null)
|
||||
{
|
||||
return productionTime;
|
||||
}
|
||||
|
||||
return productionTime * fenceLevel.ScavCaseTimeModifier;
|
||||
}
|
||||
|
||||
public void AddScavCaseRewardsToProfile(PmcData pmcData, List<Item> rewards, string recipeId)
|
||||
{
|
||||
pmcData.Hideout.Production[$"ScavCase{recipeId}"] = new Production { Products = rewards, RecipeId = recipeId };
|
||||
pmcData.Hideout.Production[$"ScavCase{recipeId}"] = new Production
|
||||
{
|
||||
Products = rewards,
|
||||
RecipeId = recipeId
|
||||
};
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID)
|
||||
@@ -619,7 +678,10 @@ public class HideoutController(
|
||||
foreach (var production in productionDict)
|
||||
{
|
||||
// Skip undefined production objects
|
||||
if (production.Value is null) continue;
|
||||
if (production.Value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Production or ScavCase
|
||||
if (production.Value.RecipeId == request.RecipeId)
|
||||
@@ -670,25 +732,36 @@ public class HideoutController(
|
||||
|
||||
// Recipe has an `isEncoded` requirement for reward(s), Add `RecodableComponent` property
|
||||
if (recipe.IsEncoded ?? false)
|
||||
{
|
||||
foreach (var reward in itemAndChildrenToSendToPlayer)
|
||||
{
|
||||
_itemHelper.AddUpdObjectToItem(reward.FirstOrDefault());
|
||||
|
||||
reward.FirstOrDefault().Upd.RecodableComponent = new UpdRecodableComponent { IsEncoded = true };
|
||||
reward.FirstOrDefault().Upd.RecodableComponent = new UpdRecodableComponent
|
||||
{
|
||||
IsEncoded = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Build an array of the tools that need to be returned to the player
|
||||
List<List<Item>> toolsToSendToPlayer = [];
|
||||
var hideoutProduction = pmcData.Hideout.Production[prodId];
|
||||
if (hideoutProduction.SptRequiredTools?.Count > 0)
|
||||
{
|
||||
foreach (var tool in hideoutProduction.SptRequiredTools)
|
||||
{
|
||||
toolsToSendToPlayer.Add([tool]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the recipe is the same as the last one - get bonus when crafting same thing multiple times
|
||||
var area = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == recipe.AreaType);
|
||||
if (area is not null && request.RecipeId != area.LastRecipe)
|
||||
// 1 point per craft upon the end of production for alternating between 2 different crafting recipes in the same module
|
||||
{
|
||||
craftingExpAmount += _hideoutConfig.ExpCraftAmount; // Default is 10
|
||||
}
|
||||
|
||||
// Update variable with time spent crafting item(s)
|
||||
// 1 point per 8 hours of crafting
|
||||
@@ -697,7 +770,7 @@ public class HideoutController(
|
||||
{
|
||||
// Spent enough time crafting to get a bonus xp multiplier
|
||||
var multiplierCrafting = Math.Floor(hoursCrafting.Value / _hideoutConfig.HoursForSkillCrafting);
|
||||
craftingExpAmount += (int)(1 * multiplierCrafting);
|
||||
craftingExpAmount += (int) (1 * multiplierCrafting);
|
||||
hoursCrafting -= _hideoutConfig.HoursForSkillCrafting * multiplierCrafting;
|
||||
}
|
||||
|
||||
@@ -729,7 +802,10 @@ public class HideoutController(
|
||||
};
|
||||
|
||||
_inventoryHelper.AddItemsToStash(sessionID, addToolsRequest, pmcData, output);
|
||||
if (output.Warnings?.Count > 0) return;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the crafting result to the stash, marked as FiR
|
||||
@@ -741,7 +817,10 @@ public class HideoutController(
|
||||
Callback = null
|
||||
};
|
||||
_inventoryHelper.AddItemsToStash(sessionID, addItemsRequest, pmcData, output);
|
||||
if (output.Warnings?.Count > 0) return;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// - increment skill point for crafting
|
||||
// - delete the production in profile Hideout.Production
|
||||
@@ -760,8 +839,11 @@ public class HideoutController(
|
||||
{
|
||||
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Crafting, craftingExpAmount);
|
||||
|
||||
var intellectAmountToGive = 0.5 * Math.Round((double)(craftingExpAmount / 15));
|
||||
if (intellectAmountToGive > 0) _profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Intellect, intellectAmountToGive);
|
||||
var intellectAmountToGive = 0.5 * Math.Round((double) (craftingExpAmount / 15));
|
||||
if (intellectAmountToGive > 0)
|
||||
{
|
||||
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Intellect, intellectAmountToGive);
|
||||
}
|
||||
}
|
||||
|
||||
area.LastRecipe = request.RecipeId;
|
||||
@@ -776,11 +858,13 @@ public class HideoutController(
|
||||
// Continuous recipes need the craft time refreshed as it gets created once on initial craft and stays the same regardless of what
|
||||
// production.json is set to
|
||||
if (recipe.Continuous.GetValueOrDefault(false))
|
||||
{
|
||||
hideoutProduction.ProductionTime = _hideoutHelper.GetAdjustedCraftTimeWithSkills(
|
||||
pmcData,
|
||||
recipe.Id,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Flag normal (not continuous) crafts as complete
|
||||
if (!recipe.Continuous ?? false)
|
||||
@@ -799,7 +883,10 @@ public class HideoutController(
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = recipe.EndProduct,
|
||||
Upd = new Upd { StackObjectsCount = recipe.Count }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = recipe.Count
|
||||
}
|
||||
};
|
||||
|
||||
// Split item into separate items with acceptable stack sizes
|
||||
@@ -814,7 +901,15 @@ public class HideoutController(
|
||||
// Add the first reward item to array when not a preset (first preset added above earlier)
|
||||
if (!rewardIsPreset)
|
||||
{
|
||||
itemAndChildrenToSendToPlayer.Add([new Item { Id = _hashUtil.Generate(), Template = recipe.EndProduct }]);
|
||||
itemAndChildrenToSendToPlayer.Add(
|
||||
[
|
||||
new Item
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = recipe.EndProduct
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Add multiple of item if recipe requests it
|
||||
@@ -844,6 +939,7 @@ public class HideoutController(
|
||||
{
|
||||
if (!pmcData.TaskConditionCounters.TryGetValue(NameTaskConditionCountersCraftingId, out _))
|
||||
// Doesn't exist, create
|
||||
{
|
||||
pmcData.TaskConditionCounters[NameTaskConditionCountersCraftingId] = new TaskConditionCounter
|
||||
{
|
||||
Id = recipe.Id,
|
||||
@@ -851,6 +947,7 @@ public class HideoutController(
|
||||
SourceId = "CounterCrafting",
|
||||
Value = 0
|
||||
};
|
||||
}
|
||||
|
||||
return pmcData.TaskConditionCounters[NameTaskConditionCountersCraftingId];
|
||||
}
|
||||
@@ -861,11 +958,13 @@ public class HideoutController(
|
||||
string? prodId = null;
|
||||
foreach (var production in ongoingProductions)
|
||||
// Production or ScavCase
|
||||
{
|
||||
if (production.Value.RecipeId == request.RecipeId)
|
||||
{
|
||||
prodId = production.Key; // Set to objects key
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prodId == null)
|
||||
{
|
||||
@@ -893,7 +992,10 @@ public class HideoutController(
|
||||
};
|
||||
|
||||
_inventoryHelper.AddItemsToStash(sessionID, addItemsRequest, pmcData, output);
|
||||
if (output.Warnings?.Count > 0) return;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the old production from output object before its sent to client
|
||||
output.ProfileChanges[sessionID].Production.Remove(request.RecipeId);
|
||||
@@ -920,6 +1022,7 @@ public class HideoutController(
|
||||
var qteDb = _databaseService.GetHideout().Qte;
|
||||
var relevantQte = qteDb.FirstOrDefault(qte => qte.Id == request.Id);
|
||||
foreach (var outcome in request.Results)
|
||||
{
|
||||
if (outcome)
|
||||
{
|
||||
// Success
|
||||
@@ -932,10 +1035,17 @@ public class HideoutController(
|
||||
pmcData.Health.Energy.Current += relevantQte.Results[QteEffectType.singleFailEffect].Energy;
|
||||
pmcData.Health.Hydration.Current += relevantQte.Results[QteEffectType.singleFailEffect].Hydration;
|
||||
}
|
||||
}
|
||||
|
||||
if (pmcData.Health.Energy.Current < 1) pmcData.Health.Energy.Current = 1;
|
||||
if (pmcData.Health.Energy.Current < 1)
|
||||
{
|
||||
pmcData.Health.Energy.Current = 1;
|
||||
}
|
||||
|
||||
if (pmcData.Health.Hydration.Current < 1) pmcData.Health.Hydration.Current = 1;
|
||||
if (pmcData.Health.Hydration.Current < 1)
|
||||
{
|
||||
pmcData.Health.Hydration.Current = 1;
|
||||
}
|
||||
|
||||
HandleMusclePain(pmcData, relevantQte.Results[QteEffectType.finishEffect]);
|
||||
}
|
||||
@@ -976,12 +1086,18 @@ public class HideoutController(
|
||||
var overallCounterItems = pmcData.Stats.Eft.OverallCounters.Items;
|
||||
|
||||
// Find counter by key
|
||||
var shootingRangeHighScore = overallCounterItems.FirstOrDefault((counter) => counter.Key.Contains(shootingRangeKey));
|
||||
var shootingRangeHighScore = overallCounterItems.FirstOrDefault(counter => counter.Key.Contains(shootingRangeKey));
|
||||
if (shootingRangeHighScore is null)
|
||||
{
|
||||
// Counter not found, add blank one
|
||||
overallCounterItems.Add(new CounterKeyValue { Key = [shootingRangeKey], Value = 0 });
|
||||
shootingRangeHighScore = overallCounterItems.FirstOrDefault((counter) => counter.Key.Contains(shootingRangeKey));
|
||||
overallCounterItems.Add(
|
||||
new CounterKeyValue
|
||||
{
|
||||
Key = [shootingRangeKey],
|
||||
Value = 0
|
||||
}
|
||||
);
|
||||
shootingRangeHighScore = overallCounterItems.FirstOrDefault(counter => counter.Key.Contains(shootingRangeKey));
|
||||
}
|
||||
|
||||
shootingRangeHighScore.Value = request.Points;
|
||||
@@ -993,10 +1109,14 @@ public class HideoutController(
|
||||
|
||||
// Create mapping of required item with corrisponding item from player inventory
|
||||
var items = request.Items.Select(
|
||||
(reqItem) =>
|
||||
reqItem =>
|
||||
{
|
||||
var item = pmcData.Inventory.Items.FirstOrDefault(invItem => invItem.Id == reqItem.Id);
|
||||
return new { inventoryItem = item, requestedItem = reqItem };
|
||||
return new
|
||||
{
|
||||
inventoryItem = item,
|
||||
requestedItem = reqItem
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1017,9 +1137,13 @@ public class HideoutController(
|
||||
item.inventoryItem.Upd.StackObjectsCount is not null &&
|
||||
item.inventoryItem.Upd.StackObjectsCount > item.requestedItem.Count
|
||||
)
|
||||
{
|
||||
item.inventoryItem.Upd.StackObjectsCount -= item.requestedItem.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
_inventoryHelper.RemoveItem(pmcData, item.inventoryItem.Id, sessionId, output);
|
||||
}
|
||||
}
|
||||
|
||||
var profileHideoutArea = pmcData.Hideout.Areas.FirstOrDefault(x => x.Type == request.AreaType);
|
||||
@@ -1029,7 +1153,7 @@ public class HideoutController(
|
||||
return _httpResponseUtil.AppendErrorToOutput(output);
|
||||
}
|
||||
|
||||
var hideoutDbData = _databaseService.GetHideout().Areas.FirstOrDefault((area) => area.Type == request.AreaType);
|
||||
var hideoutDbData = _databaseService.GetHideout().Areas.FirstOrDefault(area => area.Type == request.AreaType);
|
||||
if (hideoutDbData is null)
|
||||
{
|
||||
_logger.Error(
|
||||
@@ -1042,14 +1166,17 @@ public class HideoutController(
|
||||
var improvements = hideoutDbData.Stages[profileHideoutArea.Level.ToString()].Improvements;
|
||||
var timestamp = _timeUtil.GetTimeStamp();
|
||||
|
||||
if (output.ProfileChanges[sessionId].Improvements is null) output.ProfileChanges[sessionId].Improvements = new Dictionary<string, HideoutImprovement>();
|
||||
if (output.ProfileChanges[sessionId].Improvements is null)
|
||||
{
|
||||
output.ProfileChanges[sessionId].Improvements = new Dictionary<string, HideoutImprovement>();
|
||||
}
|
||||
|
||||
foreach (var improvement in improvements)
|
||||
{
|
||||
var improvementDetails = new HideoutImprovement
|
||||
{
|
||||
Completed = false,
|
||||
ImproveCompleteTimestamp = (long)(timestamp + improvement.ImprovementTime)
|
||||
ImproveCompleteTimestamp = (long) (timestamp + improvement.ImprovementTime)
|
||||
};
|
||||
output.ProfileChanges[sessionId].Improvements[improvement.Id] = improvementDetails;
|
||||
|
||||
@@ -1102,7 +1229,7 @@ public class HideoutController(
|
||||
|
||||
var itemDetails = _databaseService
|
||||
.GetHideout()
|
||||
.Customisation.Globals.FirstOrDefault((cust) => cust.Id == request.OfferId);
|
||||
.Customisation.Globals.FirstOrDefault(cust => cust.Id == request.OfferId);
|
||||
if (itemDetails is null)
|
||||
{
|
||||
_logger.Error($"Unable to find customisation: {request.OfferId} in db, cannot apply to hideout");
|
||||
@@ -1144,7 +1271,7 @@ public class HideoutController(
|
||||
{
|
||||
// Check if we've already added this mannequin
|
||||
var existingMannequin = pmcData.Inventory.Items.FirstOrDefault(
|
||||
(item) => item.ParentId == equipmentPresetHideoutArea.Id && item.SlotId == mannequinSlot.Name
|
||||
item => item.ParentId == equipmentPresetHideoutArea.Id && item.SlotId == mannequinSlot.Name
|
||||
);
|
||||
|
||||
// No child, add it
|
||||
@@ -1179,7 +1306,7 @@ public class HideoutController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle HideoutCustomizationSetMannequinPose event
|
||||
/// Handle HideoutCustomizationSetMannequinPose event
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
@@ -1214,12 +1341,16 @@ public class HideoutController(
|
||||
public void Update()
|
||||
{
|
||||
foreach (var sessionID in _saveServer.GetProfiles())
|
||||
{
|
||||
if (sessionID.Value.CharacterData.PmcData.Hideout is not null &&
|
||||
_profileActivityService.ActiveWithinLastMinutes(
|
||||
sessionID.Key,
|
||||
_hideoutConfig.UpdateProfileHideoutWhenActiveWithinMinutes
|
||||
)
|
||||
)
|
||||
{
|
||||
_hideoutHelper.UpdatePlayerHideout(sessionID.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.InRaid;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -16,11 +16,11 @@ public class InRaidController(
|
||||
ConfigServer _configServer
|
||||
)
|
||||
{
|
||||
protected InRaidConfig _inRaidConfig = _configServer.GetConfig<InRaidConfig>();
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected InRaidConfig _inRaidConfig = _configServer.GetConfig<InRaidConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Save locationId to active profiles in-raid object AND app context
|
||||
/// Save locationId to active profiles in-raid object AND app context
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="info">Register player request</param>
|
||||
@@ -30,9 +30,9 @@ public class InRaidController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle raid/profile/scavsave
|
||||
/// Save profile state to disk
|
||||
/// Handles pmc/pscav
|
||||
/// Handle raid/profile/scavsave
|
||||
/// Save profile state to disk
|
||||
/// Handles pmc/pscav
|
||||
/// </summary>
|
||||
/// <param name="offRaidProfileData"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
@@ -43,11 +43,13 @@ public class InRaidController(
|
||||
// If equipment match overwrite existing data from update to date raid data for scavenger screen to work correctly.
|
||||
// otherwise Scav inventory will be overwritten and break scav regeneration, breaking profile.
|
||||
if (serverScavProfile.Inventory.Equipment == offRaidProfileData.Inventory.Equipment)
|
||||
{
|
||||
serverScavProfile.Inventory.Items = offRaidProfileData.Inventory.Items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the inraid config from configs/inraid.json
|
||||
/// Get the inraid config from configs/inraid.json
|
||||
/// </summary>
|
||||
public InRaidConfig GetInRaidConfig()
|
||||
{
|
||||
@@ -55,7 +57,6 @@ public class InRaidController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
@@ -66,7 +67,6 @@ public class InRaidController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
|
||||
@@ -47,18 +47,21 @@ public class InsuranceController(
|
||||
|
||||
/**
|
||||
* Process insurance items of all profiles prior to being given back to the player through the mail service.
|
||||
*
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public void ProcessReturn()
|
||||
{
|
||||
// Process each installed profile.
|
||||
foreach (var sessionId in _saveServer.GetProfiles()) ProcessReturnByProfile(sessionId.Key);
|
||||
foreach (var sessionId in _saveServer.GetProfiles())
|
||||
{
|
||||
ProcessReturnByProfile(sessionId.Key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process insurance items of a single profile prior to being given back to the player through the mail service.
|
||||
*
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
public void ProcessReturnByProfile(string sessionId)
|
||||
@@ -67,14 +70,17 @@ public class InsuranceController(
|
||||
var insuranceDetails = FilterInsuredItems(sessionId);
|
||||
|
||||
// Skip profile if no insured items to process
|
||||
if (insuranceDetails.Count == 0) return;
|
||||
if (insuranceDetails.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessInsuredItems(insuranceDetails, sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all insured items that are ready to be processed in a specific profile.
|
||||
*
|
||||
*
|
||||
* @param sessionID Session ID of the profile to check.
|
||||
* @param time The time to check ready status against. Current time by default.
|
||||
* @returns All insured items that are ready to be processed.
|
||||
@@ -86,15 +92,19 @@ public class InsuranceController(
|
||||
|
||||
var profileInsuranceDetails = _saveServer.GetProfile(sessionId).InsuranceList;
|
||||
if (profileInsuranceDetails.Count > 0)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Found {profileInsuranceDetails.Count} insurance packages in profile {sessionId}");
|
||||
}
|
||||
}
|
||||
|
||||
return profileInsuranceDetails.Where(insured => insuranceTime >= insured.ScheduledTime).ToList();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method orchestrates the processing of insured items in a profile.
|
||||
*
|
||||
*
|
||||
* @param insuranceDetails The insured items to process.
|
||||
* @param sessionID The session ID that should receive the processed items.
|
||||
* @returns void
|
||||
@@ -102,9 +112,11 @@ public class InsuranceController(
|
||||
protected void ProcessInsuredItems(List<Insurance> insuranceDetails, string sessionId)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Processing {insuranceDetails.Count} insurance packages, which includes a total of: {CountAllInsuranceItems(insuranceDetails)} items, in profile: {sessionId}"
|
||||
);
|
||||
}
|
||||
|
||||
// Iterate over each of the insurance packages.
|
||||
foreach (var insured in insuranceDetails)
|
||||
@@ -147,7 +159,7 @@ public class InsuranceController(
|
||||
|
||||
/**
|
||||
* Remove an insurance package from a profile using the package's system data information.
|
||||
*
|
||||
*
|
||||
* @param sessionID The session ID of the profile to remove the package from.
|
||||
* @param index The array index of the insurance package to remove.
|
||||
* @returns void
|
||||
@@ -164,12 +176,15 @@ public class InsuranceController(
|
||||
)
|
||||
.ToList();
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Removed processed insurance package. Remaining packages: {profile.InsuranceList.Count}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Removed processed insurance package. Remaining packages: {profile.InsuranceList.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the items that should be deleted based on the given Insurance object.
|
||||
*
|
||||
*
|
||||
* @param rootItemParentID - The ID that should be assigned to all "hideout"/root items.
|
||||
* @param insured - The insurance object containing the items to evaluate for deletion.
|
||||
* @returns A Set containing the IDs of items that should be deleted.
|
||||
@@ -189,7 +204,10 @@ public class InsuranceController(
|
||||
);
|
||||
|
||||
// Process all items that are not attached, attachments; those are handled separately, by value.
|
||||
if (hasRegularItems) ProcessRegularItems(insured, toDelete, parentAttachmentsMap);
|
||||
if (hasRegularItems)
|
||||
{
|
||||
ProcessRegularItems(insured, toDelete, parentAttachmentsMap);
|
||||
}
|
||||
|
||||
// Process attached, attachments, by value, only if there are any.
|
||||
if (parentAttachmentsMap.Count > 0)
|
||||
@@ -203,8 +221,12 @@ public class InsuranceController(
|
||||
|
||||
// Log the number of items marked for deletion, if any
|
||||
if (!toDelete.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Marked {toDelete.Count} items for deletion from insurance.");
|
||||
}
|
||||
}
|
||||
|
||||
return toDelete;
|
||||
}
|
||||
@@ -213,7 +235,7 @@ public class InsuranceController(
|
||||
* Initialize a Map object that holds main-parents to all of their attachments. Note that "main-parent" in this
|
||||
* context refers to the parent item that an attachment is attached to. For example, a suppressor attached to a gun,
|
||||
* not the backpack that the gun is located in (the gun's parent).
|
||||
*
|
||||
*
|
||||
* @param rootItemParentID - The ID that should be assigned to all "hideout"/root items.
|
||||
* @param insured - The insurance object containing the items to evaluate.
|
||||
* @param itemsMap - A Map object for quick item look-up by item ID.
|
||||
@@ -291,7 +313,10 @@ public class InsuranceController(
|
||||
// Update (or add to) the main-parent to attachments map.
|
||||
if (mainParentToAttachmentsMap.ContainsKey(mainParent.Id))
|
||||
{
|
||||
if (mainParentToAttachmentsMap.TryGetValue(mainParent.Id, out var parent)) parent.Add(insuredItem);
|
||||
if (mainParentToAttachmentsMap.TryGetValue(mainParent.Id, out var parent))
|
||||
{
|
||||
parent.Add(insuredItem);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -305,7 +330,7 @@ public class InsuranceController(
|
||||
/**
|
||||
* Remove attachments that can not be moddable in-raid from the parentAttachmentsMap. If no moddable attachments
|
||||
* remain, the parent is removed from the map as well.
|
||||
*
|
||||
*
|
||||
* @param parentAttachmentsMap - A Map object containing parent item IDs to arrays of their attachment items.
|
||||
* @param itemsMap - A Map object for quick item look-up by item ID.
|
||||
* @returns A Map object containing parent item IDs to arrays of their attachment items which are not moddable in-raid.
|
||||
@@ -327,14 +352,24 @@ public class InsuranceController(
|
||||
// attachment on the main-parent. For example, if the attachment is a stock, we need to check to see if
|
||||
// it's moddable in the upper receiver (attachment/parent), which is attached to the gun (main-parent).
|
||||
if (attachment.ParentId is not null)
|
||||
{
|
||||
if (itemsMap.TryGetValue(attachment.ParentId, out var directParentItem))
|
||||
{
|
||||
attachmentParentItem = directParentItem;
|
||||
}
|
||||
}
|
||||
|
||||
if (_itemHelper.IsRaidModdable(attachment, attachmentParentItem) ?? false) moddableAttachments.Add(attachment);
|
||||
if (_itemHelper.IsRaidModdable(attachment, attachmentParentItem) ?? false)
|
||||
{
|
||||
moddableAttachments.Add(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
// If any moddable attachments remain, add them to the updated map.
|
||||
if (moddableAttachments.Count > 0) updatedMap.TryAdd(map.Key, moddableAttachments);
|
||||
if (moddableAttachments.Count > 0)
|
||||
{
|
||||
updatedMap.TryAdd(map.Key, moddableAttachments);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedMap;
|
||||
@@ -344,7 +379,7 @@ public class InsuranceController(
|
||||
* Process "regular" insurance items. Any insured item that is not an attached, attachment is considered a "regular"
|
||||
* item. This method iterates over them, preforming item deletion rolls to see if they should be deleted. If so,
|
||||
* they (and their attached, attachments, if any) are marked for deletion in the toDelete Set.
|
||||
*
|
||||
*
|
||||
* @param insured The insurance object containing the items to evaluate.
|
||||
* @param toDelete A Set to keep track of items marked for deletion.
|
||||
* @param parentAttachmentsMap A Map object containing parent item IDs to arrays of their attachment items.
|
||||
@@ -355,7 +390,10 @@ public class InsuranceController(
|
||||
foreach (var insuredItem in insured.Items)
|
||||
{
|
||||
// Skip if the item is an attachment. These are handled separately.
|
||||
if (_itemHelper.IsAttachmentAttached(insuredItem)) continue;
|
||||
if (_itemHelper.IsAttachmentAttached(insuredItem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Roll for item deletion
|
||||
var itemRoll = RollForDelete(insured.TraderId, insuredItem);
|
||||
@@ -371,7 +409,10 @@ public class InsuranceController(
|
||||
insured.Items,
|
||||
insuredItem.Id
|
||||
);
|
||||
foreach (var item in itemAndChildren) toDelete.Add(item.Id);
|
||||
foreach (var item in itemAndChildren)
|
||||
{
|
||||
toDelete.Add(item.Id);
|
||||
}
|
||||
|
||||
// Remove the parent (and its children) from the parentAttachmentsMap.
|
||||
parentAttachmentsMap.Remove(insuredItem.Id);
|
||||
@@ -387,7 +428,7 @@ public class InsuranceController(
|
||||
|
||||
/**
|
||||
* Process parent items and their attachments, updating the toDelete Set accordingly.
|
||||
*
|
||||
*
|
||||
* @param mainParentToAttachmentsMap A Map object containing parent item IDs to arrays of their attachment items.
|
||||
* @param itemsMap A Map object for quick item look-up by item ID.
|
||||
* @param traderId The trader ID from the Insurance object.
|
||||
@@ -400,12 +441,18 @@ public class InsuranceController(
|
||||
{
|
||||
// Skip processing if parentId is already marked for deletion, as all attachments for that parent will
|
||||
// already be marked for deletion as well.
|
||||
if (toDelete.Contains(parentObj.Key)) continue;
|
||||
if (toDelete.Contains(parentObj.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Log the parent item's name.
|
||||
itemsMap.TryGetValue(parentObj.Key, out var parentItem);
|
||||
var parentName = _itemHelper.GetItemName(parentItem.Template);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Processing attachments of parent {parentName}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Processing attachments of parent {parentName}");
|
||||
}
|
||||
|
||||
// Process the attachments for this individual parent item.
|
||||
ProcessAttachmentByParent(parentObj.Value, insuredTraderId, toDelete);
|
||||
@@ -417,7 +464,7 @@ public class InsuranceController(
|
||||
* their maximum price. For each attachment, a roll is made to determine if a deletion should be made. Once the
|
||||
* number of deletions has been counted, the attachments are added to the toDelete Set, starting with the most
|
||||
* valuable attachments first.
|
||||
*
|
||||
*
|
||||
* @param attachments The array of attachment items to sort, filter, and roll.
|
||||
* @param traderId The ID of the trader to that has ensured these items.
|
||||
* @param toDelete The array that accumulates the IDs of the items to be deleted.
|
||||
@@ -434,17 +481,25 @@ public class InsuranceController(
|
||||
// Create prob array and add all attachments with rouble price as the weight
|
||||
var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(_mathUtil, _cloner);
|
||||
foreach (var attachmentTpl in weightedAttachmentByPrice)
|
||||
{
|
||||
attachmentsProbabilityArray.Add(
|
||||
new ProbabilityObject<string, double?>(attachmentTpl.Key, attachmentTpl.Value, null)
|
||||
);
|
||||
}
|
||||
|
||||
// Draw x attachments from weighted array to remove from parent, remove from pool after being picked
|
||||
var attachmentIdsToRemove = attachmentsProbabilityArray.Draw((int)countOfAttachmentsToRemove, false);
|
||||
foreach (var attachmentId in attachmentIdsToRemove) toDelete.Add(attachmentId);
|
||||
var attachmentIdsToRemove = attachmentsProbabilityArray.Draw((int) countOfAttachmentsToRemove, false);
|
||||
foreach (var attachmentId in attachmentIdsToRemove)
|
||||
{
|
||||
toDelete.Add(attachmentId);
|
||||
}
|
||||
|
||||
LogAttachmentsBeingRemoved(attachmentIdsToRemove, attachments, weightedAttachmentByPrice);
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Number of attachments to be deleted: {attachmentIdsToRemove.Count}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Number of attachments to be deleted: {attachmentIdsToRemove.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
private void LogAttachmentsBeingRemoved(List<string> attachmentIdsToRemove, List<Item> attachments, Dictionary<string, double> attachmentPrices)
|
||||
@@ -453,10 +508,13 @@ public class InsuranceController(
|
||||
foreach (var attachmentId in attachmentIdsToRemove)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Attachment {index} Id: {attachmentId} Tpl: {attachments.FirstOrDefault((x) => x.Id == attachmentId)?.Template} - " +
|
||||
$"Attachment {index} Id: {attachmentId} Tpl: {attachments.FirstOrDefault(x => x.Id == attachmentId)?.Template} - " +
|
||||
$"Price: {attachmentPrices[attachmentId]}"
|
||||
);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@@ -469,7 +527,10 @@ public class InsuranceController(
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
var price = _ragfairPriceService.GetDynamicItemPrice(attachment.Template, Money.ROUBLES);
|
||||
if (price is not null) result[attachment.Id] = Math.Round(price ?? 0);
|
||||
if (price is not null)
|
||||
{
|
||||
result[attachment.Id] = Math.Round(price ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
_weightedRandomHelper.ReduceWeightValues(result);
|
||||
@@ -481,7 +542,10 @@ public class InsuranceController(
|
||||
{
|
||||
var removeCount = 0;
|
||||
|
||||
if (_randomUtil.GetChance100(_insuranceConfig.ChanceNoAttachmentsTakenPercent)) return removeCount;
|
||||
if (_randomUtil.GetChance100(_insuranceConfig.ChanceNoAttachmentsTakenPercent))
|
||||
{
|
||||
return removeCount;
|
||||
}
|
||||
|
||||
// Get attachments count above or equal to price set in config
|
||||
return weightedAttachmentByPrice
|
||||
@@ -496,7 +560,7 @@ public class InsuranceController(
|
||||
|
||||
/**
|
||||
* Handle sending the insurance message to the user that potentially contains the valid insurance items.
|
||||
*
|
||||
*
|
||||
* @param sessionID The session ID that should receive the insurance message.
|
||||
* @param insurance The context of insurance to use.
|
||||
* @returns void
|
||||
@@ -511,11 +575,17 @@ public class InsuranceController(
|
||||
if (IsMapLabsAndInsuranceDisabled(insurance))
|
||||
// Trader has labs-specific messages
|
||||
// Wipe out returnable items
|
||||
{
|
||||
HandleLabsInsurance(traderDialogMessages, insurance);
|
||||
}
|
||||
else if (insurance.Items?.Count == 0)
|
||||
// Not labs and no items to return
|
||||
{
|
||||
if (traderDialogMessages.TryGetValue("insuranceFailed", out var insuranceFailedTemplates))
|
||||
{
|
||||
insurance.MessageTemplateId = _randomUtil.GetArrayValue(insuranceFailedTemplates);
|
||||
}
|
||||
}
|
||||
|
||||
// Send the insurance message
|
||||
_mailSendService.SendLocalisedNpcMessageToPlayer(
|
||||
@@ -556,7 +626,10 @@ public class InsuranceController(
|
||||
private bool? RollForDelete(string traderId, Item? insuredItem = null)
|
||||
{
|
||||
var trader = _traderHelper.GetTraderById(traderId);
|
||||
if (trader is null) return null;
|
||||
if (trader is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
const int maxRoll = 9999;
|
||||
const int conversionFactor = 100;
|
||||
@@ -569,7 +642,9 @@ public class InsuranceController(
|
||||
var itemName = insuredItem is not null ? $"{_itemHelper.GetItemName(insuredItem.Template)}" : "";
|
||||
var status = roll ? "Delete" : "Keep";
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Rolling {itemName} with {trader} - Return {traderReturnChance}% - Roll: {returnChance} - Status: {status}");
|
||||
}
|
||||
|
||||
return roll;
|
||||
}
|
||||
@@ -577,7 +652,7 @@ public class InsuranceController(
|
||||
/**
|
||||
* Handle Insure event
|
||||
* Add insurance to an item
|
||||
*
|
||||
*
|
||||
* @param pmcData Player profile
|
||||
* @param body Insurance request
|
||||
* @param sessionID Session id
|
||||
@@ -594,6 +669,7 @@ public class InsuranceController(
|
||||
|
||||
// Get price of all items being insured, add to 'itemsToPay'
|
||||
foreach (var key in body.Items)
|
||||
{
|
||||
itemsToPay.Add(
|
||||
new IdWithCount
|
||||
{
|
||||
@@ -605,6 +681,7 @@ public class InsuranceController(
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var options = new ProcessBuyTradeRequestData
|
||||
{
|
||||
@@ -619,15 +696,27 @@ public class InsuranceController(
|
||||
|
||||
// pay for the item insurance
|
||||
_paymentService.PayMoney(pmcData, options, sessionId, output);
|
||||
if (output.Warnings?.Count > 0) return output;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// add items to InsuredItems list once money has been paid
|
||||
pmcData.InsuredItems ??= [];
|
||||
foreach (var key in body.Items)
|
||||
{
|
||||
pmcData.InsuredItems.Add(new InsuredItem { TId = body.TransactionId, ItemId = inventoryItemsHash[key].Id });
|
||||
pmcData.InsuredItems.Add(
|
||||
new InsuredItem
|
||||
{
|
||||
TId = body.TransactionId,
|
||||
ItemId = inventoryItemsHash[key].Id
|
||||
}
|
||||
);
|
||||
// If Item is Helmet or Body Armour -> Handle insurance of soft inserts
|
||||
if (_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(inventoryItemsHash[key].Template)) InsureSoftInserts(inventoryItemsHash[key], pmcData, body);
|
||||
if (_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(inventoryItemsHash[key].Template))
|
||||
{
|
||||
InsureSoftInserts(inventoryItemsHash[key], pmcData, body);
|
||||
}
|
||||
}
|
||||
|
||||
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Charisma, itemsToInsureCount * 0.01);
|
||||
@@ -651,16 +740,25 @@ public class InsuranceController(
|
||||
|
||||
foreach (var softInsertSlot in softInsertSlots)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"SoftInsertSlots: {softInsertSlot.SlotId}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"SoftInsertSlots: {softInsertSlot.SlotId}");
|
||||
}
|
||||
|
||||
pmcData.InsuredItems.Add(new InsuredItem { TId = body.TransactionId, ItemId = softInsertSlot.Id });
|
||||
pmcData.InsuredItems.Add(
|
||||
new InsuredItem
|
||||
{
|
||||
TId = body.TransactionId,
|
||||
ItemId = softInsertSlot.Id
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client/insurance/items/list/cost
|
||||
* Calculate insurance cost
|
||||
*
|
||||
*
|
||||
* @param request request object
|
||||
* @param sessionID session id
|
||||
* @returns IGetInsuranceCostResponseData object to send to client
|
||||
@@ -683,7 +781,11 @@ public class InsuranceController(
|
||||
// Ensure hash has item in it
|
||||
if (!inventoryItemsHash.ContainsKey(itemId))
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Item with id: {itemId} missing from player inventory, skipping");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Item with id: {itemId} missing from player inventory, skipping");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Inventory;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Dialog;
|
||||
using Core.Models.Utils;
|
||||
using Core.Routers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using Core.Models.Eft.Profile;
|
||||
using Core.Models.Spt.Dialog;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -44,7 +44,10 @@ public class InventoryController(
|
||||
public void MoveItem(PmcData pmcData, InventoryMoveRequestData moveRequest, string sessionId,
|
||||
ItemEventRouterResponse output)
|
||||
{
|
||||
if (output.Warnings?.Count > 0) return;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Changes made to result apply to character inventory
|
||||
var ownerInventoryItems = _inventoryHelper.GetOwnerInventoryItems(moveRequest, moveRequest.Item, sessionId);
|
||||
@@ -83,7 +86,9 @@ public class InventoryController(
|
||||
// Item is moving into or out of place of fame dog tag slot
|
||||
if (moveRequest.To?.Container != null &&
|
||||
(moveRequest.To.Container.StartsWith("dogtag") || originalLocationSlotId.StartsWith("dogtag")))
|
||||
{
|
||||
_hideoutHelper.ApplyPlaceOfFameDogtagBonus(pmcData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -100,7 +105,7 @@ public class InventoryController(
|
||||
_httpResponseUtil.AppendErrorToOutput(
|
||||
output,
|
||||
_localisationService.GetText("inventory-edit_trader_item"),
|
||||
(BackendErrorCodes)228
|
||||
(BackendErrorCodes) 228
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,32 +158,32 @@ public class InventoryController(
|
||||
_logger.Success($"Set trader {mailEvent.Entity}: Standing to: {mailEvent.Value}");
|
||||
break;
|
||||
case ProfileChangeEventType.ProfileLevel:
|
||||
pmcData.Info.Experience = (int)mailEvent.Value.Value;
|
||||
pmcData.Info.Experience = (int) mailEvent.Value.Value;
|
||||
// Will calculate level below
|
||||
_traderHelper.ValidateTraderStandingsAndPlayerLevelForProfile(sessionId);
|
||||
_logger.Success($"Set profile xp to: {mailEvent.Value}");
|
||||
break;
|
||||
case ProfileChangeEventType.SkillPoints:
|
||||
{
|
||||
var profileSkill = pmcData.Skills.Common.FirstOrDefault(x => x.Id == mailEvent.Entity);
|
||||
if (profileSkill is null)
|
||||
{
|
||||
_logger.Warning($"Unable to find skill with name: {mailEvent.Entity}");
|
||||
continue;
|
||||
var profileSkill = pmcData.Skills.Common.FirstOrDefault(x => x.Id == mailEvent.Entity);
|
||||
if (profileSkill is null)
|
||||
{
|
||||
_logger.Warning($"Unable to find skill with name: {mailEvent.Entity}");
|
||||
continue;
|
||||
}
|
||||
|
||||
profileSkill.Progress = mailEvent.Value;
|
||||
_logger.Success($"Set profile skill: {mailEvent.Entity} to: {mailEvent.Value}");
|
||||
break;
|
||||
}
|
||||
|
||||
profileSkill.Progress = mailEvent.Value;
|
||||
_logger.Success($"Set profile skill: {mailEvent.Entity} to: {mailEvent.Value}");
|
||||
break;
|
||||
}
|
||||
case ProfileChangeEventType.ExamineAllItems:
|
||||
{
|
||||
var itemsToInspect = _itemHelper.GetItems().Where(x => x.Type != "Node");
|
||||
FlagItemsAsInspectedAndRewardXp(itemsToInspect.Select(x => x.Id), fullProfile);
|
||||
_logger.Success($"Flagged {itemsToInspect.Count()} items as examined");
|
||||
{
|
||||
var itemsToInspect = _itemHelper.GetItems().Where(x => x.Type != "Node");
|
||||
FlagItemsAsInspectedAndRewardXp(itemsToInspect.Select(x => x.Id), fullProfile);
|
||||
_logger.Success($"Flagged {itemsToInspect.Count()} items as examined");
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileChangeEventType.UnlockTrader:
|
||||
pmcData.TradersInfo[mailEvent.Entity].Unlocked = true;
|
||||
_logger.Success($"Trader {mailEvent.Entity} Unlocked");
|
||||
@@ -191,16 +196,19 @@ public class InventoryController(
|
||||
|
||||
break;
|
||||
case ProfileChangeEventType.HideoutAreaLevel:
|
||||
{
|
||||
var areaName = mailEvent.Entity;
|
||||
var newValue = mailEvent.Value;
|
||||
var hideoutAreaType = Enum.Parse<HideoutAreas>(areaName ?? "NOTSET");
|
||||
{
|
||||
var areaName = mailEvent.Entity;
|
||||
var newValue = mailEvent.Value;
|
||||
var hideoutAreaType = Enum.Parse<HideoutAreas>(areaName ?? "NOTSET");
|
||||
|
||||
var desiredArea = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == hideoutAreaType);
|
||||
if (desiredArea is not null) desiredArea.Level = newValue;
|
||||
var desiredArea = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == hideoutAreaType);
|
||||
if (desiredArea is not null)
|
||||
{
|
||||
desiredArea.Level = newValue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
_logger.Warning($"Unhandled profile reward event: {mailEvent.Type}");
|
||||
|
||||
@@ -265,7 +273,10 @@ public class InventoryController(
|
||||
var containerSettings = _inventoryHelper.GetInventoryConfig().SealedAirdropContainer;
|
||||
rewards.AddRange(_lootGenerator.GetSealedWeaponCaseLoot(containerSettings));
|
||||
|
||||
if (containerSettings.FoundInRaid) foundInRaid = containerSettings.FoundInRaid;
|
||||
if (containerSettings.FoundInRaid)
|
||||
{
|
||||
foundInRaid = containerSettings.FoundInRaid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -278,7 +289,10 @@ public class InventoryController(
|
||||
{
|
||||
rewards.AddRange(_lootGenerator.GetRandomLootContainerLoot(rewardContainerDetails));
|
||||
|
||||
if (rewardContainerDetails.FoundInRaid) foundInRaid = rewardContainerDetails.FoundInRaid;
|
||||
if (rewardContainerDetails.FoundInRaid)
|
||||
{
|
||||
foundInRaid = rewardContainerDetails.FoundInRaid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +307,10 @@ public class InventoryController(
|
||||
UseSortingTable = true
|
||||
};
|
||||
_inventoryHelper.AddItemsToStash(sessionId, addItemsRequest, pmcData, output);
|
||||
if (output.Warnings?.Count > 0) return;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find and delete opened container item from player inventory
|
||||
@@ -345,16 +362,23 @@ public class InventoryController(
|
||||
inventoryItem.ParentId = change.ParentId;
|
||||
inventoryItem.SlotId = change.SlotId;
|
||||
if (change.Location is not null)
|
||||
{
|
||||
inventoryItem.Location = change.Location;
|
||||
}
|
||||
else
|
||||
{
|
||||
inventoryItem.Location = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData body,
|
||||
string sessionId)
|
||||
{
|
||||
foreach (var id in body.Ids) pmcData.Encyclopedia[id] = true;
|
||||
foreach (var id in body.Ids)
|
||||
{
|
||||
pmcData.Encyclopedia[id] = true;
|
||||
}
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
@@ -364,6 +388,7 @@ public class InventoryController(
|
||||
{
|
||||
string? itemId = null;
|
||||
if (request.FromOwner is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
itemId = GetExaminedItemTpl(request, sessionId);
|
||||
@@ -372,17 +397,25 @@ public class InventoryController(
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("inventory-examine_item_does_not_exist", request.Item));
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId is null)
|
||||
// item template
|
||||
{
|
||||
if (_databaseService.GetItems().ContainsKey(request.Item))
|
||||
{
|
||||
itemId = request.Item;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId is null)
|
||||
{
|
||||
// Player inventory
|
||||
var target = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
|
||||
if (target is not null) itemId = target.Template;
|
||||
if (target is not null)
|
||||
{
|
||||
itemId = target.Template;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemId is not null)
|
||||
@@ -394,25 +427,35 @@ public class InventoryController(
|
||||
|
||||
protected string? GetExaminedItemTpl(InventoryExamineRequestData request, string? sessionId)
|
||||
{
|
||||
if (_presetHelper.IsPreset(request.Item)) return _presetHelper.GetBaseItemTpl(request.Item);
|
||||
if (_presetHelper.IsPreset(request.Item))
|
||||
{
|
||||
return _presetHelper.GetBaseItemTpl(request.Item);
|
||||
}
|
||||
|
||||
if (request.FromOwner.Id == Traders.FENCE)
|
||||
// Get tpl from fence assorts
|
||||
{
|
||||
return _fenceService.GetRawFenceAssorts().Items.FirstOrDefault(x => x.Id == request.Item)?.Template;
|
||||
}
|
||||
|
||||
if (request.FromOwner.Type == "Trader")
|
||||
// Not fence
|
||||
// get tpl from trader assort
|
||||
{
|
||||
return _databaseService
|
||||
.GetTrader(request.FromOwner.Id)
|
||||
.Assort.Items.FirstOrDefault(item => item.Id == request.Item)
|
||||
?.Template;
|
||||
}
|
||||
|
||||
if (request.FromOwner.Type == "RagFair")
|
||||
{
|
||||
// Try to get tplId from items.json first
|
||||
var item = _itemHelper.GetItem(request.Item);
|
||||
if (item.Key) return item.Value.Id;
|
||||
if (item.Key)
|
||||
{
|
||||
return item.Value.Id;
|
||||
}
|
||||
|
||||
// Try alternate way of getting offer if first approach fails
|
||||
var offer = _ragfairOfferService.GetOfferByOfferId(request.Item) ??
|
||||
@@ -420,14 +463,20 @@ public class InventoryController(
|
||||
|
||||
// Try find examine item inside offer items array
|
||||
var matchingItem = offer.Items.FirstOrDefault(offerItem => offerItem.Id == request.Item);
|
||||
if (matchingItem is not null) return matchingItem.Template;
|
||||
if (matchingItem is not null)
|
||||
{
|
||||
return matchingItem.Template;
|
||||
}
|
||||
|
||||
// Unable to find item in database or ragfair
|
||||
_logger.Warning(_localisationService.GetText("inventory-unable_to_find_item", request.Item));
|
||||
}
|
||||
|
||||
// get hideout item
|
||||
if (request.FromOwner.Type == "HideoutProduction") return request.Item;
|
||||
if (request.FromOwner.Type == "HideoutProduction")
|
||||
{
|
||||
return request.Item;
|
||||
}
|
||||
|
||||
if (request.FromOwner.Type == "Mail")
|
||||
{
|
||||
@@ -441,7 +490,10 @@ public class InventoryController(
|
||||
// get the Id given and get the Template ID from that
|
||||
var item = message.Items.Data.FirstOrDefault(item => item.Id == request.Item);
|
||||
|
||||
if (item is not null) return item.Template;
|
||||
if (item is not null)
|
||||
{
|
||||
return item.Template;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Error($"Unable to get item with id: {request.Item}");
|
||||
@@ -481,13 +533,20 @@ public class InventoryController(
|
||||
$"Unable to tag item: {request.Item} as it cannot be found in player {sessionId} inventory"
|
||||
);
|
||||
|
||||
return new ItemEventRouterResponse { Warnings = [], ProfileChanges = { } };
|
||||
return new ItemEventRouterResponse
|
||||
{
|
||||
Warnings = []
|
||||
};
|
||||
}
|
||||
|
||||
// Null guard
|
||||
itemToTag.Upd ??= new Upd();
|
||||
|
||||
itemToTag.Upd.Tag = new UpdTag { Color = request.TagColor, Name = request.TagName };
|
||||
itemToTag.Upd.Tag = new UpdTag
|
||||
{
|
||||
Color = request.TagColor,
|
||||
Name = request.TagName
|
||||
};
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
@@ -499,7 +558,9 @@ public class InventoryController(
|
||||
|
||||
// Fix for toggling items while on they're in the Scav inventory
|
||||
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
|
||||
{
|
||||
playerData = _profileHelper.GetScavProfile(sessionId);
|
||||
}
|
||||
|
||||
var itemToToggle = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item);
|
||||
if (itemToToggle is not null)
|
||||
@@ -509,14 +570,20 @@ public class InventoryController(
|
||||
_localisationService.GetText("inventory-item_to_toggle_missing_upd", itemToToggle.Id)
|
||||
);
|
||||
|
||||
itemToToggle.Upd.Togglable = new UpdTogglable() { On = request.Value };
|
||||
itemToToggle.Upd.Togglable = new UpdTogglable
|
||||
{
|
||||
On = request.Value
|
||||
};
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
|
||||
_logger.Warning(_localisationService.GetText("inventory-unable_to_toggle_item_not_found", request.Item));
|
||||
|
||||
return new ItemEventRouterResponse { Warnings = [], ProfileChanges = { } };
|
||||
return new ItemEventRouterResponse
|
||||
{
|
||||
Warnings = []
|
||||
};
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData request, string sessionId)
|
||||
@@ -525,7 +592,10 @@ public class InventoryController(
|
||||
var playerData = pmcData;
|
||||
|
||||
// We may be folding data on scav profile, get that profile instead
|
||||
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id) playerData = _profileHelper.GetScavProfile(sessionId);
|
||||
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
|
||||
{
|
||||
playerData = _profileHelper.GetScavProfile(sessionId);
|
||||
}
|
||||
|
||||
var itemToFold = playerData.Inventory.Items.FirstOrDefault(item => item?.Id == request.Item);
|
||||
if (itemToFold is null)
|
||||
@@ -535,13 +605,19 @@ public class InventoryController(
|
||||
_localisationService.GetText("inventory-unable_to_fold_item_not_found_in_inventory", request.Item)
|
||||
);
|
||||
|
||||
return new ItemEventRouterResponse { Warnings = [], ProfileChanges = { } };
|
||||
return new ItemEventRouterResponse
|
||||
{
|
||||
Warnings = []
|
||||
};
|
||||
}
|
||||
|
||||
// Item may not have upd object
|
||||
_itemHelper.AddUpdObjectToItem(itemToFold);
|
||||
|
||||
itemToFold.Upd.Foldable = new UpdFoldable { Folded = request.Value };
|
||||
itemToFold.Upd.Foldable = new UpdFoldable
|
||||
{
|
||||
Folded = request.Value
|
||||
};
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
@@ -555,10 +631,14 @@ public class InventoryController(
|
||||
{
|
||||
// During post-raid scav transfer, the swap may be in the scav inventory
|
||||
var playerData = pmcData;
|
||||
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id) playerData = _profileHelper.GetScavProfile(sessionId);
|
||||
if (request.FromOwner?.Type == "Profile" && request.FromOwner.Id != playerData.Id)
|
||||
{
|
||||
playerData = _profileHelper.GetScavProfile(sessionId);
|
||||
}
|
||||
|
||||
var itemOne = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item);
|
||||
if (itemOne is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"inventory-unable_to_find_item_to_swap",
|
||||
@@ -569,9 +649,11 @@ public class InventoryController(
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var itemTwo = playerData.Inventory.Items.FirstOrDefault(x => x.Id == request.Item2);
|
||||
if (itemTwo is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"inventory-unable_to_find_item_to_swap",
|
||||
@@ -582,6 +664,7 @@ public class InventoryController(
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// to.id is the parentid
|
||||
itemOne.ParentId = request.To.Id;
|
||||
@@ -591,16 +674,24 @@ public class InventoryController(
|
||||
|
||||
// Request object has location data, add it in, otherwise remove existing location from object
|
||||
if (request.To.Location is not null)
|
||||
{
|
||||
itemOne.Location = request.To.Location;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemOne.Location = null;
|
||||
}
|
||||
|
||||
itemTwo.ParentId = request.To2.Id;
|
||||
itemTwo.SlotId = request.To2.Container;
|
||||
if (request.To2.Location is not null)
|
||||
{
|
||||
itemTwo.Location = request.To2.Location;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemTwo.Location = null;
|
||||
}
|
||||
|
||||
// Client already informed of inventory locations, nothing for us to do
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
@@ -644,17 +735,27 @@ public class InventoryController(
|
||||
return;
|
||||
}
|
||||
|
||||
sourceItem.Upd ??= new Upd { StackObjectsCount = 1 };
|
||||
sourceItem.Upd ??= new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
|
||||
var sourceStackCount = sourceItem.Upd.StackObjectsCount;
|
||||
if (sourceStackCount > request.Count)
|
||||
// Source items stack count greater than new desired count
|
||||
{
|
||||
sourceItem.Upd.StackObjectsCount = sourceStackCount - request.Count;
|
||||
}
|
||||
else
|
||||
// Moving a full stack onto a smaller stack
|
||||
{
|
||||
sourceItem.Upd.StackObjectsCount = sourceStackCount - 1;
|
||||
}
|
||||
|
||||
destinationItem.Upd ??= new Upd { StackObjectsCount = 1 };
|
||||
destinationItem.Upd ??= new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
|
||||
var destinationStackCount = destinationItem.Upd.StackObjectsCount;
|
||||
destinationItem.Upd.StackObjectsCount = destinationStackCount + request.Count;
|
||||
@@ -667,7 +768,7 @@ public class InventoryController(
|
||||
var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(body, body.Item, sessionID);
|
||||
|
||||
// Get source item (can be from player or trader or mail)
|
||||
var sourceItem = inventoryItems.From.FirstOrDefault((x) => x.Id == body.Item);
|
||||
var sourceItem = inventoryItems.From.FirstOrDefault(x => x.Id == body.Item);
|
||||
if (sourceItem is null)
|
||||
{
|
||||
var errorMessage = $"Unable to merge stacks as source item: {body.With} cannot be found";
|
||||
@@ -679,7 +780,7 @@ public class InventoryController(
|
||||
}
|
||||
|
||||
// Get item being merged into
|
||||
var destinationItem = inventoryItems.To.FirstOrDefault((x) => x.Id == body.With);
|
||||
var destinationItem = inventoryItems.To.FirstOrDefault(x => x.Id == body.With);
|
||||
if (destinationItem is null)
|
||||
{
|
||||
var errorMessage = $"Unable to merge stacks as destination item: {body.With} cannot be found";
|
||||
@@ -692,25 +793,44 @@ public class InventoryController(
|
||||
|
||||
if (destinationItem.Upd?.StackObjectsCount is null)
|
||||
// No stackcount on destination, add one
|
||||
destinationItem.Upd = new Upd { StackObjectsCount = 1 };
|
||||
{
|
||||
destinationItem.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
}
|
||||
|
||||
if (sourceItem.Upd is null)
|
||||
sourceItem.Upd = new Upd { StackObjectsCount = 1 };
|
||||
{
|
||||
sourceItem.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
}
|
||||
else if (sourceItem.Upd.StackObjectsCount is null)
|
||||
// Items pulled out of raid can have no stack count if the stack should be 1
|
||||
{
|
||||
sourceItem.Upd.StackObjectsCount = 1;
|
||||
}
|
||||
|
||||
// Remove FiR status from destination stack when source stack has no FiR but destination does
|
||||
if (!sourceItem.Upd.SpawnedInSession.GetValueOrDefault(false) &&
|
||||
destinationItem.Upd.SpawnedInSession.GetValueOrDefault(false))
|
||||
{
|
||||
destinationItem.Upd.SpawnedInSession = false;
|
||||
}
|
||||
|
||||
destinationItem.Upd.StackObjectsCount +=
|
||||
sourceItem.Upd.StackObjectsCount; // Add source stackcount to destination
|
||||
output.ProfileChanges[sessionID]
|
||||
.Items.DeletedItems.Add(new Item { Id = sourceItem.Id }); // Inform client source item being deleted
|
||||
.Items.DeletedItems.Add(
|
||||
new Item
|
||||
{
|
||||
Id = sourceItem.Id
|
||||
}
|
||||
); // Inform client source item being deleted
|
||||
|
||||
var indexOfItemToRemove = inventoryItems.From.FindIndex((x) => x.Id == sourceItem.Id);
|
||||
var indexOfItemToRemove = inventoryItems.From.FindIndex(x => x.Id == sourceItem.Id);
|
||||
if (indexOfItemToRemove == -1)
|
||||
{
|
||||
var errorMessage = $"Unable to find item: {sourceItem.Id} to remove from sender inventory";
|
||||
@@ -733,12 +853,12 @@ public class InventoryController(
|
||||
// Handle cartridge edge-case
|
||||
if (request.Container.Location is null && request.Container.ContainerName == "cartridges")
|
||||
{
|
||||
var matchingItems = inventoryItems.To.Where((x) => x.ParentId == request.Container.Id);
|
||||
var matchingItems = inventoryItems.To.Where(x => x.ParentId == request.Container.Id);
|
||||
request.Container.Location = matchingItems.Count(); // Wrong location for first cartridge
|
||||
}
|
||||
|
||||
// The item being merged has three possible sources: pmc, scav or mail, getOwnerInventoryItems() handles getting correct one
|
||||
var itemToSplit = inventoryItems.From.FirstOrDefault((x) => x.Id == request.SplitItem);
|
||||
var itemToSplit = inventoryItems.From.FirstOrDefault(x => x.Id == request.SplitItem);
|
||||
if (itemToSplit is null)
|
||||
{
|
||||
var errorMessage = $"Unable to split stack as source item: {request.SplitItem} cannot be found";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Launcher;
|
||||
@@ -9,6 +8,7 @@ using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
using Info = Core.Models.Eft.Profile.Info;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class LauncherController(
|
||||
.Where(profileName => !_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profileName))
|
||||
.ToList();
|
||||
|
||||
return new ConnectResponse()
|
||||
return new ConnectResponse
|
||||
{
|
||||
BackendUrl = _httpServerHelper.GetBackendUrl(),
|
||||
Name = _coreConfig.ServerName,
|
||||
@@ -83,7 +83,10 @@ public class LauncherController(
|
||||
foreach (var kvp in _saveServer.GetProfiles())
|
||||
{
|
||||
var account = _saveServer.GetProfile(kvp.Key).ProfileInfo;
|
||||
if (info?.Username == account?.Username) return kvp.Key;
|
||||
if (info?.Username == account?.Username)
|
||||
{
|
||||
return kvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -92,8 +95,12 @@ public class LauncherController(
|
||||
public string Register(RegisterData info)
|
||||
{
|
||||
foreach (var kvp in _saveServer.GetProfiles())
|
||||
{
|
||||
if (info.Username == _saveServer.GetProfile(kvp.Key).ProfileInfo?.Username)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return CreateAccount(info);
|
||||
}
|
||||
@@ -139,7 +146,10 @@ public class LauncherController(
|
||||
{
|
||||
var sessionID = Login(info);
|
||||
|
||||
if (!string.IsNullOrEmpty(sessionID)) _saveServer.GetProfile(sessionID).ProfileInfo!.Username = info.Change;
|
||||
if (!string.IsNullOrEmpty(sessionID))
|
||||
{
|
||||
_saveServer.GetProfile(sessionID).ProfileInfo!.Username = info.Change;
|
||||
}
|
||||
|
||||
return sessionID;
|
||||
}
|
||||
@@ -148,7 +158,10 @@ public class LauncherController(
|
||||
{
|
||||
var sessionID = Login(info);
|
||||
|
||||
if (!string.IsNullOrEmpty(sessionID)) _saveServer.GetProfile(sessionID).ProfileInfo!.Password = info.Change;
|
||||
if (!string.IsNullOrEmpty(sessionID))
|
||||
{
|
||||
_saveServer.GetProfile(sessionID).ProfileInfo!.Password = info.Change;
|
||||
}
|
||||
|
||||
return sessionID;
|
||||
}
|
||||
@@ -160,7 +173,10 @@ public class LauncherController(
|
||||
*/
|
||||
public string? Wipe(RegisterData info)
|
||||
{
|
||||
if (!_coreConfig.AllowProfileWipe) return null;
|
||||
if (!_coreConfig.AllowProfileWipe)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sessionID = Login(info);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Launcher;
|
||||
using Core.Models.Spt.Config;
|
||||
@@ -7,6 +6,7 @@ using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
using Info = Core.Models.Eft.Profile.Info;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class LauncherV2Controller(
|
||||
protected CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a simple string of Pong!
|
||||
/// Returns a simple string of Pong!
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string Ping()
|
||||
@@ -37,8 +37,8 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all available profile types and descriptions for creation.
|
||||
/// - This is also localised.
|
||||
/// Returns all available profile types and descriptions for creation.
|
||||
/// - This is also localised.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, string> Types()
|
||||
@@ -63,7 +63,7 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if login details were correct.
|
||||
/// Checks if login details were correct.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
@@ -75,22 +75,26 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new profile.
|
||||
/// Register a new profile.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
public bool Register(RegisterData info)
|
||||
{
|
||||
foreach (var session in _saveServer.GetProfiles())
|
||||
{
|
||||
if (info.Username == _saveServer.GetProfile(session.Key).ProfileInfo!.Username)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CreateAccount(info);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make a password change.
|
||||
/// Make a password change.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
@@ -99,14 +103,16 @@ public class LauncherV2Controller(
|
||||
var sessionId = GetSessionId(info);
|
||||
|
||||
if (sessionId is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_saveServer.GetProfile(sessionId).ProfileInfo!.Password = info.Password;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove profile from server.
|
||||
/// Remove profile from server.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
@@ -118,8 +124,8 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Servers SPT Version.
|
||||
/// - "4.0.0"
|
||||
/// Gets the Servers SPT Version.
|
||||
/// - "4.0.0"
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string SptVersion()
|
||||
@@ -128,8 +134,8 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compatible EFT Version.
|
||||
/// - "0.14.9.31124"
|
||||
/// Gets the compatible EFT Version.
|
||||
/// - "0.14.9.31124"
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string EftVersion()
|
||||
@@ -138,7 +144,7 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Servers loaded mods.
|
||||
/// Gets the Servers loaded mods.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, PackageJsonData> LoadedMods()
|
||||
@@ -147,7 +153,7 @@ public class LauncherV2Controller(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the account from provided details.
|
||||
/// Creates the account from provided details.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
@@ -192,8 +198,12 @@ public class LauncherV2Controller(
|
||||
protected string? GetSessionId(LoginRequestData info)
|
||||
{
|
||||
foreach (var profile in _saveServer.GetProfiles())
|
||||
{
|
||||
if (info.Username == profile.Value.ProfileInfo!.Username && info.Password == profile.Value.ProfileInfo.Password)
|
||||
{
|
||||
return profile.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Location;
|
||||
using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ public class LocationController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle client/locations
|
||||
/// Get all maps base location properties without loot data
|
||||
/// Handle client/locations
|
||||
/// Get all maps base location properties without loot data
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Players Id</param>
|
||||
/// <returns>LocationsGenerateAllResponse</returns>
|
||||
@@ -37,7 +37,11 @@ public class LocationController(
|
||||
var mapBase = kvp.Value.Base;
|
||||
if (mapBase == null)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Map: {kvp} has no base json file, skipping generation");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Map: {kvp} has no base json file, skipping generation");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -55,13 +59,16 @@ public class LocationController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/airdrop/loot
|
||||
/// Handle client/airdrop/loot
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public GetAirdropLootResponse GetAirDropLoot(GetAirdropLootRequest? request)
|
||||
{
|
||||
if (request?.ContainerId is not null) return _airdropService.GenerateCustomAirdropLoot(request);
|
||||
if (request?.ContainerId is not null)
|
||||
{
|
||||
return _airdropService.GenerateCustomAirdropLoot(request);
|
||||
}
|
||||
|
||||
return _airdropService.GenerateAirdropLoot();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Core.Context;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Match;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using static Core.Services.MatchLocationService;
|
||||
|
||||
namespace Core.Controllers;
|
||||
@@ -25,7 +25,6 @@ public class MatchController(
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool GetEnabled()
|
||||
@@ -34,7 +33,7 @@ public class MatchController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/delete
|
||||
/// Handle client/match/group/delete
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
public void DeleteGroup(DeleteGroupRequest info) // TODO: info is `any` in the node server
|
||||
@@ -43,7 +42,7 @@ public class MatchController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle match/group/start_game
|
||||
/// Handle match/group/start_game
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
@@ -78,13 +77,13 @@ public class MatchController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/group/status
|
||||
/// Handle client/match/group/status
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
public MatchGroupStatusResponse GetGroupStatus(MatchGroupStatusRequest info)
|
||||
{
|
||||
return new MatchGroupStatusResponse()
|
||||
return new MatchGroupStatusResponse
|
||||
{
|
||||
Players = [],
|
||||
MaxPveCountExceeded = false
|
||||
@@ -92,7 +91,7 @@ public class MatchController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle /client/raid/configuration
|
||||
/// Handle /client/raid/configuration
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="sessionId"></param>
|
||||
@@ -105,26 +104,31 @@ public class MatchController(
|
||||
|
||||
// Set pmcs to difficulty set in pre-raid screen if override in bot config isnt enabled
|
||||
if (!_pmcConfig.UseDifficultyOverride)
|
||||
{
|
||||
_pmcConfig.Difficulty = ConvertDifficultyDropdownIntoBotDifficulty(
|
||||
request.WavesSettings.BotDifficulty.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a difficulty value from pre-raid screen to a bot difficulty
|
||||
/// Convert a difficulty value from pre-raid screen to a bot difficulty
|
||||
/// </summary>
|
||||
/// <param name="botDifficulty">dropdown difficulty value</param>
|
||||
/// <returns>bot difficulty</returns>
|
||||
private string ConvertDifficultyDropdownIntoBotDifficulty(string botDifficulty)
|
||||
{
|
||||
// Edge case medium - must be altered
|
||||
if (botDifficulty.ToLower() == "medium") return "normal";
|
||||
if (botDifficulty.ToLower() == "medium")
|
||||
{
|
||||
return "normal";
|
||||
}
|
||||
|
||||
return botDifficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/local/start
|
||||
/// Handle client/match/local/start
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -135,7 +139,7 @@ public class MatchController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/match/local/end
|
||||
/// Handle client/match/local/end
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="request"></param>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Notes;
|
||||
using Core.Routers;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -12,7 +12,6 @@ public class NoteController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="body"></param>
|
||||
@@ -23,14 +22,17 @@ public class NoteController(
|
||||
NoteActionData body,
|
||||
string sessionId)
|
||||
{
|
||||
var newNote = new Note { Time = body.Note.Time, Text = body.Note.Text };
|
||||
var newNote = new Note
|
||||
{
|
||||
Time = body.Note.Time,
|
||||
Text = body.Note.Text
|
||||
};
|
||||
pmcData.Notes.DataNotes.Add(newNote);
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="body"></param>
|
||||
@@ -49,7 +51,6 @@ public class NoteController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="body"></param>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Notifier;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
using Core.Services;
|
||||
using System.Diagnostics.Tracing;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -14,11 +11,10 @@ public class NotifierController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolve an array of session notifications.
|
||||
///
|
||||
/// If no notifications are currently queued then intermittently check for new notifications until either
|
||||
/// one or more appear or when a timeout expires.
|
||||
/// If no notifications are available after the timeout, use a default message.
|
||||
/// Resolve an array of session notifications.
|
||||
/// If no notifications are currently queued then intermittently check for new notifications until either
|
||||
/// one or more appear or when a timeout expires.
|
||||
/// If no notifications are available after the timeout, use a default message.
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
public async Task NotifyAsync(string sessionId)
|
||||
@@ -67,7 +63,7 @@ public class NotifierController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/notifier/channel/create
|
||||
/// Handle client/notifier/channel/create
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -84,7 +80,6 @@ public class NotifierController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -14,7 +13,7 @@ public class PresetController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Keyed by item tpl, value = collection of preset ids
|
||||
/// Keyed by item tpl, value = collection of preset ids
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -13,9 +12,9 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
[Injectable]
|
||||
@@ -32,9 +31,9 @@ public class PrestigeController(
|
||||
)
|
||||
{
|
||||
protected double _prestigePercentage = 0.05;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle /client/prestige/list
|
||||
/// Handle /client/prestige/list
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="info"></param>
|
||||
@@ -47,33 +46,33 @@ public class PrestigeController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Handle /client/prestige/obtain</para>
|
||||
/// Going to Prestige 1 grants the below
|
||||
/// <list type="bullet">
|
||||
/// <item>5% of skills should be transfered over</item>
|
||||
/// <item>5% of mastering should be transfered over</item>
|
||||
/// <item>Earned achievements should be transfered over</item>
|
||||
/// <item>Profile stats should be transfered over</item>
|
||||
/// <item>Prestige progress should be transfered over</item>
|
||||
/// <item>Items and rewards for Prestige 1</item>
|
||||
/// </list>
|
||||
/// Going to Prestige 2 grants the below
|
||||
/// <list type="bullet">
|
||||
/// <item>10% of skills should be transfered over</item>
|
||||
/// <item>10% of mastering should be transfered over</item>
|
||||
/// <item>Earned achievements should be transfered over</item>
|
||||
/// <item>Profile stats should be transfered over</item>
|
||||
/// <item>Prestige progress should be transfered over</item>
|
||||
/// <item>Items and rewards for Prestige 2</item>
|
||||
/// </list>
|
||||
/// Each time reseting the below
|
||||
/// <list type="bullet">
|
||||
/// <item>Trader standing</item>
|
||||
/// <item>Task progress</item>
|
||||
/// <item>Character level</item>
|
||||
/// <item>Stash</item>
|
||||
/// <item>Hideout progress</item>
|
||||
/// </list>
|
||||
/// <para>Handle /client/prestige/obtain</para>
|
||||
/// Going to Prestige 1 grants the below
|
||||
/// <list type="bullet">
|
||||
/// <item>5% of skills should be transfered over</item>
|
||||
/// <item>5% of mastering should be transfered over</item>
|
||||
/// <item>Earned achievements should be transfered over</item>
|
||||
/// <item>Profile stats should be transfered over</item>
|
||||
/// <item>Prestige progress should be transfered over</item>
|
||||
/// <item>Items and rewards for Prestige 1</item>
|
||||
/// </list>
|
||||
/// Going to Prestige 2 grants the below
|
||||
/// <list type="bullet">
|
||||
/// <item>10% of skills should be transfered over</item>
|
||||
/// <item>10% of mastering should be transfered over</item>
|
||||
/// <item>Earned achievements should be transfered over</item>
|
||||
/// <item>Profile stats should be transfered over</item>
|
||||
/// <item>Prestige progress should be transfered over</item>
|
||||
/// <item>Items and rewards for Prestige 2</item>
|
||||
/// </list>
|
||||
/// Each time reseting the below
|
||||
/// <list type="bullet">
|
||||
/// <item>Trader standing</item>
|
||||
/// <item>Task progress</item>
|
||||
/// <item>Character level</item>
|
||||
/// <item>Stash</item>
|
||||
/// <item>Hideout progress</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void ObtainPrestige(
|
||||
@@ -90,7 +89,7 @@ public class PrestigeController(
|
||||
HeadId = prePrestigePmc.Customization.Head,
|
||||
VoiceId = _databaseService.GetTemplates()
|
||||
.Customization.FirstOrDefault(
|
||||
(customisation) => customisation.Value.Name == prePrestigePmc.Info.Voice
|
||||
customisation => customisation.Value.Name == prePrestigePmc.Info.Voice
|
||||
)
|
||||
.Value.Id
|
||||
};
|
||||
@@ -100,22 +99,26 @@ public class PrestigeController(
|
||||
|
||||
// Get freshly reset profile ready for editing
|
||||
var newProfile = _profileHelper.GetFullProfile(sessionId);
|
||||
|
||||
|
||||
// set this here so we can use the prestigeLevel for further calcs
|
||||
newProfile.CharacterData.PmcData.Info.PrestigeLevel = prePrestigePmc.Info.PrestigeLevel ?? 0;
|
||||
newProfile.CharacterData.PmcData.Info.PrestigeLevel++;
|
||||
|
||||
|
||||
// Copy skills to new profile
|
||||
var commonSkillsToCopy = prePrestigePmc.Skills.Common;
|
||||
foreach (var skillToCopy in commonSkillsToCopy)
|
||||
{
|
||||
// Set progress 5% of what it was * prestige level to get 5% or 10% for prestige 1 or 2 respectivly
|
||||
skillToCopy.Progress = (skillToCopy.Progress.Value * _prestigePercentage) * newProfile.CharacterData.PmcData.Info.PrestigeLevel;
|
||||
var existingSkill = newProfile.CharacterData.PmcData.Skills.Common.FirstOrDefault((skill) => skill.Id == skillToCopy.Id);
|
||||
skillToCopy.Progress = skillToCopy.Progress.Value * _prestigePercentage * newProfile.CharacterData.PmcData.Info.PrestigeLevel;
|
||||
var existingSkill = newProfile.CharacterData.PmcData.Skills.Common.FirstOrDefault(skill => skill.Id == skillToCopy.Id);
|
||||
if (existingSkill is not null)
|
||||
{
|
||||
existingSkill.Progress = skillToCopy.Progress;
|
||||
}
|
||||
else
|
||||
{
|
||||
newProfile.CharacterData.PmcData.Skills.Common.Add(skillToCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy mastering to new profile
|
||||
@@ -123,14 +126,18 @@ public class PrestigeController(
|
||||
foreach (var skillToCopy in masteringSkillsToCopy)
|
||||
{
|
||||
// Set progress 5% of what it was * prestige level to get 5% or 10% for prestige 1 or 2 respectivly
|
||||
skillToCopy.Progress = (skillToCopy.Progress.Value * _prestigePercentage) * newProfile.CharacterData.PmcData.Info.PrestigeLevel;
|
||||
skillToCopy.Progress = skillToCopy.Progress.Value * _prestigePercentage * newProfile.CharacterData.PmcData.Info.PrestigeLevel;
|
||||
var existingSkill = newProfile.CharacterData.PmcData.Skills.Mastering.FirstOrDefault(
|
||||
(skill) => skill.Id == skillToCopy.Id
|
||||
skill => skill.Id == skillToCopy.Id
|
||||
);
|
||||
if (existingSkill is not null)
|
||||
{
|
||||
existingSkill.Progress = skillToCopy.Progress;
|
||||
}
|
||||
else
|
||||
{
|
||||
newProfile.CharacterData.PmcData.Skills.Mastering.Add(skillToCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// Add existing completed achievements and new one for prestige
|
||||
@@ -138,7 +145,9 @@ public class PrestigeController(
|
||||
|
||||
// Add "Prestigious" achievement
|
||||
if (!newProfile.CharacterData.PmcData.Achievements.ContainsKey("676091c0f457869a94017a23"))
|
||||
{
|
||||
newProfile.CharacterData.PmcData.Achievements.Add("676091c0f457869a94017a23", _timeUtil.GetTimeStamp());
|
||||
}
|
||||
// TODO: is there one for second prestige
|
||||
|
||||
// Add existing Stats to profile
|
||||
@@ -153,12 +162,13 @@ public class PrestigeController(
|
||||
|
||||
// Flag profile as having achieved this prestige level
|
||||
newProfile.CharacterData.PmcData.Prestige[currentPrestigeData.Id] = _timeUtil.GetTimeStamp();
|
||||
|
||||
|
||||
if (request is not null)
|
||||
// Copy transferred items
|
||||
{
|
||||
foreach (var transferRequest in request)
|
||||
{
|
||||
var item = prePrestigePmc.Inventory.Items.FirstOrDefault((item) => item.Id == transferRequest.Id);
|
||||
var item = prePrestigePmc.Inventory.Items.FirstOrDefault(item => item.Id == transferRequest.Id);
|
||||
var addItemRequest = new AddItemDirectRequest
|
||||
{
|
||||
ItemWithModsToAdd = [item],
|
||||
@@ -173,6 +183,7 @@ public class PrestigeController(
|
||||
_eventOutputHolder.GetOutput(sessionId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Force save of above changes to disk
|
||||
_saveServer.SaveProfile(sessionId);
|
||||
@@ -181,48 +192,55 @@ public class PrestigeController(
|
||||
private void AddPrestigeRewardsToProfile(string sessionId, SptProfile newProfile, IEnumerable<Reward> rewards)
|
||||
{
|
||||
foreach (var reward in rewards)
|
||||
{
|
||||
switch (reward.Type)
|
||||
{
|
||||
case RewardType.CustomizationDirect:
|
||||
{
|
||||
_profileHelper.AddHideoutCustomisationUnlock(newProfile, reward, CustomisationSource.PRESTIGE);
|
||||
break;
|
||||
}
|
||||
{
|
||||
_profileHelper.AddHideoutCustomisationUnlock(newProfile, reward, CustomisationSource.PRESTIGE);
|
||||
break;
|
||||
}
|
||||
case RewardType.Skill:
|
||||
if (Enum.TryParse(reward.Target, out SkillTypes result))
|
||||
{
|
||||
_profileHelper.AddSkillPointsToPlayer(
|
||||
newProfile.CharacterData.PmcData,
|
||||
result,
|
||||
((JsonElement)reward.Value).ToObject<double>()
|
||||
((JsonElement) reward.Value).ToObject<double>()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error($"Unable to parse reward Target to Enum: {reward.Target}");
|
||||
}
|
||||
|
||||
break;
|
||||
case RewardType.Item:
|
||||
{
|
||||
var addItemRequest = new AddItemDirectRequest
|
||||
{
|
||||
ItemWithModsToAdd = reward.Items,
|
||||
FoundInRaid = reward.Items.FirstOrDefault()?.Upd?.SpawnedInSession,
|
||||
UseSortingTable = false,
|
||||
Callback = null
|
||||
};
|
||||
_inventoryHelper.AddItemToStash(
|
||||
sessionId,
|
||||
addItemRequest,
|
||||
newProfile.CharacterData.PmcData,
|
||||
_eventOutputHolder.GetOutput(sessionId)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case RewardType.ExtraDailyQuest:
|
||||
{
|
||||
_logger.Info("additional quests will be added when generating repeatables");
|
||||
break;
|
||||
}
|
||||
var addItemRequest = new AddItemDirectRequest
|
||||
{
|
||||
ItemWithModsToAdd = reward.Items,
|
||||
FoundInRaid = reward.Items.FirstOrDefault()?.Upd?.SpawnedInSession,
|
||||
UseSortingTable = false,
|
||||
Callback = null
|
||||
};
|
||||
_inventoryHelper.AddItemToStash(
|
||||
sessionId,
|
||||
addItemRequest,
|
||||
newProfile.CharacterData.PmcData,
|
||||
_eventOutputHolder.GetOutput(sessionId)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case RewardType.ExtraDailyQuest:
|
||||
{
|
||||
_logger.Info("additional quests will be added when generating repeatables");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
_logger.Error($"Unhandled prestige reward type: {reward.Type}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -12,6 +11,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -51,7 +51,10 @@ public class ProfileController(
|
||||
public MiniProfile GetMiniProfile(string sessionID)
|
||||
{
|
||||
var profile = _saveServer.GetProfile(sessionID);
|
||||
if (profile?.CharacterData == null) throw new Exception($"Unable to find character data for id: {sessionID}. Profile may be corrupt");
|
||||
if (profile?.CharacterData == null)
|
||||
{
|
||||
throw new Exception($"Unable to find character data for id: {sessionID}. Profile may be corrupt");
|
||||
}
|
||||
|
||||
var pmc = profile.CharacterData.PmcData;
|
||||
var maxLvl = _profileHelper.GetMaxLevel();
|
||||
@@ -60,6 +63,7 @@ public class ProfileController(
|
||||
var currlvl = pmc?.Info?.Level.GetValueOrDefault(1);
|
||||
var xpToNextLevel = _profileHelper.GetExperience((currlvl ?? 1) + 1);
|
||||
if (pmc?.Info?.Level == null)
|
||||
{
|
||||
return new MiniProfile
|
||||
{
|
||||
Username = profile.ProfileInfo?.Username ?? "",
|
||||
@@ -75,6 +79,7 @@ public class ProfileController(
|
||||
ProfileId = profile.ProfileInfo?.ProfileId ?? "",
|
||||
SptData = _profileHelper.GetDefaultSptDataObject()
|
||||
};
|
||||
}
|
||||
|
||||
return new MiniProfile
|
||||
{
|
||||
@@ -128,9 +133,15 @@ public class ProfileController(
|
||||
*/
|
||||
public string ValidateNickname(ValidateNicknameRequestData info, string sessionID)
|
||||
{
|
||||
if (info.Nickname.Length < 3) return "tooshort";
|
||||
if (info.Nickname.Length < 3)
|
||||
{
|
||||
return "tooshort";
|
||||
}
|
||||
|
||||
if (_profileHelper.IsNicknameTaken(info, sessionID)) return "taken";
|
||||
if (_profileHelper.IsNicknameTaken(info, sessionID))
|
||||
{
|
||||
return "taken";
|
||||
}
|
||||
|
||||
return "OK";
|
||||
}
|
||||
@@ -141,7 +152,13 @@ public class ProfileController(
|
||||
*/
|
||||
public string ChangeNickname(ProfileChangeNicknameRequestData info, string sessionID)
|
||||
{
|
||||
var output = ValidateNickname(new ValidateNicknameRequestData() { Nickname = info.Nickname }, sessionID);
|
||||
var output = ValidateNickname(
|
||||
new ValidateNicknameRequestData
|
||||
{
|
||||
Nickname = info.Nickname
|
||||
},
|
||||
sessionID
|
||||
);
|
||||
|
||||
if (output == "OK")
|
||||
{
|
||||
@@ -177,7 +194,10 @@ public class ProfileController(
|
||||
{
|
||||
var pmcProfile = profile?.CharacterData?.PmcData;
|
||||
|
||||
if (!pmcProfile?.Info?.LowerNickname?.Contains(info.Nickname.ToLower()) ?? false) continue;
|
||||
if (!pmcProfile?.Info?.LowerNickname?.Contains(info.Nickname.ToLower()) ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(_profileHelper.GetChatRoomMemberFromPmcProfile(pmcProfile));
|
||||
}
|
||||
@@ -191,13 +211,29 @@ public class ProfileController(
|
||||
public GetProfileStatusResponseData GetProfileStatus(string sessionId)
|
||||
{
|
||||
var account = _saveServer.GetProfile(sessionId).ProfileInfo;
|
||||
var response = new GetProfileStatusResponseData()
|
||||
var response = new GetProfileStatusResponseData
|
||||
{
|
||||
MaxPveCountExceeded = false,
|
||||
Profiles =
|
||||
[
|
||||
new ProfileStatusData { ProfileId = account.ScavengerId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0 },
|
||||
new ProfileStatusData { ProfileId = account.ProfileId, ProfileToken = null, Status = "Free", Sid = "", Ip = "", Port = 0 }
|
||||
new ProfileStatusData
|
||||
{
|
||||
ProfileId = account.ScavengerId,
|
||||
ProfileToken = null,
|
||||
Status = "Free",
|
||||
Sid = "",
|
||||
Ip = "",
|
||||
Port = 0
|
||||
},
|
||||
new ProfileStatusData
|
||||
{
|
||||
ProfileId = account.ProfileId,
|
||||
ProfileToken = null,
|
||||
Status = "Free",
|
||||
Sid = "",
|
||||
Ip = "",
|
||||
Port = 0
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -243,7 +279,7 @@ public class ProfileController(
|
||||
{
|
||||
Nickname = profileToViewPmc.Info.Nickname,
|
||||
Side = profileToViewPmc.Info.Side,
|
||||
Experience = profileToViewPmc.Info.Experience as int?,
|
||||
Experience = profileToViewPmc.Info.Experience,
|
||||
MemberCategory = profileToViewPmc.Info.MemberCategory as int?,
|
||||
BannedState = profileToViewPmc.Info.BannedState,
|
||||
BannedUntil = profileToViewPmc.Info.BannedUntil,
|
||||
@@ -294,11 +330,20 @@ public class ProfileController(
|
||||
public bool SetChosenProfileIcon(string sessionId, GetProfileSettingsRequest request)
|
||||
{
|
||||
var profileToUpdate = _profileHelper.GetPmcProfile(sessionId);
|
||||
if (profileToUpdate == null) return false;
|
||||
if (profileToUpdate == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.MemberCategory != null) profileToUpdate.Info.SelectedMemberCategory = request.MemberCategory as MemberCategory?;
|
||||
if (request.MemberCategory != null)
|
||||
{
|
||||
profileToUpdate.Info.SelectedMemberCategory = request.MemberCategory as MemberCategory?;
|
||||
}
|
||||
|
||||
if (request.SquadInviteRestriction != null) profileToUpdate.Info.SquadInviteRestriction = request.SquadInviteRestriction;
|
||||
if (request.SquadInviteRestriction != null)
|
||||
{
|
||||
profileToUpdate.Info.SquadInviteRestriction = request.SquadInviteRestriction;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Text.Json;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -13,7 +11,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Extensions;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
@@ -55,7 +53,7 @@ public class QuestController(
|
||||
|
||||
// Does quest exist in profile
|
||||
// Restarting a failed quest can mean quest exists in profile
|
||||
var existingQuestStatus = pmcData.Quests.FirstOrDefault((x) => x.QId == acceptedQuest.QuestId);
|
||||
var existingQuestStatus = pmcData.Quests.FirstOrDefault(x => x.QId == acceptedQuest.QuestId);
|
||||
if (existingQuestStatus is not null)
|
||||
{
|
||||
// Update existing
|
||||
@@ -99,7 +97,7 @@ public class QuestController(
|
||||
MessageType.QUEST_START,
|
||||
messageId,
|
||||
startedQuestRewardItems.ToList(),
|
||||
_timeUtil.GetHoursAsSeconds((int)_questHelper.GetMailItemRedeemTimeHoursForProfile(pmcData))
|
||||
_timeUtil.GetHoursAsSeconds((int) _questHelper.GetMailItemRedeemTimeHoursForProfile(pmcData))
|
||||
);
|
||||
|
||||
// Having accepted new quest, look for newly unlocked quests and inform client of them
|
||||
@@ -107,7 +105,10 @@ public class QuestController(
|
||||
acceptedQuest.QuestId,
|
||||
sessionID
|
||||
);
|
||||
if (newlyAccessibleQuests.Count > 0) acceptQuestResponse.ProfileChanges[sessionID].Quests.AddRange(newlyAccessibleQuests);
|
||||
if (newlyAccessibleQuests.Count > 0)
|
||||
{
|
||||
acceptQuestResponse.ProfileChanges[sessionID].Quests.AddRange(newlyAccessibleQuests);
|
||||
}
|
||||
|
||||
return acceptQuestResponse;
|
||||
}
|
||||
@@ -117,9 +118,11 @@ public class QuestController(
|
||||
foreach (var condition in questConditions)
|
||||
{
|
||||
if (pmcData.TaskConditionCounters.TryGetValue(condition.Id, out var counter))
|
||||
{
|
||||
_logger.Error(
|
||||
$"Unable to add new task condition counter: {condition.ConditionType} for qeust: {questId} to profile: {pmcData.SessionId} as it already exists:"
|
||||
);
|
||||
}
|
||||
|
||||
switch (condition.ConditionType)
|
||||
{
|
||||
@@ -181,7 +184,11 @@ public class QuestController(
|
||||
var matchingQuest = repeatableQuest.ActiveQuests.FirstOrDefault(x => x.Id == acceptedQuest.QuestId);
|
||||
if (matchingQuest is not null)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Accepted repeatable quest {acceptedQuest.QuestId} from {repeatableQuest.Name}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Accepted repeatable quest {acceptedQuest.QuestId} from {repeatableQuest.Name}");
|
||||
}
|
||||
|
||||
matchingQuest.SptRepatableGroupName = repeatableQuest.Name;
|
||||
|
||||
return matchingQuest;
|
||||
@@ -222,7 +229,7 @@ public class QuestController(
|
||||
|
||||
if (pmcData.TaskConditionCounters.TryGetValue("ConditionId", out var counter))
|
||||
{
|
||||
handedInCount -= (int)(counter.Value ?? 0);
|
||||
handedInCount -= (int) (counter.Value ?? 0);
|
||||
|
||||
if (handedInCount <= 0)
|
||||
{
|
||||
@@ -257,23 +264,25 @@ public class QuestController(
|
||||
var matchingItemInProfile = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemHandover.Id);
|
||||
if (!(matchingItemInProfile is not null && handoverRequirements.Target.List.Contains(matchingItemInProfile.Template)))
|
||||
// Item handed in by player doesn't match what was requested
|
||||
{
|
||||
return ShowQuestItemHandoverMatchError(
|
||||
handoverQuestRequest,
|
||||
matchingItemInProfile,
|
||||
handoverRequirements,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the right quantity of given items
|
||||
var itemCountToRemove = Math.Min(itemHandover.Count ?? 0, handedInCount - totalItemCountToRemove);
|
||||
totalItemCountToRemove += itemCountToRemove;
|
||||
if ((itemHandover.Count - itemCountToRemove) > 0)
|
||||
if (itemHandover.Count - itemCountToRemove > 0)
|
||||
{
|
||||
// Remove single item with no children
|
||||
_questHelper.ChangeItemStack(
|
||||
pmcData,
|
||||
itemHandover.Id,
|
||||
(int)(itemHandover.Count - itemCountToRemove),
|
||||
(int) (itemHandover.Count - itemCountToRemove),
|
||||
sessionID,
|
||||
output
|
||||
);
|
||||
@@ -301,6 +310,7 @@ public class QuestController(
|
||||
|
||||
// Important: loop backward when removing items from the array we're looping on
|
||||
while (index-- > 0)
|
||||
{
|
||||
if (toRemove.Contains(pmcData.Inventory.Items[index].Id))
|
||||
{
|
||||
var removedItem = _cloner.Clone(pmcData.Inventory.Items[index]);
|
||||
@@ -318,11 +328,15 @@ public class QuestController(
|
||||
childItems.RemoveAt(0); // Remove the parent
|
||||
|
||||
// Sort by the current `location` and update
|
||||
childItems.Sort((a, b) => (int)a.Location > (int)b.Location ? 1 : -1);
|
||||
childItems.Sort((a, b) => (int) a.Location > (int) b.Location ? 1 : -1);
|
||||
|
||||
for (var i = 0; i < childItems.Count; i++) childItems[i].Location = i;
|
||||
for (var i = 0; i < childItems.Count; i++)
|
||||
{
|
||||
childItems[i].Location = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,32 +22,32 @@ namespace Core.Controllers;
|
||||
[Injectable]
|
||||
public class RagfairController
|
||||
{
|
||||
private readonly ISptLogger<RagfairController> _logger;
|
||||
private readonly TimeUtil _timeUtil;
|
||||
private readonly JsonUtil _jsonUtil;
|
||||
private readonly HttpResponseUtil _httpResponseUtil;
|
||||
private readonly EventOutputHolder _eventOutputHolder;
|
||||
private readonly RagfairServer _ragfairServer;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly InventoryHelper _inventoryHelper;
|
||||
private readonly RagfairSellHelper _ragfairSellHelper;
|
||||
private readonly HandbookHelper _handbookHelper;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
private readonly PaymentHelper _paymentHelper;
|
||||
private readonly RagfairHelper _ragfairHelper;
|
||||
private readonly RagfairSortHelper _ragfairSortHelper;
|
||||
private readonly RagfairOfferHelper _ragfairOfferHelper;
|
||||
private readonly TraderHelper _traderHelper;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly RagfairTaxService _ragfairTaxService;
|
||||
private readonly RagfairOfferService _ragfairOfferService;
|
||||
private readonly PaymentService _paymentService;
|
||||
private readonly RagfairPriceService _ragfairPriceService;
|
||||
private readonly RagfairOfferGenerator _ragfairOfferGenerator;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly EventOutputHolder _eventOutputHolder;
|
||||
private readonly HandbookHelper _handbookHelper;
|
||||
private readonly HttpResponseUtil _httpResponseUtil;
|
||||
private readonly InventoryHelper _inventoryHelper;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly JsonUtil _jsonUtil;
|
||||
private readonly LocalisationService _localisationService;
|
||||
private readonly ISptLogger<RagfairController> _logger;
|
||||
private readonly PaymentHelper _paymentHelper;
|
||||
private readonly PaymentService _paymentService;
|
||||
private readonly ProfileHelper _profileHelper;
|
||||
|
||||
private readonly RagfairConfig _ragfairConfig;
|
||||
private readonly RagfairHelper _ragfairHelper;
|
||||
private readonly RagfairOfferGenerator _ragfairOfferGenerator;
|
||||
private readonly RagfairOfferHelper _ragfairOfferHelper;
|
||||
private readonly RagfairOfferService _ragfairOfferService;
|
||||
private readonly RagfairPriceService _ragfairPriceService;
|
||||
private readonly RagfairSellHelper _ragfairSellHelper;
|
||||
private readonly RagfairServer _ragfairServer;
|
||||
private readonly RagfairSortHelper _ragfairSortHelper;
|
||||
private readonly RagfairTaxService _ragfairTaxService;
|
||||
private readonly TimeUtil _timeUtil;
|
||||
private readonly TraderHelper _traderHelper;
|
||||
|
||||
public RagfairController(
|
||||
ISptLogger<RagfairController> logger,
|
||||
@@ -252,7 +252,7 @@ public class RagfairController
|
||||
// Get specific assort purchase data and set current purchase buy value
|
||||
traderPurchases.TryGetValue(assortId, out var assortTraderPurchaseData);
|
||||
|
||||
offer.BuyRestrictionCurrent = (int?)assortTraderPurchaseData?.PurchaseCount ?? 0;
|
||||
offer.BuyRestrictionCurrent = (int?) assortTraderPurchaseData?.PurchaseCount ?? 0;
|
||||
offer.BuyRestrictionMax = offerRootItem.Upd.BuyRestrictionMax;
|
||||
}
|
||||
|
||||
@@ -374,7 +374,12 @@ public class RagfairController
|
||||
// Get the average offer price, excluding barter offers
|
||||
var average = GetAveragePriceFromOffers(offers, minMax, ignoreTraderOffers);
|
||||
|
||||
return new GetItemPriceResult { Avg = Math.Round(average), Min = minMax.Min, Max = minMax.Max };
|
||||
return new GetItemPriceResult
|
||||
{
|
||||
Avg = Math.Round(average),
|
||||
Min = minMax.Min,
|
||||
Max = minMax.Max
|
||||
};
|
||||
}
|
||||
|
||||
// No offers listed, get price from live ragfair price list prices.json
|
||||
@@ -385,7 +390,12 @@ public class RagfairController
|
||||
tplPrice = _handbookHelper.GetTemplatePrice(getPriceRequest.TemplateId);
|
||||
}
|
||||
|
||||
return new GetItemPriceResult { Avg = tplPrice, Min = tplPrice, Max = tplPrice };
|
||||
return new GetItemPriceResult
|
||||
{
|
||||
Avg = tplPrice,
|
||||
Min = tplPrice,
|
||||
Max = tplPrice
|
||||
};
|
||||
}
|
||||
|
||||
private double GetAveragePriceFromOffers(List<RagfairOffer> offers, MinMax minMax, bool ignoreTraderOffers)
|
||||
@@ -582,7 +592,12 @@ public class RagfairController
|
||||
|
||||
// Average offer price for single item (or whole weapon)
|
||||
var averages =
|
||||
GetItemMinAvgMaxFleaPriceValues(new GetMarketPriceRequestData { TemplateId = offer.Items[0].Template });
|
||||
GetItemMinAvgMaxFleaPriceValues(
|
||||
new GetMarketPriceRequestData
|
||||
{
|
||||
TemplateId = offer.Items[0].Template
|
||||
}
|
||||
);
|
||||
var averageOfferPrice = averages.Avg;
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
@@ -608,7 +623,7 @@ public class RagfairController
|
||||
);
|
||||
|
||||
// Create array of sell times for items listed
|
||||
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal);
|
||||
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int) stackCountTotal);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (_ragfairConfig.Sell.Fees)
|
||||
@@ -618,7 +633,7 @@ public class RagfairController
|
||||
newRootOfferItem,
|
||||
pmcData,
|
||||
playerListedPriceInRub,
|
||||
(int)stackCountTotal,
|
||||
(int) stackCountTotal,
|
||||
offerRequest,
|
||||
output
|
||||
);
|
||||
@@ -688,7 +703,10 @@ public class RagfairController
|
||||
|
||||
// Single price for an item
|
||||
var averages = GetItemMinAvgMaxFleaPriceValues(
|
||||
new GetMarketPriceRequestData { TemplateId = firstListingAndChidren[0].Template }
|
||||
new GetMarketPriceRequestData
|
||||
{
|
||||
TemplateId = firstListingAndChidren[0].Template
|
||||
}
|
||||
);
|
||||
var singleItemPrice = averages.Avg;
|
||||
|
||||
@@ -715,7 +733,7 @@ public class RagfairController
|
||||
);
|
||||
|
||||
// Create array of sell times for items listed + sell all at once as its a pack
|
||||
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal, true);
|
||||
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int) stackCountTotal, true);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (_ragfairConfig.Sell.Fees)
|
||||
@@ -725,7 +743,7 @@ public class RagfairController
|
||||
newRootOfferItem,
|
||||
pmcData,
|
||||
playerListedPriceInRub,
|
||||
(int)stackCountTotal,
|
||||
(int) stackCountTotal,
|
||||
offerRequest,
|
||||
output
|
||||
);
|
||||
@@ -789,7 +807,12 @@ public class RagfairController
|
||||
|
||||
// Average offer price for single item (or whole weapon)
|
||||
var averages =
|
||||
GetItemMinAvgMaxFleaPriceValues(new GetMarketPriceRequestData { TemplateId = rootItem.Template });
|
||||
GetItemMinAvgMaxFleaPriceValues(
|
||||
new GetMarketPriceRequestData
|
||||
{
|
||||
TemplateId = rootItem.Template
|
||||
}
|
||||
);
|
||||
var averageOfferPriceSingleItem = averages.Avg;
|
||||
|
||||
// Check for and apply item price modifer if it exists in config
|
||||
@@ -807,7 +830,7 @@ public class RagfairController
|
||||
playerListedPriceInRub,
|
||||
qualityMultiplier
|
||||
);
|
||||
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal);
|
||||
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int) stackCountTotal);
|
||||
|
||||
// Subtract flea market fee from stash
|
||||
if (_ragfairConfig.Sell.Fees)
|
||||
@@ -817,7 +840,7 @@ public class RagfairController
|
||||
rootItem,
|
||||
pmcData,
|
||||
playerListedPriceInRub,
|
||||
(int)stackCountTotal,
|
||||
(int) stackCountTotal,
|
||||
offerRequest,
|
||||
output
|
||||
);
|
||||
@@ -972,12 +995,18 @@ public class RagfairController
|
||||
{
|
||||
errorMessage = _localisationService.GetText(
|
||||
"ragfair-unable_to_find_item_in_inventory",
|
||||
new { id = itemId }
|
||||
new
|
||||
{
|
||||
id = itemId
|
||||
}
|
||||
);
|
||||
_logger.Error(errorMessage);
|
||||
|
||||
return new GetItemsToListOnFleaFromInventoryResult
|
||||
{ Items = itemsToReturn, ErrorMessage = errorMessage };
|
||||
{
|
||||
Items = itemsToReturn,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
item = _itemHelper.FixItemStackCount(item);
|
||||
@@ -989,16 +1018,17 @@ public class RagfairController
|
||||
errorMessage = _localisationService.GetText("ragfair-unable_to_find_requested_items_in_inventory");
|
||||
_logger.Error(errorMessage);
|
||||
|
||||
return new GetItemsToListOnFleaFromInventoryResult { ErrorMessage = errorMessage };
|
||||
return new GetItemsToListOnFleaFromInventoryResult
|
||||
{
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
return new GetItemsToListOnFleaFromInventoryResult { Items = itemsToReturn, ErrorMessage = errorMessage };
|
||||
}
|
||||
|
||||
public record GetItemsToListOnFleaFromInventoryResult
|
||||
{
|
||||
public List<List<Item>>? Items { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
return new GetItemsToListOnFleaFromInventoryResult
|
||||
{
|
||||
Items = itemsToReturn,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
public ItemEventRouterResponse RemoveOffer(RemoveOfferRequestData removeRequest, string sessionId)
|
||||
@@ -1046,7 +1076,7 @@ public class RagfairController
|
||||
{
|
||||
// `expireSeconds` Default is 71 seconds
|
||||
var newEndTime = _ragfairConfig.Sell.ExpireSeconds + _timeUtil.GetTimeStamp();
|
||||
playerProfileOffers[playerOfferIndex].EndTime = (long?)Math.Round((double)newEndTime);
|
||||
playerProfileOffers[playerOfferIndex].EndTime = (long?) Math.Round((double) newEndTime);
|
||||
}
|
||||
|
||||
return output;
|
||||
@@ -1087,7 +1117,7 @@ public class RagfairController
|
||||
var sellInOncePiece = playerOffer.SellInOnePiece.GetValueOrDefault(false);
|
||||
if (!sellInOncePiece)
|
||||
{
|
||||
count = (int)playerOffer.Items.Sum(offerItem => offerItem.Upd?.StackObjectsCount ?? 0);
|
||||
count = (int) playerOffer.Items.Sum(offerItem => offerItem.Upd?.StackObjectsCount ?? 0);
|
||||
}
|
||||
|
||||
var tax = _ragfairTaxService.CalculateTax(
|
||||
@@ -1110,7 +1140,7 @@ public class RagfairController
|
||||
}
|
||||
|
||||
// Add extra time to offer
|
||||
playerOffers[playerOfferIndex].EndTime += (long?)Math.Round((decimal)secondsToAdd);
|
||||
playerOffers[playerOfferIndex].EndTime += (long?) Math.Round((decimal) secondsToAdd);
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -1127,7 +1157,14 @@ public class RagfairController
|
||||
{
|
||||
TransactionId = "ragfair",
|
||||
Action = "TradingConfirm",
|
||||
SchemeItems = [new IdWithCount { Id = _paymentHelper.GetCurrency(currency), Count = Math.Round(value) }],
|
||||
SchemeItems =
|
||||
[
|
||||
new IdWithCount
|
||||
{
|
||||
Id = _paymentHelper.GetCurrency(currency),
|
||||
Count = Math.Round(value)
|
||||
}
|
||||
],
|
||||
Type = "",
|
||||
ItemId = "",
|
||||
Count = 0,
|
||||
@@ -1152,4 +1189,19 @@ public class RagfairController
|
||||
|
||||
return offerToReturn;
|
||||
}
|
||||
|
||||
public record GetItemsToListOnFleaFromInventoryResult
|
||||
{
|
||||
public List<List<Item>>? Items
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string? ErrorMessage
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Repair;
|
||||
using Core.Routers;
|
||||
using Core.Services;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -14,8 +14,8 @@ public class RepairController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle TraderRepair event
|
||||
/// Repair with trader
|
||||
/// Handle TraderRepair event
|
||||
/// Repair with trader
|
||||
/// </summary>
|
||||
/// <param name="sessionId">session id</param>
|
||||
/// <param name="body">endpoint request data</param>
|
||||
@@ -42,7 +42,10 @@ public class RepairController(
|
||||
output
|
||||
);
|
||||
|
||||
if (output.Warnings?.Count > 0) return output;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
// Add repaired item to output object
|
||||
output.ProfileChanges[sessionID].Items.ChangedItems.Add(repairDetails.RepairedItem);
|
||||
@@ -55,8 +58,8 @@ public class RepairController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle Repair event
|
||||
/// Repair with repair kit
|
||||
/// Handle Repair event
|
||||
/// Repair with repair kit
|
||||
/// </summary>
|
||||
/// <param name="sessionId">session id</param>
|
||||
/// <param name="body">endpoint request data</param>
|
||||
|
||||
@@ -89,7 +89,10 @@ public class RepeatableQuestController(
|
||||
);
|
||||
|
||||
// If the configuration dictates to replace with the same quest type, adjust the available quest types
|
||||
if (repeatableConfig?.KeepDailyQuestTypeOnReplacement is not null) repeatableConfig.Types = [questToReplace.Type.ToString()];
|
||||
if (repeatableConfig?.KeepDailyQuestTypeOnReplacement is not null)
|
||||
{
|
||||
repeatableConfig.Types = [questToReplace.Type.ToString()];
|
||||
}
|
||||
|
||||
// Generate meta-data for what type/levelrange of quests can be generated for player
|
||||
var allowedQuestTypes = GenerateQuestPool(repeatableConfig, pmcData.Info.Level);
|
||||
@@ -114,9 +117,11 @@ public class RepeatableQuestController(
|
||||
repeatablesOfTypeInProfile.ActiveQuests.Add(newRepeatableQuest);
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Removing: {repeatableConfig.Name} quest: {questToReplace.Id} from trader: {questToReplace.TraderId} as its been replaced"
|
||||
);
|
||||
}
|
||||
|
||||
RemoveQuestFromProfile(fullProfile, questToReplace.Id);
|
||||
|
||||
@@ -146,9 +151,12 @@ public class RepeatableQuestController(
|
||||
foreach (var cost in previousChangeRequirement.ChangeCost)
|
||||
{
|
||||
// Not free, Charge player + appy charisma bonus to cost of replacement
|
||||
cost.Count = (int)Math.Truncate(cost.Count.Value * (1 - Math.Truncate(charismaBonus / 100) * 0.001));
|
||||
cost.Count = (int) Math.Truncate(cost.Count.Value * (1 - Math.Truncate(charismaBonus / 100) * 0.001));
|
||||
_paymentService.AddPaymentToOutput(pmcData, cost.TemplateId, cost.Count.Value, sessionID, output);
|
||||
if (output.Warnings.Count > 0) return output;
|
||||
if (output.Warnings.Count > 0)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,10 +229,14 @@ public class RepeatableQuestController(
|
||||
if (repeatablesOfTypeInProfile.ActiveQuests.Count == 1)
|
||||
// Only one repeatable quest being replaced (e.g. scav_daily), remove everything ready for new quest requirement to be added
|
||||
// Will assist in cleanup of existing profiles data
|
||||
{
|
||||
repeatablesOfTypeInProfile.ChangeRequirement.Clear();
|
||||
}
|
||||
else
|
||||
// Multiple active quests of this type (e.g. daily or weekly) are active, just remove the single replaced quest
|
||||
{
|
||||
repeatablesOfTypeInProfile.ChangeRequirement.Remove(replacedQuestId);
|
||||
}
|
||||
}
|
||||
|
||||
private RepeatableQuest? AttemptToGenerateRepeatableQuest(string sessionId, PmcData pmcData,
|
||||
@@ -245,12 +257,17 @@ public class RepeatableQuestController(
|
||||
|
||||
if (newRepeatableQuest is not null)
|
||||
// Successfully generated a quest, exit loop
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (attempts > maxAttempts) _logger.Error("We were stuck in repeatable quest generation. This should never happen. Please report");
|
||||
if (attempts > maxAttempts)
|
||||
{
|
||||
_logger.Error("We were stuck in repeatable quest generation. This should never happen. Please report");
|
||||
}
|
||||
|
||||
return newRepeatableQuest;
|
||||
}
|
||||
@@ -262,10 +279,12 @@ public class RepeatableQuestController(
|
||||
|
||||
// Find quest we're replacing in scav profile quests array and remove it
|
||||
if (fullProfile.CharacterData.ScavData is not null)
|
||||
{
|
||||
_questHelper.FindAndRemoveQuestFromArrayIfExists(
|
||||
questToReplaceId,
|
||||
fullProfile.CharacterData.ScavData.Quests
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,9 +302,15 @@ public class RepeatableQuestController(
|
||||
repeatablesInProfile.ActiveQuests.FirstOrDefault(repeatable => repeatable.Id == questId);
|
||||
if (questToReplace is null)
|
||||
// Not found, skip to next repeatable sub-type
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return new GetRepeatableByIdResult { Quest = questToReplace, RepeatableType = repeatablesInProfile };
|
||||
return new GetRepeatableByIdResult
|
||||
{
|
||||
Quest = questToReplace,
|
||||
RepeatableType = repeatablesInProfile
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -308,14 +333,19 @@ public class RepeatableQuestController(
|
||||
var canAccessRepeatables = CanProfileAccessRepeatableQuests(repeatableConfig, pmcData);
|
||||
if (!canAccessRepeatables)
|
||||
// Don't send any repeatables, even existing ones
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Existing repeatables are still valid, add to return data and move to next sub-type
|
||||
if (currentTime < generatedRepeatables.EndTime - 1)
|
||||
{
|
||||
returnData.Add(generatedRepeatables);
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"[Quest Check] {repeatableTypeLower} quests are still valid.");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"[Quest Check] {repeatableTypeLower} quests are still valid.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -325,7 +355,10 @@ public class RepeatableQuestController(
|
||||
// Set endtime to be now + new duration
|
||||
generatedRepeatables.EndTime = currentTime + repeatableConfig.ResetTime;
|
||||
generatedRepeatables.InactiveQuests = [];
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generating new {repeatableTypeLower}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Generating new {repeatableTypeLower}");
|
||||
}
|
||||
|
||||
// Put old quests to inactive (this is required since only then the client makes them fail due to non-completion)
|
||||
// Also need to push them to the "inactiveQuests" list since we need to remove them from offraidData.profile.Quests
|
||||
@@ -362,7 +395,10 @@ public class RepeatableQuestController(
|
||||
}
|
||||
|
||||
// check if there are no more quest types available
|
||||
if (questTypePool.Types.Count == 0) break;
|
||||
if (questTypePool.Types.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
quest.Side = repeatableConfig.Side;
|
||||
generatedRepeatables.ActiveQuests.Add(quest);
|
||||
@@ -370,13 +406,14 @@ public class RepeatableQuestController(
|
||||
|
||||
// Nullguard
|
||||
fullProfile.SptData.FreeRepeatableRefreshUsedCount ??= new Dictionary<string, int>();
|
||||
|
||||
|
||||
// Reset players free quest count for this repeatable sub-type as we're generating new repeatables for this group (daily/weekly)
|
||||
fullProfile.SptData.FreeRepeatableRefreshUsedCount[repeatableTypeLower] = 0;
|
||||
|
||||
// Create stupid redundant change requirements from quest data
|
||||
generatedRepeatables.ChangeRequirement = new Dictionary<string, ChangeRequirement>();
|
||||
foreach (var quest in generatedRepeatables.ActiveQuests)
|
||||
{
|
||||
generatedRepeatables.ChangeRequirement.TryAdd(
|
||||
quest.Id,
|
||||
new ChangeRequirement
|
||||
@@ -385,6 +422,7 @@ public class RepeatableQuestController(
|
||||
ChangeStandingCost = _randomUtil.GetArrayValue([0, 0.01]) // Randomise standing loss to replace
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Reset free repeatable values in player profile to defaults
|
||||
generatedRepeatables.FreeChanges = repeatableConfig.FreeChanges;
|
||||
@@ -440,12 +478,18 @@ public class RepeatableQuestController(
|
||||
private bool CanProfileAccessRepeatableQuests(RepeatableQuestConfig repeatableConfig, PmcData pmcData)
|
||||
{
|
||||
// PMC and daily quests not unlocked yet
|
||||
if (repeatableConfig.Side == "Pmc" && !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)) return false;
|
||||
if (repeatableConfig.Side == "Pmc" && !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scav and daily quests not unlocked yet
|
||||
if (repeatableConfig.Side == "Scav" && !PlayerHasDailyScavQuestsUnlocked(pmcData))
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug("Daily scav quests still locked, Intel center not built");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug("Daily scav quests still locked, Intel center not built");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -482,16 +526,21 @@ public class RepeatableQuestController(
|
||||
foreach (var activeQuest in generatedRepeatables.ActiveQuests)
|
||||
{
|
||||
var questStatusInProfile = pmcData.Quests.FirstOrDefault(quest => quest.QId == activeQuest.Id);
|
||||
if (questStatusInProfile is null) continue;
|
||||
if (questStatusInProfile is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep finished quests in list so player can hand in
|
||||
if (questStatusInProfile.Status == QuestStatusEnum.AvailableForFinish)
|
||||
{
|
||||
questsToKeep.Add(activeQuest);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug( // TODO: this shouldnt happen, doesnt on live
|
||||
$"Keeping repeatable quest: {activeQuest.Id} in activeQuests since it is available to hand in"
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -518,11 +567,13 @@ public class RepeatableQuestController(
|
||||
|
||||
// Populate Exploration and Pickup quest locations
|
||||
foreach (var (location, value) in locations)
|
||||
{
|
||||
if (location != ELocationName.any)
|
||||
{
|
||||
questPool.Pool.Exploration.Locations[location] = value;
|
||||
questPool.Pool.Pickup.Locations[location] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Add "any" to pickup quest pool
|
||||
questPool.Pool.Pickup.Locations[ELocationName.any] = ["any"];
|
||||
@@ -533,9 +584,16 @@ public class RepeatableQuestController(
|
||||
// Populate Elimination quest targets and their locations
|
||||
foreach (var targetKvP in targetsConfig)
|
||||
// Target is boss
|
||||
{
|
||||
if (targetKvP.Data.IsBoss.GetValueOrDefault(false))
|
||||
{
|
||||
questPool.Pool.Elimination.Targets.Add(targetKvP.Key, new TargetLocation { Locations = ["any"] });
|
||||
questPool.Pool.Elimination.Targets.Add(
|
||||
targetKvP.Key,
|
||||
new TargetLocation
|
||||
{
|
||||
Locations = ["any"]
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -550,8 +608,11 @@ public class RepeatableQuestController(
|
||||
: possibleLocations;
|
||||
|
||||
questPool.Pool.Elimination.Targets[targetKvP.Key] = new TargetLocation
|
||||
{ Locations = allowedLocations.Select(x => x.ToString()).ToList() };
|
||||
{
|
||||
Locations = allowedLocations.Select(x => x.ToString()).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return questPool;
|
||||
}
|
||||
@@ -588,10 +649,17 @@ public class RepeatableQuestController(
|
||||
{
|
||||
var locationNames = new List<string>();
|
||||
foreach (var locationName in value)
|
||||
{
|
||||
if (IsPmcLevelAllowedOnLocation(locationName, pmcLevel))
|
||||
{
|
||||
locationNames.Add(locationName);
|
||||
}
|
||||
}
|
||||
|
||||
if (locationNames.Count > 0) allowedLocation[location] = locationNames;
|
||||
if (locationNames.Count > 0)
|
||||
{
|
||||
allowedLocation[location] = locationNames;
|
||||
}
|
||||
}
|
||||
|
||||
return allowedLocation;
|
||||
@@ -606,10 +674,16 @@ public class RepeatableQuestController(
|
||||
protected bool IsPmcLevelAllowedOnLocation(string location, int pmcLevel)
|
||||
{
|
||||
// All PMC levels are allowed for 'any' location requirement
|
||||
if (location == ELocationName.any.ToString()) return true;
|
||||
if (location == ELocationName.any.ToString())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var locationBase = _databaseService.GetLocation(location.ToLower())?.Base;
|
||||
if (locationBase is null) return true;
|
||||
if (locationBase is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return pmcLevel <= locationBase.RequiredPlayerLevelMax && pmcLevel >= locationBase.RequiredPlayerLevelMin;
|
||||
}
|
||||
@@ -623,11 +697,15 @@ public class RepeatableQuestController(
|
||||
private int GetQuestCount(RepeatableQuestConfig repeatableConfig, PmcData pmcData)
|
||||
{
|
||||
var questCount = repeatableConfig.NumQuests.GetValueOrDefault(0);
|
||||
if (questCount == 0) _logger.Warning($"Repeatable {repeatableConfig.Name} quests have a count of 0");
|
||||
if (questCount == 0)
|
||||
{
|
||||
_logger.Warning($"Repeatable {repeatableConfig.Name} quests have a count of 0");
|
||||
}
|
||||
|
||||
// Add elite bonus to daily quests
|
||||
if (repeatableConfig.Name.ToLower() == "daily" && _profileHelper.HasEliteSkillLevel(SkillTypes.Charisma, pmcData))
|
||||
// Elite charisma skill gives extra daily quest(s)
|
||||
{
|
||||
questCount += _databaseService
|
||||
.GetGlobals()
|
||||
.Configuration
|
||||
@@ -637,13 +715,16 @@ public class RepeatableQuestController(
|
||||
.EliteBonusSettings
|
||||
.RepeatableQuestExtraCount
|
||||
.GetValueOrDefault(0);
|
||||
|
||||
}
|
||||
|
||||
// Prestige level 2 gives additional daily and weekly
|
||||
// do the logic for all other than "daily_savage"
|
||||
// use bigger than or equal incase modders add more
|
||||
if (repeatableConfig.Name.ToLower() != "daily_savage" && pmcData.Info.PrestigeLevel >= 2)
|
||||
{
|
||||
questCount++;
|
||||
|
||||
}
|
||||
|
||||
return questCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -12,6 +11,7 @@ using Core.Routers;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Controllers;
|
||||
@@ -41,7 +41,7 @@ public class TradeController(
|
||||
protected TraderConfig _traderConfig = _configServer.GetConfig<TraderConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Handle TradingConfirm event
|
||||
/// Handle TradingConfirm event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -58,7 +58,7 @@ public class TradeController(
|
||||
if (request.Type == "buy_from_trader")
|
||||
{
|
||||
var foundInRaid = _traderConfig.PurchasesAreFoundInRaid;
|
||||
var buyData = (ProcessBuyTradeRequestData)request;
|
||||
var buyData = (ProcessBuyTradeRequestData) request;
|
||||
_tradeHelper.buyItem(pmcData, buyData, sessionID, foundInRaid, output);
|
||||
|
||||
return output;
|
||||
@@ -67,7 +67,7 @@ public class TradeController(
|
||||
// Selling
|
||||
if (request.Type == "sell_to_trader")
|
||||
{
|
||||
var sellData = (ProcessSellTradeRequestData)request;
|
||||
var sellData = (ProcessSellTradeRequestData) request;
|
||||
_tradeHelper.sellItem(pmcData, pmcData, sellData, sessionID, output);
|
||||
|
||||
return output;
|
||||
@@ -80,7 +80,7 @@ public class TradeController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RagFairBuyOffer event
|
||||
/// Handle RagFairBuyOffer event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -97,11 +97,13 @@ public class TradeController(
|
||||
{
|
||||
var fleaOffer = _ragfairServer.GetOffer(offer.Id);
|
||||
if (fleaOffer is null)
|
||||
{
|
||||
return _httpResponseUtil.AppendErrorToOutput(
|
||||
output,
|
||||
$"Offer with ID {offer.Id} not found",
|
||||
BackendErrorCodes.OfferNotFound
|
||||
);
|
||||
}
|
||||
|
||||
if (offer.Count == 0)
|
||||
{
|
||||
@@ -113,19 +115,26 @@ public class TradeController(
|
||||
}
|
||||
|
||||
if (_ragfairOfferHelper.OfferIsFromTrader(fleaOffer))
|
||||
{
|
||||
BuyTraderItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
|
||||
}
|
||||
else
|
||||
{
|
||||
BuyPmcItemFromRagfair(sessionID, pmcData, fleaOffer, offer, output);
|
||||
}
|
||||
|
||||
// Exit loop early if problem found
|
||||
if (output.Warnings?.Count > 0) return output;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buy an item off the flea sold by a trader
|
||||
/// Buy an item off the flea sold by a trader
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
@@ -143,7 +152,10 @@ public class TradeController(
|
||||
if (PlayerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer, pmcData))
|
||||
{
|
||||
var errorMessage = $"Unable to buy item: {fleaOffer.Items[0].Template} from trader: {fleaOffer.User.Id} as loyalty level too low, skipping";
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug(errorMessage);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(errorMessage);
|
||||
}
|
||||
|
||||
_httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.RagfairUnavailable);
|
||||
|
||||
@@ -165,7 +177,7 @@ public class TradeController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buy an item off the flea sold by a PMC
|
||||
/// Buy an item off the flea sold by a PMC
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
@@ -192,7 +204,10 @@ public class TradeController(
|
||||
|
||||
// buyItem() must occur prior to removing the offer stack, otherwise item inside offer doesn't exist for confirmTrading() to use
|
||||
_tradeHelper.buyItem(pmcData, buyData, sessionId, _ragfairConfig.Dynamic.PurchasesAreFoundInRaid, output);
|
||||
if (output.Warnings?.Count > 0) return;
|
||||
if (output.Warnings?.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// resolve when a profile buy another profile's offer
|
||||
var offerOwnerId = fleaOffer.User?.Id;
|
||||
@@ -212,7 +227,7 @@ public class TradeController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the provided offerid and ownerid from a player made offer
|
||||
/// Is the provided offerid and ownerid from a player made offer
|
||||
/// </summary>
|
||||
/// <param name="offerId">id of the offer</param>
|
||||
/// <param name="offerOwnerId">Owner id</param>
|
||||
@@ -222,19 +237,24 @@ public class TradeController(
|
||||
string? offerOwnerId)
|
||||
{
|
||||
// No ownerid, not player offer
|
||||
if (offerOwnerId is null) return false;
|
||||
if (offerOwnerId is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var offerCreatorProfile = _profileHelper.GetPmcProfile(offerOwnerId);
|
||||
if (offerCreatorProfile is null || offerCreatorProfile.RagfairInfo.Offers?.Count == 0)
|
||||
// No profile or no offers
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does offer id exist in profile
|
||||
return offerCreatorProfile.RagfairInfo.Offers.Any(offer => offer.Id == offerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does Player have necessary trader loyalty to purchase flea offer
|
||||
/// Does Player have necessary trader loyalty to purchase flea offer
|
||||
/// </summary>
|
||||
/// <param name="fleaOffer">Flea offer being bought</param>
|
||||
/// <param name="pmcData">Player profile</param>
|
||||
@@ -247,7 +267,7 @@ public class TradeController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle SellAllFromSavage event
|
||||
/// Handle SellAllFromSavage event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -260,13 +280,13 @@ public class TradeController(
|
||||
{
|
||||
var output = _eventOutputHolder.GetOutput(sessionId);
|
||||
|
||||
MailMoneyToPlayer(sessionId, (int)request.TotalValue, Traders.FENCE);
|
||||
MailMoneyToPlayer(sessionId, (int) request.TotalValue, Traders.FENCE);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send the specified rouble total to player as mail
|
||||
/// Send the specified rouble total to player as mail
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="roublesToSend">amount of roubles to send</param>
|
||||
@@ -276,14 +296,20 @@ public class TradeController(
|
||||
int roublesToSend,
|
||||
string trader)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Selling scav items to fence for {roublesToSend} roubles");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Selling scav items to fence for {roublesToSend} roubles");
|
||||
}
|
||||
|
||||
// Create single currency item with all currency on it
|
||||
var rootCurrencyReward = new Item
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = Money.ROUBLES,
|
||||
Upd = new Upd { StackObjectsCount = roublesToSend }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = roublesToSend
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure money is properly split to follow its max stack size limit
|
||||
@@ -301,7 +327,7 @@ public class TradeController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up an items children and gets total handbook price for them
|
||||
/// Looks up an items children and gets total handbook price for them
|
||||
/// </summary>
|
||||
/// <param name="parentItemId">parent item that has children we want to sum price of</param>
|
||||
/// <param name="items">All items (parent + children)</param>
|
||||
@@ -322,10 +348,12 @@ public class TradeController(
|
||||
var itemDetails = _itemHelper.GetItem(itemToSell.Template);
|
||||
if (!(itemDetails.Key && _itemHelper.IsOfBaseclasses(itemDetails.Value.Id, traderDetails.ItemsBuy.Category)))
|
||||
// Skip if tpl isn't item OR item doesn't fulfil match traders buy categories
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get price of item multiplied by how many are in stack
|
||||
totalPrice += (int)((handbookPrices[itemToSell.Template] ?? 0) * (itemToSell.Upd?.StackObjectsCount ?? 1));
|
||||
totalPrice += (int) ((handbookPrices[itemToSell.Template] ?? 0) * (itemToSell.Upd?.StackObjectsCount ?? 1));
|
||||
}
|
||||
|
||||
return totalPrice;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -10,7 +9,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -35,9 +34,9 @@ public class TraderController(
|
||||
protected TraderConfig _traderConfig = _configServer.GetConfig<TraderConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Runs when onLoad event is fired
|
||||
/// Iterate over traders, ensure a pristine copy of their assorts is stored in traderAssortService
|
||||
/// Store timestamp of next assort refresh in nextResupply property of traders .base object
|
||||
/// Runs when onLoad event is fired
|
||||
/// Iterate over traders, ensure a pristine copy of their assorts is stored in traderAssortService
|
||||
/// Store timestamp of next assort refresh in nextResupply property of traders .base object
|
||||
/// </summary>
|
||||
public void Load()
|
||||
{
|
||||
@@ -77,7 +76,7 @@ public class TraderController(
|
||||
|
||||
// Set to next hour on clock or current time + 60 minutes
|
||||
trader.Base.NextResupply =
|
||||
traderResetStartsWithServer ? (int)_traderHelper.GetNextUpdateTimestamp(trader.Base.Id) : (int)nextHourTimestamp;
|
||||
traderResetStartsWithServer ? (int) _traderHelper.GetNextUpdateTimestamp(trader.Base.Id) : (int) nextHourTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +96,9 @@ public class TraderController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs when onUpdate is fired
|
||||
/// If current time is > nextResupply(expire) time of trader, refresh traders assorts and
|
||||
/// Fence is handled slightly differently
|
||||
/// Runs when onUpdate is fired
|
||||
/// If current time is > nextResupply(expire) time of trader, refresh traders assorts and
|
||||
/// Fence is handled slightly differently
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool Update()
|
||||
@@ -111,10 +110,14 @@ public class TraderController(
|
||||
case Traders.LIGHTHOUSEKEEPER:
|
||||
continue;
|
||||
case Traders.FENCE:
|
||||
{
|
||||
if (_fenceService.NeedsPartialRefresh()) _fenceService.GenerateFenceAssorts();
|
||||
continue;
|
||||
}
|
||||
{
|
||||
if (_fenceService.NeedsPartialRefresh())
|
||||
{
|
||||
_fenceService.GenerateFenceAssorts();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Trader needs to be refreshed
|
||||
@@ -131,7 +134,7 @@ public class TraderController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/api/traderSettings
|
||||
/// Handle client/trading/api/traderSettings
|
||||
/// </summary>
|
||||
/// <param name="sessionId">session id</param>
|
||||
/// <returns>Return a list of all traders</returns>
|
||||
@@ -143,7 +146,10 @@ public class TraderController(
|
||||
{
|
||||
traders.Add(_traderHelper.GetTrader(traderId, sessionId));
|
||||
|
||||
if (pmcData?.Info != null) _traderHelper.LevelUp(traderId, pmcData);
|
||||
if (pmcData?.Info != null)
|
||||
{
|
||||
_traderHelper.LevelUp(traderId, pmcData);
|
||||
}
|
||||
}
|
||||
|
||||
traders.Sort(SortByTraderId);
|
||||
@@ -151,7 +157,7 @@ public class TraderController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Order traders by their traderId (Ttid)
|
||||
/// Order traders by their traderId (Ttid)
|
||||
/// </summary>
|
||||
/// <param name="traderA">First trader to compare</param>
|
||||
/// <param name="traderB">Second trader to compare</param>
|
||||
@@ -162,7 +168,7 @@ public class TraderController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/api/getTrader
|
||||
/// Handle client/trading/api/getTrader
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="traderId"></param>
|
||||
@@ -173,7 +179,7 @@ public class TraderController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/trading/api/getTraderAssort
|
||||
/// Handle client/trading/api/getTraderAssort
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="traderId"></param>
|
||||
@@ -184,7 +190,7 @@ public class TraderController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/items/prices/TRADERID
|
||||
/// Handle client/items/prices/TRADERID
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public GetItemPricesResponse GetItemPrices(string sessionId, string traderId)
|
||||
@@ -195,7 +201,7 @@ public class TraderController(
|
||||
{
|
||||
SupplyNextTime = _traderHelper.GetNextUpdateTimestamp(traderId),
|
||||
Prices = handbookPrices,
|
||||
CurrencyCourses = new Dictionary<string, double>()
|
||||
CurrencyCourses = new Dictionary<string, double>
|
||||
{
|
||||
{ "5449016a4bdc2d6f028b456f", handbookPrices[Money.ROUBLES] },
|
||||
{ "569668774bdc2da2298b4568", handbookPrices[Money.EUROS] },
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Generators;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Weather;
|
||||
@@ -8,7 +7,7 @@ using Core.Models.Spt.Weather;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -26,7 +25,7 @@ public class WeatherController(
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/weather
|
||||
/// Handle client/weather
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public WeatherData Generate()
|
||||
@@ -47,7 +46,7 @@ public class WeatherController(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle client/localGame/weather
|
||||
/// Handle client/localGame/weather
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Eft.Wishlist;
|
||||
using Core.Routers;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Controllers;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class WishlistController(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle AddToWishList
|
||||
/// Handle AddToWishList
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -23,13 +23,16 @@ public class WishlistController(
|
||||
AddToWishlistRequest request,
|
||||
string sessionId)
|
||||
{
|
||||
foreach (var item in request.Items) pmcData.WishList.Dictionary.Add(item.Key, item.Value);
|
||||
foreach (var item in request.Items)
|
||||
{
|
||||
pmcData.WishList.Dictionary.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle RemoveFromWishList event
|
||||
/// Handle RemoveFromWishList event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="request"></param>
|
||||
@@ -40,13 +43,16 @@ public class WishlistController(
|
||||
RemoveFromWishlistRequest request,
|
||||
string sessionId)
|
||||
{
|
||||
foreach (var itemId in request.Items) pmcData.WishList.Dictionary.Remove(itemId);
|
||||
foreach (var itemId in request.Items)
|
||||
{
|
||||
pmcData.WishList.Dictionary.Remove(itemId);
|
||||
}
|
||||
|
||||
return _eventOutputHolder.GetOutput(sessionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle changeWishlistItemCategory event
|
||||
/// Handle changeWishlistItemCategory event
|
||||
/// </summary>
|
||||
/// <param name="pmcData"></param>
|
||||
/// <param name="request"></param>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SptDependencyInjection\SptDependencyInjection.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SptDependencyInjection\SptDependencyInjection.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+25
-12
@@ -20,7 +20,10 @@ public abstract class Router
|
||||
|
||||
protected List<HandledRoute> GetInternalHandledRoutes()
|
||||
{
|
||||
if (handledRoutes.Count == 0) handledRoutes = GetHandledRoutes();
|
||||
if (handledRoutes.Count == 0)
|
||||
{
|
||||
handledRoutes = GetHandledRoutes();
|
||||
}
|
||||
|
||||
return handledRoutes;
|
||||
}
|
||||
@@ -28,13 +31,15 @@ public abstract class Router
|
||||
public bool CanHandle(string url, bool partialMatch = false)
|
||||
{
|
||||
if (partialMatch)
|
||||
{
|
||||
return GetInternalHandledRoutes()
|
||||
.Where((r) => r.dynamic)
|
||||
.Any((r) => url.Contains(r.route));
|
||||
.Where(r => r.dynamic)
|
||||
.Any(r => url.Contains(r.route));
|
||||
}
|
||||
|
||||
return GetInternalHandledRoutes()
|
||||
.Where((r) => !r.dynamic)
|
||||
.Any((r) => r.route == url);
|
||||
.Where(r => !r.dynamic)
|
||||
.Any(r => r.route == url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +48,7 @@ public abstract class StaticRouter : Router
|
||||
private readonly List<RouteAction> _actions;
|
||||
private readonly JsonUtil _jsonUtil;
|
||||
|
||||
public StaticRouter(JsonUtil jsonUtil, List<RouteAction> routes) : base()
|
||||
public StaticRouter(JsonUtil jsonUtil, List<RouteAction> routes)
|
||||
{
|
||||
_actions = routes;
|
||||
_jsonUtil = jsonUtil;
|
||||
@@ -54,22 +59,26 @@ public abstract class StaticRouter : Router
|
||||
var action = _actions.Single(route => route.url == url);
|
||||
var type = action.bodyType;
|
||||
IRequestData? info = null;
|
||||
if (type != null && !string.IsNullOrEmpty(body)) info = (IRequestData?)_jsonUtil.Deserialize(body, type);
|
||||
if (type != null && !string.IsNullOrEmpty(body))
|
||||
{
|
||||
info = (IRequestData?) _jsonUtil.Deserialize(body, type);
|
||||
}
|
||||
|
||||
return action.action(url, info, sessionID, output);
|
||||
}
|
||||
|
||||
protected override List<HandledRoute> GetHandledRoutes()
|
||||
{
|
||||
return _actions.Select((route) => new HandledRoute(route.url, false)).ToList();
|
||||
return _actions.Select(route => new HandledRoute(route.url, false)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class DynamicRouter : Router
|
||||
{
|
||||
private readonly List<RouteAction> actions;
|
||||
private readonly JsonUtil _jsonUtil;
|
||||
private readonly List<RouteAction> actions;
|
||||
|
||||
public DynamicRouter(JsonUtil jsonUtil, List<RouteAction> routes) : base()
|
||||
public DynamicRouter(JsonUtil jsonUtil, List<RouteAction> routes)
|
||||
{
|
||||
actions = routes;
|
||||
_jsonUtil = jsonUtil;
|
||||
@@ -80,13 +89,17 @@ public abstract class DynamicRouter : Router
|
||||
var action = actions.First(r => url.Contains(r.url));
|
||||
var type = action.bodyType;
|
||||
IRequestData? info = null;
|
||||
if (type != null && !string.IsNullOrEmpty(body)) info = (IRequestData?)_jsonUtil.Deserialize(body, type);
|
||||
if (type != null && !string.IsNullOrEmpty(body))
|
||||
{
|
||||
info = (IRequestData?) _jsonUtil.Deserialize(body, type);
|
||||
}
|
||||
|
||||
return action.action(url, info, sessionID, output);
|
||||
}
|
||||
|
||||
protected override List<HandledRoute> GetHandledRoutes()
|
||||
{
|
||||
return actions.Select((route) => new HandledRoute(route.url, true)).ToList();
|
||||
return actions.Select(route => new HandledRoute(route.url, true)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -11,6 +10,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using BodyPart = Core.Models.Eft.Common.Tables.BodyPart;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
@@ -42,7 +42,7 @@ public class BotGenerator(
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Generate a player scav bot object
|
||||
/// Generate a player scav bot object
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="role">e.g. assault / pmcbot</param>
|
||||
@@ -110,7 +110,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create 1 bot of the type/side/difficulty defined in botGenerationDetails
|
||||
/// Create 1 bot of the type/side/difficulty defined in botGenerationDetails
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="botGenerationDetails">details on how to generate bots</param>
|
||||
@@ -128,13 +128,16 @@ public class BotGenerator(
|
||||
? 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 null) _logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
|
||||
if (botJsonTemplateClone is null)
|
||||
{
|
||||
_logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type");
|
||||
}
|
||||
|
||||
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a clone of the default bot base object and adjust its role/side/difficulty values
|
||||
/// Get a clone of the default bot base object and adjust its role/side/difficulty values
|
||||
/// </summary>
|
||||
/// <param name="botRole">Role bot should have</param>
|
||||
/// <param name="botSide">Side bot should have</param>
|
||||
@@ -151,7 +154,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a clone of the database\bots\base.json file
|
||||
/// Get a clone of the database\bots\base.json file
|
||||
/// </summary>
|
||||
/// <returns>BotBase object</returns>
|
||||
public BotBase GetCloneOfBotBase()
|
||||
@@ -160,7 +163,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a IBotBase object with equipment/loot/exp etc
|
||||
/// Create a IBotBase object with equipment/loot/exp etc
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="bot">Bots base file</param>
|
||||
@@ -182,12 +185,14 @@ public class BotGenerator(
|
||||
|
||||
// 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,
|
||||
@@ -210,20 +215,27 @@ public class BotGenerator(
|
||||
|
||||
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;
|
||||
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 = (int)GetExperienceRewardForKillByDifficulty(
|
||||
bot.Info.Settings.Experience = (int) GetExperienceRewardForKillByDifficulty(
|
||||
botJsonTemplate.BotExperience.Reward,
|
||||
botGenerationDetails.BotDifficulty,
|
||||
botGenerationDetails.Role
|
||||
@@ -248,7 +260,10 @@ public class BotGenerator(
|
||||
{
|
||||
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);
|
||||
if (bot.Info.GameVersion == GameEditions.UNHEARD)
|
||||
{
|
||||
AddAdditionalPocketLootWeightsForUnheardBot(botJsonTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
// Add drip
|
||||
@@ -266,7 +281,10 @@ public class BotGenerator(
|
||||
bot.Info.GameVersion
|
||||
);
|
||||
|
||||
if (_botConfig.BotRolesWithDogTags.Contains(botRoleLowercase)) AddDogtagToBot(bot);
|
||||
if (_botConfig.BotRolesWithDogTags.Contains(botRoleLowercase))
|
||||
{
|
||||
AddDogtagToBot(bot);
|
||||
}
|
||||
|
||||
// Generate new bot ID
|
||||
AddIdsToBot(bot, botGenerationDetails);
|
||||
@@ -275,13 +293,16 @@ public class BotGenerator(
|
||||
GenerateInventoryId(bot);
|
||||
|
||||
// Set role back to originally requested now its been generated
|
||||
if (botGenerationDetails.EventRole is not null) bot.Info.Settings.Role = botGenerationDetails.EventRole;
|
||||
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 altered 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>
|
||||
@@ -291,7 +312,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get exp for kill by bot difficulty
|
||||
/// Get exp for kill by bot difficulty
|
||||
/// </summary>
|
||||
/// <param name="experiences">Dict of difficulties and experience</param>
|
||||
/// <param name="botDifficulty">the killed bots difficulty</param>
|
||||
@@ -301,7 +322,10 @@ public class BotGenerator(
|
||||
{
|
||||
if (!experiences.TryGetValue(botDifficulty.ToLower(), out var result))
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`");
|
||||
}
|
||||
|
||||
return _randomUtil.GetDouble(experiences["normal"].Min.Value, experiences["normal"].Max.Value);
|
||||
}
|
||||
@@ -310,7 +334,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the standing value change when player kills a bot
|
||||
/// Get the standing value change when player kills a bot
|
||||
/// </summary>
|
||||
/// <param name="standingForKill">Dictionary of standing values keyed by bot difficulty</param>
|
||||
/// <param name="botDifficulty">Difficulty of bot to look up</param>
|
||||
@@ -329,7 +353,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the agressor bonus value when player kills a bot
|
||||
/// Get the agressor bonus value when player kills a bot
|
||||
/// </summary>
|
||||
/// <param name="aggressorBonus">Dictionary of standing values keyed by bot difficulty</param>
|
||||
/// <param name="botDifficulty">Difficulty of bot to look up</param>
|
||||
@@ -348,7 +372,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set weighting of flagged equipment to 0
|
||||
/// Set weighting of flagged equipment to 0
|
||||
/// </summary>
|
||||
/// <param name="botJsonTemplate">Bot data to adjust</param>
|
||||
/// <param name="botGenerationDetails">Generation details of bot</param>
|
||||
@@ -361,7 +385,9 @@ public class BotGenerator(
|
||||
|
||||
if (blacklist?.Gear is null)
|
||||
// Nothing to filter by
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (equipmentSlot, blacklistedTpls) in blacklist.Gear)
|
||||
{
|
||||
@@ -369,12 +395,14 @@ public class BotGenerator(
|
||||
|
||||
foreach (var blacklistedTpl in blacklistedTpls)
|
||||
// Set weighting to 0, will never be picked
|
||||
{
|
||||
equipmentDict[blacklistedTpl] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Complete Summary
|
||||
/// TODO: Complete Summary
|
||||
/// </summary>
|
||||
/// <param name="botJsonTemplate">Bot data to adjust</param>
|
||||
public void AddAdditionalPocketLootWeightsForUnheardBot(BotType botJsonTemplate)
|
||||
@@ -386,7 +414,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove items from item.json/lootableItemBlacklist from bots inventory
|
||||
/// Remove items from item.json/lootableItemBlacklist from bots inventory
|
||||
/// </summary>
|
||||
/// <param name="botInventory">Bot to filter</param>
|
||||
public void RemoveBlacklistedLootFromBotTemplate(BotTypeInventory botInventory)
|
||||
@@ -398,24 +426,34 @@ public class BotGenerator(
|
||||
foreach (var lootContainerKey in lootContainersToFilter)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => string.Equals(x.Name, lootContainerKey, StringComparison.CurrentCultureIgnoreCase));
|
||||
var propValue = (Dictionary<string, double>)prop.GetValue(botInventory.Items);
|
||||
var propValue = (Dictionary<string, double>) prop.GetValue(botInventory.Items);
|
||||
|
||||
// No container, skip
|
||||
if (propValue?.Count == 0) continue;
|
||||
if (propValue?.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<string> tplsToRemove = [];
|
||||
foreach (var (key, _) in propValue)
|
||||
{
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(key))
|
||||
{
|
||||
tplsToRemove.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var blacklistedTplToRemove in tplsToRemove) propValue.Remove(blacklistedTplToRemove);
|
||||
foreach (var blacklistedTplToRemove in tplsToRemove)
|
||||
{
|
||||
propValue.Remove(blacklistedTplToRemove);
|
||||
}
|
||||
|
||||
prop.SetValue(botInventory.Items, propValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose various appearance settings for a bot using weights: head/body/feet/hands
|
||||
/// Choose various appearance settings for a bot using weights: head/body/feet/hands
|
||||
/// </summary>
|
||||
/// <param name="bot">Bot to adjust</param>
|
||||
/// <param name="appearance">Appearance settings to choose from</param>
|
||||
@@ -438,17 +476,26 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log the number of PMCs generated to the debug console
|
||||
/// Log the number of PMCs generated to the debug console
|
||||
/// </summary>
|
||||
/// <param name="output">Generated bot array, ready to send to client</param>
|
||||
public void LogPmcGeneratedCount(List<BotBase> output)
|
||||
{
|
||||
var pmcCount = output.Aggregate(0, (acc, cur) => { return cur.Info.Side is "Bear" or "Usec" ? acc + 1 : acc; });
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generated {output.Count} total bots. Replaced {pmcCount} with PMCs");
|
||||
var pmcCount = output.Aggregate(
|
||||
0,
|
||||
(acc, cur) =>
|
||||
{
|
||||
return cur.Info.Side is "Bear" or "Usec" ? acc + 1 : acc;
|
||||
}
|
||||
);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Generated {output.Count} total bots. Replaced {pmcCount} with PMCs");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts health object to the required format
|
||||
/// Converts health object to the required format
|
||||
/// </summary>
|
||||
/// <param name="healthObj">health object from bot json</param>
|
||||
/// <param name="playerScav">Is a pscav bot being generated</param>
|
||||
@@ -557,14 +604,16 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sum up body parts max hp values, return the bodyPart collection with lowest value
|
||||
/// Sum up body parts max hp values, return the bodyPart collection with lowest value
|
||||
/// </summary>
|
||||
/// <param name="bodies">Body parts to sum up</param>
|
||||
/// <returns>Lowest hp collection</returns>
|
||||
public BodyPart? GetLowestHpBody(List<BodyPart> bodies)
|
||||
{
|
||||
if (bodies.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
BodyPart result = new();
|
||||
var props = result.GetType().GetProperties();
|
||||
@@ -575,7 +624,7 @@ public class BotGenerator(
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var value = (MinMax)prop.GetValue(bodyPart);
|
||||
var value = (MinMax) prop.GetValue(bodyPart);
|
||||
hpTotal += value.Max;
|
||||
}
|
||||
|
||||
@@ -591,7 +640,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a bots skills with randomsied progress value between the min and max values
|
||||
/// Get a bots skills with randomsied progress value between the min and max values
|
||||
/// </summary>
|
||||
/// <param name="botSkills">Skills that should have their progress value randomised</param>
|
||||
/// <returns>Skills</returns>
|
||||
@@ -608,7 +657,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomise the progress value of passed in skills based on the min/max value
|
||||
/// Randomise the progress value of passed in skills based on the min/max value
|
||||
/// </summary>
|
||||
/// <param name="skills">Skills to randomise</param>
|
||||
/// <param name="isCommonSkills">Are the skills 'common' skills</param>
|
||||
@@ -616,14 +665,19 @@ public class BotGenerator(
|
||||
public List<BaseSkill> GetSkillsWithRandomisedProgressValue(Dictionary<string, MinMax>? skills, bool isCommonSkills)
|
||||
{
|
||||
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;
|
||||
if (skill == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All skills have id and progress props
|
||||
var skillToAdd = new BaseSkill
|
||||
@@ -647,7 +701,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate an id+aid for a bot and apply
|
||||
/// Generate an id+aid for a bot and apply
|
||||
/// </summary>
|
||||
/// <param name="bot">bot to update</param>
|
||||
/// <param name="botGenerationDetails"></param>
|
||||
@@ -661,8 +715,8 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update a profiles profile.Inventory.equipment value with a freshly generated one.
|
||||
/// Update all inventory items that make use of this value too.
|
||||
/// Update a profiles profile.Inventory.equipment value with a freshly generated one.
|
||||
/// Update all inventory items that make use of this value too.
|
||||
/// </summary>
|
||||
/// <param name="profile">Profile to update</param>
|
||||
public void GenerateInventoryId(BotBase profile)
|
||||
@@ -681,10 +735,16 @@ public class BotGenerator(
|
||||
|
||||
// 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;
|
||||
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;
|
||||
if (item.ParentId == profile.Inventory.Equipment)
|
||||
{
|
||||
item.ParentId = newInventoryItemId;
|
||||
}
|
||||
}
|
||||
|
||||
// Update inventory equipment id to new one we generated
|
||||
@@ -692,9 +752,9 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomise a bots game version and account category.
|
||||
/// Chooses from all the game versions (standard, eod etc).
|
||||
/// Chooses account type (default, Sherpa, etc).
|
||||
/// Randomise a bots game version and account category.
|
||||
/// Chooses from all the game versions (standard, eod etc).
|
||||
/// Chooses account type (default, Sherpa, etc).
|
||||
/// </summary>
|
||||
/// <param name="botInfo">bot info object to update</param>
|
||||
/// <returns>Chosen game version</returns>
|
||||
@@ -734,7 +794,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a side-specific (usec/bear) dogtag item to a bots inventory
|
||||
/// Add a side-specific (usec/bear) dogtag item to a bots inventory
|
||||
/// </summary>
|
||||
/// <param name="bot">bot to add dogtag to</param>
|
||||
/// <returns></returns>
|
||||
@@ -756,7 +816,7 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a dogtag tpl that matches the bots game version and side
|
||||
/// Get a dogtag tpl that matches the bots game version and side
|
||||
/// </summary>
|
||||
/// <param name="side">Usec/Bear</param>
|
||||
/// <param name="gameVersion">edge_of_darkness / standard</param>
|
||||
@@ -764,6 +824,7 @@ public class BotGenerator(
|
||||
public string GetDogtagTplByGameVersionAndSide(string side, string gameVersion)
|
||||
{
|
||||
if (side.ToLower() == "usec")
|
||||
{
|
||||
switch (gameVersion)
|
||||
{
|
||||
case GameEditions.EDGE_OF_DARKNESS:
|
||||
@@ -773,6 +834,7 @@ public class BotGenerator(
|
||||
default:
|
||||
return ItemTpl.BARTER_DOGTAG_USEC;
|
||||
}
|
||||
}
|
||||
|
||||
switch (gameVersion)
|
||||
{
|
||||
@@ -786,14 +848,14 @@ public class BotGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust a PMCs pocket tpl to UHD if necessary, otherwise do nothing
|
||||
/// Adjust a PMCs pocket tpl to UHD if necessary, otherwise do nothing
|
||||
/// </summary>
|
||||
/// <param name="bot">Pmc object to adjust</param>
|
||||
public void SetPmcPocketsByGameVersion(BotBase bot)
|
||||
{
|
||||
if (bot.Info.GameVersion == GameEditions.UNHEARD)
|
||||
{
|
||||
var pockets = bot.Inventory.Items.FirstOrDefault((item) => item.SlotId == "Pockets");
|
||||
var pockets = bot.Inventory.Items.FirstOrDefault(item => item.SlotId == "Pockets");
|
||||
pockets.Template = ItemTpl.POCKETS_1X4_TUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -10,6 +9,7 @@ using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ public class BotInventoryGenerator(
|
||||
ConfigServer _configServer
|
||||
)
|
||||
{
|
||||
private BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
|
||||
// Slots handled individually inside `GenerateAndAddEquipmentToBot`
|
||||
private List<EquipmentSlots> _excludedEquipmentSlots =
|
||||
private readonly List<EquipmentSlots> _excludedEquipmentSlots =
|
||||
[
|
||||
EquipmentSlots.Pockets,
|
||||
EquipmentSlots.FirstPrimaryWeapon,
|
||||
@@ -54,7 +54,7 @@ public class BotInventoryGenerator(
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Add equipment/weapons/loot to bot
|
||||
/// Add equipment/weapons/loot to bot
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="botJsonTemplate">Base json db file for the bot having its loot generated</param>
|
||||
@@ -107,7 +107,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a pmcInventory object with all the base/generic items needed
|
||||
/// Create a pmcInventory object with all the base/generic items needed
|
||||
/// </summary>
|
||||
/// <returns>PmcInventory object</returns>
|
||||
public BotBaseInventory GenerateInventoryBase()
|
||||
@@ -123,12 +123,36 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
Items =
|
||||
[
|
||||
new Item { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
|
||||
new Item { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 },
|
||||
new Item { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID },
|
||||
new Item { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE },
|
||||
new Item { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE },
|
||||
new Item { Id = hideoutCustomizationStashId, Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION }
|
||||
new Item
|
||||
{
|
||||
Id = equipmentId,
|
||||
Template = ItemTpl.INVENTORY_DEFAULT
|
||||
},
|
||||
new Item
|
||||
{
|
||||
Id = stashId,
|
||||
Template = ItemTpl.STASH_STANDARD_STASH_10X30
|
||||
},
|
||||
new Item
|
||||
{
|
||||
Id = questRaidItemsId,
|
||||
Template = ItemTpl.STASH_QUESTRAID
|
||||
},
|
||||
new Item
|
||||
{
|
||||
Id = questStashItemsId,
|
||||
Template = ItemTpl.STASH_QUESTOFFLINE
|
||||
},
|
||||
new Item
|
||||
{
|
||||
Id = sortingTableId,
|
||||
Template = ItemTpl.SORTINGTABLE_SORTING_TABLE
|
||||
},
|
||||
new Item
|
||||
{
|
||||
Id = hideoutCustomizationStashId,
|
||||
Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION
|
||||
}
|
||||
],
|
||||
Equipment = equipmentId,
|
||||
Stash = stashId,
|
||||
@@ -143,7 +167,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add equipment to a bot
|
||||
/// Add equipment to a bot
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="templateInventory">bot/x.json data from db</param>
|
||||
@@ -165,12 +189,16 @@ public class BotInventoryGenerator(
|
||||
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);
|
||||
@@ -184,7 +212,10 @@ public class BotInventoryGenerator(
|
||||
{
|
||||
// Skip some slots as they need to be done in a specific order + with specific parameter values
|
||||
// e.g. Weapons
|
||||
if (_excludedEquipmentSlots.Contains(equipmentSlot)) continue;
|
||||
if (_excludedEquipmentSlots.Contains(equipmentSlot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateEquipment(
|
||||
new GenerateEquipmentProperties
|
||||
@@ -193,7 +224,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = value,
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -211,7 +247,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = GetPocketPoolByGameEdition(chosenGameVersion, templateInventory),
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -227,7 +268,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.FaceCover],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -242,7 +288,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Headwear],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -257,7 +308,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.Earpiece],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -272,7 +328,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.ArmorVest],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -283,15 +344,22 @@ public class BotInventoryGenerator(
|
||||
// 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;
|
||||
if (botEquipConfig.ForceRigWhenNoVest.GetValueOrDefault(false) && !hasArmorVest)
|
||||
{
|
||||
wornItemChances.EquipmentChances["TacticalVest"] = 100;
|
||||
}
|
||||
|
||||
GenerateEquipment(
|
||||
new GenerateEquipmentProperties
|
||||
@@ -300,7 +368,12 @@ public class BotInventoryGenerator(
|
||||
RootEquipmentPool = templateInventory.Equipment[EquipmentSlots.TacticalVest],
|
||||
ModPool = templateInventory.Mods,
|
||||
SpawnChances = wornItemChances,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
Inventory = botInventory,
|
||||
BotEquipmentConfig = botEquipConfig,
|
||||
RandomisationDetails = randomistionDetails,
|
||||
@@ -312,12 +385,15 @@ public class BotInventoryGenerator(
|
||||
protected Dictionary<string, double> GetPocketPoolByGameEdition(string chosenGameVersion, BotTypeInventory templateInventory)
|
||||
{
|
||||
return chosenGameVersion == GameEditions.UNHEARD
|
||||
? new Dictionary<string, double> { [ItemTpl.POCKETS_1X4_TUE] = 1 }
|
||||
? new Dictionary<string, double>
|
||||
{
|
||||
[ItemTpl.POCKETS_1X4_TUE] = 1
|
||||
}
|
||||
: templateInventory.Equipment.GetValueOrDefault(EquipmentSlots.Pockets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove non-armored rigs from parameter data
|
||||
/// Remove non-armored rigs from parameter data
|
||||
/// </summary>
|
||||
/// <param name="templateEquipment">Equipment to filter TacticalVest of</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
@@ -329,7 +405,10 @@ public class BotInventoryGenerator(
|
||||
|
||||
if (!tacVestsWithArmor.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -338,7 +417,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove armored rigs from parameter data
|
||||
/// Remove armored rigs from parameter data
|
||||
/// </summary>
|
||||
/// <param name="templateEquipment">Equipment to filter TacticalVest by</param>
|
||||
/// <param name="botRole">Role of bot vests are being filtered for</param>
|
||||
@@ -352,7 +431,10 @@ public class BotInventoryGenerator(
|
||||
|
||||
if (!allowEmptyResult && !tacVestsWithoutArmor.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -361,7 +443,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a piece of equipment with mods to inventory from the provided pools
|
||||
/// Add a piece of equipment with mods to inventory from the provided pools
|
||||
/// </summary>
|
||||
/// <param name="settings">Values to adjust how item is chosen and added to bot</param>
|
||||
/// <returns>true when item added</returns>
|
||||
@@ -396,7 +478,10 @@ public class BotInventoryGenerator(
|
||||
var attempts = 0;
|
||||
while (!found)
|
||||
{
|
||||
if (!settings.RootEquipmentPool.Any()) return false;
|
||||
if (!settings.RootEquipmentPool.Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool);
|
||||
var dbResult = _itemHelper.GetItem(chosenItemTpl);
|
||||
@@ -404,7 +489,10 @@ public class BotInventoryGenerator(
|
||||
if (!dbResult.Key)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("bot-missing_item_template", chosenItemTpl));
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
|
||||
}
|
||||
|
||||
// Remove picked item
|
||||
settings.RootEquipmentPool.Remove(chosenItemTpl);
|
||||
@@ -423,7 +511,10 @@ public class BotInventoryGenerator(
|
||||
if (compatibilityResult.Incompatible ?? false)
|
||||
{
|
||||
// Tried x different items that failed, stop
|
||||
if (attempts > maxAttempts) return false;
|
||||
if (attempts > maxAttempts)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove picked item from pool
|
||||
settings.RootEquipmentPool.Remove(chosenItemTpl);
|
||||
@@ -460,10 +551,13 @@ public class BotInventoryGenerator(
|
||||
settings.RandomisationDetails?.RandomisedArmorSlots != null &&
|
||||
settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString()))
|
||||
// Filter out mods from relevant blacklist
|
||||
{
|
||||
settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem(
|
||||
pickedItemDb.Id,
|
||||
botEquipBlacklist.Equipment
|
||||
);
|
||||
}
|
||||
|
||||
var itemIsOnGenerateModBlacklist = settings.GenerateModsBlacklist != null && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id);
|
||||
// Does item have slots for sub-mods to be inserted into
|
||||
if (pickedItemDb.Properties?.Slots?.Count > 0 && !itemIsOnGenerateModBlacklist)
|
||||
@@ -490,7 +584,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all possible mods for item and filter down based on equipment blacklist from bot.json config
|
||||
/// Get all possible mods for item and filter down based on equipment blacklist from bot.json config
|
||||
/// </summary>
|
||||
/// <param name="itemTpl">Item mod pool is being retrieved and filtered</param>
|
||||
/// <param name="equipmentBlacklist">Blacklist to filter mod pool with</param>
|
||||
@@ -501,11 +595,15 @@ public class BotInventoryGenerator(
|
||||
foreach (var modSlot in modPool)
|
||||
{
|
||||
// Get blacklist
|
||||
if (!equipmentBlacklist.TryGetValue(modSlot.Key, out var blacklistedMods)) blacklistedMods = [];
|
||||
if (!equipmentBlacklist.TryGetValue(modSlot.Key, out var blacklistedMods))
|
||||
{
|
||||
blacklistedMods = [];
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// Get mods not on blacklist
|
||||
var filteredMods = modPool[modSlot.Key].Where((slotName) => !blacklistedMods.Contains(slotName));
|
||||
var filteredMods = modPool[modSlot.Key].Where(slotName => !blacklistedMods.Contains(slotName));
|
||||
if (!filteredMods.Any())
|
||||
{
|
||||
_logger.Warning($"Filtering {modSlot.Key} pool resulting in 0 items, skipping filter");
|
||||
@@ -519,7 +617,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Work out what weapons bot should have equipped and add them to bot inventory
|
||||
/// Work out what weapons bot should have equipped and add them to bot inventory
|
||||
/// </summary>
|
||||
/// <param name="templateInventory">bot/x.json data from db</param>
|
||||
/// <param name="equipmentChances">Chances bot can have equipment equipped</param>
|
||||
@@ -535,7 +633,9 @@ public class BotInventoryGenerator(
|
||||
var weaponSlotsToFill = GetDesiredWeaponsForBot(equipmentChances);
|
||||
foreach (var desiredWeapons in weaponSlotsToFill)
|
||||
// Add weapon to bot if true and bot json has something to put into the slot
|
||||
{
|
||||
if (desiredWeapons.ShouldSpawn && templateInventory.Equipment[desiredWeapons.Slot].Any())
|
||||
{
|
||||
AddWeaponAndMagazinesToInventory(
|
||||
sessionId,
|
||||
desiredWeapons,
|
||||
@@ -547,10 +647,12 @@ public class BotInventoryGenerator(
|
||||
itemGenerationLimitsMinMax,
|
||||
botLevel
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate if the bot should have weapons in Primary/Secondary/Holster slots
|
||||
/// Calculate if the bot should have weapons in Primary/Secondary/Holster slots
|
||||
/// </summary>
|
||||
/// <param name="equipmentChances">Chances bot has certain equipment</param>
|
||||
/// <returns>What slots bot should have weapons generated for</returns>
|
||||
@@ -561,7 +663,8 @@ public class BotInventoryGenerator(
|
||||
[
|
||||
new DesiredWeapons
|
||||
{
|
||||
Slot = EquipmentSlots.FirstPrimaryWeapon, ShouldSpawn = shouldSpawnPrimary
|
||||
Slot = EquipmentSlots.FirstPrimaryWeapon,
|
||||
ShouldSpawn = shouldSpawnPrimary
|
||||
},
|
||||
new DesiredWeapons
|
||||
{
|
||||
@@ -577,7 +680,7 @@ public class BotInventoryGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add weapon + spare mags/ammo to bots inventory
|
||||
/// Add weapon + spare mags/ammo to bots inventory
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="weaponSlot">Weapon slot being generated</param>
|
||||
@@ -616,7 +719,15 @@ public class BotInventoryGenerator(
|
||||
|
||||
public class DesiredWeapons
|
||||
{
|
||||
public EquipmentSlots Slot { get; set; }
|
||||
public EquipmentSlots Slot
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool ShouldSpawn { get; set; }
|
||||
public bool ShouldSpawn
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Bot;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -6,7 +5,7 @@ using Core.Models.Spt.Bots;
|
||||
using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
@@ -19,7 +18,7 @@ public class BotLevelGenerator(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a randomised bot level and exp value
|
||||
/// Return a randomised bot level and exp value
|
||||
/// </summary>
|
||||
/// <param name="levelDetails">Min and max of level for bot</param>
|
||||
/// <param name="botGenerationDetails">Details to help generate a bot</param>
|
||||
@@ -27,7 +26,14 @@ public class BotLevelGenerator(
|
||||
/// <returns>IRandomisedBotLevelResult object</returns>
|
||||
public RandomisedBotLevelResult GenerateBotLevel(MinMax levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot)
|
||||
{
|
||||
if (!botGenerationDetails.IsPmc.GetValueOrDefault(false)) return new RandomisedBotLevelResult { Exp = 0, Level = 1 };
|
||||
if (!botGenerationDetails.IsPmc.GetValueOrDefault(false))
|
||||
{
|
||||
return new RandomisedBotLevelResult
|
||||
{
|
||||
Exp = 0,
|
||||
Level = 1
|
||||
};
|
||||
}
|
||||
|
||||
var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
|
||||
var botLevelRange = GetRelativePmcBotLevelRange(botGenerationDetails, levelDetails, expTable.Length);
|
||||
@@ -38,12 +44,22 @@ public class BotLevelGenerator(
|
||||
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;
|
||||
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);
|
||||
if (level < expTable.Length - 1)
|
||||
{
|
||||
exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
|
||||
}
|
||||
|
||||
return new RandomisedBotLevelResult { Level = level, Exp = exp };
|
||||
return new RandomisedBotLevelResult
|
||||
{
|
||||
Level = level,
|
||||
Exp = exp
|
||||
};
|
||||
}
|
||||
|
||||
public double ChooseBotLevel(double min, double max, int shift, double number)
|
||||
@@ -52,7 +68,7 @@ public class BotLevelGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the min and max level a PMC can be
|
||||
/// Return the min and max level a PMC can be
|
||||
/// </summary>
|
||||
/// <param name="botGenerationDetails">Details to help generate a bot</param>
|
||||
/// <param name="levelDetails"></param>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Utils;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Generators;
|
||||
@@ -36,7 +36,6 @@ public class BotLootGenerator(
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns></returns>
|
||||
@@ -47,7 +46,10 @@ public class BotLootGenerator(
|
||||
// Clone limits and set all values to 0 to use as a running total
|
||||
var limitsForBotDict = _cloner.Clone(limits);
|
||||
// Init current count of items we want to limit
|
||||
foreach (var limit in limitsForBotDict) limitsForBotDict[limit.Key] = 0;
|
||||
foreach (var limit in limitsForBotDict)
|
||||
{
|
||||
limitsForBotDict[limit.Key] = 0;
|
||||
}
|
||||
|
||||
return new ItemSpawnLimitSettings
|
||||
{
|
||||
@@ -57,7 +59,7 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add loot to bots containers
|
||||
/// Add loot to bots containers
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id</param>
|
||||
/// <param name="botJsonTemplate">Base json db file for the bot having its loot generated</param>
|
||||
@@ -110,7 +112,10 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
// Forced pmc healing loot into secure container
|
||||
if (isPmc && _pmcConfig.ForceHealingItemsIntoSecure) AddForcedMedicalItemsToPmcSecure(botInventory, botRole);
|
||||
if (isPmc && _pmcConfig.ForceHealingItemsIntoSecure)
|
||||
{
|
||||
AddForcedMedicalItemsToPmcSecure(botInventory, botRole);
|
||||
}
|
||||
|
||||
var botItemLimits = GetItemSpawnLimitsForBot(botRole);
|
||||
|
||||
@@ -229,6 +234,7 @@ public class BotLootGenerator(
|
||||
{
|
||||
// Add randomly generated weapon to PMC backpacks
|
||||
if (isPmc && _randomUtil.GetChance100(_pmcConfig.LooseWeaponInBackpackChancePercent))
|
||||
{
|
||||
AddLooseWeaponsToInventorySlot(
|
||||
sessionId,
|
||||
botInventory,
|
||||
@@ -240,6 +246,7 @@ public class BotLootGenerator(
|
||||
botLevel,
|
||||
filledContainerIds
|
||||
);
|
||||
}
|
||||
|
||||
var backpackLootRoubleTotal = GetBackpackRoubleTotalByLevel(botLevel, isPmc);
|
||||
AddLootFromPool(
|
||||
@@ -264,6 +271,7 @@ public class BotLootGenerator(
|
||||
// TacticalVest - generate loot if they have one
|
||||
if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest))
|
||||
// Vest
|
||||
{
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(
|
||||
botRole,
|
||||
@@ -281,6 +289,7 @@ public class BotLootGenerator(
|
||||
isPmc,
|
||||
filledContainerIds
|
||||
);
|
||||
}
|
||||
|
||||
// Pockets
|
||||
AddLootFromPool(
|
||||
@@ -305,6 +314,7 @@ public class BotLootGenerator(
|
||||
|
||||
// only add if not a pmc or is pmc and flag is true
|
||||
if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig))
|
||||
{
|
||||
AddLootFromPool(
|
||||
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Secure, botJsonTemplate),
|
||||
[EquipmentSlots.SecuredContainer],
|
||||
@@ -316,12 +326,16 @@ public class BotLootGenerator(
|
||||
isPmc,
|
||||
filledContainerIds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private MinMaxLootItemValue? GetSingleItemLootPriceLimits(int botLevel, bool isPmc)
|
||||
{
|
||||
// TODO - extend to other bot types
|
||||
if (!isPmc) return null;
|
||||
if (!isPmc)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var matchingValue = _pmcConfig?.LootItemLimitsRub?.FirstOrDefault(
|
||||
minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
@@ -331,24 +345,26 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rouble cost total for loot in a bots backpack by the bots levl
|
||||
/// Will return 0 for non PMCs
|
||||
/// Gets the rouble cost total for loot in a bots backpack by the bots levl
|
||||
/// Will return 0 for non PMCs
|
||||
/// </summary>
|
||||
/// <param name="botLevel">Bots level</param>
|
||||
/// <param name="isPmc">Is the bot a PMC</param>
|
||||
/// <returns>int</returns>
|
||||
private double? GetBackpackRoubleTotalByLevel(int botLevel, bool isPmc)
|
||||
{
|
||||
if (!isPmc) return 0;
|
||||
if (!isPmc)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault(
|
||||
(minMaxValue) => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
|
||||
);
|
||||
return matchingValue?.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="botInventory"></param>
|
||||
/// <returns></returns>
|
||||
@@ -356,15 +372,21 @@ public class BotLootGenerator(
|
||||
{
|
||||
List<EquipmentSlots> result = [EquipmentSlots.Pockets];
|
||||
|
||||
if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.TacticalVest.ToString())) result.Add(EquipmentSlots.TacticalVest);
|
||||
if ((botInventory.Items ?? []).Any(item => item.SlotId == EquipmentSlots.TacticalVest.ToString()))
|
||||
{
|
||||
result.Add(EquipmentSlots.TacticalVest);
|
||||
}
|
||||
|
||||
if ((botInventory.Items ?? []).Any((item) => item.SlotId == EquipmentSlots.Backpack.ToString())) result.Add(EquipmentSlots.Backpack);
|
||||
if ((botInventory.Items ?? []).Any(item => item.SlotId == EquipmentSlots.Backpack.ToString()))
|
||||
{
|
||||
result.Add(EquipmentSlots.Backpack);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force healing items onto bot to ensure they can heal in-raid
|
||||
/// Force healing items onto bot to ensure they can heal in-raid
|
||||
/// </summary>
|
||||
/// <param name="botInventory">Inventory to add items to</param>
|
||||
/// <param name="botRole">Role of bot (pmcBEAR/pmcUSEC)</param>
|
||||
@@ -372,7 +394,10 @@ public class BotLootGenerator(
|
||||
{
|
||||
// surv12
|
||||
AddLootFromPool(
|
||||
new Dictionary<string, double> { { "5d02797c86f774203f38e30a", 1 } },
|
||||
new Dictionary<string, double>
|
||||
{
|
||||
{ "5d02797c86f774203f38e30a", 1 }
|
||||
},
|
||||
[EquipmentSlots.SecuredContainer],
|
||||
1,
|
||||
botInventory,
|
||||
@@ -384,7 +409,10 @@ public class BotLootGenerator(
|
||||
|
||||
// AFAK
|
||||
AddLootFromPool(
|
||||
new Dictionary<string, double> { { "60098ad7c2240c0fe85c570a", 1 } },
|
||||
new Dictionary<string, double>
|
||||
{
|
||||
{ "60098ad7c2240c0fe85c570a", 1 }
|
||||
},
|
||||
[EquipmentSlots.SecuredContainer],
|
||||
10,
|
||||
botInventory,
|
||||
@@ -396,7 +424,7 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take random items from a pool and add to an inventory until totalItemCount or totalValueLimit or space limit is reached
|
||||
/// Take random items from a pool and add to an inventory until totalItemCount or totalValueLimit or space limit is reached
|
||||
/// </summary>
|
||||
/// <param name="pool">Pool of items to pick from with weight</param>
|
||||
/// <param name="equipmentSlots">What equipment slot will the loot items be added to</param>
|
||||
@@ -423,7 +451,10 @@ public class BotLootGenerator(
|
||||
// Loot pool has items
|
||||
var poolSize = pool.Count;
|
||||
|
||||
if (poolSize <= 0) return;
|
||||
if (poolSize <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double currentTotalRub = 0;
|
||||
|
||||
@@ -431,7 +462,10 @@ public class BotLootGenerator(
|
||||
for (var i = 0; i < totalItemCount; i++)
|
||||
{
|
||||
// Pool can become empty if item spawn limits keep removing items
|
||||
if (pool.Count == 0) return;
|
||||
if (pool.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var weightedItemTpl = _weightedRandomHelper.GetWeightedValue(pool);
|
||||
var (key, itemToAddTemplate) = _itemHelper.GetItem(weightedItemTpl);
|
||||
@@ -484,12 +518,14 @@ public class BotLootGenerator(
|
||||
{
|
||||
// Add each currency to wallet
|
||||
foreach (var itemToAdd in itemsToAdd)
|
||||
{
|
||||
_inventoryHelper.PlaceItemInContainer(
|
||||
containerGrid,
|
||||
itemToAdd,
|
||||
itemWithChildrenToAdd[0].Id,
|
||||
"main"
|
||||
);
|
||||
}
|
||||
|
||||
itemWithChildrenToAdd.AddRange(itemsToAdd.SelectMany(x => x));
|
||||
}
|
||||
@@ -516,7 +552,10 @@ public class BotLootGenerator(
|
||||
{
|
||||
// Bot has no container to put item in, exit
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to add: {totalItemCount} items to bot as it lacks a container to include them");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -524,11 +563,13 @@ public class BotLootGenerator(
|
||||
if (fitItemIntoContainerAttempts >= 4)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Failed placing item: {itemToAddTemplate.Id} - {itemToAddTemplate.Name}: {i} of: {totalItemCount} items into: {botRole} " +
|
||||
$"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} " +
|
||||
$"times, reason: {itemAddedResult}, skipping"
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -544,13 +585,15 @@ public class BotLootGenerator(
|
||||
if (totalValueLimitRub > 0)
|
||||
{
|
||||
currentTotalRub += _handbookHelper.GetTemplatePrice(itemToAddTemplate.Id);
|
||||
if (currentTotalRub > totalValueLimitRub) break;
|
||||
if (currentTotalRub > totalValueLimitRub)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="walletId"></param>
|
||||
/// <returns></returns>
|
||||
@@ -560,8 +603,8 @@ public class BotLootGenerator(
|
||||
|
||||
// Choose how many stacks of currency will be added to wallet
|
||||
var itemCount = _randomUtil.GetInt(
|
||||
(int)_botConfig.WalletLoot.ItemCount.Min,
|
||||
(int)_botConfig.WalletLoot.ItemCount.Max
|
||||
(int) _botConfig.WalletLoot.ItemCount.Min,
|
||||
(int) _botConfig.WalletLoot.ItemCount.Max
|
||||
);
|
||||
for (var index = 0; index < itemCount; index++)
|
||||
{
|
||||
@@ -587,7 +630,7 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some items need child items to function, add them to the itemToAddChildrenTo array
|
||||
/// Some items need child items to function, add them to the itemToAddChildrenTo array
|
||||
/// </summary>
|
||||
/// <param name="itemToAddTemplate">Db template of item to check</param>
|
||||
/// <param name="itemToAddChildrenTo">Item to add children to</param>
|
||||
@@ -597,19 +640,28 @@ public class BotLootGenerator(
|
||||
{
|
||||
// Fill ammo box
|
||||
if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
_itemHelper.AddCartridgesToAmmoBox(itemToAddChildrenTo, itemToAddTemplate);
|
||||
}
|
||||
// Make money a stack
|
||||
else if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.MONEY))
|
||||
{
|
||||
RandomiseMoneyStackSize(botRole, itemToAddTemplate, itemToAddChildrenTo[0]);
|
||||
}
|
||||
// Make ammo a stack
|
||||
else if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO))
|
||||
{
|
||||
RandomiseAmmoStackSize(isPmc, itemToAddTemplate, itemToAddChildrenTo[0]);
|
||||
}
|
||||
// Must add soft inserts/plates
|
||||
else if (_itemHelper.ItemRequiresSoftInserts(itemToAddTemplate.Id)) _itemHelper.AddChildSlotItems(itemToAddChildrenTo, itemToAddTemplate, null, false);
|
||||
else if (_itemHelper.ItemRequiresSoftInserts(itemToAddTemplate.Id))
|
||||
{
|
||||
_itemHelper.AddChildSlotItems(itemToAddChildrenTo, itemToAddTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add generated weapons to inventory as loot
|
||||
/// Add generated weapons to inventory as loot
|
||||
/// </summary>
|
||||
/// <param name="sessionId"></param>
|
||||
/// <param name="botInventory">Inventory to add preset to</param>
|
||||
@@ -639,11 +691,14 @@ public class BotLootGenerator(
|
||||
]
|
||||
);
|
||||
var randomisedWeaponCount = _randomUtil.GetInt(
|
||||
(int)_pmcConfig.LooseWeaponInBackpackLootMinMax.Min,
|
||||
(int)_pmcConfig.LooseWeaponInBackpackLootMinMax.Max
|
||||
(int) _pmcConfig.LooseWeaponInBackpackLootMinMax.Min,
|
||||
(int) _pmcConfig.LooseWeaponInBackpackLootMinMax.Max
|
||||
);
|
||||
|
||||
if (randomisedWeaponCount <= 0) return;
|
||||
if (randomisedWeaponCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < randomisedWeaponCount; i++)
|
||||
{
|
||||
@@ -667,13 +722,17 @@ public class BotLootGenerator(
|
||||
);
|
||||
|
||||
if (result != ItemAddedResult.SUCCESS)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Failed to add additional weapon {generatedWeapon.Weapon[0].Id} to bot backpack, reason: {result.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if an item has reached its bot-specific spawn limit
|
||||
/// Check if an item has reached its bot-specific spawn limit
|
||||
/// </summary>
|
||||
/// <param name="itemTemplate">Item we check to see if its reached spawn limit</param>
|
||||
/// <param name="botRole">Bot type</param>
|
||||
@@ -684,22 +743,31 @@ public class BotLootGenerator(
|
||||
// PMCs and scavs have different sections of bot config for spawn limits
|
||||
if (itemSpawnLimits is not null && itemSpawnLimits.GlobalLimits?.Count == 0)
|
||||
// No items found in spawn limit, drop out
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// No spawn limits, skipping
|
||||
if (itemSpawnLimits is null) return false;
|
||||
if (itemSpawnLimits is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var idToCheckFor = GetMatchingIdFromSpawnLimits(itemTemplate, itemSpawnLimits.GlobalLimits);
|
||||
if (idToCheckFor is null)
|
||||
// ParentId or tplid not found in spawnLimits, not a spawn limited item, skip
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Use tryAdd to see if it exists, and automatically add 1
|
||||
if (!itemSpawnLimits.CurrentLimits.TryAdd(idToCheckFor, 1))
|
||||
// if it does exist, come in here and increment
|
||||
// Increment item count with this bot type
|
||||
{
|
||||
itemSpawnLimits.CurrentLimits[idToCheckFor]++;
|
||||
}
|
||||
|
||||
|
||||
// Check if over limit
|
||||
@@ -710,17 +778,19 @@ public class BotLootGenerator(
|
||||
if (currentLimitCount > currentLimitCount * 10)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
_localisationService.GetText(
|
||||
"bot-item_spawn_limit_reached_skipping_item",
|
||||
new
|
||||
{
|
||||
botRole = botRole,
|
||||
botRole,
|
||||
itemName = itemTemplate.Name,
|
||||
attempts = currentLimitCount
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -732,7 +802,7 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomise the stack size of a money object, uses different values for pmc or scavs
|
||||
/// Randomise the stack size of a money object, uses different values for pmc or scavs
|
||||
/// </summary>
|
||||
/// <param name="botRole">Role bot has that has money stack</param>
|
||||
/// <param name="itemTemplate">item details from db</param>
|
||||
@@ -740,7 +810,10 @@ public class BotLootGenerator(
|
||||
public void RandomiseMoneyStackSize(string botRole, TemplateItem itemTemplate, Item moneyItem)
|
||||
{
|
||||
// Get all currency weights for this bot type
|
||||
if (!_botConfig.CurrencyStackSize.TryGetValue(botRole, out var currencyWeights)) currencyWeights = _botConfig.CurrencyStackSize["default"];
|
||||
if (!_botConfig.CurrencyStackSize.TryGetValue(botRole, out var currencyWeights))
|
||||
{
|
||||
currencyWeights = _botConfig.CurrencyStackSize["default"];
|
||||
}
|
||||
|
||||
var currencyWeight = currencyWeights[moneyItem.Template];
|
||||
|
||||
@@ -750,7 +823,7 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomise the size of an ammo stack
|
||||
/// Randomise the size of an ammo stack
|
||||
/// </summary>
|
||||
/// <param name="isPmc">Is ammo on a PMC bot</param>
|
||||
/// <param name="itemTemplate">item details from db</param>
|
||||
@@ -764,16 +837,22 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get spawn limits for a specific bot type from bot.json config
|
||||
/// If no limit found for a non pmc bot, fall back to defaults
|
||||
/// Get spawn limits for a specific bot type from bot.json config
|
||||
/// If no limit found for a non pmc bot, fall back to defaults
|
||||
/// </summary>
|
||||
/// <param name="botRole">what role does the bot have</param>
|
||||
/// <returns>Dictionary of tplIds and limit</returns>
|
||||
public Dictionary<string, double> GetItemSpawnLimitsForBotType(string botRole)
|
||||
{
|
||||
if (_botHelper.IsBotPmc(botRole)) return _botConfig.ItemSpawnLimits["pmc"];
|
||||
if (_botHelper.IsBotPmc(botRole))
|
||||
{
|
||||
return _botConfig.ItemSpawnLimits["pmc"];
|
||||
}
|
||||
|
||||
if (_botConfig.ItemSpawnLimits.ContainsKey(botRole.ToLower())) return _botConfig.ItemSpawnLimits[botRole.ToLower()];
|
||||
if (_botConfig.ItemSpawnLimits.ContainsKey(botRole.ToLower()))
|
||||
{
|
||||
return _botConfig.ItemSpawnLimits[botRole.ToLower()];
|
||||
}
|
||||
|
||||
_logger.Warning(_localisationService.GetText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole));
|
||||
|
||||
@@ -781,17 +860,23 @@ public class BotLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the parentId or tplId of item inside spawnLimits object if it exists
|
||||
/// Get the parentId or tplId of item inside spawnLimits object if it exists
|
||||
/// </summary>
|
||||
/// <param name="itemTemplate">item we want to look for in spawn limits</param>
|
||||
/// <param name="spawnLimits">Limits to check for item</param>
|
||||
/// <returns>id as string, otherwise undefined</returns>
|
||||
public string? GetMatchingIdFromSpawnLimits(TemplateItem itemTemplate, Dictionary<string, double> spawnLimits)
|
||||
{
|
||||
if (spawnLimits.ContainsKey(itemTemplate.Id)) return itemTemplate.Id;
|
||||
if (spawnLimits.ContainsKey(itemTemplate.Id))
|
||||
{
|
||||
return itemTemplate.Id;
|
||||
}
|
||||
|
||||
// tplId not found in spawnLimits, check if parentId is
|
||||
if (spawnLimits.ContainsKey(itemTemplate.Parent)) return itemTemplate.Parent;
|
||||
if (spawnLimits.ContainsKey(itemTemplate.Parent))
|
||||
{
|
||||
return itemTemplate.Parent;
|
||||
}
|
||||
|
||||
// parentId and tplId not found
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Generators.WeaponGen;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
@@ -11,6 +10,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Generators;
|
||||
@@ -34,11 +34,11 @@ public class BotWeaponGenerator(
|
||||
IEnumerable<IInventoryMagGen> inventoryMagGenComponents
|
||||
)
|
||||
{
|
||||
protected IEnumerable<IInventoryMagGen> _inventoryMagGenComponents = MagGenSetUp(inventoryMagGenComponents);
|
||||
protected const string _modMagazineSlotId = "mod_magazine";
|
||||
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
|
||||
protected IEnumerable<IInventoryMagGen> _inventoryMagGenComponents = MagGenSetUp(inventoryMagGenComponents);
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
protected RepairConfig _repairConfig = _configServer.GetConfig<RepairConfig>();
|
||||
protected const string _modMagazineSlotId = "mod_magazine";
|
||||
|
||||
private static List<IInventoryMagGen> MagGenSetUp(IEnumerable<IInventoryMagGen> components)
|
||||
{
|
||||
@@ -48,7 +48,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a random weapon based on weightings and generate a functional weapon
|
||||
/// Pick a random weapon based on weightings and generate a functional weapon
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session identifier</param>
|
||||
/// <param name="equipmentSlot">Primary/secondary/holster</param>
|
||||
@@ -77,7 +77,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random weighted weapon from a bot's pool of weapons.
|
||||
/// Gets a random weighted weapon from a bot's pool of weapons.
|
||||
/// </summary>
|
||||
/// <param name="equipmentSlot">Primary/secondary/holster</param>
|
||||
/// <param name="botTemplateInventory">e.g. assault.json</param>
|
||||
@@ -86,14 +86,16 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
EquipmentSlots key;
|
||||
if (!EquipmentSlots.TryParse(equipmentSlot, out key))
|
||||
{
|
||||
_logger.Error($"Unable to parse equipment slot '{equipmentSlot}'");
|
||||
}
|
||||
|
||||
var weaponPool = botTemplateInventory.Equipment[key];
|
||||
return _weightedRandomHelper.GetWeightedValue<string>(weaponPool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a weapon based on the supplied weapon template.
|
||||
/// Generates a weapon based on the supplied weapon template.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="weaponTpl">Weapon template to generate (use pickWeightedWeaponTplFromPool()).</param>
|
||||
@@ -140,7 +142,9 @@ public class BotWeaponGenerator(
|
||||
// Chance to add randomised weapon enhancement
|
||||
if (isPmc && _randomUtil.GetChance100(_pmcConfig.WeaponHasEnhancementChancePercent))
|
||||
// Add buff to weapon root
|
||||
{
|
||||
_repairService.AddBuff(_repairConfig.RepairKit.Weapon, weaponWithModsArray[0]);
|
||||
}
|
||||
|
||||
// Add mods to weapon base
|
||||
if (modPool.Keys.Contains(weaponTpl))
|
||||
@@ -159,7 +163,12 @@ public class BotWeaponGenerator(
|
||||
ParentTemplate = weaponItemTemplate,
|
||||
ModSpawnChances = modChances,
|
||||
AmmoTpl = ammoTpl,
|
||||
BotData = new BotData { Role = botRole, Level = botLevel, EquipmentRole = botEquipmentRole },
|
||||
BotData = new BotData
|
||||
{
|
||||
Role = botRole,
|
||||
Level = botLevel,
|
||||
EquipmentRole = botEquipmentRole
|
||||
},
|
||||
ModLimits = modLimits,
|
||||
WeaponStats = new WeaponStats(),
|
||||
ConflictingItemTpls = new HashSet<string>()
|
||||
@@ -173,6 +182,7 @@ public class BotWeaponGenerator(
|
||||
// Use weapon preset from globals.json if weapon isn't valid
|
||||
if (!IsWeaponValid(weaponWithModsArray, botRole))
|
||||
// Weapon is bad, fall back to weapons preset
|
||||
{
|
||||
weaponWithModsArray = GetPresetWeaponMods(
|
||||
weaponTpl,
|
||||
slotName,
|
||||
@@ -180,10 +190,14 @@ public class BotWeaponGenerator(
|
||||
weaponItemTemplate,
|
||||
botRole
|
||||
);
|
||||
}
|
||||
|
||||
var tempList = _cloner.Clone(weaponWithModsArray.Where((item) => item.SlotId == _modMagazineSlotId));
|
||||
var tempList = _cloner.Clone(weaponWithModsArray.Where(item => item.SlotId == _modMagazineSlotId));
|
||||
// Fill existing magazines to full and sync ammo type
|
||||
foreach (var magazine in tempList) FillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
|
||||
foreach (var magazine in tempList)
|
||||
{
|
||||
FillExistingMagazines(weaponWithModsArray, magazine, ammoTpl);
|
||||
}
|
||||
|
||||
// Add cartridge(s) to gun chamber(s)
|
||||
if (weaponItemTemplate.Properties.Chambers?.Any() ??
|
||||
@@ -191,12 +205,12 @@ public class BotWeaponGenerator(
|
||||
weaponItemTemplate.Properties.Chambers[0].Props.Filters[0].Filter.Contains(ammoTpl)))
|
||||
{
|
||||
// Guns have variety of possible Chamber ids, patron_in_weapon/patron_in_weapon_000/patron_in_weapon_001
|
||||
var chamberSlotNames = weaponItemTemplate.Properties.Chambers.Select((chamberSlot) => chamberSlot.Name);
|
||||
var chamberSlotNames = weaponItemTemplate.Properties.Chambers.Select(chamberSlot => chamberSlot.Name);
|
||||
AddCartridgeToChamber(weaponWithModsArray, ammoTpl, chamberSlotNames.ToList());
|
||||
}
|
||||
|
||||
// Fill UBGL if found
|
||||
var ubglMod = weaponWithModsArray.FirstOrDefault((x) => x.SlotId == "mod_launcher");
|
||||
var ubglMod = weaponWithModsArray.FirstOrDefault(x => x.SlotId == "mod_launcher");
|
||||
string? ubglAmmoTpl = null;
|
||||
if (ubglMod is not null)
|
||||
{
|
||||
@@ -216,8 +230,8 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert cartridge(s) into a weapon
|
||||
/// Handles all chambers - patron_in_weapon, patron_in_weapon_000 etc
|
||||
/// Insert cartridge(s) into a weapon
|
||||
/// Handles all chambers - patron_in_weapon, patron_in_weapon_000 etc
|
||||
/// </summary>
|
||||
/// <param name="weaponWithModsList">Weapon and mods</param>
|
||||
/// <param name="ammoTemplate">Cartridge to add to weapon</param>
|
||||
@@ -226,7 +240,7 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
foreach (var slotId in chamberSlotIds)
|
||||
{
|
||||
var existingItemWithSlot = weaponWithModsList.FirstOrDefault((x) => x.SlotId == slotId);
|
||||
var existingItemWithSlot = weaponWithModsList.FirstOrDefault(x => x.SlotId == slotId);
|
||||
if (existingItemWithSlot is null)
|
||||
{
|
||||
// Not found, add new slot to weapon
|
||||
@@ -237,7 +251,10 @@ public class BotWeaponGenerator(
|
||||
Template = ammoTemplate,
|
||||
ParentId = weaponWithModsList[0].Id,
|
||||
SlotId = slotId,
|
||||
Upd = new Upd { StackObjectsCount = 1 }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -245,14 +262,17 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
// Already exists, update values
|
||||
existingItemWithSlot.Template = ammoTemplate;
|
||||
existingItemWithSlot.Upd = new Upd { StackObjectsCount = 1 };
|
||||
existingItemWithSlot.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a list with weapon base as the only element and
|
||||
/// add additional properties based on weapon type
|
||||
/// Create a list with weapon base as the only element and
|
||||
/// add additional properties based on weapon type
|
||||
/// </summary>
|
||||
/// <param name="weaponTemplate">Weapon template to create item with</param>
|
||||
/// <param name="weaponParentId">Weapons parent id</param>
|
||||
@@ -277,7 +297,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the mods necessary to kit out a weapon to its preset level
|
||||
/// Get the mods necessary to kit out a weapon to its preset level
|
||||
/// </summary>
|
||||
/// <param name="weaponTemplate">Weapon to find preset for</param>
|
||||
/// <param name="equipmentSlot">The slot the weapon will be placed in</param>
|
||||
@@ -294,12 +314,14 @@ public class BotWeaponGenerator(
|
||||
// TODO: Preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
|
||||
Preset preset = null;
|
||||
foreach (var (_, itemPreset) in _databaseService.GetGlobals().ItemPresets)
|
||||
{
|
||||
if (itemPreset.Items[0].Template == weaponTemplate)
|
||||
{
|
||||
preset = _cloner.Clone(itemPreset);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preset is not null)
|
||||
{
|
||||
@@ -319,7 +341,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if all required slots are occupied on a weapon and all its mods.
|
||||
/// Checks if all required slots are occupied on a weapon and all its mods.
|
||||
/// </summary>
|
||||
/// <param name="weaponItemList">Weapon + mods</param>
|
||||
/// <param name="botRole">Role of bot weapon is for</param>
|
||||
@@ -329,14 +351,17 @@ public class BotWeaponGenerator(
|
||||
foreach (var mod in weaponItemList)
|
||||
{
|
||||
var modTemplate = _itemHelper.GetItem(mod.Template).Value;
|
||||
if (!modTemplate.Properties.Slots?.Any() ?? false) continue;
|
||||
if (!modTemplate.Properties.Slots?.Any() ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate over required slots in db item, check mod exists for that slot
|
||||
foreach (var modSlotTemplate in modTemplate.Properties.Slots?.Where((slot) => slot.Required.GetValueOrDefault(false)) ?? [])
|
||||
foreach (var modSlotTemplate in modTemplate.Properties.Slots?.Where(slot => slot.Required.GetValueOrDefault(false)) ?? [])
|
||||
{
|
||||
var slotName = modSlotTemplate.Name;
|
||||
var hasWeaponSlotItem = weaponItemList.Any(
|
||||
(weaponItem) => weaponItem.ParentId == mod.Id && weaponItem.SlotId == slotName
|
||||
weaponItem => weaponItem.ParentId == mod.Id && weaponItem.SlotId == slotName
|
||||
);
|
||||
if (!hasWeaponSlotItem)
|
||||
{
|
||||
@@ -348,7 +373,7 @@ public class BotWeaponGenerator(
|
||||
modSlot = modSlotTemplate.Name,
|
||||
modName = modTemplate.Name,
|
||||
slotId = mod.SlotId,
|
||||
botRole = botRole
|
||||
botRole
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -362,8 +387,8 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates extra magazines or bullets (if magazine is internal) and adds them to TacticalVest and Pockets.
|
||||
/// Additionally, adds extra bullets to SecuredContainer
|
||||
/// Generates extra magazines or bullets (if magazine is internal) and adds them to TacticalVest and Pockets.
|
||||
/// Additionally, adds extra bullets to SecuredContainer
|
||||
/// </summary>
|
||||
/// <param name="generatedWeaponResult">Object with properties for generated weapon (weapon mods pool / weapon template / ammo tpl)</param>
|
||||
/// <param name="magWeights">Magazine weights for count to add to inventory</param>
|
||||
@@ -395,7 +420,10 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
// Has an UBGL
|
||||
if (generatedWeaponResult.ChosenUbglAmmoTemplate is not null) AddUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
|
||||
if (generatedWeaponResult.ChosenUbglAmmoTemplate is not null)
|
||||
{
|
||||
AddUbglGrenadesToBotInventory(weaponAndMods, generatedWeaponResult, inventory);
|
||||
}
|
||||
|
||||
var inventoryMagGenModel = new InventoryMagGen(
|
||||
magWeights,
|
||||
@@ -405,7 +433,7 @@ public class BotWeaponGenerator(
|
||||
inventory
|
||||
);
|
||||
|
||||
_inventoryMagGenComponents.FirstOrDefault((v) => v.CanHandleInventoryMagGen(inventoryMagGenModel))
|
||||
_inventoryMagGenComponents.FirstOrDefault(v => v.CanHandleInventoryMagGen(inventoryMagGenModel))
|
||||
.Process(inventoryMagGenModel);
|
||||
|
||||
// Add x stacks of bullets to SecuredContainer (bots use a magic mag packing skill to reload instantly)
|
||||
@@ -418,7 +446,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add Grenades for UBGL to bot's vest and secure container
|
||||
/// Add Grenades for UBGL to bot's vest and secure container
|
||||
/// </summary>
|
||||
/// <param name="weaponMods">Weapon list with mods</param>
|
||||
/// <param name="generatedWeaponResult">Result of weapon generation</param>
|
||||
@@ -426,13 +454,17 @@ public class BotWeaponGenerator(
|
||||
protected void AddUbglGrenadesToBotInventory(List<Item> weaponMods, GenerateWeaponResult generatedWeaponResult, BotBaseInventory inventory)
|
||||
{
|
||||
// Find ubgl mod item + get details of it from db
|
||||
var ubglMod = weaponMods.FirstOrDefault((x) => x.SlotId == "mod_launcher");
|
||||
var ubglMod = weaponMods.FirstOrDefault(x => x.SlotId == "mod_launcher");
|
||||
var ubglDbTemplate = _itemHelper.GetItem(ubglMod.Template).Value;
|
||||
|
||||
// Define min/max of how many grenades bot will have
|
||||
GenerationData ubglMinMax = new()
|
||||
{
|
||||
Weights = new Dictionary<double, double> { { 1, 1 }, { 2, 1 } },
|
||||
Weights = new Dictionary<double, double>
|
||||
{
|
||||
{ 1, 1 },
|
||||
{ 2, 1 }
|
||||
},
|
||||
Whitelist = new Dictionary<string, double>()
|
||||
};
|
||||
|
||||
@@ -448,7 +480,7 @@ public class BotWeaponGenerator(
|
||||
inventory
|
||||
);
|
||||
_inventoryMagGenComponents
|
||||
.FirstOrDefault((v) => v.CanHandleInventoryMagGen(ubglAmmoGenModel))
|
||||
.FirstOrDefault(v => v.CanHandleInventoryMagGen(ubglAmmoGenModel))
|
||||
.Process(ubglAmmoGenModel);
|
||||
|
||||
// Store extra grenades in secure container
|
||||
@@ -456,7 +488,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add ammo to the secure container.
|
||||
/// Add ammo to the secure container.
|
||||
/// </summary>
|
||||
/// <param name="stackCount">How many stacks of ammo to add.</param>
|
||||
/// <param name="ammoTpl">Ammo type to add.</param>
|
||||
@@ -468,12 +500,23 @@ public class BotWeaponGenerator(
|
||||
{
|
||||
var id = _hashUtil.Generate();
|
||||
_botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
|
||||
new List<EquipmentSlots> { EquipmentSlots.SecuredContainer },
|
||||
new List<EquipmentSlots>
|
||||
{
|
||||
EquipmentSlots.SecuredContainer
|
||||
},
|
||||
id,
|
||||
ammoTemplate,
|
||||
new List<Item>
|
||||
{
|
||||
new() { Id = id, Template = ammoTemplate, Upd = new Upd { StackObjectsCount = stackSize } }
|
||||
new()
|
||||
{
|
||||
Id = id,
|
||||
Template = ammoTemplate,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = stackSize
|
||||
}
|
||||
}
|
||||
},
|
||||
inventory
|
||||
);
|
||||
@@ -481,7 +524,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a weapons magazine template from a weapon template.
|
||||
/// Get a weapons magazine template from a weapon template.
|
||||
/// </summary>
|
||||
/// <param name="weaponMods">Mods from a weapon template.</param>
|
||||
/// <param name="weaponTemplate">Weapon to get magazine template for.</param>
|
||||
@@ -489,32 +532,39 @@ public class BotWeaponGenerator(
|
||||
/// <returns>Magazine template string.</returns>
|
||||
protected string GetMagazineTemplateFromWeaponTemplate(List<Item> weaponMods, TemplateItem weaponTemplate, string botRole)
|
||||
{
|
||||
var magazine = weaponMods.FirstOrDefault((m) => m.SlotId == _modMagazineSlotId);
|
||||
var magazine = weaponMods.FirstOrDefault(m => m.SlotId == _modMagazineSlotId);
|
||||
if (magazine is null)
|
||||
{
|
||||
// Edge case - magazineless chamber loaded weapons dont have magazines, e.g. mp18
|
||||
// return default mag tpl
|
||||
if (weaponTemplate.Properties.ReloadMode == ReloadMode.OnlyBarrel) return _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
|
||||
if (weaponTemplate.Properties.ReloadMode == ReloadMode.OnlyBarrel)
|
||||
{
|
||||
return _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
|
||||
}
|
||||
|
||||
// log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver)
|
||||
if (!weaponTemplate.Properties.IsChamberLoad ?? false)
|
||||
// Shouldn't happen
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"bot-weapon_missing_magazine_or_chamber",
|
||||
new
|
||||
{
|
||||
weaponId = weaponTemplate.Id,
|
||||
botRole = botRole
|
||||
botRole
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var defaultMagTplId = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate);
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"[{botRole}] Unable to find magazine for weapon: {weaponTemplate.Id} {weaponTemplate.Name}, using mag template default: {defaultMagTplId}."
|
||||
);
|
||||
}
|
||||
|
||||
return defaultMagTplId;
|
||||
}
|
||||
@@ -523,7 +573,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and returns a compatible ammo template based on the bots ammo weightings (x.json/inventory/equipment/ammo)
|
||||
/// Finds and returns a compatible ammo template based on the bots ammo weightings (x.json/inventory/equipment/ammo)
|
||||
/// </summary>
|
||||
/// <param name="cartridgePool">Dictionary of all cartridges keyed by type e.g. Caliber556x45NATO</param>
|
||||
/// <param name="weaponTemplate">Weapon details from database we want to pick ammo for</param>
|
||||
@@ -534,6 +584,7 @@ public class BotWeaponGenerator(
|
||||
if (!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon) || cartridgePoolForWeapon?.Keys.Count == 0)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
_localisationService.GetText(
|
||||
"bot-no_caliber_data_for_weapon_falling_back_to_default",
|
||||
@@ -545,6 +596,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Immediately returns, default ammo is guaranteed to be compatible
|
||||
return weaponTemplate.Properties.DefAmmo;
|
||||
@@ -554,30 +606,42 @@ public class BotWeaponGenerator(
|
||||
var compatibleCartridgesInTemplate = GetCompatibleCartridgesFromWeaponTemplate(weaponTemplate);
|
||||
if (compatibleCartridgesInTemplate is null)
|
||||
// No chamber data found in weapon, send default
|
||||
{
|
||||
return weaponTemplate.Properties.DefAmmo;
|
||||
}
|
||||
|
||||
// Inner join the weapons allowed + passed in cartridge pool to get compatible cartridges
|
||||
Dictionary<string, double> compatibleCartridges = new();
|
||||
foreach (var cartridge in cartridgePoolForWeapon)
|
||||
{
|
||||
if (compatibleCartridgesInTemplate.Contains(cartridge.Key))
|
||||
{
|
||||
compatibleCartridges[cartridge.Key] = cartridgePoolForWeapon[cartridge.Key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!compatibleCartridges.Any())
|
||||
// No compatible cartridges, use default
|
||||
{
|
||||
return weaponTemplate.Properties.DefAmmo;
|
||||
}
|
||||
|
||||
return _weightedRandomHelper.GetWeightedValue<string>(compatibleCartridges);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the cartridge ids from a weapon template that work with the weapon
|
||||
/// Get the cartridge ids from a weapon template that work with the weapon
|
||||
/// </summary>
|
||||
/// <param name="weaponTemplate">Weapon db template to get cartridges for</param>
|
||||
/// <returns>List of cartridge tpls</returns>
|
||||
protected List<string>? GetCompatibleCartridgesFromWeaponTemplate(TemplateItem weaponTemplate)
|
||||
{
|
||||
var cartridges = weaponTemplate.Properties?.Chambers.FirstOrDefault()?.Props?.Filters?[0].Filter;
|
||||
if (cartridges is not null) return cartridges;
|
||||
if (cartridges is not null)
|
||||
{
|
||||
return cartridges;
|
||||
}
|
||||
|
||||
// Fallback to the magazine if possible, e.g. for revolvers
|
||||
// Grab the magazines template
|
||||
var firstMagazine = weaponTemplate.Properties.Slots.FirstOrDefault(slot => slot.Name == "mod_magazine");
|
||||
@@ -589,32 +653,42 @@ public class BotWeaponGenerator(
|
||||
if (cartridges is null)
|
||||
// Normal magazines
|
||||
// None found, try the cartridges array
|
||||
{
|
||||
cartridges = magProperties.Cartridges.FirstOrDefault()?.Props.Filters[0].Filter;
|
||||
}
|
||||
|
||||
return cartridges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a weapons compatible cartridge caliber
|
||||
/// Get a weapons compatible cartridge caliber
|
||||
/// </summary>
|
||||
/// <param name="weaponTemplate">Weapon to look up caliber of</param>
|
||||
/// <returns>Caliber as string</returns>
|
||||
protected string? GetWeaponCaliber(TemplateItem weaponTemplate)
|
||||
{
|
||||
if (weaponTemplate.Properties.Caliber is not null) return weaponTemplate.Properties.Caliber;
|
||||
if (weaponTemplate.Properties.Caliber is not null)
|
||||
{
|
||||
return weaponTemplate.Properties.Caliber;
|
||||
}
|
||||
|
||||
if (weaponTemplate.Properties.AmmoCaliber is not null)
|
||||
// 9x18pmm has a typo, should be Caliber9x18PM
|
||||
{
|
||||
return weaponTemplate.Properties.AmmoCaliber == "Caliber9x18PMM"
|
||||
? "Caliber9x18PM"
|
||||
: weaponTemplate.Properties.AmmoCaliber;
|
||||
}
|
||||
|
||||
if (weaponTemplate.Properties.LinkedWeapon is not null)
|
||||
{
|
||||
var ammoInChamber = _itemHelper.GetItem(
|
||||
weaponTemplate.Properties.Chambers[0].Props.Filters[0].Filter[0]
|
||||
);
|
||||
if (!ammoInChamber.Key) return null;
|
||||
if (!ammoInChamber.Key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ammoInChamber.Value.Properties.Caliber;
|
||||
}
|
||||
@@ -623,7 +697,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill existing magazines to full, while replacing their contents with specified ammo
|
||||
/// Fill existing magazines to full, while replacing their contents with specified ammo
|
||||
/// </summary>
|
||||
/// <param name="weaponMods">Weapon with children</param>
|
||||
/// <param name="magazine">Magazine item</param>
|
||||
@@ -645,13 +719,17 @@ public class BotWeaponGenerator(
|
||||
// Exchange of the camora ammo is not necessary we could also just check for stackSize > 0 here
|
||||
// and remove the else
|
||||
if (_botWeaponGeneratorHelper.MagazineIsCylinderRelated(parentItem.Name))
|
||||
{
|
||||
FillCamorasWithAmmo(weaponMods, magazine.Id, cartridgeTemplate);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOrUpdateMagazinesChildWithAmmo(weaponMods, magazine, cartridgeTemplate, magazineTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add desired ammo template as item to weapon modifications list, placed as child to UBGL.
|
||||
/// Add desired ammo template as item to weapon modifications list, placed as child to UBGL.
|
||||
/// </summary>
|
||||
/// <param name="weaponMods">Weapon with children.</param>
|
||||
/// <param name="ubglMod">UBGL item.</param>
|
||||
@@ -665,13 +743,16 @@ public class BotWeaponGenerator(
|
||||
Template = ubglAmmoTpl,
|
||||
ParentId = ubglMod.Id,
|
||||
SlotId = "patron_in_weapon",
|
||||
Upd = new Upd { StackObjectsCount = 1 }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add cartridge item to weapon item list, if it already exists, update
|
||||
/// Add cartridge item to weapon item list, if it already exists, update
|
||||
/// </summary>
|
||||
/// <param name="weaponWithMods">Weapon items list to amend</param>
|
||||
/// <param name="magazine">Magazine item details we're adding cartridges to</param>
|
||||
@@ -680,11 +761,13 @@ public class BotWeaponGenerator(
|
||||
protected void AddOrUpdateMagazinesChildWithAmmo(List<Item> weaponWithMods, Item magazine, string chosenAmmoTpl, TemplateItem magazineTemplate)
|
||||
{
|
||||
var magazineCartridgeChildItem = weaponWithMods.FirstOrDefault(
|
||||
(m) => m.ParentId == magazine.Id && m.SlotId == "cartridges"
|
||||
m => m.ParentId == magazine.Id && m.SlotId == "cartridges"
|
||||
);
|
||||
if (magazineCartridgeChildItem is not null)
|
||||
// Delete the existing cartridge object and create fresh below
|
||||
{
|
||||
weaponWithMods.Remove(magazineCartridgeChildItem);
|
||||
}
|
||||
|
||||
// Create array with just magazine
|
||||
List<Item> magazineWithCartridges = [magazine];
|
||||
@@ -699,7 +782,7 @@ public class BotWeaponGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill each Camora with a bullet
|
||||
/// Fill each Camora with a bullet
|
||||
/// </summary>
|
||||
/// <param name="weaponMods">Weapon mods to find and update camora mod(s) from</param>
|
||||
/// <param name="magazineId">Magazine id to find and add to</param>
|
||||
@@ -709,14 +792,21 @@ public class BotWeaponGenerator(
|
||||
// for CylinderMagazine we exchange the ammo in the "camoras".
|
||||
// This might not be necessary since we already filled the camoras with a random whitelisted and compatible ammo type,
|
||||
// but I'm not sure whether this is also used elsewhere
|
||||
var camoras = weaponMods.Where((x) => x.ParentId == magazineId && x.SlotId.StartsWith("camora"));
|
||||
var camoras = weaponMods.Where(x => x.ParentId == magazineId && x.SlotId.StartsWith("camora"));
|
||||
foreach (var camora in camoras)
|
||||
{
|
||||
camora.Template = ammoTpl;
|
||||
if (camora.Upd is not null)
|
||||
{
|
||||
camora.Upd.StackObjectsCount = 1;
|
||||
}
|
||||
else
|
||||
camora.Upd = new Upd { StackObjectsCount = 1 };
|
||||
{
|
||||
camora.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Core.Helpers;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
@@ -8,6 +7,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
@@ -40,29 +40,47 @@ public class FenceBaseAssortGenerator(
|
||||
foreach (var rootItemDb in itemHelper.GetItems().Where(IsValidFenceItem))
|
||||
{
|
||||
// Skip blacklisted items
|
||||
if (itemFilterService.IsItemBlacklisted(rootItemDb.Id)) continue;
|
||||
if (itemFilterService.IsItemBlacklisted(rootItemDb.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip reward item blacklist
|
||||
if (itemFilterService.IsItemRewardBlacklisted(rootItemDb.Id)) continue;
|
||||
if (itemFilterService.IsItemRewardBlacklisted(rootItemDb.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invalid
|
||||
if (!itemHelper.IsValidItem(rootItemDb.Id)) continue;
|
||||
if (!itemHelper.IsValidItem(rootItemDb.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Item base type blacklisted
|
||||
if (traderConfig.Fence.Blacklist.Count > 0)
|
||||
{
|
||||
if (traderConfig.Fence.Blacklist.Contains(rootItemDb.Id) ||
|
||||
itemHelper.IsOfBaseclasses(rootItemDb.Id, traderConfig.Fence.Blacklist)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Only allow rigs with no slots (carrier rigs)
|
||||
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.VEST) &&
|
||||
(rootItemDb.Properties?.Slots?.Count ?? 0) > 0
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip seasonal event items when not in seasonal event
|
||||
if (traderConfig.Fence.BlacklistSeasonalItems && blockedSeasonalItems.Contains(rootItemDb.Id)) continue;
|
||||
if (traderConfig.Fence.BlacklistSeasonalItems && blockedSeasonalItems.Contains(rootItemDb.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create item object in array
|
||||
var itemWithChildrenToAdd = new List<Item>
|
||||
@@ -73,19 +91,30 @@ public class FenceBaseAssortGenerator(
|
||||
Template = rootItemDb.Id,
|
||||
ParentId = "hideout",
|
||||
SlotId = "hideout",
|
||||
Upd = new Upd { StackObjectsCount = 9999999 }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 9999999
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure ammo is not above penetration limit value
|
||||
if (itemHelper.IsOfBaseclasses(rootItemDb.Id, [BaseClasses.AMMO_BOX, BaseClasses.AMMO]))
|
||||
{
|
||||
if (IsAmmoAbovePenetrationLimit(rootItemDb))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX))
|
||||
// Only add cartridges to box if box has no children
|
||||
{
|
||||
if (itemWithChildrenToAdd.Count == 1)
|
||||
{
|
||||
itemHelper.AddCartridgesToAmmoBox(itemWithChildrenToAdd, rootItemDb);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure IDs are unique
|
||||
itemHelper.RemapRootItemId(itemWithChildrenToAdd);
|
||||
@@ -96,9 +125,9 @@ public class FenceBaseAssortGenerator(
|
||||
}
|
||||
|
||||
// Create barter scheme (price)
|
||||
var barterSchemeToAdd = new BarterScheme()
|
||||
var barterSchemeToAdd = new BarterScheme
|
||||
{
|
||||
Count = Math.Round((double)fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd)),
|
||||
Count = Math.Round((double) fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd)),
|
||||
Template = Money.ROUBLES
|
||||
};
|
||||
|
||||
@@ -117,7 +146,10 @@ public class FenceBaseAssortGenerator(
|
||||
foreach (var defaultPreset in defaultPresets)
|
||||
{
|
||||
// Skip presets we've already added
|
||||
if (baseFenceAssort.Items.Any((item) => item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id)) continue;
|
||||
if (baseFenceAssort.Items.Any(item => item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct preset + mods
|
||||
var itemAndChildren = itemHelper.ReplaceIDs(_cloner.Clone(defaultPreset.Items));
|
||||
@@ -132,7 +164,7 @@ public class FenceBaseAssortGenerator(
|
||||
{
|
||||
mod.ParentId = "hideout";
|
||||
mod.SlotId = "hideout";
|
||||
mod.Upd = new Upd()
|
||||
mod.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 1,
|
||||
SptPresetId =
|
||||
@@ -209,7 +241,10 @@ public class FenceBaseAssortGenerator(
|
||||
}
|
||||
|
||||
// Plain old ammo, get its pen property
|
||||
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO)) return rootItemDb.Properties.PenetrationPower;
|
||||
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO))
|
||||
{
|
||||
return rootItemDb.Properties.PenetrationPower;
|
||||
}
|
||||
|
||||
// Not an ammobox or ammo
|
||||
return null;
|
||||
@@ -224,12 +259,16 @@ public class FenceBaseAssortGenerator(
|
||||
{
|
||||
// Armor has no mods, make no additions
|
||||
var hasMods = itemDbDetails.Properties.Slots.Count > 0;
|
||||
if (!hasMods) return;
|
||||
if (!hasMods)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for and add required soft inserts to armors
|
||||
var requiredSlots = itemDbDetails.Properties.Slots.Where((slot) => slot.Required ?? false).ToList();
|
||||
var requiredSlots = itemDbDetails.Properties.Slots.Where(slot => slot.Required ?? false).ToList();
|
||||
var hasRequiredSlots = requiredSlots.Count > 0;
|
||||
if (hasRequiredSlots)
|
||||
{
|
||||
foreach (var requiredSlot in requiredSlots)
|
||||
{
|
||||
var modItemDbDetails = itemHelper.GetItem(requiredSlot.Props.Filters[0].Plate).Value;
|
||||
@@ -237,7 +276,9 @@ public class FenceBaseAssortGenerator(
|
||||
requiredSlot.Props.Filters[0].Plate; // `Plate` property appears to be the 'default' item for slot
|
||||
if (string.IsNullOrEmpty(plateTpl))
|
||||
// Some bsg plate properties are empty, skip mod
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var mod = new Item
|
||||
{
|
||||
@@ -257,21 +298,25 @@ public class FenceBaseAssortGenerator(
|
||||
|
||||
armor.Add(mod);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for and add plate items
|
||||
var plateSlots = itemDbDetails.Properties.Slots.Where((slot) => itemHelper.IsRemovablePlateSlot(slot.Name))
|
||||
var plateSlots = itemDbDetails.Properties.Slots.Where(slot => itemHelper.IsRemovablePlateSlot(slot.Name))
|
||||
.ToList();
|
||||
if (plateSlots.Count > 0)
|
||||
{
|
||||
foreach (var plateSlot in plateSlots)
|
||||
{
|
||||
var plateTpl = plateSlot.Props.Filters[0].Plate;
|
||||
if (string.IsNullOrEmpty(plateTpl))
|
||||
// Bsg data lacks a default plate, skip adding mod
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var modItemDbDetails = itemHelper.GetItem(plateTpl).Value;
|
||||
armor.Add(
|
||||
new Item()
|
||||
new Item
|
||||
{
|
||||
Id = hashUtil.Generate(),
|
||||
Template = plateSlot.Props.Filters[0].Plate, // `Plate` property appears to be the 'default' item for slot
|
||||
@@ -288,6 +333,7 @@ public class FenceBaseAssortGenerator(
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,35 +51,43 @@ public class LocationLootGenerator(
|
||||
|
||||
var staticWeaponsOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticWeapons);
|
||||
if (staticWeaponsOnMapClone is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText("location-unable_to_find_static_weapon_for_map", locationBase.Name)
|
||||
);
|
||||
}
|
||||
|
||||
// Add mounted weapons to output loot
|
||||
result.AddRange(staticWeaponsOnMapClone);
|
||||
|
||||
var allStaticContainersOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticContainers);
|
||||
if (allStaticContainersOnMapClone is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText("location-unable_to_find_static_container_for_map", locationBase.Name)
|
||||
);
|
||||
}
|
||||
|
||||
// Containers that MUST be added to map (e.g. quest containers)
|
||||
var staticForcedOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticForced);
|
||||
if (staticForcedOnMapClone is null)
|
||||
{
|
||||
_logger.Error(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_find_forced_static_data_for_map",
|
||||
locationBase.Name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Remove christmas items from loot data
|
||||
if (!_seasonalEventService.ChristmasEventEnabled())
|
||||
{
|
||||
allStaticContainersOnMapClone = allStaticContainersOnMapClone.Where(
|
||||
item => !_seasonalEventConfig.ChristmasContainerIds.Contains(item.Template.Id)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var staticRandomisableContainersOnMap = GetRandomisableContainersOnMap(allStaticContainersOnMapClone);
|
||||
|
||||
@@ -106,7 +114,10 @@ public class LocationLootGenerator(
|
||||
staticLootItemCount += containerWithLoot.Template.Items.Count;
|
||||
}
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Added {guaranteedContainers.Count} guaranteed containers");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Added {guaranteedContainers.Count} guaranteed containers");
|
||||
}
|
||||
|
||||
// Randomisation is turned off globally or just turned off for this map
|
||||
if (
|
||||
@@ -117,9 +128,12 @@ public class LocationLootGenerator(
|
||||
)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Container randomisation disabled, Adding {staticRandomisableContainersOnMap.Count} containers to {locationBase.Name}"
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var container in staticRandomisableContainersOnMap)
|
||||
{
|
||||
var containerWithLoot = AddLootToContainer(
|
||||
@@ -153,11 +167,17 @@ public class LocationLootGenerator(
|
||||
foreach (var (key, data) in mapping)
|
||||
{
|
||||
// Count chosen was 0, skip
|
||||
if (data.ChosenCount == 0) continue;
|
||||
if (data.ChosenCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.ContainerIdsWithProbability.Count == 0)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -169,14 +189,21 @@ public class LocationLootGenerator(
|
||||
// Roll each containers probability, if it passes, it gets added
|
||||
data.ContainerIdsWithProbability = new Dictionary<string, double>();
|
||||
foreach (var containerId in containerIdsCopy)
|
||||
{
|
||||
if (_randomUtil.GetChance100(containerIdsCopy[containerId.Key] * 100))
|
||||
{
|
||||
data.ContainerIdsWithProbability[containerId.Key] = containerIdsCopy[containerId.Key];
|
||||
}
|
||||
}
|
||||
|
||||
// Set desired count to size of array (we want all containers chosen)
|
||||
data.ChosenCount = data.ContainerIdsWithProbability.Count;
|
||||
|
||||
// EDGE CASE: chosen container count could be 0
|
||||
if (data.ChosenCount == 0) continue;
|
||||
if (data.ChosenCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass possible containers into function to choose some
|
||||
@@ -190,9 +217,12 @@ public class LocationLootGenerator(
|
||||
if (containerObject is null)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Container: {chosenContainerId} not found in staticRandomisableContainersOnMap, this is bad"
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -271,9 +301,12 @@ public class LocationLootGenerator(
|
||||
if (containerData.ChosenCount > containerIds.Count)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Group: {groupId} wants {containerData.ChosenCount} containers but pool only has {containerIds.Count}, add what's available"
|
||||
);
|
||||
}
|
||||
|
||||
return containerIds;
|
||||
}
|
||||
|
||||
@@ -286,7 +319,7 @@ public class LocationLootGenerator(
|
||||
containerDistribution.Add(new ProbabilityObject<string, double>(x, value, value));
|
||||
}
|
||||
|
||||
chosenContainerIds.AddRange(containerDistribution.Draw((int)containerData.ChosenCount));
|
||||
chosenContainerIds.AddRange(containerDistribution.Draw((int) containerData.ChosenCount));
|
||||
|
||||
return chosenContainerIds;
|
||||
}
|
||||
@@ -303,25 +336,33 @@ public class LocationLootGenerator(
|
||||
// Create dictionary of all group ids and choose a count of containers the map will spawn of that group
|
||||
var mapping = new Dictionary<string, ContainerGroupCount>();
|
||||
foreach (var groupKvP in staticContainerGroupData.ContainersGroups)
|
||||
{
|
||||
if (staticContainerGroupData.ContainersGroups.TryGetValue(groupKvP.Key, out var groupData))
|
||||
{
|
||||
mapping[groupKvP.Key] = new ContainerGroupCount
|
||||
{
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
ChosenCount = _randomUtil.GetInt(
|
||||
(int)Math.Round(
|
||||
(int) Math.Round(
|
||||
groupData.MinContainers.Value *
|
||||
_locationConfig.ContainerRandomisationSettings.ContainerGroupMinSizeMultiplier
|
||||
),
|
||||
(int)Math.Round(
|
||||
(int) Math.Round(
|
||||
groupData.MaxContainers.Value *
|
||||
_locationConfig.ContainerRandomisationSettings.ContainerGroupMaxSizeMultiplier
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Add an empty group for containers without a group id but still have a < 100% chance to spawn
|
||||
// Likely bad BSG data, will be fixed...eventually, example of the groupids: `NEED_TO_BE_FIXED1`,`NEED_TO_BE_FIXED_SE02`, `NEED_TO_BE_FIXED_NW_01`
|
||||
mapping[""] = new ContainerGroupCount { ContainerIdsWithProbability = new Dictionary<string, double>(), ChosenCount = -1 };
|
||||
mapping[""] = new ContainerGroupCount
|
||||
{
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
ChosenCount = -1
|
||||
};
|
||||
|
||||
// Iterate over all containers and add to group keyed by groupId
|
||||
// Containers without a group go into a group with empty key ""
|
||||
@@ -342,9 +383,11 @@ public class LocationLootGenerator(
|
||||
if (container.Probability >= 1)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Container {container.Template.Id} with group: {groupData.GroupId} had 100 % chance to spawn was picked as random container, skipping"
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -397,8 +440,8 @@ public class LocationLootGenerator(
|
||||
|
||||
// Some containers need to have items forced into it (quest keys etc)
|
||||
var tplsForced = staticForced
|
||||
.Where((forcedStaticProp) => forcedStaticProp.ContainerId == containerClone.Template.Id)
|
||||
.Select((x) => x.ItemTpl);
|
||||
.Where(forcedStaticProp => forcedStaticProp.ContainerId == containerClone.Template.Id)
|
||||
.Select(x => x.ItemTpl);
|
||||
|
||||
// Draw random loot
|
||||
// Allow money to spawn more than once in container
|
||||
@@ -409,14 +452,17 @@ public class LocationLootGenerator(
|
||||
// Filter out items picked that're already in the above `tplsForced` array
|
||||
var chosenTpls = containerLootPool
|
||||
.Draw(itemCountToAdd, _locationConfig.AllowDuplicateItemsInStaticContainers, locklist)
|
||||
.Where((tpl) => !tplsForced.Contains(tpl));
|
||||
.Where(tpl => !tplsForced.Contains(tpl));
|
||||
|
||||
// Add forced loot to chosen item pool
|
||||
var tplsToAddToContainer = tplsForced.Concat(chosenTpls);
|
||||
foreach (var tplToAdd in tplsToAddToContainer)
|
||||
{
|
||||
var chosenItemWithChildren = CreateStaticLootItem(tplToAdd, staticAmmoDist, parentId);
|
||||
if (chosenItemWithChildren is null) continue;
|
||||
if (chosenItemWithChildren is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var items = _locationConfig.TplsToStripChildItemsFrom.Contains(tplToAdd)
|
||||
? [chosenItemWithChildren.Items[0]] // Strip children from parent
|
||||
@@ -431,7 +477,9 @@ public class LocationLootGenerator(
|
||||
{
|
||||
if (failedToFitCount > _locationConfig.FitLootIntoContainerAttempts)
|
||||
// x attempts to fit an item, container is probably full, stop trying to add more
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Can't fit item, skip
|
||||
failedToFitCount++;
|
||||
@@ -451,7 +499,12 @@ public class LocationLootGenerator(
|
||||
var rotation = result.Rotation.GetValueOrDefault(false) ? 1 : 0;
|
||||
|
||||
items[0].SlotId = "main";
|
||||
items[0].Location = new ItemLocation { X = result.X, Y = result.Y, R = rotation };
|
||||
items[0].Location = new ItemLocation
|
||||
{
|
||||
X = result.X,
|
||||
Y = result.Y,
|
||||
R = rotation
|
||||
};
|
||||
|
||||
// Add loot to container before returning
|
||||
containerClone.Template.Items.AddRange(items);
|
||||
@@ -528,7 +581,8 @@ public class LocationLootGenerator(
|
||||
"location-unable_to_find_count_distribution_for_container",
|
||||
new
|
||||
{
|
||||
containerId = containerTypeId, locationName
|
||||
containerId = containerTypeId,
|
||||
locationName
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -538,6 +592,7 @@ public class LocationLootGenerator(
|
||||
|
||||
foreach (var itemCountDistribution in countDistribution)
|
||||
// Add each count of items into array
|
||||
{
|
||||
itemCountArray.Add(
|
||||
new ProbabilityObject<int, float?>(
|
||||
itemCountDistribution.Count.Value,
|
||||
@@ -545,8 +600,9 @@ public class LocationLootGenerator(
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (int)Math.Round(GetStaticLootMultiplierForLocation(locationName) * itemCountArray.Draw()[0]);
|
||||
return (int) Math.Round(GetStaticLootMultiplierForLocation(locationName) * itemCountArray.Draw()[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -578,10 +634,15 @@ public class LocationLootGenerator(
|
||||
{
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(icd.Tpl))
|
||||
// Skip seasonal event items if they're not enabled
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl)) continue;
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemDistribution.Add(new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null));
|
||||
}
|
||||
@@ -617,18 +678,18 @@ public class LocationLootGenerator(
|
||||
if (!_seasonalEventService.ChristmasEventEnabled())
|
||||
{
|
||||
dynamicLootDist.Spawnpoints = dynamicLootDist.Spawnpoints.Where(
|
||||
(point) => !point.Template.Id.StartsWith("christmas")
|
||||
point => !point.Template.Id.StartsWith("christmas")
|
||||
)
|
||||
.ToList();
|
||||
dynamicLootDist.SpawnpointsForced = dynamicLootDist.SpawnpointsForced.Where(
|
||||
(point) => !point.Template.Id.StartsWith("christmas")
|
||||
point => !point.Template.Id.StartsWith("christmas")
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Build the list of forced loot from both `spawnpointsForced` and any point marked `IsAlwaysSpawn`
|
||||
dynamicForcedSpawnPoints.AddRange(dynamicLootDist.SpawnpointsForced);
|
||||
dynamicForcedSpawnPoints.AddRange(dynamicLootDist.Spawnpoints.Where((point) => point.Template.IsAlwaysSpawn ?? false));
|
||||
dynamicForcedSpawnPoints.AddRange(dynamicLootDist.Spawnpoints.Where(point => point.Template.IsAlwaysSpawn ?? false));
|
||||
|
||||
// Add forced loot
|
||||
AddForcedLoot(loot, dynamicForcedSpawnPoints, locationName, staticAmmoDist);
|
||||
@@ -639,8 +700,8 @@ public class LocationLootGenerator(
|
||||
var desiredSpawnpointCount = Math.Round(
|
||||
GetLooseLootMultiplerForLocation(locationName) *
|
||||
_randomUtil.GetNormallyDistributedRandomNumber(
|
||||
(double)dynamicLootDist.SpawnpointCount.Mean,
|
||||
(double)dynamicLootDist.SpawnpointCount.Std
|
||||
(double) dynamicLootDist.SpawnpointCount.Mean,
|
||||
(double) dynamicLootDist.SpawnpointCount.Std
|
||||
)
|
||||
);
|
||||
|
||||
@@ -655,12 +716,19 @@ public class LocationLootGenerator(
|
||||
// Point is blacklisted, skip
|
||||
if (blacklistedSpawnpoints?.Contains(spawnpoint.Template.Id) ?? false)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Ignoring loose loot location: {spawnpoint.Template.Id}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Ignoring loose loot location: {spawnpoint.Template.Id}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've handled IsAlwaysSpawn above, so skip them
|
||||
if (spawnpoint.Template.IsAlwaysSpawn ?? false) continue;
|
||||
if (spawnpoint.Template.IsAlwaysSpawn ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 100%, add it to guaranteed
|
||||
if (spawnpoint.Probability == 1)
|
||||
@@ -681,8 +749,12 @@ public class LocationLootGenerator(
|
||||
// Only draw random spawn points if needed
|
||||
if (randomSpawnpointCount > 0 && spawnpointArray.Count > 0)
|
||||
// Add randomly chosen spawn points
|
||||
foreach (var si in spawnpointArray.Draw((int)randomSpawnpointCount, false))
|
||||
{
|
||||
foreach (var si in spawnpointArray.Draw((int) randomSpawnpointCount, false))
|
||||
{
|
||||
chosenSpawnpoints.Add(spawnpointArray.Data(si));
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out duplicate locationIds // prob can be done better
|
||||
chosenSpawnpoints = chosenSpawnpoints.GroupBy(spawnpoint => spawnpoint.LocationId).Select(group => group.First()).ToList();
|
||||
@@ -690,7 +762,9 @@ public class LocationLootGenerator(
|
||||
// Do we have enough items in pool to fulfill requirement
|
||||
var tooManySpawnPointsRequested = desiredSpawnpointCount - chosenSpawnpoints.Count > 0;
|
||||
if (tooManySpawnPointsRequested)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
_localisationService.GetText(
|
||||
"location-spawn_point_count_requested_vs_found",
|
||||
@@ -702,6 +776,8 @@ public class LocationLootGenerator(
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over spawnpoints
|
||||
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
|
||||
@@ -720,36 +796,43 @@ public class LocationLootGenerator(
|
||||
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
spawnPoint.Template.Items = spawnPoint.Template.Items.Where(
|
||||
(item) => !_itemFilterService.IsLootableItemBlacklisted(item.Template)
|
||||
item => !_itemFilterService.IsLootableItemBlacklisted(item.Template)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
// Ensure no seasonal items are in pool if not in-season
|
||||
if (!seasonalEventActive)
|
||||
{
|
||||
spawnPoint.Template.Items = spawnPoint.Template.Items.Where(
|
||||
(item) => !seasonalItemTplBlacklist.Contains(item.Template)
|
||||
item => !seasonalItemTplBlacklist.Contains(item.Template)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Spawn point has no items after filtering, skip
|
||||
if (spawnPoint.Template.Items is null || spawnPoint.Template.Items.Count == 0)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
_localisationService.GetText("location-spawnpoint_missing_items", spawnPoint.Template.Id)
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get an array of allowed IDs after above filtering has occured
|
||||
var validItemIds = spawnPoint.Template.Items.Select((item) => item.Id).ToList();
|
||||
var validItemIds = spawnPoint.Template.Items.Select(item => item.Id).ToList();
|
||||
|
||||
// Construct container to hold above filtered items, letting us pick an item for the spot
|
||||
var itemArray = new ProbabilityObjectArray<string, double?>(_mathUtil, _cloner);
|
||||
foreach (var itemDist in spawnPoint.ItemDistribution)
|
||||
{
|
||||
if (!validItemIds.Contains(itemDist.ComposedKey.Key)) continue;
|
||||
if (!validItemIds.Contains(itemDist.ComposedKey.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemArray.Add(new ProbabilityObject<string, double?>(itemDist.ComposedKey.Key, itemDist.RelativeProbability ?? 0, null));
|
||||
}
|
||||
@@ -764,7 +847,7 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
// Draw a random item from spawn points possible items
|
||||
var chosenComposedKey = itemArray.Draw(1).FirstOrDefault();
|
||||
var chosenComposedKey = itemArray.Draw().FirstOrDefault();
|
||||
var createItemResult = CreateDynamicLootItem(
|
||||
chosenComposedKey,
|
||||
spawnPoint.Template.Items,
|
||||
@@ -796,16 +879,20 @@ public class LocationLootGenerator(
|
||||
var lootToForceSingleAmountOnMap = _locationConfig.ForcedLootSingleSpawnById.GetValueOrDefault(locationName);
|
||||
if (lootToForceSingleAmountOnMap is not null)
|
||||
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
|
||||
{
|
||||
foreach (var itemTpl in lootToForceSingleAmountOnMap)
|
||||
{
|
||||
// Get all spawn positions for item tpl in forced loot array
|
||||
var items = forcedSpawnPoints.Where(
|
||||
(forcedSpawnPoint) => forcedSpawnPoint.Template.Items[0].Template == itemTpl
|
||||
forcedSpawnPoint => forcedSpawnPoint.Template.Items[0].Template == itemTpl
|
||||
);
|
||||
if (items is null || !items.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to adjust loot item {itemTpl} as it does not exist inside {locationName} forced loot.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -813,12 +900,14 @@ public class LocationLootGenerator(
|
||||
var spawnpointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
|
||||
foreach (var si in items)
|
||||
// use locationId as template.Id is the same across all items
|
||||
{
|
||||
spawnpointArray.Add(new ProbabilityObject<string, Spawnpoint>(si.LocationId, si.Probability ?? 0, si));
|
||||
}
|
||||
|
||||
// Choose 1 out of all found spawn positions for spawn id and add to loot array
|
||||
foreach (var spawnPointLocationId in spawnpointArray.Draw(1, false))
|
||||
{
|
||||
var itemToAdd = items.FirstOrDefault((item) => item.LocationId == spawnPointLocationId);
|
||||
var itemToAdd = items.FirstOrDefault(item => item.LocationId == spawnPointLocationId);
|
||||
var lootItem = itemToAdd?.Template;
|
||||
if (lootItem is null)
|
||||
{
|
||||
@@ -838,6 +927,7 @@ public class LocationLootGenerator(
|
||||
lootLocationTemplates.Add(lootItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
|
||||
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
@@ -848,10 +938,16 @@ public class LocationLootGenerator(
|
||||
var firstLootItemTpl = forcedLootLocation.Template.Items.FirstOrDefault().Template;
|
||||
|
||||
// Skip spawn positions processed already
|
||||
if (lootToForceSingleAmountOnMap?.Contains(firstLootItemTpl) ?? false) continue;
|
||||
if (lootToForceSingleAmountOnMap?.Contains(firstLootItemTpl) ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip adding seasonal items when seasonal event is not active
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(firstLootItemTpl)) continue;
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(firstLootItemTpl))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var locationTemplateToAdd = forcedLootLocation.Template;
|
||||
var createItemResult = CreateDynamicLootItem(
|
||||
@@ -866,7 +962,7 @@ public class LocationLootGenerator(
|
||||
|
||||
// Push forced location into array as long as it doesnt exist already
|
||||
var existingLocation = lootLocationTemplates.Any(
|
||||
(spawnPoint) => spawnPoint.Id == locationTemplateToAdd.Id
|
||||
spawnPoint => spawnPoint.Id == locationTemplateToAdd.Id
|
||||
);
|
||||
if (!existingLocation)
|
||||
{
|
||||
@@ -875,20 +971,29 @@ public class LocationLootGenerator(
|
||||
else
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Attempted to add a forced loot location with Id: {locationTemplateToAdd.Id} to map {locationName} that already has that id in use, skipping"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ContainerItem CreateDynamicLootItem(string? chosenComposedKey, List<Item> items, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist)
|
||||
{
|
||||
var chosenItem = items.FirstOrDefault((item) => item.Id == chosenComposedKey);
|
||||
var chosenItem = items.FirstOrDefault(item => item.Id == chosenComposedKey);
|
||||
var chosenTpl = chosenItem?.Template;
|
||||
if (chosenTpl is null) throw new Exception($"Item for tpl {chosenComposedKey} was not found in the spawn point");
|
||||
if (chosenTpl is null)
|
||||
{
|
||||
throw new Exception($"Item for tpl {chosenComposedKey} was not found in the spawn point");
|
||||
}
|
||||
|
||||
var itemTemplate = _itemHelper.GetItem(chosenTpl).Value;
|
||||
if (itemTemplate is null) _logger.Error($"Item tpl: {chosenTpl} cannot be found in database");
|
||||
if (itemTemplate is null)
|
||||
{
|
||||
_logger.Error($"Item tpl: {chosenTpl} cannot be found in database");
|
||||
}
|
||||
|
||||
// Item array to return
|
||||
List<Item> itemWithMods = [];
|
||||
@@ -906,24 +1011,42 @@ public class LocationLootGenerator(
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenTpl,
|
||||
Upd = new Upd { StackObjectsCount = stackCount }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = stackCount
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
// Fill with cartridges
|
||||
List<Item> ammoBoxItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
|
||||
List<Item> ammoBoxItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenTpl
|
||||
}
|
||||
];
|
||||
_itemHelper.AddCartridgesToAmmoBox(ammoBoxItem, itemTemplate);
|
||||
itemWithMods.AddRange(ammoBoxItem);
|
||||
}
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
|
||||
{
|
||||
// Create array with just magazine
|
||||
List<Item> magazineItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
|
||||
List<Item> magazineItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenTpl
|
||||
}
|
||||
];
|
||||
|
||||
if (_randomUtil.GetChance100(_locationConfig.StaticMagazineLootHasAmmoChancePercent))
|
||||
// Add randomised amount of cartridges
|
||||
{
|
||||
_itemHelper.FillMagazineWithRandomCartridge(
|
||||
magazineItem,
|
||||
itemTemplate, // Magazine template
|
||||
@@ -931,6 +1054,7 @@ public class LocationLootGenerator(
|
||||
null,
|
||||
_locationConfig.MinFillLooseMagazinePercent / 100
|
||||
);
|
||||
}
|
||||
|
||||
itemWithMods.AddRange(magazineItem);
|
||||
}
|
||||
@@ -945,7 +1069,9 @@ public class LocationLootGenerator(
|
||||
|
||||
if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template))
|
||||
// Strip children from parent before adding
|
||||
{
|
||||
itemWithChildren = [itemWithChildren[0]];
|
||||
}
|
||||
|
||||
itemWithMods.AddRange(itemWithChildren);
|
||||
}
|
||||
@@ -953,7 +1079,12 @@ public class LocationLootGenerator(
|
||||
// Get inventory size of item
|
||||
var size = _itemHelper.GetItemSize(itemWithMods, itemWithMods[0].Id);
|
||||
|
||||
return new ContainerItem { Items = itemWithMods, Width = size.Width, Height = size.Height };
|
||||
return new ContainerItem
|
||||
{
|
||||
Items = itemWithMods,
|
||||
Width = size.Width,
|
||||
Height = size.Height
|
||||
};
|
||||
}
|
||||
|
||||
private double GetLooseLootMultiplerForLocation(string location)
|
||||
@@ -983,11 +1114,21 @@ public class LocationLootGenerator(
|
||||
|
||||
var width = itemTemplate.Properties.Width;
|
||||
var height = itemTemplate.Properties.Height;
|
||||
List<Item> items = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
|
||||
List<Item> items =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenTpl
|
||||
}
|
||||
];
|
||||
var rootItem = items.FirstOrDefault();
|
||||
|
||||
// Use passed in parentId as override for new item
|
||||
if (!string.IsNullOrEmpty(parentId)) rootItem.ParentId = parentId;
|
||||
if (!string.IsNullOrEmpty(parentId))
|
||||
{
|
||||
rootItem.ParentId = parentId;
|
||||
}
|
||||
|
||||
if (
|
||||
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MONEY) ||
|
||||
@@ -999,7 +1140,10 @@ public class LocationLootGenerator(
|
||||
? 1
|
||||
: _randomUtil.GetInt(itemTemplate.Properties.StackMinRandom.Value, itemTemplate.Properties.StackMaxRandom.Value);
|
||||
|
||||
rootItem.Upd = new Upd { StackObjectsCount = stackCount };
|
||||
rootItem.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = stackCount
|
||||
};
|
||||
}
|
||||
// No spawn point, use default template
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.WEAPON))
|
||||
@@ -1028,7 +1172,12 @@ public class LocationLootGenerator(
|
||||
items = GetArmorItems(chosenTpl, rootItem, items, itemTemplate);
|
||||
}
|
||||
|
||||
return new ContainerItem { Items = items, Width = width, Height = height };
|
||||
return new ContainerItem
|
||||
{
|
||||
Items = items,
|
||||
Width = width,
|
||||
Height = height
|
||||
};
|
||||
}
|
||||
|
||||
private List<Item> GetArmorItems(string chosenTpl, Item? rootItem, List<Item> items, TemplateItem armorDbTemplate)
|
||||
@@ -1047,11 +1196,13 @@ public class LocationLootGenerator(
|
||||
{
|
||||
// We make base item in calling method, no need to do it here
|
||||
if ((armorDbTemplate.Properties.Slots?.Count ?? 0) > 0)
|
||||
{
|
||||
items = _itemHelper.AddChildSlotItems(
|
||||
items,
|
||||
armorDbTemplate,
|
||||
_locationConfig.EquipmentLootSettings.ModSpawnChancePercent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -1092,7 +1243,10 @@ public class LocationLootGenerator(
|
||||
else
|
||||
{
|
||||
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesn't have any default presets and kills this code below as it has no chidren to reparent
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"createStaticLootItem() No preset found for weapon: {chosenTpl}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"createStaticLootItem() No preset found for weapon: {chosenTpl}");
|
||||
}
|
||||
}
|
||||
|
||||
rootItem = items[0];
|
||||
@@ -1114,7 +1268,10 @@ public class LocationLootGenerator(
|
||||
|
||||
try
|
||||
{
|
||||
if (children?.Count > 0) items = _itemHelper.ReparentItemAndChildren(rootItem, children);
|
||||
if (children?.Count > 0)
|
||||
{
|
||||
items = _itemHelper.ReparentItemAndChildren(rootItem, children);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1124,7 +1281,7 @@ public class LocationLootGenerator(
|
||||
new
|
||||
{
|
||||
tpl = chosenTpl,
|
||||
parentId = parentId
|
||||
parentId
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -1185,20 +1342,40 @@ public class LocationLootGenerator(
|
||||
public class ContainerGroupCount
|
||||
{
|
||||
[JsonPropertyName("containerIdsWithProbability")]
|
||||
public Dictionary<string, double>? ContainerIdsWithProbability { get; set; }
|
||||
public Dictionary<string, double>? ContainerIdsWithProbability
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("chosenCount")]
|
||||
public double? ChosenCount { get; set; }
|
||||
public double? ChosenCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public class ContainerItem
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public List<Item>? Items { get; set; }
|
||||
public List<Item>? Items
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
public double? Width { get; set; }
|
||||
public double? Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("height")]
|
||||
public double? Height { get; set; }
|
||||
public double? Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Core.Helpers;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Common;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -11,6 +10,7 @@ using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Generators;
|
||||
@@ -31,7 +31,7 @@ public class LootGenerator(
|
||||
)
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a list of items based on configuration options parameter
|
||||
/// Generate a list of items based on configuration options parameter
|
||||
/// </summary>
|
||||
/// <param name="options">parameters to adjust how loot is generated</param>
|
||||
/// <returns>An array of loot items</returns>
|
||||
@@ -50,7 +50,7 @@ public class LootGenerator(
|
||||
// Get list of all sealed containers from db - they're all the same, just for flavor
|
||||
var itemsDb = _itemHelper.GetItems();
|
||||
var sealedWeaponContainerPool = itemsDb.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
item.Name.Contains("event_container_airdrop")
|
||||
);
|
||||
|
||||
@@ -86,9 +86,13 @@ public class LootGenerator(
|
||||
{
|
||||
var randomisedItemCount = _randomUtil.GetDouble(options.ItemCount.Min.Value, options.ItemCount.Max.Value);
|
||||
for (var index = 0; index < randomisedItemCount; index++)
|
||||
{
|
||||
if (!FindAndAddRandomItemToLoot(rewardPoolResults.ItemPool, itemTypeCounts, options, result))
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var globalDefaultPresets = _presetHelper.GetDefaultPresets().Values;
|
||||
@@ -101,13 +105,15 @@ public class LootGenerator(
|
||||
if (randomisedWeaponPresetCount > 0)
|
||||
{
|
||||
var weaponDefaultPresets = globalDefaultPresets.Where(
|
||||
(preset) =>
|
||||
preset =>
|
||||
_itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
if (weaponDefaultPresets.Any())
|
||||
{
|
||||
for (var index = 0; index < randomisedWeaponPresetCount; index++)
|
||||
{
|
||||
if (
|
||||
!FindAndAddRandomPresetToLoot(
|
||||
weaponDefaultPresets,
|
||||
@@ -117,7 +123,11 @@ public class LootGenerator(
|
||||
)
|
||||
)
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter default presets to just armors and then filter again by protection level
|
||||
@@ -128,18 +138,20 @@ public class LootGenerator(
|
||||
if (randomisedArmorPresetCount > 0)
|
||||
{
|
||||
var armorDefaultPresets = globalDefaultPresets.Where(
|
||||
(preset) =>
|
||||
preset =>
|
||||
_itemHelper.ArmorItemCanHoldMods(preset.Encyclopedia)
|
||||
);
|
||||
var levelFilteredArmorPresets = armorDefaultPresets.Where(
|
||||
(armor) =>
|
||||
armor =>
|
||||
IsArmorOfDesiredProtectionLevel(armor, options)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
// Add some armors to rewards
|
||||
if (levelFilteredArmorPresets.Any())
|
||||
{
|
||||
for (var index = 0; index < randomisedArmorPresetCount; index++)
|
||||
{
|
||||
if (
|
||||
!FindAndAddRandomPresetToLoot(
|
||||
levelFilteredArmorPresets,
|
||||
@@ -149,15 +161,19 @@ public class LootGenerator(
|
||||
)
|
||||
)
|
||||
// Failed to add, reduce index so we get another attempt
|
||||
{
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate An array of items
|
||||
/// TODO - handle weapon presets/ammo packs
|
||||
/// Generate An array of items
|
||||
/// TODO - handle weapon presets/ammo packs
|
||||
/// </summary>
|
||||
/// <param name="forcedLootDict">Dictionary of item tpls with minmax values</param>
|
||||
/// <returns>Array of Item</returns>
|
||||
@@ -192,7 +208,7 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get pool of items from item db that fit passed in param criteria
|
||||
/// Get pool of items from item db that fit passed in param criteria
|
||||
/// </summary>
|
||||
/// <param name="itemTplBlacklist">Prevent these items</param>
|
||||
/// <param name="itemTypeWhitelist">Only allow these items</param>
|
||||
@@ -215,8 +231,8 @@ public class LootGenerator(
|
||||
// Get all items that match the blacklisted types and fold into item blacklist
|
||||
var itemTypeBlacklist = _itemFilterService.GetItemRewardBaseTypeBlacklist();
|
||||
var itemsMatchingTypeBlacklist = itemsDb
|
||||
.Where((templateItem) => _itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist))
|
||||
.Select((templateItem) => templateItem.Id);
|
||||
.Where(templateItem => _itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist))
|
||||
.Select(templateItem => templateItem.Id);
|
||||
|
||||
// Clear out blacklist
|
||||
itemBlacklist = [];
|
||||
@@ -226,11 +242,15 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
if (!allowBossItems)
|
||||
{
|
||||
foreach (var bossItem in _itemFilterService.GetBossItems())
|
||||
{
|
||||
itemBlacklist.Add(bossItem);
|
||||
}
|
||||
}
|
||||
|
||||
var items = itemsDb.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
!itemBlacklist.Contains(item.Id) &&
|
||||
item.Type.ToLower() == "item" &&
|
||||
!item.Properties.QuestItem.GetValueOrDefault(false) &&
|
||||
@@ -238,17 +258,15 @@ public class LootGenerator(
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return new ItemRewardPoolResults { ItemPool = items, Blacklist = itemBlacklist };
|
||||
}
|
||||
|
||||
public record ItemRewardPoolResults
|
||||
{
|
||||
public List<TemplateItem> ItemPool { get; set; }
|
||||
public HashSet<string> Blacklist { get; set; }
|
||||
return new ItemRewardPoolResults
|
||||
{
|
||||
ItemPool = items,
|
||||
Blacklist = itemBlacklist
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter armor items by their front plates protection level - top if it's a helmet
|
||||
/// Filter armor items by their front plates protection level - top if it's a helmet
|
||||
/// </summary>
|
||||
/// <param name="armor">Armor preset to check</param>
|
||||
/// <param name="options">Loot request options - armor level etc</param>
|
||||
@@ -258,8 +276,11 @@ public class LootGenerator(
|
||||
string[] relevantSlots = ["front_plate", "helmet_top", "soft_armor_front"];
|
||||
foreach (var slotId in relevantSlots)
|
||||
{
|
||||
var armorItem = armor.Items.FirstOrDefault((item) => item?.SlotId?.ToLower() == slotId);
|
||||
if (armorItem is null) continue;
|
||||
var armorItem = armor.Items.FirstOrDefault(item => item?.SlotId?.ToLower() == slotId);
|
||||
if (armorItem is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var armorDetails = _itemHelper.GetItem(armorItem.Template).Value;
|
||||
var armorClass = armorDetails.Properties.ArmorClass;
|
||||
@@ -271,20 +292,27 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct item limit record to hold max and current item count for each item type
|
||||
/// Construct item limit record to hold max and current item count for each item type
|
||||
/// </summary>
|
||||
/// <param name="limits">limits as defined in config</param>
|
||||
/// <returns>record, key: item tplId, value: current/max item count allowed</returns>
|
||||
private Dictionary<string, ItemLimit> InitItemLimitCounter(Dictionary<string, double> limits)
|
||||
{
|
||||
var itemTypeCounts = new Dictionary<string, ItemLimit>();
|
||||
foreach (var itemTypeId in limits) itemTypeCounts[itemTypeId.Key] = new ItemLimit() { Current = 0, Max = limits[itemTypeId.Key] };
|
||||
foreach (var itemTypeId in limits)
|
||||
{
|
||||
itemTypeCounts[itemTypeId.Key] = new ItemLimit
|
||||
{
|
||||
Current = 0,
|
||||
Max = limits[itemTypeId.Key]
|
||||
};
|
||||
}
|
||||
|
||||
return itemTypeCounts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a random item in items.json and add to result array
|
||||
/// Find a random item in items.json and add to result array
|
||||
/// </summary>
|
||||
/// <param name="items">items to choose from</param>
|
||||
/// <param name="itemTypeCounts">item limit counts</param>
|
||||
@@ -298,10 +326,16 @@ public class LootGenerator(
|
||||
var randomItem = _randomUtil.GetArrayValue(items);
|
||||
|
||||
var itemLimitCount = itemTypeCounts.TryGetValue(randomItem.Parent, out var randomItemLimitCount);
|
||||
if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max) return false;
|
||||
if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip armors as they need to come from presets
|
||||
if (_itemHelper.ArmorItemCanHoldMods(randomItem.Id)) return false;
|
||||
if (_itemHelper.ArmorItemCanHoldMods(randomItem.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var newLootItem = new Item
|
||||
{
|
||||
@@ -315,21 +349,26 @@ public class LootGenerator(
|
||||
};
|
||||
|
||||
// Special case - handle items that need a stackcount > 1
|
||||
if (randomItem.Properties.StackMaxSize > 1) newLootItem.Upd.StackObjectsCount = GetRandomisedStackCount(randomItem, options);
|
||||
if (randomItem.Properties.StackMaxSize > 1)
|
||||
{
|
||||
newLootItem.Upd.StackObjectsCount = GetRandomisedStackCount(randomItem, options);
|
||||
}
|
||||
|
||||
newLootItem.Template = randomItem.Id;
|
||||
result.Add(newLootItem);
|
||||
|
||||
if (randomItemLimitCount is not null)
|
||||
// Increment item count as it's in limit array
|
||||
{
|
||||
randomItemLimitCount.Current++;
|
||||
}
|
||||
|
||||
// Item added okay
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a randomised stack count for an item between its StackMinRandom and StackMaxSize values
|
||||
/// Get a randomised stack count for an item between its StackMinRandom and StackMaxSize values
|
||||
/// </summary>
|
||||
/// <param name="item">item to get stack count of</param>
|
||||
/// <param name="options">loot options</param>
|
||||
@@ -341,15 +380,15 @@ public class LootGenerator(
|
||||
|
||||
if (options.ItemStackLimits.TryGetValue(item.Id, out var itemLimits))
|
||||
{
|
||||
min = (int?)itemLimits.Min;
|
||||
max = (int?)itemLimits.Max;
|
||||
min = (int?) itemLimits.Min;
|
||||
max = (int?) itemLimits.Max;
|
||||
}
|
||||
|
||||
return _randomUtil.GetInt((min ?? 1), max ?? 1);
|
||||
return _randomUtil.GetInt(min ?? 1, max ?? 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a random item in items.json and add to result list
|
||||
/// Find a random item in items.json and add to result list
|
||||
/// </summary>
|
||||
/// <param name="presetPool">Presets to choose from</param>
|
||||
/// <param name="itemTypeCounts">Item limit counts</param>
|
||||
@@ -373,7 +412,10 @@ public class LootGenerator(
|
||||
// No `_encyclopedia` property, not possible to reliably get root item tpl
|
||||
if (chosenPreset.Encyclopedia is null)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Preset with id: {chosenPreset?.Id} lacks encyclopedia property, skipping");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Preset with id: {chosenPreset?.Id} lacks encyclopedia property, skipping");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -382,13 +424,19 @@ public class LootGenerator(
|
||||
var itemDbDetails = _itemHelper.GetItem(chosenPreset.Encyclopedia);
|
||||
if (!itemDbDetails.Key)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip preset if root item is blacklisted
|
||||
if (itemBlacklist.Contains(chosenPreset.Items[0].Template)) return false;
|
||||
if (itemBlacklist.Contains(chosenPreset.Items[0].Template))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some custom mod items lack a parent property
|
||||
if (itemDbDetails.Value.Parent is null)
|
||||
@@ -400,23 +448,31 @@ public class LootGenerator(
|
||||
|
||||
// Check chosen preset hasn't exceeded spawn limit
|
||||
var hasItemLimitCount = itemTypeCounts.TryGetValue(itemDbDetails.Value.Parent, out var itemLimitCount);
|
||||
if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max) return false;
|
||||
if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var presetAndMods = _itemHelper.ReplaceIDs(_cloner.Clone(chosenPreset.Items));
|
||||
_itemHelper.RemapRootItemId(presetAndMods);
|
||||
// Add chosen preset tpl to result array
|
||||
foreach (var item in presetAndMods) result.Add(item);
|
||||
foreach (var item in presetAndMods)
|
||||
{
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
if (itemLimitCount is not null)
|
||||
// Increment item count as item has been chosen and its inside itemLimitCount dictionary
|
||||
{
|
||||
itemLimitCount.Current++;
|
||||
}
|
||||
|
||||
// Item added okay
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sealed weapon containers have a weapon + associated mods inside them + assortment of other things (food/meds)
|
||||
/// Sealed weapon containers have a weapon + associated mods inside them + assortment of other things (food/meds)
|
||||
/// </summary>
|
||||
/// <param name="containerSettings">sealed weapon container settings</param>
|
||||
/// <returns>List of items with children lists</returns>
|
||||
@@ -474,7 +530,7 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get non-weapon mod rewards for a sealed container
|
||||
/// Get non-weapon mod rewards for a sealed container
|
||||
/// </summary>
|
||||
/// <param name="containerSettings">Sealed weapon container settings</param>
|
||||
/// <param name="weaponDetailsDb">Details for the weapon to reward player</param>
|
||||
@@ -488,14 +544,17 @@ public class LootGenerator(
|
||||
{
|
||||
var rewardCount = _randomUtil.GetDouble(settings.Min.Value, settings.Max.Value);
|
||||
|
||||
if (rewardCount == 0) continue;
|
||||
if (rewardCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edge case - ammo boxes
|
||||
if (rewardKey == BaseClasses.AMMO_BOX)
|
||||
{
|
||||
// Get ammoboxes from db
|
||||
var ammoBoxesDetails = containerSettings.AmmoBoxWhitelist.Select(
|
||||
(tpl) =>
|
||||
tpl =>
|
||||
{
|
||||
var itemDetails = _itemHelper.GetItem(tpl);
|
||||
return itemDetails.Value;
|
||||
@@ -505,12 +564,15 @@ public class LootGenerator(
|
||||
// Need to find boxes that matches weapons caliber
|
||||
var weaponCaliber = weaponDetailsDb.Properties.AmmoCaliber;
|
||||
var ammoBoxesMatchingCaliber = ammoBoxesDetails.Where(
|
||||
(x) =>
|
||||
x =>
|
||||
x.Properties.AmmoCaliber == weaponCaliber
|
||||
);
|
||||
if (!ammoBoxesMatchingCaliber.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"No ammo box with caliber {weaponCaliber} found, skipping");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"No ammo box with caliber {weaponCaliber} found, skipping");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -518,7 +580,14 @@ public class LootGenerator(
|
||||
for (var index = 0; index < rewardCount; index++)
|
||||
{
|
||||
var chosenAmmoBox = _randomUtil.GetArrayValue(ammoBoxesMatchingCaliber);
|
||||
var ammoBoxReward = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenAmmoBox.Id } };
|
||||
var ammoBoxReward = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenAmmoBox.Id
|
||||
}
|
||||
};
|
||||
_itemHelper.AddCartridgesToAmmoBox(ammoBoxReward, chosenAmmoBox);
|
||||
rewards.Add(ammoBoxReward);
|
||||
}
|
||||
@@ -529,7 +598,7 @@ public class LootGenerator(
|
||||
// Get all items of the desired type + not quest items + not globally blacklisted
|
||||
var rewardItemPool = _databaseService.GetItems()
|
||||
.Values.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
item.Parent == rewardKey &&
|
||||
item.Type.ToLower() == "item" &&
|
||||
_itemFilterService.IsItemBlacklisted(item.Id) &&
|
||||
@@ -539,7 +608,10 @@ public class LootGenerator(
|
||||
|
||||
if (rewardItemPool.Count() == 0)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"No items with base type of {rewardKey} found, skipping");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"No items with base type of {rewardKey} found, skipping");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -548,7 +620,14 @@ public class LootGenerator(
|
||||
{
|
||||
// Choose a random item from pool
|
||||
var chosenRewardItem = _randomUtil.GetArrayValue(rewardItemPool);
|
||||
var rewardItem = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenRewardItem.Id } };
|
||||
var rewardItem = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenRewardItem.Id
|
||||
}
|
||||
};
|
||||
|
||||
rewards.Add(rewardItem);
|
||||
}
|
||||
@@ -558,7 +637,7 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over the container weaponModRewardLimits settings and create a list of weapon mods to reward player
|
||||
/// Iterate over the container weaponModRewardLimits settings and create a list of weapon mods to reward player
|
||||
/// </summary>
|
||||
/// <param name="containerSettings">Sealed weapon container settings</param>
|
||||
/// <param name="linkedItemsToWeapon">All items that can be attached/inserted into weapon</param>
|
||||
@@ -574,18 +653,24 @@ public class LootGenerator(
|
||||
var rewardCount = _randomUtil.GetDouble(settings.Min.Value, settings.Max.Value);
|
||||
|
||||
// Nothing to add, skip reward type
|
||||
if (rewardCount == 0) continue;
|
||||
if (rewardCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get items that fulfil reward type criteria from items that fit on gun
|
||||
var relatedItems = linkedItemsToWeapon?.Where(
|
||||
(item) => item?.Parent == rewardKey && !_itemFilterService.IsItemBlacklisted(item.Id)
|
||||
item => item?.Parent == rewardKey && !_itemFilterService.IsItemBlacklisted(item.Id)
|
||||
);
|
||||
if (relatedItems is null || relatedItems.Count() == 0)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"No items found to fulfil reward type: {rewardKey} for weapon: {chosenWeaponPreset.Name}, skipping type"
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -593,7 +678,14 @@ public class LootGenerator(
|
||||
for (var index = 0; index < rewardCount; index++)
|
||||
{
|
||||
var chosenItem = _randomUtil.DrawRandomFromList(relatedItems.ToList());
|
||||
var reward = new List<Item> { new() { Id = _hashUtil.Generate(), Template = chosenItem[0].Id } };
|
||||
var reward = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenItem[0].Id
|
||||
}
|
||||
};
|
||||
|
||||
modRewards.Add(reward);
|
||||
}
|
||||
@@ -603,7 +695,7 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle event-related loot containers - currently just the halloween jack-o-lanterns that give food rewards
|
||||
/// Handle event-related loot containers - currently just the halloween jack-o-lanterns that give food rewards
|
||||
/// </summary>
|
||||
/// <param name="rewardContainerDetails"></param>
|
||||
/// <returns>List of item with children lists</returns>
|
||||
@@ -630,7 +722,14 @@ public class LootGenerator(
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Item> rewardItem = [new() { Id = _hashUtil.Generate(), Template = chosenRewardItemTpl }];
|
||||
List<Item> rewardItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = chosenRewardItemTpl
|
||||
}
|
||||
];
|
||||
itemsToReturn.Add(rewardItem);
|
||||
}
|
||||
|
||||
@@ -638,29 +737,54 @@ public class LootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a reward item based on the reward details data
|
||||
/// Pick a reward item based on the reward details data
|
||||
/// </summary>
|
||||
/// <param name="rewardContainerDetails"></param>
|
||||
/// <returns>Single tpl</returns>
|
||||
protected string PickRewardItem(RewardDetails rewardContainerDetails)
|
||||
{
|
||||
if (rewardContainerDetails.RewardTplPool is not null && rewardContainerDetails.RewardTplPool.Count > 0)
|
||||
{
|
||||
return _weightedRandomHelper.GetWeightedValue(rewardContainerDetails.RewardTplPool);
|
||||
}
|
||||
|
||||
return _randomUtil.GetArrayValue(
|
||||
GetItemRewardPool([], rewardContainerDetails.RewardTypePool, true, true)
|
||||
.ItemPool.Select(
|
||||
(item) => item.Id
|
||||
item => item.Id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public record ItemRewardPoolResults
|
||||
{
|
||||
public List<TemplateItem> ItemPool
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public HashSet<string> Blacklist
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemLimit
|
||||
{
|
||||
[JsonPropertyName("current")]
|
||||
public double Current { get; set; }
|
||||
public double Current
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonPropertyName("max")]
|
||||
public double Max { get; set; }
|
||||
public double Max
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Utils;
|
||||
using Core.Helpers;
|
||||
using Core.Services;
|
||||
using Core.Servers;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class PMCLootGenerator
|
||||
{
|
||||
private readonly ISptLogger<PMCLootGenerator> _logger;
|
||||
private readonly ConfigServer _configServer;
|
||||
private readonly DatabaseService _databaseService;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly ItemFilterService _itemFilterService;
|
||||
private readonly ItemHelper _itemHelper;
|
||||
private readonly ISptLogger<PMCLootGenerator> _logger;
|
||||
private readonly PmcConfig _pmcConfig;
|
||||
private readonly RagfairPriceService _ragfairPriceService;
|
||||
private readonly SeasonalEventService _seasonalEventService;
|
||||
private readonly WeightedRandomHelper _weightedRandomHelper;
|
||||
private readonly ConfigServer _configServer;
|
||||
|
||||
private Dictionary<string, double>? _backpackLootPool;
|
||||
private Dictionary<string, double>? _pocketLootPool;
|
||||
private Dictionary<string, double>? _vestLootPool;
|
||||
private readonly PmcConfig _pmcConfig;
|
||||
|
||||
public PMCLootGenerator(
|
||||
ISptLogger<PMCLootGenerator> logger,
|
||||
@@ -50,7 +50,7 @@ public class PMCLootGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a List of loot items a PMC can have in their pockets
|
||||
/// Create a List of loot items a PMC can have in their pockets
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns>Dictionary of string and number</returns>
|
||||
@@ -69,7 +69,7 @@ public class PMCLootGenerator
|
||||
var blacklist = GetLootBlacklist();
|
||||
|
||||
var itemsToAdd = items.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
|
||||
_itemHelper.IsValidItem(item.Value.Id) &&
|
||||
!blacklist.Contains(item.Value.Id) &&
|
||||
@@ -79,6 +79,7 @@ public class PMCLootGenerator
|
||||
|
||||
foreach (var (tpl, template) in itemsToAdd)
|
||||
// If pmc has price override, use that. Otherwise, use flea price
|
||||
{
|
||||
if (pmcPriceOverrides.ContainsKey(tpl))
|
||||
{
|
||||
_pocketLootPool[tpl] = pmcPriceOverrides[tpl];
|
||||
@@ -89,12 +90,15 @@ public class PMCLootGenerator
|
||||
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
|
||||
_pocketLootPool[tpl] = price ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
var highestPrice = _pocketLootPool.Max(price => price.Value);
|
||||
foreach (var (key, _) in _pocketLootPool)
|
||||
// Invert price so cheapest has a larger weight
|
||||
// Times by highest price so most expensive item has weight of 1
|
||||
{
|
||||
_pocketLootPool[key] = Math.Round(1 / _pocketLootPool[key] * highestPrice);
|
||||
}
|
||||
|
||||
_weightedRandomHelper.ReduceWeightValues(_pocketLootPool);
|
||||
}
|
||||
@@ -105,16 +109,31 @@ public class PMCLootGenerator
|
||||
private HashSet<string> GetLootBlacklist()
|
||||
{
|
||||
var blacklist = new HashSet<string>();
|
||||
foreach (var blacklistedItem in _pmcConfig.PocketLoot.Blacklist) blacklist.Add(blacklistedItem);
|
||||
foreach (var blacklistedItem in _pmcConfig.GlobalLootBlacklist) blacklist.Add(blacklistedItem);
|
||||
foreach (var blacklistedItem in _itemFilterService.GetBlacklistedItems()) blacklist.Add(blacklistedItem);
|
||||
foreach (var blacklistedItem in _seasonalEventService.GetInactiveSeasonalEventItems()) blacklist.Add(blacklistedItem);
|
||||
foreach (var blacklistedItem in _pmcConfig.PocketLoot.Blacklist)
|
||||
{
|
||||
blacklist.Add(blacklistedItem);
|
||||
}
|
||||
|
||||
foreach (var blacklistedItem in _pmcConfig.GlobalLootBlacklist)
|
||||
{
|
||||
blacklist.Add(blacklistedItem);
|
||||
}
|
||||
|
||||
foreach (var blacklistedItem in _itemFilterService.GetBlacklistedItems())
|
||||
{
|
||||
blacklist.Add(blacklistedItem);
|
||||
}
|
||||
|
||||
foreach (var blacklistedItem in _seasonalEventService.GetInactiveSeasonalEventItems())
|
||||
{
|
||||
blacklist.Add(blacklistedItem);
|
||||
}
|
||||
|
||||
return blacklist;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a List of loot items a PMC can have in their vests
|
||||
/// Create a List of loot items a PMC can have in their vests
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns>Dictionary of string and number</returns>
|
||||
@@ -133,7 +152,7 @@ public class PMCLootGenerator
|
||||
var blacklist = GetLootBlacklist();
|
||||
|
||||
var itemsToAdd = items.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
|
||||
_itemHelper.IsValidItem(item.Value.Id) &&
|
||||
!blacklist.Contains(item.Value.Id) &&
|
||||
@@ -143,6 +162,7 @@ public class PMCLootGenerator
|
||||
|
||||
foreach (var (tpl, template) in itemsToAdd)
|
||||
// If pmc has price override, use that. Otherwise, use flea price
|
||||
{
|
||||
if (pmcPriceOverrides.ContainsKey(tpl))
|
||||
{
|
||||
_vestLootPool[tpl] = pmcPriceOverrides[tpl];
|
||||
@@ -153,12 +173,15 @@ public class PMCLootGenerator
|
||||
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
|
||||
_vestLootPool[tpl] = price ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
var highestPrice = _vestLootPool.Max(price => price.Value);
|
||||
foreach (var (key, _) in _vestLootPool)
|
||||
// Invert price so cheapest has a larger weight
|
||||
// Times by highest price so most expensive item has weight of 1
|
||||
{
|
||||
_vestLootPool[key] = Math.Round(1 / _vestLootPool[key] * highestPrice);
|
||||
}
|
||||
|
||||
_weightedRandomHelper.ReduceWeightValues(_vestLootPool);
|
||||
}
|
||||
@@ -167,8 +190,8 @@ public class PMCLootGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if item has a width/height that lets it fit into a 2x2 slot
|
||||
/// 1x1 / 1x2 / 2x1 / 2x2
|
||||
/// Check if item has a width/height that lets it fit into a 2x2 slot
|
||||
/// 1x1 / 1x2 / 2x1 / 2x2
|
||||
/// </summary>
|
||||
/// <param name="item">Item to check size of</param>
|
||||
/// <returns>true if it fits</returns>
|
||||
@@ -178,8 +201,8 @@ public class PMCLootGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if item has a width/height that lets it fit into a 1x2 slot
|
||||
/// 1x1 / 1x2 / 2x1
|
||||
/// Check if item has a width/height that lets it fit into a 1x2 slot
|
||||
/// 1x1 / 1x2 / 2x1
|
||||
/// </summary>
|
||||
/// <param name="item">Item to check size of</param>
|
||||
/// <returns>true if it fits</returns>
|
||||
@@ -193,7 +216,7 @@ public class PMCLootGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a List of loot items a PMC can have in their backpack
|
||||
/// Create a List of loot items a PMC can have in their backpack
|
||||
/// </summary>
|
||||
/// <param name="botRole"></param>
|
||||
/// <returns>Dictionary of string and number</returns>
|
||||
@@ -212,7 +235,7 @@ public class PMCLootGenerator
|
||||
var blacklist = GetLootBlacklist();
|
||||
|
||||
var itemsToAdd = items.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
allowedItemTypeWhitelist.Contains(item.Value.Parent) &&
|
||||
_itemHelper.IsValidItem(item.Value.Id) &&
|
||||
!blacklist.Contains(item.Value.Id) &&
|
||||
@@ -221,6 +244,7 @@ public class PMCLootGenerator
|
||||
|
||||
foreach (var (tpl, template) in itemsToAdd)
|
||||
// If pmc has price override, use that. Otherwise, use flea price
|
||||
{
|
||||
if (pmcPriceOverrides.ContainsKey(tpl))
|
||||
{
|
||||
_backpackLootPool[tpl] = pmcPriceOverrides[tpl];
|
||||
@@ -231,12 +255,15 @@ public class PMCLootGenerator
|
||||
var price = _ragfairPriceService.GetDynamicItemPrice(tpl, Money.ROUBLES);
|
||||
_backpackLootPool[tpl] = price ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
var highestPrice = _backpackLootPool.Max(price => price.Value);
|
||||
foreach (var (key, _) in _backpackLootPool)
|
||||
// Invert price so cheapest has a larger weight
|
||||
// Times by highest price so most expensive item has weight of 1
|
||||
{
|
||||
_backpackLootPool[key] = Math.Round(1 / _backpackLootPool[key] * highestPrice);
|
||||
}
|
||||
|
||||
_weightedRandomHelper.ReduceWeightValues(_backpackLootPool);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
@@ -11,6 +10,7 @@ using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using Core.Utils.Json;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PlayerScavGenerator(
|
||||
protected PlayerScavConfig _playerScavConfig = _configServer.GetConfig<PlayerScavConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Update a player profile to include a new player scav profile
|
||||
/// Update a player profile to include a new player scav profile
|
||||
/// </summary>
|
||||
/// <param name="sessionID">session id to specify what profile is updated</param>
|
||||
/// <returns>profile object</returns>
|
||||
@@ -55,9 +55,14 @@ public class PlayerScavGenerator(
|
||||
|
||||
// use karma level to get correct karmaSettings
|
||||
if (!_playerScavConfig.KarmaLevel.TryGetValue(scavKarmaLevel.ToString(), out var playerScavKarmaSettings))
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel));
|
||||
}
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Generated player scav loadout with karma level {scavKarmaLevel}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Generated player scav loadout with karma level {scavKarmaLevel}");
|
||||
}
|
||||
|
||||
// Edit baseBotNode values
|
||||
var baseBotNode = ConstructBotBaseTemplate(playerScavKarmaSettings.BotTypeForLoot);
|
||||
@@ -96,7 +101,11 @@ public class PlayerScavGenerator(
|
||||
scavData.Info.Experience = GetScavExperience(existingScavDataClone);
|
||||
scavData.Quests = existingScavDataClone.Quests ?? [];
|
||||
scavData.TaskConditionCounters = existingScavDataClone.TaskConditionCounters ?? new Dictionary<string, TaskConditionCounter>();
|
||||
scavData.Notes = existingScavDataClone.Notes ?? new Notes { DataNotes = new List<Note>() };
|
||||
scavData.Notes = existingScavDataClone.Notes ??
|
||||
new Notes
|
||||
{
|
||||
DataNotes = new List<Note>()
|
||||
};
|
||||
scavData.WishList = existingScavDataClone.WishList ?? new DictionaryOrList<string, int>(new Dictionary<string, int>(), new List<int>());
|
||||
scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new Dictionary<string, bool>();
|
||||
|
||||
@@ -124,7 +133,7 @@ public class PlayerScavGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add items picked from `playerscav.lootItemsToAddChancePercent`
|
||||
/// Add items picked from `playerscav.lootItemsToAddChancePercent`
|
||||
/// </summary>
|
||||
/// <param name="possibleItemsToAdd">dict of tpl + % chance to be added</param>
|
||||
/// <param name="scavData"></param>
|
||||
@@ -136,7 +145,9 @@ public class PlayerScavGenerator(
|
||||
{
|
||||
var shouldAdd = _randomUtil.GetChance100(tpl.Value);
|
||||
if (!shouldAdd)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemResult = _itemHelper.GetItem(tpl.Key);
|
||||
if (!itemResult.Key)
|
||||
@@ -146,7 +157,7 @@ public class PlayerScavGenerator(
|
||||
}
|
||||
|
||||
var itemTemplate = itemResult.Value;
|
||||
var itemsToAdd = new List<Item>()
|
||||
var itemsToAdd = new List<Item>
|
||||
{
|
||||
new()
|
||||
{
|
||||
@@ -165,14 +176,18 @@ public class PlayerScavGenerator(
|
||||
);
|
||||
|
||||
if (result != ItemAddedResult.SUCCESS)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Unable to add keycard to bot. Reason: {result.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the scav karama level for a profile
|
||||
/// Is also the fence trader rep level
|
||||
/// Get the scav karama level for a profile
|
||||
/// Is also the fence trader rep level
|
||||
/// </summary>
|
||||
/// <param name="pmcData">pmc profile</param>
|
||||
/// <returns>karma level</returns>
|
||||
@@ -186,14 +201,16 @@ public class PlayerScavGenerator(
|
||||
}
|
||||
|
||||
if (fenceInfo.Standing > 6)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
return Math.Floor(fenceInfo.Standing ?? 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a baseBot template
|
||||
/// If the parameter doesnt match "assault", take parts from the loot type and apply to the return bot template
|
||||
/// Get a baseBot template
|
||||
/// If the parameter doesnt match "assault", take parts from the loot type and apply to the return bot template
|
||||
/// </summary>
|
||||
/// <param name="botTypeForLoot">bot type to use for inventory/chances</param>
|
||||
/// <returns>IBotType object</returns>
|
||||
@@ -204,7 +221,9 @@ public class PlayerScavGenerator(
|
||||
|
||||
// Loot bot is same as base bot, return base with no modification
|
||||
if (botTypeForLoot == baseScavType)
|
||||
{
|
||||
return asssaultBase;
|
||||
}
|
||||
|
||||
var lootBase = _cloner.Clone(_botHelper.GetBotTemplate(botTypeForLoot));
|
||||
asssaultBase.BotInventory = lootBase.BotInventory;
|
||||
@@ -215,7 +234,7 @@ public class PlayerScavGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjust equipment/mod/item generation values based on scav karma levels
|
||||
/// Adjust equipment/mod/item generation values based on scav karma levels
|
||||
/// </summary>
|
||||
/// <param name="karmaSettings">Values to modify the bot template with</param>
|
||||
/// <param name="baseBotNode">bot template to modify according to karama level settings</param>
|
||||
@@ -225,12 +244,17 @@ public class PlayerScavGenerator(
|
||||
foreach (var equipmentKvP in karmaSettings.Modifiers.Equipment)
|
||||
{
|
||||
// Adjustment value zero, nothing to do
|
||||
if (equipmentKvP.Value == 0) continue;
|
||||
if (equipmentKvP.Value == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try add new key with value
|
||||
if (!baseBotNode.BotChances.EquipmentChances.TryAdd(equipmentKvP.Key, equipmentKvP.Value))
|
||||
// Unable to add new, update existing
|
||||
{
|
||||
baseBotNode.BotChances.EquipmentChances[equipmentKvP.Key] += equipmentKvP.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust mod chance values
|
||||
@@ -238,7 +262,10 @@ public class PlayerScavGenerator(
|
||||
{
|
||||
// Adjustment value zero, nothing to do
|
||||
if (modKvP.Value == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (karmaSettings.Modifiers.Mod.TryGetValue(modKvP.Key, out var value))
|
||||
{
|
||||
baseBotNode.BotChances.WeaponModsChances.TryAdd(modKvP.Key, 0);
|
||||
@@ -260,14 +287,19 @@ public class PlayerScavGenerator(
|
||||
foreach (var equipmentBlacklistKvP in karmaSettings.EquipmentBlacklist)
|
||||
{
|
||||
baseBotNode.BotInventory.Equipment.TryGetValue(equipmentBlacklistKvP.Key, out var equipmentDict);
|
||||
foreach (var itemToRemove in equipmentBlacklistKvP.Value) equipmentDict.Remove(itemToRemove);
|
||||
foreach (var itemToRemove in equipmentBlacklistKvP.Value)
|
||||
{
|
||||
equipmentDict.Remove(itemToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Skills GetScavSkills(PmcData scavProfile)
|
||||
{
|
||||
if (scavProfile?.Skills != null)
|
||||
{
|
||||
return scavProfile.Skills;
|
||||
}
|
||||
|
||||
return GetDefaultScavSkills();
|
||||
}
|
||||
@@ -285,7 +317,9 @@ public class PlayerScavGenerator(
|
||||
protected Stats GetScavStats(PmcData scavProfile)
|
||||
{
|
||||
if (scavProfile?.Stats != null)
|
||||
{
|
||||
return scavProfile.Stats;
|
||||
}
|
||||
|
||||
return _profileHelper.GetDefaultCounters();
|
||||
}
|
||||
@@ -294,7 +328,9 @@ public class PlayerScavGenerator(
|
||||
{
|
||||
// Info can be null on initial account creation
|
||||
if (scavProfile?.Info?.Level == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return scavProfile?.Info?.Level ?? 1;
|
||||
}
|
||||
@@ -303,14 +339,16 @@ public class PlayerScavGenerator(
|
||||
{
|
||||
// Info can be null on initial account creation
|
||||
if (scavProfile?.Info?.Experience == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return scavProfile?.Info?.Experience ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set cooldown till scav is playable
|
||||
/// take into account scav cooldown bonus
|
||||
/// Set cooldown till scav is playable
|
||||
/// take into account scav cooldown bonus
|
||||
/// </summary>
|
||||
/// <param name="scavData">scav profile</param>
|
||||
/// <param name="pmcData">pmc profile</param>
|
||||
@@ -323,21 +361,29 @@ public class PlayerScavGenerator(
|
||||
var modifier = 1;
|
||||
|
||||
foreach (var bonus in pmcData.Bonuses)
|
||||
{
|
||||
if (bonus.Type == BonusType.ScavCooldownTimer)
|
||||
// Value is negative, so add.
|
||||
// Also note that for scav cooldown, multiple bonuses stack additively.
|
||||
modifier += (int)(bonus?.Value ?? 1) / 100;
|
||||
{
|
||||
modifier += (int) (bonus?.Value ?? 1) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
var fenceInfo = _fenceService.GetFenceInfo(pmcData);
|
||||
modifier *= (int)(fenceInfo.SavageCooldownModifier ?? 1);
|
||||
modifier *= (int) (fenceInfo.SavageCooldownModifier ?? 1);
|
||||
scavLockDuration *= modifier;
|
||||
|
||||
var fullProfile = _profileHelper.GetFullProfile(pmcData?.SessionId);
|
||||
if (fullProfile?.ProfileInfo?.Edition.ToLower().StartsWith(AccountTypes.SPT_DEVELOPER) ?? false)
|
||||
{
|
||||
scavLockDuration = 10;
|
||||
}
|
||||
|
||||
if (scavData?.Info != null)
|
||||
scavData.Info.SavageLockTime = Math.Round((double)(_timeUtil.GetTimeStamp() / 1000 + scavLockDuration ?? 0));
|
||||
{
|
||||
scavData.Info.SavageLockTime = Math.Round((double) (_timeUtil.GetTimeStamp() / 1000 + scavLockDuration ?? 0));
|
||||
}
|
||||
|
||||
return scavData;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Core.Helpers;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
@@ -8,6 +7,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
@@ -42,7 +42,10 @@ public class RagfairAssortGenerator(
|
||||
*/
|
||||
public List<List<Item>> GetAssortItems()
|
||||
{
|
||||
if (!AssortsAreGenerated()) generatedAssortItems = GenerateRagfairAssortItems();
|
||||
if (!AssortsAreGenerated())
|
||||
{
|
||||
generatedAssortItems = GenerateRagfairAssortItems();
|
||||
}
|
||||
|
||||
return generatedAssortItems;
|
||||
}
|
||||
@@ -65,7 +68,7 @@ public class RagfairAssortGenerator(
|
||||
List<List<Item>> results = [];
|
||||
|
||||
/** Get cloned items from db */
|
||||
var dbItemsClone = itemHelper.GetItems().Where((item) => item.Type != "Node");
|
||||
var dbItemsClone = itemHelper.GetItems().Where(item => item.Type != "Node");
|
||||
|
||||
/** Store processed preset tpls so we dont add them when procesing non-preset items */
|
||||
List<string> processedArmorItems = [];
|
||||
@@ -84,15 +87,22 @@ public class RagfairAssortGenerator(
|
||||
|
||||
presetAndMods[0].ParentId = "hideout";
|
||||
presetAndMods[0].SlotId = "hideout";
|
||||
presetAndMods[0].Upd = new Upd()
|
||||
{ StackObjectsCount = 99999999, UnlimitedCount = true, SptPresetId = preset.Id };
|
||||
presetAndMods[0].Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 99999999,
|
||||
UnlimitedCount = true,
|
||||
SptPresetId = preset.Id
|
||||
};
|
||||
|
||||
results.Add(presetAndMods);
|
||||
}
|
||||
|
||||
foreach (var item in dbItemsClone)
|
||||
{
|
||||
if (!itemHelper.IsValidItem(item.Id, ragfairItemInvalidBaseTypes)) continue;
|
||||
if (!itemHelper.IsValidItem(item.Id, ragfairItemInvalidBaseTypes))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip seasonal items when not in-season
|
||||
if (
|
||||
@@ -100,11 +110,15 @@ public class RagfairAssortGenerator(
|
||||
!seasonalEventActive &&
|
||||
seasonalItemTplBlacklist.Contains(item.Id)
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (processedArmorItems.Contains(item.Id))
|
||||
// Already processed
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ragfairAssort = CreateRagfairAssortRootItem(
|
||||
item.Id,
|
||||
@@ -138,14 +152,21 @@ public class RagfairAssortGenerator(
|
||||
protected Item CreateRagfairAssortRootItem(string tplId, string? id = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
id = hashUtil.Generate();
|
||||
return new Item()
|
||||
}
|
||||
|
||||
return new Item
|
||||
{
|
||||
Id = id,
|
||||
Template = tplId,
|
||||
ParentId = "hideout",
|
||||
SlotId = "hideout",
|
||||
Upd = new Upd() { StackObjectsCount = 99999999, UnlimitedCount = true }
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = 99999999,
|
||||
UnlimitedCount = true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
using Core.Helpers;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Ragfair;
|
||||
using Core.Models.Enums;
|
||||
@@ -12,9 +10,8 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using Server;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
using Core.Models.Eft.Player;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Generators;
|
||||
@@ -43,13 +40,16 @@ public class RagfairOfferGenerator(
|
||||
ICloner cloner
|
||||
)
|
||||
{
|
||||
protected List<TplWithFleaPrice>? allowedFleaPriceItemsForBarter;
|
||||
protected BotConfig botConfig = configServer.GetConfig<BotConfig>();
|
||||
|
||||
/**
|
||||
* Internal counter to ensure each offer created has a unique value for its intId property
|
||||
*/
|
||||
protected int offerCounter;
|
||||
|
||||
protected RagfairConfig ragfairConfig = configServer.GetConfig<RagfairConfig>();
|
||||
protected TraderConfig traderConfig = configServer.GetConfig<TraderConfig>();
|
||||
protected BotConfig botConfig = configServer.GetConfig<BotConfig>();
|
||||
protected List<TplWithFleaPrice>? allowedFleaPriceItemsForBarter;
|
||||
|
||||
/** Internal counter to ensure each offer created has a unique value for its intId property */
|
||||
protected int offerCounter = 0;
|
||||
|
||||
/**
|
||||
* Create a flea offer and store it in the Ragfair server offers array
|
||||
@@ -98,12 +98,12 @@ public class RagfairOfferGenerator(
|
||||
var isTrader = ragfairServerHelper.IsTrader(userID);
|
||||
|
||||
var offerRequirements = barterScheme.Select(
|
||||
(barter) =>
|
||||
barter =>
|
||||
{
|
||||
var offerRequirement = new OfferRequirement
|
||||
{
|
||||
Template = barter.Template,
|
||||
Count = Math.Round((double)barter.Count, 2),
|
||||
Count = Math.Round((double) barter.Count, 2),
|
||||
OnlyFunctional = barter.OnlyFunctional ?? false
|
||||
};
|
||||
|
||||
@@ -126,9 +126,11 @@ public class RagfairOfferGenerator(
|
||||
// Hydrate ammo boxes with cartridges + ensure only 1 item is present (ammo box)
|
||||
// On offer refresh don't re-add cartridges to ammo box that already has cartridges
|
||||
if (itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX) && itemsClone.Count == 1)
|
||||
{
|
||||
itemHelper.AddCartridgesToAmmoBox(itemsClone, itemHelper.GetItem(items[0].Template).Value);
|
||||
}
|
||||
|
||||
var roubleListingPrice = Math.Round((double)ConvertOfferRequirementsIntoRoubles(offerRequirements));
|
||||
var roubleListingPrice = Math.Round((double) ConvertOfferRequirementsIntoRoubles(offerRequirements));
|
||||
var singleItemListingPrice = isPackOffer ? roubleListingPrice / itemStackCount : roubleListingPrice;
|
||||
|
||||
var offer = new RagfairOffer
|
||||
@@ -164,11 +166,13 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
// Trader offer
|
||||
if (isTrader)
|
||||
return new RagfairOfferUser()
|
||||
{
|
||||
return new RagfairOfferUser
|
||||
{
|
||||
Id = userID,
|
||||
MemberType = MemberCategory.Trader
|
||||
};
|
||||
}
|
||||
|
||||
var isPlayerOffer = profileHelper.IsPlayer(userID);
|
||||
if (isPlayerOffer)
|
||||
@@ -188,14 +192,14 @@ public class RagfairOfferGenerator(
|
||||
}
|
||||
|
||||
// Fake pmc offer
|
||||
return new RagfairOfferUser()
|
||||
return new RagfairOfferUser
|
||||
{
|
||||
Id = userID,
|
||||
MemberType = MemberCategory.Default,
|
||||
Nickname = botHelper.GetPmcNicknameOfMaxLength(botConfig.BotNameLengthLimit),
|
||||
Rating = randomUtil.GetDouble(
|
||||
(double)ragfairConfig.Dynamic.Rating.Min,
|
||||
(double)ragfairConfig.Dynamic.Rating.Max
|
||||
(double) ragfairConfig.Dynamic.Rating.Min,
|
||||
(double) ragfairConfig.Dynamic.Rating.Max
|
||||
),
|
||||
IsRatingGrowing = randomUtil.GetBool(),
|
||||
Avatar = null,
|
||||
@@ -212,9 +216,11 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
var roublePrice = 0;
|
||||
foreach (var requirement in offerRequirements)
|
||||
roublePrice += (int)(paymentHelper.IsMoneyTpl(requirement.Template)
|
||||
? Math.Round((double)CalculateRoublePrice((int)requirement.Count, requirement.Template))
|
||||
{
|
||||
roublePrice += (int) (paymentHelper.IsMoneyTpl(requirement.Template)
|
||||
? Math.Round((double) CalculateRoublePrice((int) requirement.Count, requirement.Template))
|
||||
: ragfairPriceService.GetFleaPriceForItem(requirement.Template) * requirement.Count); // get flea price for barter offer items
|
||||
}
|
||||
|
||||
return roublePrice;
|
||||
}
|
||||
@@ -227,7 +233,10 @@ public class RagfairOfferGenerator(
|
||||
*/
|
||||
protected string GetAvatarUrl(bool isTrader, string userId)
|
||||
{
|
||||
if (isTrader) return databaseService.GetTrader(userId).Base.Avatar;
|
||||
if (isTrader)
|
||||
{
|
||||
return databaseService.GetTrader(userId).Base.Avatar;
|
||||
}
|
||||
|
||||
return "/files/trader/avatar/unknown.jpg";
|
||||
}
|
||||
@@ -240,7 +249,10 @@ public class RagfairOfferGenerator(
|
||||
*/
|
||||
protected int CalculateRoublePrice(int currencyCount, string currencyType)
|
||||
{
|
||||
if (currencyType == Money.ROUBLES) return currencyCount;
|
||||
if (currencyType == Money.ROUBLES)
|
||||
{
|
||||
return currencyCount;
|
||||
}
|
||||
|
||||
return handbookHelper.InRUB(currencyCount, currencyType);
|
||||
}
|
||||
@@ -252,7 +264,10 @@ public class RagfairOfferGenerator(
|
||||
*/
|
||||
protected string GetTraderId(string userId)
|
||||
{
|
||||
if (profileHelper.IsPlayer(userId)) return saveServer.GetProfile(userId).CharacterData.PmcData.Id;
|
||||
if (profileHelper.IsPlayer(userId))
|
||||
{
|
||||
return saveServer.GetProfile(userId).CharacterData.PmcData.Id;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
@@ -266,14 +281,18 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
if (profileHelper.IsPlayer(userId))
|
||||
// Player offer
|
||||
{
|
||||
return saveServer.GetProfile(userId).CharacterData?.PmcData?.RagfairInfo?.Rating;
|
||||
}
|
||||
|
||||
if (ragfairServerHelper.IsTrader(userId))
|
||||
// Trader offer
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Generated pmc offer
|
||||
return randomUtil.GetDouble((double)ragfairConfig.Dynamic.Rating.Min, (double)ragfairConfig.Dynamic.Rating.Max);
|
||||
return randomUtil.GetDouble((double) ragfairConfig.Dynamic.Rating.Min, (double) ragfairConfig.Dynamic.Rating.Max);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,11 +304,15 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
if (profileHelper.IsPlayer(userID))
|
||||
// player offer
|
||||
{
|
||||
return saveServer.GetProfile(userID).CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false;
|
||||
}
|
||||
|
||||
if (ragfairServerHelper.IsTrader(userID))
|
||||
// trader offer
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// generated offer
|
||||
// 50/50 growing/falling
|
||||
@@ -308,15 +331,18 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
// Player offer = current time + offerDurationTimeInHour;
|
||||
var offerDurationTimeHours = databaseService.GetGlobals().Configuration.RagFair.OfferDurationTimeInHour;
|
||||
return (long)(timeUtil.GetTimeStamp() + Math.Round((double)offerDurationTimeHours * TimeUtil.OneHourAsSeconds));
|
||||
return (long) (timeUtil.GetTimeStamp() + Math.Round((double) offerDurationTimeHours * TimeUtil.OneHourAsSeconds));
|
||||
}
|
||||
|
||||
if (ragfairServerHelper.IsTrader(userID))
|
||||
// Trader offer
|
||||
return (long)databaseService.GetTrader(userID).Base.NextResupply;
|
||||
{
|
||||
return (long) databaseService.GetTrader(userID).Base.NextResupply;
|
||||
}
|
||||
|
||||
// Generated fake-player offer
|
||||
return (long)Math.Round(time + randomUtil.GetDouble(ragfairConfig.Dynamic.EndTimeSeconds.Min.Value, ragfairConfig.Dynamic.EndTimeSeconds.Max.Value)
|
||||
return (long) Math.Round(
|
||||
time + randomUtil.GetDouble(ragfairConfig.Dynamic.EndTimeSeconds.Min.Value, ragfairConfig.Dynamic.EndTimeSeconds.Max.Value)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -334,18 +360,31 @@ public class RagfairOfferGenerator(
|
||||
? expiredOffers ?? []
|
||||
: ragfairAssortGenerator.GetAssortItems();
|
||||
stopwatch.Stop();
|
||||
if (logger.IsLogEnabled(LogLevel.Debug)) logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts");
|
||||
if (logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts");
|
||||
}
|
||||
|
||||
stopwatch.Restart();
|
||||
var tasks = new List<Task>();
|
||||
foreach (var assortItem in assortItemsToProcess)
|
||||
{
|
||||
tasks.Add(
|
||||
Task.Factory.StartNew(
|
||||
() => { CreateOffersFromAssort(assortItem, replacingExpiredOffers, ragfairConfig.Dynamic); }
|
||||
() =>
|
||||
{
|
||||
CreateOffersFromAssort(assortItem, replacingExpiredOffers, ragfairConfig.Dynamic);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
stopwatch.Stop();
|
||||
if (logger.IsLogEnabled(LogLevel.Debug)) logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to CreateOffersFromAssort");
|
||||
if (logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to CreateOffersFromAssort");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,11 +402,16 @@ public class RagfairOfferGenerator(
|
||||
var isPreset = presetHelper.IsPreset(assortItemWithChildren[0].Upd.SptPresetId);
|
||||
|
||||
// Only perform checks on newly generated items, skip expired items being refreshed
|
||||
if (!(isExpiredOffer || ragfairServerHelper.IsItemValidRagfairItem(itemToSellDetails))) return;
|
||||
if (!(isExpiredOffer || ragfairServerHelper.IsItemValidRagfairItem(itemToSellDetails)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Armor presets can hold plates above the allowed flea level, remove if necessary
|
||||
if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList)
|
||||
{
|
||||
RemoveBannedPlatesFromPreset(assortItemWithChildren, ragfairConfig.Dynamic.Blacklist.ArmorPlate);
|
||||
}
|
||||
|
||||
// Get number of offers to create
|
||||
// Limit to 1 offer when processing expired - like-for-like replacement
|
||||
@@ -408,18 +452,25 @@ public class RagfairOfferGenerator(
|
||||
{
|
||||
if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template))
|
||||
// Cant hold armor inserts, skip
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var plateSlots = presetWithChildren.Where((item) => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower())).ToList();
|
||||
var plateSlots = presetWithChildren.Where(item => itemHelper.GetRemovablePlateSlotIds().Contains(item.SlotId?.ToLower())).ToList();
|
||||
if (plateSlots.Count == 0)
|
||||
// Has no plate slots e.g. "front_plate", exit
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var removedPlate = false;
|
||||
foreach (var plateSlot in plateSlots)
|
||||
{
|
||||
var plateDetails = itemHelper.GetItem(plateSlot.Template).Value;
|
||||
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLower())) continue;
|
||||
if (plateSettings.IgnoreSlots.Contains(plateSlot.SlotId.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var plateArmorLevel = plateDetails.Properties.ArmorClass ?? 0;
|
||||
if (plateArmorLevel > plateSettings.MaxProtectionLevel)
|
||||
@@ -472,14 +523,17 @@ public class RagfairOfferGenerator(
|
||||
if (shouldRemovePlates && itemHelper.ArmorItemHasRemovablePlateSlots(itemWithChildren[0].Template))
|
||||
{
|
||||
var offerItemPlatesToRemove = itemWithChildren.Where(
|
||||
(item) =>
|
||||
item =>
|
||||
armorConfig.PlateSlotIdToRemovePool.Contains(item.SlotId?.ToLower())
|
||||
);
|
||||
|
||||
// Latest first, to ensure we don't move later items off by 1 each time we remove an item below it
|
||||
var indexesToRemove = offerItemPlatesToRemove.Select(plateItem => itemWithChildren.IndexOf(plateItem))
|
||||
.ToList();
|
||||
foreach (var index in indexesToRemove.OrderByDescending(x => x)) itemWithChildren.RemoveAt(index);
|
||||
foreach (var index in indexesToRemove.OrderByDescending(x => x))
|
||||
{
|
||||
itemWithChildren.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +555,10 @@ public class RagfairOfferGenerator(
|
||||
// Apply randomised properties
|
||||
RandomiseOfferItemUpdProperties(sellerId, itemWithChildren, itemToSellDetails);
|
||||
barterScheme = CreateBarterBarterScheme(itemWithChildren, ragfairConfig.Dynamic.Barter);
|
||||
if (ragfairConfig.Dynamic.Barter.MakeSingleStackOnly) itemWithChildren[0].Upd.StackObjectsCount = 1;
|
||||
if (ragfairConfig.Dynamic.Barter.MakeSingleStackOnly)
|
||||
{
|
||||
itemWithChildren[0].Upd.StackObjectsCount = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -551,7 +608,9 @@ public class RagfairOfferGenerator(
|
||||
// We only want to process 'base/root' items, no children
|
||||
if (item.SlotId != "hideout")
|
||||
// skip mod items
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Run blacklist check on trader offers
|
||||
if (blacklist.TraderItems)
|
||||
@@ -564,7 +623,10 @@ public class RagfairOfferGenerator(
|
||||
}
|
||||
|
||||
// Don't include items that BSG has blacklisted from flea
|
||||
if (blacklist.EnableBsgList && !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false)) continue;
|
||||
if (blacklist.EnableBsgList && !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var isPreset = presetHelper.IsPreset(item.Id);
|
||||
@@ -577,7 +639,12 @@ public class RagfairOfferGenerator(
|
||||
logger.Warning(
|
||||
localisationService.GetText(
|
||||
"ragfair-missing_barter_scheme",
|
||||
new { itemId = item.Id, tpl = item.Template, name = trader.Base.Nickname }
|
||||
new
|
||||
{
|
||||
itemId = item.Id,
|
||||
tpl = item.Template,
|
||||
name = trader.Base.Nickname
|
||||
}
|
||||
)
|
||||
);
|
||||
continue;
|
||||
@@ -586,7 +653,7 @@ public class RagfairOfferGenerator(
|
||||
var barterSchemeItems = assortsClone.BarterScheme[item.Id][0];
|
||||
var loyalLevel = assortsClone.LoyalLevelItems[item.Id];
|
||||
|
||||
var offer = CreateAndAddFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel, false);
|
||||
var offer = CreateAndAddFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel);
|
||||
|
||||
// Refresh complete, reset flag to false
|
||||
trader.Base.RefreshTraderRagfairOffers = false;
|
||||
@@ -610,11 +677,15 @@ public class RagfairOfferGenerator(
|
||||
var parentId = GetDynamicConditionIdForTpl(itemDetails.Id);
|
||||
if (string.IsNullOrEmpty(parentId))
|
||||
// No condition details found, don't proceed with modifying item conditions
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Roll random chance to randomise item condition
|
||||
if (randomUtil.GetChance100(ragfairConfig.Dynamic.Condition[parentId].ConditionChance * 100))
|
||||
{
|
||||
RandomiseItemCondition(parentId, itemWithMods, itemDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,8 +699,12 @@ public class RagfairOfferGenerator(
|
||||
// Get keys from condition config dictionary
|
||||
var configConditions = ragfairConfig.Dynamic.Condition.Keys;
|
||||
foreach (var baseClass in configConditions)
|
||||
{
|
||||
if (itemHelper.IsOfBaseclass(tpl, baseClass))
|
||||
{
|
||||
return baseClass;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -649,10 +724,10 @@ public class RagfairOfferGenerator(
|
||||
var rootItem = itemWithMods[0];
|
||||
|
||||
var itemConditionValues = ragfairConfig.Dynamic.Condition[conditionSettingsId];
|
||||
var maxMultiplier = randomUtil.GetDouble((double)itemConditionValues.Max.Min, (double)itemConditionValues.Max.Min);
|
||||
var maxMultiplier = randomUtil.GetDouble((double) itemConditionValues.Max.Min, (double) itemConditionValues.Max.Min);
|
||||
var currentMultiplier = randomUtil.GetDouble(
|
||||
(double)itemConditionValues.Current.Min,
|
||||
(double)itemConditionValues.Current.Max
|
||||
(double) itemConditionValues.Current.Min,
|
||||
(double) itemConditionValues.Current.Max
|
||||
);
|
||||
|
||||
// Randomise armor + plates + armor related things
|
||||
@@ -663,12 +738,15 @@ public class RagfairOfferGenerator(
|
||||
RandomiseArmorDurabilityValues(itemWithMods, currentMultiplier, maxMultiplier);
|
||||
|
||||
// Add hits to visor
|
||||
var visorMod = itemWithMods.FirstOrDefault((item) => item.ParentId == BaseClasses.ARMORED_EQUIPMENT && item.SlotId == "mod_equipment_000");
|
||||
var visorMod = itemWithMods.FirstOrDefault(item => item.ParentId == BaseClasses.ARMORED_EQUIPMENT && item.SlotId == "mod_equipment_000");
|
||||
if (randomUtil.GetChance100(25) && visorMod != null)
|
||||
{
|
||||
itemHelper.AddUpdObjectToItem(visorMod);
|
||||
|
||||
visorMod.Upd.FaceShield = new UpdFaceShield() { Hits = randomUtil.GetInt(1, 3) };
|
||||
visorMod.Upd.FaceShield = new UpdFaceShield
|
||||
{
|
||||
Hits = randomUtil.GetInt(1, 3)
|
||||
};
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -685,7 +763,7 @@ public class RagfairOfferGenerator(
|
||||
if (rootItem.Upd?.MedKit != null)
|
||||
{
|
||||
// Randomize health
|
||||
var hpResource = Math.Round((double)rootItem.Upd.MedKit.HpResource * maxMultiplier);
|
||||
var hpResource = Math.Round((double) rootItem.Upd.MedKit.HpResource * maxMultiplier);
|
||||
rootItem.Upd.MedKit.HpResource = hpResource == 0D ? 1D : hpResource;
|
||||
return;
|
||||
}
|
||||
@@ -693,14 +771,14 @@ public class RagfairOfferGenerator(
|
||||
if (rootItem.Upd?.Key != null && itemDetails.Properties.MaximumNumberOfUsage > 1)
|
||||
{
|
||||
// Randomize key uses
|
||||
rootItem.Upd.Key.NumberOfUsages = (int?)Math.Round(itemDetails.Properties.MaximumNumberOfUsage.Value * (1 - maxMultiplier));
|
||||
rootItem.Upd.Key.NumberOfUsages = (int?) Math.Round(itemDetails.Properties.MaximumNumberOfUsage.Value * (1 - maxMultiplier));
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootItem.Upd?.FoodDrink != null)
|
||||
{
|
||||
// randomize food/drink value
|
||||
var hpPercent = Math.Round((double)itemDetails.Properties.MaxResource * maxMultiplier);
|
||||
var hpPercent = Math.Round((double) itemDetails.Properties.MaxResource * maxMultiplier);
|
||||
rootItem.Upd.FoodDrink.HpPercent = hpPercent == 0D ? 1D : hpPercent;
|
||||
|
||||
return;
|
||||
@@ -709,7 +787,7 @@ public class RagfairOfferGenerator(
|
||||
if (rootItem.Upd?.RepairKit != null)
|
||||
{
|
||||
// randomize repair kit (armor/weapon) uses
|
||||
var resource = Math.Round((double)itemDetails.Properties.MaxRepairResource * maxMultiplier);
|
||||
var resource = Math.Round((double) itemDetails.Properties.MaxRepairResource * maxMultiplier);
|
||||
rootItem.Upd.RepairKit.Resource = resource == 0D ? 1D : resource;
|
||||
|
||||
return;
|
||||
@@ -718,9 +796,12 @@ public class RagfairOfferGenerator(
|
||||
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL))
|
||||
{
|
||||
var totalCapacity = itemDetails.Properties.MaxResource;
|
||||
var remainingFuel = Math.Round((double)totalCapacity * maxMultiplier);
|
||||
rootItem.Upd.Resource = new UpdResource()
|
||||
{ UnitsConsumed = totalCapacity - remainingFuel, Value = remainingFuel };
|
||||
var remainingFuel = Math.Round((double) totalCapacity * maxMultiplier);
|
||||
rootItem.Upd.Resource = new UpdResource
|
||||
{
|
||||
UnitsConsumed = totalCapacity - remainingFuel,
|
||||
Value = remainingFuel
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,7 +822,7 @@ public class RagfairOfferGenerator(
|
||||
// Max
|
||||
var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
|
||||
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
|
||||
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double)lowestMaxDurability, (double)baseMaxDurability));
|
||||
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability, (double) baseMaxDurability));
|
||||
|
||||
// Current
|
||||
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
|
||||
@@ -772,12 +853,12 @@ public class RagfairOfferGenerator(
|
||||
|
||||
var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
|
||||
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
|
||||
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double)lowestMaxDurability, (double)baseMaxDurability));
|
||||
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability, (double) baseMaxDurability));
|
||||
|
||||
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
|
||||
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability));
|
||||
|
||||
armorItem.Upd.Repairable = new UpdRepairable()
|
||||
armorItem.Upd.Repairable = new UpdRepairable
|
||||
{
|
||||
Durability = chosenCurrentDurability == 0D ? 1D : chosenCurrentDurability, // Never var value become 0
|
||||
MaxDurability = chosenMaxDurability
|
||||
@@ -803,22 +884,31 @@ public class RagfairOfferGenerator(
|
||||
|
||||
if (isRepairable && props.Durability > 0)
|
||||
{
|
||||
item.Upd.Repairable = new UpdRepairable()
|
||||
{ Durability = props.Durability, MaxDurability = props.Durability };
|
||||
item.Upd.Repairable = new UpdRepairable
|
||||
{
|
||||
Durability = props.Durability,
|
||||
MaxDurability = props.Durability
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMedkit && props.MaxHpResource > 0)
|
||||
{
|
||||
item.Upd.MedKit = new UpdMedKit() { HpResource = props.MaxHpResource };
|
||||
item.Upd.MedKit = new UpdMedKit
|
||||
{
|
||||
HpResource = props.MaxHpResource
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isKey)
|
||||
{
|
||||
item.Upd.Key = new UpdKey() { NumberOfUsages = 0 };
|
||||
item.Upd.Key = new UpdKey
|
||||
{
|
||||
NumberOfUsages = 0
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -826,12 +916,21 @@ public class RagfairOfferGenerator(
|
||||
// Food/drink
|
||||
if (isConsumable)
|
||||
{
|
||||
item.Upd.FoodDrink = new UpdFoodDrink() { HpPercent = props.MaxResource };
|
||||
item.Upd.FoodDrink = new UpdFoodDrink
|
||||
{
|
||||
HpPercent = props.MaxResource
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRepairKit) item.Upd.RepairKit = new UpdRepairKit() { Resource = props.MaxRepairResource };
|
||||
if (isRepairKit)
|
||||
{
|
||||
item.Upd.RepairKit = new UpdRepairKit
|
||||
{
|
||||
Resource = props.MaxRepairResource
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -850,7 +949,10 @@ public class RagfairOfferGenerator(
|
||||
);
|
||||
|
||||
// Dont make items under a designated rouble value into barter offers
|
||||
if (priceOfOfferItem < barterConfig.MinRoubleCostToBecomeBarter) return CreateCurrencyBarterScheme(offerItems, false);
|
||||
if (priceOfOfferItem < barterConfig.MinRoubleCostToBecomeBarter)
|
||||
{
|
||||
return CreateCurrencyBarterScheme(offerItems, false);
|
||||
}
|
||||
|
||||
// Get a randomised number of barter items to list offer for
|
||||
var barterItemCount = randomUtil.GetInt(barterConfig.ItemCountMin, barterConfig.ItemCountMax);
|
||||
@@ -874,12 +976,22 @@ public class RagfairOfferGenerator(
|
||||
.ToList();
|
||||
|
||||
// No items on flea have a matching price, fall back to currency
|
||||
if (itemsInsidePriceBounds.Count == 0) return CreateCurrencyBarterScheme(offerItems, false);
|
||||
if (itemsInsidePriceBounds.Count == 0)
|
||||
{
|
||||
return CreateCurrencyBarterScheme(offerItems, false);
|
||||
}
|
||||
|
||||
// Choose random item from price-filtered flea items
|
||||
var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds);
|
||||
|
||||
return [new BarterScheme() { Count = barterItemCount, Template = randomItem.Tpl }];
|
||||
return
|
||||
[
|
||||
new BarterScheme
|
||||
{
|
||||
Count = barterItemCount,
|
||||
Template = randomItem.Tpl
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -895,7 +1007,13 @@ public class RagfairOfferGenerator(
|
||||
|
||||
// Only get prices for items that also exist in items.json
|
||||
var filteredFleaItems = fleaPrices
|
||||
.Select(kvTpl => new TplWithFleaPrice { Tpl = kvTpl.Key, Price = kvTpl.Value })
|
||||
.Select(
|
||||
kvTpl => new TplWithFleaPrice
|
||||
{
|
||||
Tpl = kvTpl.Key,
|
||||
Price = kvTpl.Value
|
||||
}
|
||||
)
|
||||
.Where(item => itemHelper.GetItem(item.Tpl).Key);
|
||||
|
||||
var itemTypeBlacklist = ragfairConfig.Dynamic.Barter.ItemTypeBlacklist;
|
||||
@@ -921,6 +1039,13 @@ public class RagfairOfferGenerator(
|
||||
var currency = ragfairServerHelper.GetDynamicOfferCurrency();
|
||||
var price = ragfairPriceService.GetDynamicOfferPriceForOffer(offerWithChildren, currency, isPackOffer) * multipler;
|
||||
|
||||
return [new BarterScheme() { Count = price, Template = currency }];
|
||||
return
|
||||
[
|
||||
new BarterScheme
|
||||
{
|
||||
Count = price,
|
||||
Template = currency
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using Core.Utils.Cloners;
|
||||
using Core.Utils.Collections;
|
||||
using Core.Utils.Json;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
@@ -31,8 +30,8 @@ public class RepeatableQuestGenerator(
|
||||
ICloner _cloner
|
||||
)
|
||||
{
|
||||
protected QuestConfig _questConfig = _configServer.GetConfig<QuestConfig>();
|
||||
protected int _maxRandomNumberAttempts = 6;
|
||||
protected QuestConfig _questConfig = _configServer.GetConfig<QuestConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by /GetClientRepeatableQuests/ and creates one element of quest type format (see
|
||||
@@ -186,7 +185,8 @@ public class RepeatableQuestGenerator(
|
||||
|
||||
// Filter locations bot can be killed on to just those not chosen by key
|
||||
possibleLocationPool.Locations = possibleLocationPool.Locations
|
||||
.Where(location => location != locationKey).ToList();
|
||||
.Where(location => location != locationKey)
|
||||
.ToList();
|
||||
|
||||
// None left after filtering
|
||||
if (possibleLocationPool.Locations.Count == 0)
|
||||
@@ -195,7 +195,6 @@ public class RepeatableQuestGenerator(
|
||||
// Remove chosen bot to eliminate from pool
|
||||
questTypePool.Pool.Elimination.Targets.Remove(botTypeToEliminate);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -237,7 +236,13 @@ public class RepeatableQuestGenerator(
|
||||
.GetDictionary()
|
||||
.Select(x => x.Value)
|
||||
.Where(x => x.Base?.Id != null)
|
||||
.Select(x => new { x.Base.Id, BossSpawn = x.Base.BossLocationSpawn });
|
||||
.Select(
|
||||
x => new
|
||||
{
|
||||
x.Base.Id,
|
||||
BossSpawn = x.Base.BossLocationSpawn
|
||||
}
|
||||
);
|
||||
// filter for the current boss to spawn on map
|
||||
var thisBossSpawns = bossSpawns
|
||||
.Select(
|
||||
@@ -258,15 +263,15 @@ public class RepeatableQuestGenerator(
|
||||
if (eliminationConfig.DistanceProbability > rand.NextDouble() && isDistanceRequirementAllowed)
|
||||
{
|
||||
// Random distance with lower values more likely; simple distribution for starters...
|
||||
distance = (int)Math.Floor(
|
||||
distance = (int) Math.Floor(
|
||||
Math.Abs(rand.NextDouble() - rand.NextDouble()) *
|
||||
(1 + eliminationConfig.MaxDistance - eliminationConfig.MinDistance) +
|
||||
eliminationConfig.MinDistance ??
|
||||
0
|
||||
);
|
||||
|
||||
distance = (int)Math.Ceiling((decimal)(distance / 5)) * 5;
|
||||
distanceDifficulty = (int)(maxDistDifficulty * distance / eliminationConfig.MaxDistance);
|
||||
distance = (int) Math.Ceiling((decimal) (distance / 5)) * 5;
|
||||
distanceDifficulty = (int) (maxDistDifficulty * distance / eliminationConfig.MaxDistance);
|
||||
}
|
||||
|
||||
string? allowedWeaponsCategory = null;
|
||||
@@ -277,7 +282,7 @@ public class RepeatableQuestGenerator(
|
||||
{
|
||||
List<string> weaponTypeBlacklist = ["Shotgun", "Pistol"];
|
||||
weaponCategoryRequirementConfig =
|
||||
(ProbabilityObjectArray<string, List<string>>)weaponCategoryRequirementConfig
|
||||
(ProbabilityObjectArray<string, List<string>>) weaponCategoryRequirementConfig
|
||||
.Where(
|
||||
category => weaponTypeBlacklist
|
||||
.Contains(category.Key)
|
||||
@@ -288,7 +293,7 @@ public class RepeatableQuestGenerator(
|
||||
List<string> weaponTypeBlacklist = ["MarksmanRifle", "DMR"];
|
||||
// Filter out far range weapons from close distance requirement
|
||||
weaponCategoryRequirementConfig =
|
||||
(ProbabilityObjectArray<string, List<string>>)weaponCategoryRequirementConfig
|
||||
(ProbabilityObjectArray<string, List<string>>) weaponCategoryRequirementConfig
|
||||
.Where(
|
||||
category => weaponTypeBlacklist
|
||||
.Contains(category.Key)
|
||||
@@ -335,7 +340,10 @@ public class RepeatableQuestGenerator(
|
||||
var quest = GenerateRepeatableTemplate("Elimination", traderId, repeatableConfig.Side, sessionId);
|
||||
|
||||
// ASSUMPTION: All fence quests are for scavs
|
||||
if (traderId == Traders.FENCE) quest.Side = "Scav";
|
||||
if (traderId == Traders.FENCE)
|
||||
{
|
||||
quest.Side = "Scav";
|
||||
}
|
||||
|
||||
var availableForFinishCondition = quest.Conditions.AvailableForFinish[0];
|
||||
availableForFinishCondition.Counter.Id = _hashUtil.Generate();
|
||||
@@ -387,10 +395,14 @@ public class RepeatableQuestGenerator(
|
||||
EliminationConfig eliminationConfig)
|
||||
{
|
||||
if (targetsConfig.Data(targetKey).IsBoss.GetValueOrDefault(false))
|
||||
{
|
||||
return _randomUtil.RandInt(eliminationConfig.MinBossKills.Value, eliminationConfig.MaxBossKills + 1);
|
||||
}
|
||||
|
||||
if (targetsConfig.Data(targetKey).IsPmc.GetValueOrDefault(false))
|
||||
{
|
||||
return _randomUtil.RandInt(eliminationConfig.MinPmcKills.Value, eliminationConfig.MaxPmcKills + 1);
|
||||
}
|
||||
|
||||
return _randomUtil.RandInt(eliminationConfig.MinKills.Value, eliminationConfig.MaxKills + 1);
|
||||
}
|
||||
@@ -448,7 +460,11 @@ public class RepeatableQuestGenerator(
|
||||
Value = 1,
|
||||
ResetOnSessionEnd = false,
|
||||
EnemyHealthEffects = [],
|
||||
Daytime = new DaytimeCounter { From = 0, To = 0 },
|
||||
Daytime = new DaytimeCounter
|
||||
{
|
||||
From = 0,
|
||||
To = 0
|
||||
},
|
||||
ConditionType = "Kills"
|
||||
};
|
||||
|
||||
@@ -459,14 +475,26 @@ public class RepeatableQuestGenerator(
|
||||
}
|
||||
|
||||
// Has specific body part hit condition
|
||||
if (targetedBodyParts is not null) killConditionProps.BodyPart = targetedBodyParts;
|
||||
if (targetedBodyParts is not null)
|
||||
{
|
||||
killConditionProps.BodyPart = targetedBodyParts;
|
||||
}
|
||||
|
||||
// Don't allow distance + melee requirement
|
||||
if (distance is not null && allowedWeaponCategory != "5b5f7a0886f77409407a7f96")
|
||||
killConditionProps.Distance = new CounterConditionDistance { CompareMethod = ">=", Value = distance.Value };
|
||||
{
|
||||
killConditionProps.Distance = new CounterConditionDistance
|
||||
{
|
||||
CompareMethod = ">=",
|
||||
Value = distance.Value
|
||||
};
|
||||
}
|
||||
|
||||
// Has specific weapon requirement
|
||||
if (allowedWeapon is not null) killConditionProps.Weapon = [allowedWeapon];
|
||||
if (allowedWeapon is not null)
|
||||
{
|
||||
killConditionProps.Weapon = [allowedWeapon];
|
||||
}
|
||||
|
||||
// Has specific weapon category requirement
|
||||
if (allowedWeaponCategory?.Length > 0)
|
||||
@@ -510,7 +538,7 @@ public class RepeatableQuestGenerator(
|
||||
// Be fair, don't var the items be more expensive than the reward
|
||||
var multi = _randomUtil.GetDouble(0.5, 1);
|
||||
var roublesBudget = Math.Floor(
|
||||
(double)(_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multi)
|
||||
(double) (_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multi)
|
||||
);
|
||||
roublesBudget = Math.Max(roublesBudget, 5000d);
|
||||
var itemSelection = possibleItemsToRetrievePool.Where(
|
||||
@@ -587,6 +615,7 @@ public class RepeatableQuestGenerator(
|
||||
var found = false;
|
||||
|
||||
for (var j = 0; j < _maxRandomNumberAttempts; j++)
|
||||
{
|
||||
if (usedItemIndexes.Contains(chosenItemIndex))
|
||||
{
|
||||
chosenItemIndex = _randomUtil.RandInt(itemSelection.Count);
|
||||
@@ -596,6 +625,7 @@ public class RepeatableQuestGenerator(
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
@@ -604,7 +634,8 @@ public class RepeatableQuestGenerator(
|
||||
"repeatable-no_reward_item_found_in_price_range",
|
||||
new
|
||||
{
|
||||
minPrice = 0, roublesBudget
|
||||
minPrice = 0,
|
||||
roublesBudget
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -637,12 +668,14 @@ public class RepeatableQuestGenerator(
|
||||
var value = minValue;
|
||||
|
||||
// Get the value range within budget
|
||||
var x = (int)Math.Floor(roublesBudget / itemUnitPrice);
|
||||
var x = (int) Math.Floor(roublesBudget / itemUnitPrice);
|
||||
maxValue = Math.Min(maxValue, x);
|
||||
if (maxValue > minValue)
|
||||
// If it doesn't blow the budget we have for the request, draw a random amount of the selected
|
||||
// Item type to be requested
|
||||
{
|
||||
value = _randomUtil.RandInt(minValue, maxValue + 1);
|
||||
}
|
||||
|
||||
roublesBudget -= value * itemUnitPrice;
|
||||
|
||||
@@ -655,7 +688,10 @@ public class RepeatableQuestGenerator(
|
||||
// Reduce the list possible items to fulfill the new budget constraint
|
||||
itemSelection = itemSelection.Where(dbItem => _itemHelper.GetItemPrice(dbItem.Id) < roublesBudget)
|
||||
.ToList();
|
||||
if (!itemSelection.Any()) break;
|
||||
if (!itemSelection.Any())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -692,10 +728,15 @@ public class RepeatableQuestGenerator(
|
||||
_itemHelper.IsOfBaseclass(itemTpl, BaseClasses.WEAPON) ||
|
||||
_itemHelper.IsOfBaseclass(itemTpl, BaseClasses.ARMOR)
|
||||
)
|
||||
{
|
||||
minDurability = _randomUtil.GetArrayValue([60, 80]);
|
||||
}
|
||||
|
||||
// By default all collected items must be FiR, except dog tags
|
||||
if (_itemHelper.IsDogtag(itemTpl)) onlyFoundInRaid = false;
|
||||
if (_itemHelper.IsDogtag(itemTpl))
|
||||
{
|
||||
onlyFoundInRaid = false;
|
||||
}
|
||||
|
||||
return new QuestCondition
|
||||
{
|
||||
|
||||
@@ -43,12 +43,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
* - Items
|
||||
* - Trader Reputation
|
||||
* - Skill level experience
|
||||
*
|
||||
*
|
||||
* The reward is dependent on the player level as given by the wiki. The exact mapping of pmcLevel to
|
||||
* experience / money / items / trader reputation can be defined in QuestConfig.js
|
||||
*
|
||||
*
|
||||
* There's also a random variation of the reward the spread of which can be also defined in the config
|
||||
*
|
||||
*
|
||||
* Additionally, a scaling factor w.r.t. quest difficulty going from 0.2...1 can be used
|
||||
* @param pmcLevel Level of player reward is being generated for
|
||||
* @param difficulty Reward scaling factor from 0.2 to 1
|
||||
@@ -73,7 +73,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
var itemRewardBudget = rewardParams.RewardRoubles;
|
||||
|
||||
// Possible improvement -> draw trader-specific items e.g. with _itemHelper.isOfBaseclass(val._id, ItemHelper.BASECLASS.FoodDrink)
|
||||
QuestRewards rewards = new() { Started = [], Success = [], Fail = [] };
|
||||
QuestRewards rewards = new()
|
||||
{
|
||||
Started = [],
|
||||
Success = [],
|
||||
Fail = []
|
||||
};
|
||||
|
||||
// Start reward index to keep track
|
||||
var rewardIndex = -1;
|
||||
@@ -130,14 +135,20 @@ public class RepeatableQuestRewardGenerator(
|
||||
var filteredRewardItemPool = inBudgetRewardItemPool.Where(
|
||||
item => !rewardTplBlacklist.Contains(item.Id)
|
||||
);
|
||||
if (filteredRewardItemPool.Count() > 0) inBudgetRewardItemPool = filteredRewardItemPool.ToList();
|
||||
if (filteredRewardItemPool.Count() > 0)
|
||||
{
|
||||
inBudgetRewardItemPool = filteredRewardItemPool.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Generating: {repeatableConfig.Name} quest for: {traderId} with budget: {itemRewardBudget} totalling: {rewardParams.RewardNumItems} items"
|
||||
);
|
||||
}
|
||||
|
||||
if (inBudgetRewardItemPool.Count > 0)
|
||||
{
|
||||
var itemsToReward = GetRewardableItemsFromPoolWithinBudget(
|
||||
@@ -172,11 +183,14 @@ public class RepeatableQuestRewardGenerator(
|
||||
rewards.Success.Add(reward);
|
||||
rewardIndex++;
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward");
|
||||
}
|
||||
}
|
||||
|
||||
// Chance of adding skill reward
|
||||
if (_randomUtil.GetChance100((double)rewardParams.SkillRewardChance * 100))
|
||||
if (_randomUtil.GetChance100((double) rewardParams.SkillRewardChance * 100))
|
||||
{
|
||||
var targetSkill = _randomUtil.GetArrayValue(eliminationConfig.PossibleSkillRewards);
|
||||
Reward reward = new()
|
||||
@@ -192,7 +206,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
};
|
||||
rewards.Success.Add(reward);
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}");
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
@@ -212,7 +229,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
var reputationConfig = rewardScaling.Reputation;
|
||||
|
||||
var effectiveDifficulty = difficulty is null ? 1 : difficulty;
|
||||
if (difficulty is null) _logger.Warning(_localisationService.GetText("repeatable-difficulty_was_nan"));
|
||||
if (difficulty is null)
|
||||
{
|
||||
_logger.Warning(_localisationService.GetText("repeatable-difficulty_was_nan"));
|
||||
}
|
||||
|
||||
return new QuestRewardValues
|
||||
{
|
||||
@@ -250,7 +270,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
return Math.Floor(
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, xpConfig) *
|
||||
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
|
||||
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ??
|
||||
0
|
||||
);
|
||||
}
|
||||
@@ -262,7 +282,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
return Math.Ceiling(
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, gpCoinConfig) *
|
||||
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
|
||||
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ??
|
||||
0
|
||||
);
|
||||
}
|
||||
@@ -275,7 +295,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
100 *
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, reputationConfig) *
|
||||
_randomUtil.GetDouble((double)(1 - rewardSpreadConfig), (double)(1 + rewardSpreadConfig)) ??
|
||||
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ??
|
||||
0
|
||||
) /
|
||||
100;
|
||||
@@ -283,7 +303,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
private int GetRewardNumItems(int pmcLevel, List<double>? levelsConfig, List<double>? itemsConfig)
|
||||
{
|
||||
return _randomUtil.RandInt(1, (int)Math.Round(_mathUtil.Interp1(pmcLevel, levelsConfig, itemsConfig) ?? 0) + 1);
|
||||
return _randomUtil.RandInt(1, (int) Math.Round(_mathUtil.Interp1(pmcLevel, levelsConfig, itemsConfig) ?? 0) + 1);
|
||||
}
|
||||
|
||||
private double GetRewardRoubles(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig,
|
||||
@@ -293,7 +313,9 @@ public class RepeatableQuestRewardGenerator(
|
||||
return Math.Floor(
|
||||
effectiveDifficulty *
|
||||
_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) *
|
||||
_randomUtil.GetDouble((1d - rewardSpreadConfig.Value), 1d + rewardSpreadConfig.Value) ?? 0);
|
||||
_randomUtil.GetDouble(1d - rewardSpreadConfig.Value, 1d + rewardSpreadConfig.Value) ??
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
private Dictionary<TemplateItem, int> GetRewardableItemsFromPoolWithinBudget(List<TemplateItem> itemPool,
|
||||
@@ -309,7 +331,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
// Get a random item
|
||||
var chosenItemFromPool = exhausableItemPool.GetRandomValue();
|
||||
if (!exhausableItemPool.HasValues()) break;
|
||||
if (!exhausableItemPool.HasValues())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle edge case - ammo
|
||||
if (_itemHelper.IsOfBaseclass(chosenItemFromPool.Id, BaseClasses.AMMO))
|
||||
@@ -332,13 +357,18 @@ public class RepeatableQuestRewardGenerator(
|
||||
// 25% chance to double, triple or quadruple reward stack
|
||||
// (Only occurs when item is stackable and not weapon, armor or ammo)
|
||||
if (CanIncreaseRewardItemStackSize(chosenItemFromPool, 70000, 25))
|
||||
{
|
||||
rewardItemStackCount = GetRandomisedRewardItemStackSizeByPrice(chosenItemFromPool);
|
||||
}
|
||||
|
||||
itemsToReturn.Add(chosenItemFromPool, rewardItemStackCount);
|
||||
|
||||
var itemCost = _presetHelper.GetDefaultPresetOrItemPrice(chosenItemFromPool.Id);
|
||||
var calculatedItemRewardBudget = itemRewardBudget - rewardItemStackCount * itemCost;
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug)) _logger.Debug($"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}");
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}");
|
||||
}
|
||||
|
||||
// If we still have budget narrow down possible items
|
||||
if (calculatedItemRewardBudget > 0)
|
||||
@@ -351,8 +381,12 @@ public class RepeatableQuestRewardGenerator(
|
||||
);
|
||||
|
||||
if (!exhausableItemPool.HasValues())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Reward pool empty with: {calculatedItemRewardBudget} roubles of budget remaining");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No budget for more items, end loop
|
||||
@@ -385,7 +419,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
var stackMaxCount = Math.Min(itemSelected.Properties.StackMaxSize.Value, 100);
|
||||
|
||||
// Ensure stack size is at least 1 + is no larger than the max possible stack size
|
||||
return (int)Math.Max(1, Math.Min(stackSizeThatFitsBudget, stackMaxCount));
|
||||
return (int) Math.Max(1, Math.Min(stackSizeThatFitsBudget, stackMaxCount));
|
||||
}
|
||||
|
||||
private bool CanIncreaseRewardItemStackSize(TemplateItem item, int maxRoublePriceToStack,
|
||||
@@ -425,7 +459,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
// Find the appropriate price tier and return a random stack size from its options
|
||||
var tier = priceTiers.FirstOrDefault(tier => rewardItemPrice < tier.Item1);
|
||||
if (tier is null) return 4; // Default to 2 if no tier matches
|
||||
if (tier is null)
|
||||
{
|
||||
return 4; // Default to 2 if no tier matches
|
||||
}
|
||||
|
||||
return _randomUtil.GetArrayValue(tier.Item2);
|
||||
}
|
||||
@@ -457,7 +494,8 @@ public class RepeatableQuestRewardGenerator(
|
||||
"repeatable-no_reward_item_found_in_price_range",
|
||||
new
|
||||
{
|
||||
minPrice, roublesBudget
|
||||
minPrice,
|
||||
roublesBudget
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -502,7 +540,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
while (defaultPresetPool.HasValues())
|
||||
{
|
||||
var randomPreset = defaultPresetPool.GetRandomValue();
|
||||
if (randomPreset is null) continue;
|
||||
if (randomPreset is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Gather all tpls so we can get prices of them
|
||||
var tpls = randomPreset.Items.Select(item => item.Template).ToList();
|
||||
@@ -526,7 +567,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
/**
|
||||
* Helper to create a reward item structured as required by the client
|
||||
*
|
||||
*
|
||||
* @param {string} tpl ItemId of the rewarded item
|
||||
* @param {integer} count Amount of items to give
|
||||
* @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index
|
||||
@@ -553,9 +594,15 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
// Get presets root item
|
||||
var rootItem = preset.FirstOrDefault(item => item.Template == tpl);
|
||||
if (rootItem is null) _logger.Warning($"Root item of preset: {tpl} not found");
|
||||
if (rootItem is null)
|
||||
{
|
||||
_logger.Warning($"Root item of preset: {tpl} not found");
|
||||
}
|
||||
|
||||
if (rootItem.Upd is not null) rootItem.Upd.SpawnedInSession = foundInRaid;
|
||||
if (rootItem.Upd is not null)
|
||||
{
|
||||
rootItem.Upd.SpawnedInSession = foundInRaid;
|
||||
}
|
||||
|
||||
questRewardItem.Items = _itemHelper.ReparentItemAndChildren(rootItem, preset);
|
||||
questRewardItem.Target = rootItem.Id; // Target property and root items id must match
|
||||
@@ -565,7 +612,7 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
/**
|
||||
* Helper to create a reward item structured as required by the client
|
||||
*
|
||||
*
|
||||
* @param {string} tpl ItemId of the rewarded item
|
||||
* @param {integer} count Amount of items to give
|
||||
* @param {integer} index All rewards will be appended to a list, for unknown reasons the client wants the index
|
||||
@@ -592,7 +639,13 @@ public class RepeatableQuestRewardGenerator(
|
||||
|
||||
var rootItem = new Item
|
||||
{
|
||||
Id = id, Template = tpl, Upd = new Upd { StackObjectsCount = count, SpawnedInSession = foundInRaid }
|
||||
Id = id,
|
||||
Template = tpl,
|
||||
Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = count,
|
||||
SpawnedInSession = foundInRaid
|
||||
}
|
||||
};
|
||||
questRewardItem.Items = [rootItem];
|
||||
|
||||
@@ -637,9 +690,15 @@ public class RepeatableQuestRewardGenerator(
|
||||
itemTemplate =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (itemTemplate.Parent == "") return false;
|
||||
if (itemTemplate.Parent == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seasonalItems.Contains(itemTemplate.Id)) return false;
|
||||
if (seasonalItems.Contains(itemTemplate.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var traderWhitelist = repeatableQuestConfig.TraderWhitelist.FirstOrDefault(
|
||||
trader => trader.TraderId == traderId
|
||||
@@ -665,7 +724,10 @@ public class RepeatableQuestRewardGenerator(
|
||||
List<string>? itemBaseWhitelist = null)
|
||||
{
|
||||
// Return early if not valid item to give as reward
|
||||
if (!_itemHelper.IsValidItem(tpl)) return false;
|
||||
if (!_itemHelper.IsValidItem(tpl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check item is not blacklisted
|
||||
if (
|
||||
@@ -674,16 +736,27 @@ public class RepeatableQuestRewardGenerator(
|
||||
repeatableQuestConfig.RewardBlacklist.Contains(tpl) ||
|
||||
_itemFilterService.IsItemBlacklisted(tpl)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Item has blacklisted base types
|
||||
if (_itemHelper.IsOfBaseclasses(tpl, repeatableQuestConfig.RewardBaseTypeBlacklist)) return false;
|
||||
if (_itemHelper.IsOfBaseclasses(tpl, repeatableQuestConfig.RewardBaseTypeBlacklist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip boss items
|
||||
if (_itemFilterService.IsBossItem(tpl)) return false;
|
||||
if (_itemFilterService.IsBossItem(tpl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trader has specific item base types they can give as rewards to player
|
||||
if (itemBaseWhitelist is not null && !_itemHelper.IsOfBaseclasses(tpl, itemBaseWhitelist)) return false;
|
||||
if (itemBaseWhitelist is not null && !_itemHelper.IsOfBaseclasses(tpl, itemBaseWhitelist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using Core.Helpers;
|
||||
using Core.Models.Common;
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Hideout;
|
||||
using Core.Models.Eft.ItemEvent;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Models.Spt.Hideout;
|
||||
@@ -12,6 +10,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
using SptCommon.Extensions;
|
||||
|
||||
namespace Core.Generators;
|
||||
@@ -31,12 +30,12 @@ public class ScavCaseRewardGenerator(
|
||||
ICloner _cloner
|
||||
)
|
||||
{
|
||||
protected ScavCaseConfig _scavCaseConfig = _configServer.GetConfig<ScavCaseConfig>();
|
||||
protected List<TemplateItem> _dbItemsCache = [];
|
||||
protected List<TemplateItem> _dbAmmoItemsCache = [];
|
||||
protected List<TemplateItem> _dbItemsCache = [];
|
||||
protected ScavCaseConfig _scavCaseConfig = _configServer.GetConfig<ScavCaseConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Create an array of rewards that will be given to the player upon completing their scav case build
|
||||
/// Create an array of rewards that will be given to the player upon completing their scav case build
|
||||
/// </summary>
|
||||
/// <param name="recipeId">recipe of the scav case craft</param>
|
||||
/// <returns>Product array</returns>
|
||||
@@ -83,8 +82,8 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all db items that are not blacklisted in scavcase config or global blacklist
|
||||
/// Store in class field
|
||||
/// Get all db items that are not blacklisted in scavcase config or global blacklist
|
||||
/// Store in class field
|
||||
/// </summary>
|
||||
protected void CacheDbItems()
|
||||
{
|
||||
@@ -93,16 +92,26 @@ public class ScavCaseRewardGenerator(
|
||||
// Get an array of seasonal items that should not be shown right now as seasonal event is not active
|
||||
var inactiveSeasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
if (!_dbItemsCache.Any())
|
||||
{
|
||||
_dbItemsCache = _databaseService.GetItems()
|
||||
.Values.Where(
|
||||
item =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (item.Parent == "") return false;
|
||||
if (item.Parent == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Type == "Node") return false;
|
||||
if (item.Type == "Node")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Properties.QuestItem ?? false) return false;
|
||||
if (item.Properties.QuestItem ?? false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip item if item id is on blacklist
|
||||
if (
|
||||
@@ -110,62 +119,102 @@ public class ScavCaseRewardGenerator(
|
||||
_scavCaseConfig.RewardItemBlacklist.Contains(item.Id) ||
|
||||
_itemFilterService.IsItemBlacklisted(item.Id)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Globally reward-blacklisted
|
||||
if (_itemFilterService.IsItemRewardBlacklisted(item.Id)) return false;
|
||||
if (_itemFilterService.IsItemRewardBlacklisted(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id)) return false;
|
||||
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip item if parent id is blacklisted
|
||||
if (_itemHelper.IsOfBaseclasses(item.Id, _scavCaseConfig.RewardItemParentBlacklist)) return false;
|
||||
if (_itemHelper.IsOfBaseclasses(item.Id, _scavCaseConfig.RewardItemParentBlacklist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inactiveSeasonalItems.Contains(item.Id)) return false;
|
||||
if (inactiveSeasonalItems.Contains(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (!_dbAmmoItemsCache.Any())
|
||||
{
|
||||
_dbAmmoItemsCache = _databaseService.GetItems()
|
||||
.Values.Where(
|
||||
item =>
|
||||
{
|
||||
// Base "Item" item has no parent, ignore it
|
||||
if (item.Parent == "") return false;
|
||||
if (item.Parent == "")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.Type != "Item") return false;
|
||||
if (item.Type != "Item")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not ammo, skip
|
||||
if (!_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO)) return false;
|
||||
if (!_itemHelper.IsOfBaseclass(item.Id, BaseClasses.AMMO))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip item if item id is on blacklist
|
||||
if (
|
||||
_scavCaseConfig.RewardItemBlacklist.Contains(item.Id) ||
|
||||
_itemFilterService.IsItemBlacklisted(item.Id)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Globally reward-blacklisted
|
||||
if (_itemFilterService.IsItemRewardBlacklisted(item.Id)) return false;
|
||||
if (_itemFilterService.IsItemRewardBlacklisted(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id)) return false;
|
||||
if (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip seasonal items
|
||||
if (inactiveSeasonalItems.Contains(item.Id)) return false;
|
||||
if (inactiveSeasonalItems.Contains(item.Id))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip ammo that doesn't stack as high as value in config
|
||||
if (item.Properties.StackMaxSize < _scavCaseConfig.AmmoRewards.MinStackSize) return false;
|
||||
if (item.Properties.StackMaxSize < _scavCaseConfig.AmmoRewards.MinStackSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a number of items to be rewards, the count is defined by the values in `itemFilters` param
|
||||
/// Pick a number of items to be rewards, the count is defined by the values in `itemFilters` param
|
||||
/// </summary>
|
||||
/// <param name="items">item pool to pick rewards from</param>
|
||||
/// <param name="itemFilters">how the rewards should be filtered down (by item count)</param>
|
||||
@@ -179,30 +228,38 @@ public class ScavCaseRewardGenerator(
|
||||
|
||||
var rewardWasMoney = false;
|
||||
var rewardWasAmmo = false;
|
||||
var randomCount = _randomUtil.GetInt((int)itemFilters.MinCount, (int)itemFilters.MaxCount);
|
||||
var randomCount = _randomUtil.GetInt((int) itemFilters.MinCount, (int) itemFilters.MaxCount);
|
||||
for (var i = 0; i < randomCount; i++)
|
||||
{
|
||||
if (RewardShouldBeMoney() && !rewardWasMoney)
|
||||
{
|
||||
// Only allow one reward to be money
|
||||
result.Add(GetRandomMoney());
|
||||
if (!_scavCaseConfig.AllowMultipleMoneyRewardsPerRarity) rewardWasMoney = true;
|
||||
if (!_scavCaseConfig.AllowMultipleMoneyRewardsPerRarity)
|
||||
{
|
||||
rewardWasMoney = true;
|
||||
}
|
||||
}
|
||||
else if (RewardShouldBeAmmo() && !rewardWasAmmo)
|
||||
{
|
||||
// Only allow one reward to be ammo
|
||||
result.Add(GetRandomAmmo(rarity));
|
||||
if (!_scavCaseConfig.AllowMultipleAmmoRewardsPerRarity) rewardWasAmmo = true;
|
||||
if (!_scavCaseConfig.AllowMultipleAmmoRewardsPerRarity)
|
||||
{
|
||||
rewardWasAmmo = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(_randomUtil.GetArrayValue(items));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose if money should be a reward based on the moneyRewardChancePercent config chance in scavCaseConfig
|
||||
/// Choose if money should be a reward based on the moneyRewardChancePercent config chance in scavCaseConfig
|
||||
/// </summary>
|
||||
/// <returns>true if reward should be money</returns>
|
||||
protected bool RewardShouldBeMoney()
|
||||
@@ -211,7 +268,7 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose if ammo should be a reward based on the ammoRewardChancePercent config chance in scavCaseConfig
|
||||
/// Choose if ammo should be a reward based on the ammoRewardChancePercent config chance in scavCaseConfig
|
||||
/// </summary>
|
||||
/// <returns>true if reward should be ammo</returns>
|
||||
protected bool RewardShouldBeAmmo()
|
||||
@@ -220,7 +277,7 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose from rouble/dollar/euro at random
|
||||
/// Choose from rouble/dollar/euro at random
|
||||
/// </summary>
|
||||
protected TemplateItem GetRandomMoney()
|
||||
{
|
||||
@@ -235,7 +292,7 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a random ammo from items.json that is not in the ammo blacklist AND inside the price range defined in scavcase.json config
|
||||
/// Get a random ammo from items.json that is not in the ammo blacklist AND inside the price range defined in scavcase.json config
|
||||
/// </summary>
|
||||
/// <param name="rarity">The rarity this ammo reward is for</param>
|
||||
/// <returns>random ammo item from items.json</returns>
|
||||
@@ -250,21 +307,26 @@ public class ScavCaseRewardGenerator(
|
||||
handbookPrice >= _scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub[rarity].Min &&
|
||||
handbookPrice <= _scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub[rarity].Max
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (!possibleAmmoPool.Any()) _logger.Warning("Unable to get a list of ammo that matches desired criteria for scav case reward");
|
||||
if (!possibleAmmoPool.Any())
|
||||
{
|
||||
_logger.Warning("Unable to get a list of ammo that matches desired criteria for scav case reward");
|
||||
}
|
||||
|
||||
// Get a random ammo and return it
|
||||
return _randomUtil.GetArrayValue(possibleAmmoPool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take all the rewards picked create the Product object array ready to return to calling code
|
||||
/// Also add a stack count to ammo and money
|
||||
/// Take all the rewards picked create the Product object array ready to return to calling code
|
||||
/// Also add a stack count to ammo and money
|
||||
/// </summary>
|
||||
/// <param name="rewardItems">items to convert</param>
|
||||
/// <returns>Product array</returns>
|
||||
@@ -274,7 +336,15 @@ public class ScavCaseRewardGenerator(
|
||||
List<List<Item>> result = [];
|
||||
foreach (var rewardItemDb in rewardItems)
|
||||
{
|
||||
List<Item> resultItem = [new() { Id = _hashUtil.Generate(), Template = rewardItemDb.Id, Upd = null }];
|
||||
List<Item> resultItem =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = _hashUtil.Generate(),
|
||||
Template = rewardItemDb.Id,
|
||||
Upd = null
|
||||
}
|
||||
];
|
||||
var rootItem = resultItem.FirstOrDefault();
|
||||
|
||||
if (_itemHelper.IsOfBaseclass(rewardItemDb.Id, BaseClasses.AMMO_BOX))
|
||||
@@ -303,7 +373,10 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
else if (_itemHelper.IsOfBaseclasses(rewardItemDb.Id, [BaseClasses.AMMO, BaseClasses.MONEY]))
|
||||
{
|
||||
rootItem.Upd = new Upd { StackObjectsCount = GetRandomAmountRewardForScavCase(rewardItemDb, rarity) };
|
||||
rootItem.Upd = new Upd
|
||||
{
|
||||
StackObjectsCount = GetRandomAmountRewardForScavCase(rewardItemDb, rarity)
|
||||
};
|
||||
}
|
||||
|
||||
result.Add(resultItem);
|
||||
@@ -325,7 +398,10 @@ public class ScavCaseRewardGenerator(
|
||||
item =>
|
||||
{
|
||||
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(item.Id);
|
||||
if (handbookPrice >= itemFilters.MinPriceRub && handbookPrice <= itemFilters.MaxPriceRub) return true;
|
||||
if (handbookPrice >= itemFilters.MinPriceRub && handbookPrice <= itemFilters.MaxPriceRub)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -334,7 +410,7 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers the reward min and max count params for each reward quality level from config and scavcase.json into a single object
|
||||
/// Gathers the reward min and max count params for each reward quality level from config and scavcase.json into a single object
|
||||
/// </summary>
|
||||
/// <param name="scavCaseDetails">production.json/scavRecipes object</param>
|
||||
/// <returns>ScavCaseRewardCountsAndPrices object</returns>
|
||||
@@ -368,7 +444,7 @@ public class ScavCaseRewardGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomises the size of ammo and money stacks
|
||||
/// Randomises the size of ammo and money stacks
|
||||
/// </summary>
|
||||
/// <param name="itemToCalculate">ammo or money item</param>
|
||||
/// <param name="rarity">rarity (common/rare/superrare)</param>
|
||||
@@ -377,31 +453,35 @@ public class ScavCaseRewardGenerator(
|
||||
{
|
||||
var amountToGive = 1;
|
||||
if (itemToCalculate.Parent == BaseClasses.AMMO)
|
||||
{
|
||||
amountToGive = _randomUtil.GetInt(
|
||||
_scavCaseConfig.AmmoRewards.MinStackSize,
|
||||
itemToCalculate.Properties.StackMaxSize ?? 0
|
||||
);
|
||||
}
|
||||
else if (itemToCalculate.Parent == BaseClasses.MONEY)
|
||||
{
|
||||
amountToGive = itemToCalculate.Id switch
|
||||
{
|
||||
Money.ROUBLES => _randomUtil.GetInt(
|
||||
(int)_scavCaseConfig.MoneyRewards.RubCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int)_scavCaseConfig.MoneyRewards.RubCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
(int) _scavCaseConfig.MoneyRewards.RubCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int) _scavCaseConfig.MoneyRewards.RubCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
),
|
||||
Money.EUROS => _randomUtil.GetInt(
|
||||
(int)_scavCaseConfig.MoneyRewards.EurCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int)_scavCaseConfig.MoneyRewards.EurCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
(int) _scavCaseConfig.MoneyRewards.EurCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int) _scavCaseConfig.MoneyRewards.EurCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
),
|
||||
Money.DOLLARS => _randomUtil.GetInt(
|
||||
(int)_scavCaseConfig.MoneyRewards.UsdCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int)_scavCaseConfig.MoneyRewards.UsdCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
(int) _scavCaseConfig.MoneyRewards.UsdCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int) _scavCaseConfig.MoneyRewards.UsdCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
),
|
||||
Money.GP => _randomUtil.GetInt(
|
||||
(int)_scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int)_scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
(int) _scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax>(rarity).Min,
|
||||
(int) _scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax>(rarity).Max
|
||||
),
|
||||
_ => amountToGive
|
||||
};
|
||||
}
|
||||
|
||||
return amountToGive;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public interface IInventoryMagGen
|
||||
{
|
||||
public abstract int GetPriority();
|
||||
public abstract bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen);
|
||||
public abstract void Process(InventoryMagGen inventoryMagGen);
|
||||
public int GetPriority();
|
||||
public bool CanHandleInventoryMagGen(InventoryMagGen inventoryMagGen);
|
||||
public void Process(InventoryMagGen inventoryMagGen);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Enums;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators.WeaponGen.Implementations;
|
||||
|
||||
@@ -27,16 +27,20 @@ public class BarrelInvetoryMagGen(
|
||||
double? randomisedAmmoStackSize;
|
||||
if (inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom == 1)
|
||||
// Doesn't stack
|
||||
{
|
||||
randomisedAmmoStackSize = _randomUtil.GetInt(3, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
randomisedAmmoStackSize = _randomUtil.GetInt(
|
||||
inventoryMagGen.GetAmmoTemplate().Properties.StackMinRandom.Value,
|
||||
inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom.Value
|
||||
);
|
||||
}
|
||||
|
||||
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.GetAmmoTemplate().Id,
|
||||
(int)randomisedAmmoStackSize,
|
||||
(int) randomisedAmmoStackSize,
|
||||
inventoryMagGen.GetPmcInventory(),
|
||||
null
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Utils;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Generators.WeaponGen.Implementations;
|
||||
@@ -61,7 +61,9 @@ public class ExternalInventoryMagGen(
|
||||
|
||||
if (fitsIntoInventory == ItemAddedResult.NO_CONTAINERS)
|
||||
// No containers to fit magazines, stop trying
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// No space for magazine and we haven't reached desired magazine count
|
||||
if (fitsIntoInventory == ItemAddedResult.NO_SPACE && i < randomizedMagazineCount)
|
||||
@@ -70,7 +72,9 @@ public class ExternalInventoryMagGen(
|
||||
if (fitAttempts > 5)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Failed {fitAttempts} times to add magazine {magazineTpl} to bot inventory, stopping");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -81,18 +85,24 @@ public class ExternalInventoryMagGen(
|
||||
|
||||
if (magazineTpl == defaultMagazineTpl)
|
||||
// We were already on default - stop here to prevent infinite loop
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Add failed magazine tpl to blacklist
|
||||
attemptedMagBlacklist.Add(magazineTpl);
|
||||
|
||||
if (defaultMagazineTpl is null)
|
||||
// No default to fall back to, stop trying to add mags
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (defaultMagazineTpl == BaseClasses.MAGAZINE)
|
||||
// Magazine base type, do not use
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Set chosen magazine tpl to the weapons default magazine tpl and try to fit into inventory next loop
|
||||
magazineTpl = defaultMagazineTpl;
|
||||
@@ -117,12 +127,17 @@ public class ExternalInventoryMagGen(
|
||||
if (result?.Id is null)
|
||||
{
|
||||
// Highly likely shotgun has no external mags
|
||||
if (isShotgun) break;
|
||||
if (isShotgun)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Unable to add additional magazine into bot inventory: vest/pockets for weapon: {weapon.Name}, attempted: {fitAttempts} times. Reason: {fitsIntoInventory}"
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -138,27 +153,38 @@ public class ExternalInventoryMagGen(
|
||||
|
||||
if (fitsIntoInventory == ItemAddedResult.SUCCESS)
|
||||
// Reset fit counter now it succeeded
|
||||
{
|
||||
fitAttempts = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TemplateItem? GetRandomExternalMagazineForInternalMagazineGun(string weaponTpl, List<string> magazineBlacklist)
|
||||
{
|
||||
// The mag Slot data for the weapon
|
||||
var magSlot = _itemHelper.GetItem(weaponTpl).Value.Properties.Slots.FirstOrDefault((x) => x.Name == "mod_magazine");
|
||||
if (magSlot is null) return null;
|
||||
var magSlot = _itemHelper.GetItem(weaponTpl).Value.Properties.Slots.FirstOrDefault(x => x.Name == "mod_magazine");
|
||||
if (magSlot is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// All possible mags that fit into the weapon excluding blacklisted
|
||||
var magazinePool = magSlot.Props.Filters[0]
|
||||
.Filter.Where((x) => !magazineBlacklist.Contains(x))
|
||||
.Filter.Where(x => !magazineBlacklist.Contains(x))
|
||||
.Select(
|
||||
(x) => _itemHelper.GetItem(x).Value
|
||||
x => _itemHelper.GetItem(x).Value
|
||||
);
|
||||
if (magazinePool is null) return null;
|
||||
if (magazinePool is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Non-internal magazines that fit into the weapon
|
||||
var externalMagazineOnlyPool = magazinePool.Where((x) => x.Properties.ReloadMagType != ReloadMode.InternalMagazine);
|
||||
if (externalMagazineOnlyPool is null || externalMagazineOnlyPool?.Count() == 0) return null;
|
||||
var externalMagazineOnlyPool = magazinePool.Where(x => x.Properties.ReloadMagType != ReloadMode.InternalMagazine);
|
||||
if (externalMagazineOnlyPool is null || externalMagazineOnlyPool?.Count() == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Randomly chosen external magazine
|
||||
return _randomUtil.GetArrayValue(externalMagazineOnlyPool);
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Enums;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators.WeaponGen.Implementations;
|
||||
|
||||
@@ -27,7 +27,7 @@ public class InternalMagazineInventoryMagGen(
|
||||
);
|
||||
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.GetAmmoTemplate().Id,
|
||||
(int)bulletCount,
|
||||
(int) bulletCount,
|
||||
inventoryMagGen.GetPmcInventory(),
|
||||
null
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Enums;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators.WeaponGen.Implementations;
|
||||
|
||||
@@ -27,7 +27,7 @@ public class UbglExternalMagGen(
|
||||
);
|
||||
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
|
||||
inventoryMagGen.GetAmmoTemplate().Id,
|
||||
(int)bulletCount,
|
||||
(int) bulletCount,
|
||||
inventoryMagGen.GetPmcInventory(),
|
||||
[EquipmentSlots.TacticalVest]
|
||||
);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators.WeaponGen;
|
||||
|
||||
[Injectable]
|
||||
public class InventoryMagGen()
|
||||
{
|
||||
private GenerationData _magCounts;
|
||||
private TemplateItem _magazineTemplate;
|
||||
private TemplateItem _weaponTemplate;
|
||||
private TemplateItem _ammoTemplate;
|
||||
private BotBaseInventory _pmcInventory;
|
||||
private readonly TemplateItem _ammoTemplate;
|
||||
private readonly TemplateItem _magazineTemplate;
|
||||
private readonly GenerationData _magCounts;
|
||||
private readonly BotBaseInventory _pmcInventory;
|
||||
private readonly TemplateItem _weaponTemplate;
|
||||
|
||||
public InventoryMagGen
|
||||
(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Helpers;
|
||||
using Core.Models.Eft.Weather;
|
||||
using Core.Models.Enums;
|
||||
@@ -6,6 +5,7 @@ using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Generators;
|
||||
|
||||
@@ -104,7 +104,10 @@ public class WeatherGenerator(
|
||||
protected SeasonalValues GetWeatherValuesBySeason(Season currentSeason)
|
||||
{
|
||||
var result = _weatherConfig.Weather.SeasonValues.TryGetValue(currentSeason.ToString(), out var value);
|
||||
if (!result) return _weatherConfig.Weather.SeasonValues["default"];
|
||||
if (!result)
|
||||
{
|
||||
return _weatherConfig.Weather.SeasonValues["default"];
|
||||
}
|
||||
|
||||
return value!;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Helpers;
|
||||
|
||||
@@ -47,11 +47,17 @@ public class AssortHelper(
|
||||
{
|
||||
// Get quest id that unlocks assort + statuses quest can be in to show assort
|
||||
var unlockValues = GetQuestIdAndStatusThatShowAssort(mergedQuestAssorts, assortId.Key);
|
||||
if (unlockValues is null) continue;
|
||||
if (unlockValues is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove assort if quest in profile does not have status that unlocks assort
|
||||
var questStatusInProfile = _questHelper.GetQuestStatus(pmcProfile, unlockValues.Value.Key);
|
||||
if (!unlockValues.Value.Value.Contains(questStatusInProfile)) strippedTraderAssorts = RemoveItemFromAssort(traderAssorts, assortId.Key, flea);
|
||||
if (!unlockValues.Value.Value.Contains(questStatusInProfile))
|
||||
{
|
||||
strippedTraderAssorts = RemoveItemFromAssort(traderAssorts, assortId.Key, flea);
|
||||
}
|
||||
}
|
||||
|
||||
return strippedTraderAssorts;
|
||||
@@ -69,22 +75,28 @@ public class AssortHelper(
|
||||
{
|
||||
if (mergedQuestAssorts.TryGetValue("started", out var dict1) && dict1.ContainsKey(assortId))
|
||||
// Assort unlocked by starting quest, assort is visible to player when : started or ready to hand in + handed in
|
||||
{
|
||||
return new KeyValuePair<string, List<QuestStatusEnum>>(
|
||||
mergedQuestAssorts["started"][assortId],
|
||||
[QuestStatusEnum.Started, QuestStatusEnum.AvailableForFinish, QuestStatusEnum.Success]
|
||||
);
|
||||
}
|
||||
|
||||
if (mergedQuestAssorts.TryGetValue("success", out var dict2) && dict2.ContainsKey(assortId))
|
||||
{
|
||||
return new KeyValuePair<string, List<QuestStatusEnum>>(
|
||||
mergedQuestAssorts["success"][assortId],
|
||||
[QuestStatusEnum.Success]
|
||||
);
|
||||
}
|
||||
|
||||
if (mergedQuestAssorts.TryGetValue("fail", out var dict3) && dict3.ContainsKey(assortId))
|
||||
{
|
||||
return new KeyValuePair<string, List<QuestStatusEnum>>(
|
||||
mergedQuestAssorts["fail"][assortId],
|
||||
[QuestStatusEnum.Fail]
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -110,8 +122,12 @@ public class AssortHelper(
|
||||
|
||||
// Remove items restricted by loyalty levels above those reached by the player
|
||||
foreach (var item in assort.LoyalLevelItems)
|
||||
{
|
||||
if (pmcProfile.TradersInfo.TryGetValue(traderId, out var info) && assort.LoyalLevelItems[item.Key] > info.LoyaltyLevel)
|
||||
{
|
||||
strippedAssort = RemoveItemFromAssort(assort, item.Key);
|
||||
}
|
||||
}
|
||||
|
||||
return strippedAssort;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Spt.Bots;
|
||||
using Core.Models.Spt.Config;
|
||||
@@ -7,6 +6,7 @@ using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using Core.Utils.Cloners;
|
||||
using SptCommon.Annotations;
|
||||
|
||||
namespace Core.Helpers;
|
||||
|
||||
@@ -24,7 +24,7 @@ public class BotDifficultyHelper(
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Get difficulty settings for desired bot type, if not found use assault bot types
|
||||
/// Get difficulty settings for desired bot type, if not found use assault bot types
|
||||
/// </summary>
|
||||
/// <param name="type">bot type to retrieve difficulty of</param>
|
||||
/// <param name="desiredDifficulty">difficulty to get settings for (easy/normal etc)</param>
|
||||
@@ -65,7 +65,7 @@ public class BotDifficultyHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get difficulty settings for a PMC
|
||||
/// Get difficulty settings for a PMC
|
||||
/// </summary>
|
||||
/// <param name="type">"usec" / "bear"</param>
|
||||
/// <param name="difficulty">what difficulty to retrieve</param>
|
||||
@@ -83,7 +83,7 @@ public class BotDifficultyHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translate chosen value from pre-raid difficulty dropdown into bot difficulty value
|
||||
/// Translate chosen value from pre-raid difficulty dropdown into bot difficulty value
|
||||
/// </summary>
|
||||
/// <param name="dropDownDifficulty">Dropdown difficulty value to convert</param>
|
||||
/// <returns>bot difficulty</returns>
|
||||
@@ -101,7 +101,7 @@ public class BotDifficultyHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose a random difficulty from - easy/normal/hard/impossible
|
||||
/// Choose a random difficulty from - easy/normal/hard/impossible
|
||||
/// </summary>
|
||||
/// <returns>random difficulty</returns>
|
||||
public string ChooseRandomDifficulty()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using SptCommon.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Models.Eft.Common.Tables;
|
||||
using Core.Models.Eft.Match;
|
||||
@@ -9,6 +8,7 @@ using Core.Models.Utils;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using SptCommon.Annotations;
|
||||
using LogLevel = Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace Core.Helpers;
|
||||
@@ -30,8 +30,8 @@ public class BotGeneratorHelper(
|
||||
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds properties to an item
|
||||
/// e.g. Repairable / HasHinge / Foldable / MaxDurability
|
||||
/// Adds properties to an item
|
||||
/// e.g. Repairable / HasHinge / Foldable / MaxDurability
|
||||
/// </summary>
|
||||
/// <param name="itemTemplate">Item extra properties are being generated for</param>
|
||||
/// <param name="botRole">Used by weapons to randomize the durability values. Null for non-equipped items</param>
|
||||
@@ -45,7 +45,10 @@ public class BotGeneratorHelper(
|
||||
var raidIsNight = raidSettings?.TimeVariant == DateTimeEnum.PAST;
|
||||
|
||||
RandomisedResourceDetails randomisationSettings = null;
|
||||
if (botRole is not null) _botConfig.LootItemResourceRandomization.TryGetValue(botRole, out randomisationSettings);
|
||||
if (botRole is not null)
|
||||
{
|
||||
_botConfig.LootItemResourceRandomization.TryGetValue(botRole, out randomisationSettings);
|
||||
}
|
||||
|
||||
|
||||
Upd itemProperties = new();
|
||||
@@ -69,21 +72,33 @@ public class BotGeneratorHelper(
|
||||
|
||||
if (itemTemplate?.Properties?.HasHinge ?? false)
|
||||
{
|
||||
itemProperties.Togglable = new UpdTogglable { On = true };
|
||||
itemProperties.Togglable = new UpdTogglable
|
||||
{
|
||||
On = true
|
||||
};
|
||||
hasProperties = true;
|
||||
}
|
||||
|
||||
if (itemTemplate?.Properties?.Foldable ?? false)
|
||||
{
|
||||
itemProperties.Foldable = new UpdFoldable { Folded = false };
|
||||
itemProperties.Foldable = new UpdFoldable
|
||||
{
|
||||
Folded = false
|
||||
};
|
||||
hasProperties = true;
|
||||
}
|
||||
|
||||
if (itemTemplate?.Properties?.WeapFireType?.Count == 0)
|
||||
{
|
||||
itemProperties.FireMode = itemTemplate.Properties.WeapFireType.Contains("fullauto")
|
||||
? new UpdFireMode { FireMode = "fullauto" }
|
||||
: new UpdFireMode { FireMode = _randomUtil.GetArrayValue(itemTemplate.Properties.WeapFireType) };
|
||||
? new UpdFireMode
|
||||
{
|
||||
FireMode = "fullauto"
|
||||
}
|
||||
: new UpdFireMode
|
||||
{
|
||||
FireMode = _randomUtil.GetArrayValue(itemTemplate.Properties.WeapFireType)
|
||||
};
|
||||
hasProperties = true;
|
||||
}
|
||||
|
||||
@@ -117,7 +132,11 @@ public class BotGeneratorHelper(
|
||||
var lightLaserActiveChance = raidIsNight
|
||||
? GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveNightChancePercent", 50)
|
||||
: GetBotEquipmentSettingFromConfig(botRole, "lightIsActiveDayChancePercent", 25);
|
||||
itemProperties.Light = new UpdLight { IsActive = _randomUtil.GetChance100(lightLaserActiveChance), SelectedMode = 0 };
|
||||
itemProperties.Light = new UpdLight
|
||||
{
|
||||
IsActive = _randomUtil.GetChance100(lightLaserActiveChance),
|
||||
SelectedMode = 0
|
||||
};
|
||||
hasProperties = true;
|
||||
}
|
||||
else if (itemTemplate?.Parent == BaseClasses.TACTICAL_COMBO)
|
||||
@@ -142,7 +161,10 @@ public class BotGeneratorHelper(
|
||||
var nvgActiveChance = raidIsNight
|
||||
? GetBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceNightPercent", 90)
|
||||
: GetBotEquipmentSettingFromConfig(botRole, "nvgIsActiveChanceDayPercent", 15);
|
||||
itemProperties.Togglable = new UpdTogglable { On = _randomUtil.GetChance100(nvgActiveChance) };
|
||||
itemProperties.Togglable = new UpdTogglable
|
||||
{
|
||||
On = _randomUtil.GetChance100(nvgActiveChance)
|
||||
};
|
||||
hasProperties = true;
|
||||
}
|
||||
|
||||
@@ -154,7 +176,10 @@ public class BotGeneratorHelper(
|
||||
"faceShieldIsActiveChancePercent",
|
||||
75
|
||||
);
|
||||
itemProperties.Togglable = new UpdTogglable { On = _randomUtil.GetChance100(faceShieldActiveChance) };
|
||||
itemProperties.Togglable = new UpdTogglable
|
||||
{
|
||||
On = _randomUtil.GetChance100(faceShieldActiveChance)
|
||||
};
|
||||
hasProperties = true;
|
||||
}
|
||||
|
||||
@@ -163,16 +188,22 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomize the HpResource for bots e.g (245/400 resources)
|
||||
/// Randomize the HpResource for bots e.g (245/400 resources)
|
||||
/// </summary>
|
||||
/// <param name="maxResource">Max resource value of medical items</param>
|
||||
/// <param name="randomizationValues">Value provided from config</param>
|
||||
/// <returns>Randomized value from maxHpResource</returns>
|
||||
private double GetRandomizedResourceValue(double maxResource, RandomisedResourceValues? randomizationValues)
|
||||
{
|
||||
if (randomizationValues is null) return maxResource;
|
||||
if (randomizationValues is null)
|
||||
{
|
||||
return maxResource;
|
||||
}
|
||||
|
||||
if (_randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent)) return maxResource;
|
||||
if (_randomUtil.GetChance100(randomizationValues.ChanceMaxResourcePercent))
|
||||
{
|
||||
return maxResource;
|
||||
}
|
||||
|
||||
return _randomUtil.GetDouble(
|
||||
_randomUtil.GetPercentOfValue(randomizationValues.ResourcePercent, maxResource, 0),
|
||||
@@ -181,7 +212,7 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the chance for the weapon attachment or helmet equipment to be set as activated
|
||||
/// Get the chance for the weapon attachment or helmet equipment to be set as activated
|
||||
/// </summary>
|
||||
/// <param name="botRole">role of bot with weapon/helmet</param>
|
||||
/// <param name="setting">the setting of the weapon attachment/helmet equipment to be activated</param>
|
||||
@@ -189,7 +220,10 @@ public class BotGeneratorHelper(
|
||||
/// <returns>Percent chance to be active</returns>
|
||||
private double? GetBotEquipmentSettingFromConfig(string? botRole, string setting, double defaultValue)
|
||||
{
|
||||
if (botRole is null) return defaultValue;
|
||||
if (botRole is null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var botEquipmentSettings = _botConfig.Equipment[GetBotEquipmentRole(botRole)];
|
||||
if (botEquipmentSettings is null)
|
||||
@@ -210,10 +244,13 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
var props = botEquipmentSettings.GetType().GetProperties();
|
||||
var propValue = (double?)props.FirstOrDefault(x => string.Equals(x.Name, setting, StringComparison.CurrentCultureIgnoreCase))
|
||||
var propValue = (double?) props.FirstOrDefault(x => string.Equals(x.Name, setting, StringComparison.CurrentCultureIgnoreCase))
|
||||
?.GetValue(botEquipmentSettings);
|
||||
|
||||
if (propValue is not null) return propValue;
|
||||
if (propValue is not null)
|
||||
{
|
||||
return propValue;
|
||||
}
|
||||
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
@@ -231,7 +268,7 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a repairable object for a weapon that containers durability + max durability properties
|
||||
/// Create a repairable object for a weapon that containers durability + max durability properties
|
||||
/// </summary>
|
||||
/// <param name="itemTemplate">weapon object being generated for</param>
|
||||
/// <param name="botRole">type of bot being generated for</param>
|
||||
@@ -245,11 +282,15 @@ public class BotGeneratorHelper(
|
||||
maxDurability
|
||||
);
|
||||
|
||||
return new UpdRepairable { Durability = Math.Round(currentDurability, 5), MaxDurability = Math.Round(maxDurability, 5) };
|
||||
return new UpdRepairable
|
||||
{
|
||||
Durability = Math.Round(currentDurability, 5),
|
||||
MaxDurability = Math.Round(maxDurability, 5)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a repairable object for an armor that containers durability + max durability properties
|
||||
/// Create a repairable object for an armor that containers durability + max durability properties
|
||||
/// </summary>
|
||||
/// <param name="itemTemplate">weapon object being generated for</param>
|
||||
/// <param name="botRole">type of bot being generated for</param>
|
||||
@@ -273,11 +314,15 @@ public class BotGeneratorHelper(
|
||||
);
|
||||
}
|
||||
|
||||
return new UpdRepairable { Durability = Math.Round(currentDurability, 5), MaxDurability = Math.Round(maxDurability, 5) };
|
||||
return new UpdRepairable
|
||||
{
|
||||
Durability = Math.Round(currentDurability, 5),
|
||||
MaxDurability = Math.Round(maxDurability, 5)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can item be added to another item without conflict
|
||||
/// Can item be added to another item without conflict
|
||||
/// </summary>
|
||||
/// <param name="itemsEquipped">Items to check compatibilities with</param>
|
||||
/// <param name="tplToCheck">Tpl of the item to check for incompatibilities</param>
|
||||
@@ -287,7 +332,15 @@ public class BotGeneratorHelper(
|
||||
{
|
||||
// Skip slots that have no incompatibilities
|
||||
List<string> slotsToCheck = ["Scabbard", "Backpack", "SecureContainer", "Holster", "ArmBand"];
|
||||
if (slotsToCheck.Contains(equipmentSlot)) return new ChooseRandomCompatibleModResult { Incompatible = false, Found = false, Reason = "" };
|
||||
if (slotsToCheck.Contains(equipmentSlot))
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = false,
|
||||
Found = false,
|
||||
Reason = ""
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Can probably be optimized to cache itemTemplates as items are added to inventory
|
||||
var equippedItemsDb = itemsEquipped.Select(equippedItem => _itemHelper.GetItem(equippedItem.Template).Value).ToList();
|
||||
@@ -306,7 +359,12 @@ public class BotGeneratorHelper(
|
||||
)
|
||||
);
|
||||
|
||||
return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, Reason = $"item: {tplToCheck} does not exist in the database" };
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
Found = false,
|
||||
Reason = $"item: {tplToCheck} does not exist in the database"
|
||||
};
|
||||
}
|
||||
|
||||
if (itemToEquip?.Properties is null)
|
||||
@@ -323,7 +381,12 @@ public class BotGeneratorHelper(
|
||||
)
|
||||
);
|
||||
|
||||
return new ChooseRandomCompatibleModResult { Incompatible = true, Found = false, Reason = $"item: {tplToCheck} does not have a _props field" };
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
Found = false,
|
||||
Reason = $"item: {tplToCheck} does not have a _props field"
|
||||
};
|
||||
}
|
||||
|
||||
// Does an equipped item have a property that blocks the desired item - check for prop "BlocksX" .e.g BlocksEarpiece / BlocksFaceCover
|
||||
@@ -333,16 +396,7 @@ public class BotGeneratorHelper(
|
||||
);
|
||||
if (blockingItem is not null)
|
||||
// this.logger.warning(`1 incompatibility found between - {itemToEquip[1]._name} and {blockingItem._name} - {equipmentSlot}`);
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true, Found = false,
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} in slot: {equipmentSlot} blocked by: {blockingItem.Id} {blockingItem.Name}", SlotBlocked = true
|
||||
};
|
||||
|
||||
// Check if any of the current inventory templates have the incoming item defined as incompatible
|
||||
blockingItem = templateItems.FirstOrDefault(x => x?.Properties?.ConflictingItems?.Contains(tplToCheck) ?? false);
|
||||
if (blockingItem is not null)
|
||||
// this.logger.warning(`2 incompatibility found between - {itemToEquip[1]._name} and {blockingItem._props.Name} - {equipmentSlot}`);
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
@@ -350,12 +404,28 @@ public class BotGeneratorHelper(
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} in slot: {equipmentSlot} blocked by: {blockingItem.Id} {blockingItem.Name}",
|
||||
SlotBlocked = true
|
||||
};
|
||||
}
|
||||
|
||||
// Check if any of the current inventory templates have the incoming item defined as incompatible
|
||||
blockingItem = templateItems.FirstOrDefault(x => x?.Properties?.ConflictingItems?.Contains(tplToCheck) ?? false);
|
||||
if (blockingItem is not null)
|
||||
// this.logger.warning(`2 incompatibility found between - {itemToEquip[1]._name} and {blockingItem._props.Name} - {equipmentSlot}`);
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
Found = false,
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} in slot: {equipmentSlot} blocked by: {blockingItem.Id} {blockingItem.Name}",
|
||||
SlotBlocked = true
|
||||
};
|
||||
}
|
||||
|
||||
// Does item being checked get blocked/block existing item
|
||||
if (itemToEquip.Properties.BlocksHeadwear ?? false)
|
||||
{
|
||||
var existingHeadwear = itemsEquipped.FirstOrDefault((x) => x.SlotId == "Headwear");
|
||||
var existingHeadwear = itemsEquipped.FirstOrDefault(x => x.SlotId == "Headwear");
|
||||
if (existingHeadwear is not null)
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
@@ -363,13 +433,15 @@ public class BotGeneratorHelper(
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingHeadwear.Template} in slot: {existingHeadwear.SlotId}",
|
||||
SlotBlocked = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Does item being checked get blocked/block existing item
|
||||
if (itemToEquip.Properties.BlocksFaceCover.GetValueOrDefault(false))
|
||||
{
|
||||
var existingFaceCover = itemsEquipped.FirstOrDefault((item) => item.SlotId == "FaceCover");
|
||||
var existingFaceCover = itemsEquipped.FirstOrDefault(item => item.SlotId == "FaceCover");
|
||||
if (existingFaceCover is not null)
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
@@ -377,13 +449,15 @@ public class BotGeneratorHelper(
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingFaceCover.Template} in slot: {existingFaceCover.SlotId}",
|
||||
SlotBlocked = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Does item being checked get blocked/block existing item
|
||||
if (itemToEquip.Properties.BlocksEarpiece.GetValueOrDefault(false))
|
||||
{
|
||||
var existingEarpiece = itemsEquipped.FirstOrDefault((item) => item.SlotId == "Earpiece");
|
||||
var existingEarpiece = itemsEquipped.FirstOrDefault(item => item.SlotId == "Earpiece");
|
||||
if (existingEarpiece is not null)
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
@@ -391,13 +465,15 @@ public class BotGeneratorHelper(
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingEarpiece.Template} in slot: {existingEarpiece.SlotId}",
|
||||
SlotBlocked = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Does item being checked get blocked/block existing item
|
||||
if (itemToEquip.Properties.BlocksArmorVest.GetValueOrDefault(false))
|
||||
{
|
||||
var existingArmorVest = itemsEquipped.FirstOrDefault((item) => item.SlotId == "ArmorVest");
|
||||
var existingArmorVest = itemsEquipped.FirstOrDefault(item => item.SlotId == "ArmorVest");
|
||||
if (existingArmorVest is not null)
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
@@ -405,24 +481,31 @@ public class BotGeneratorHelper(
|
||||
Reason = $"{tplToCheck} {itemToEquip.Name} is blocked by: {existingArmorVest.Template} in slot: {existingArmorVest.SlotId}",
|
||||
SlotBlocked = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the incoming item has any inventory items defined as incompatible
|
||||
var blockingInventoryItem = itemsEquipped.FirstOrDefault((x) => itemToEquip.Properties.ConflictingItems?.Contains(x.Template) ?? false);
|
||||
var blockingInventoryItem = itemsEquipped.FirstOrDefault(x => itemToEquip.Properties.ConflictingItems?.Contains(x.Template) ?? false);
|
||||
if (blockingInventoryItem is not null)
|
||||
// this.logger.warning(`3 incompatibility found between - {itemToEquip[1]._name} and {blockingInventoryItem._tpl} - {equipmentSlot}`)
|
||||
{
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = true,
|
||||
Found = false,
|
||||
Reason = $"{tplToCheck} blocks existing item {blockingInventoryItem.Template} in slot {blockingInventoryItem.SlotId}"
|
||||
};
|
||||
}
|
||||
|
||||
return new ChooseRandomCompatibleModResult { Incompatible = false, Reason = "" };
|
||||
return new ChooseRandomCompatibleModResult
|
||||
{
|
||||
Incompatible = false,
|
||||
Reason = ""
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a bots role to the equipment role used in config/bot.json
|
||||
/// Convert a bots role to the equipment role used in config/bot.json
|
||||
/// </summary>
|
||||
/// <param name="botRole">Role to convert</param>
|
||||
/// <returns>Equipment role (e.g. pmc / assault / bossTagilla)</returns>
|
||||
@@ -437,7 +520,7 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item with all its children into specified equipmentSlots, wherever it fits.
|
||||
/// Adds an item with all its children into specified equipmentSlots, wherever it fits.
|
||||
/// </summary>
|
||||
/// <param name="equipmentSlots">Slot to add item+children into</param>
|
||||
/// <param name="rootItemId">Root item id to use as mod items parentid</param>
|
||||
@@ -458,7 +541,10 @@ public class BotGeneratorHelper(
|
||||
var missingContainerCount = 0;
|
||||
foreach (var equipmentSlotId in equipmentSlots)
|
||||
{
|
||||
if (containersIdFull?.Contains(equipmentSlotId.ToString()) ?? false) continue;
|
||||
if (containersIdFull?.Contains(equipmentSlotId.ToString()) ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get container to put item into
|
||||
var container = inventory.Items.FirstOrDefault(item => item.SlotId == equipmentSlotId.ToString());
|
||||
@@ -469,9 +555,11 @@ public class BotGeneratorHelper(
|
||||
{
|
||||
// Bot doesn't have any containers we want to add item to
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug(
|
||||
$"Unable to add item: {itemWithChildren.FirstOrDefault()?.Template} to bot as it lacks the following containers: {string.Join(",", equipmentSlots)}"
|
||||
);
|
||||
}
|
||||
|
||||
return ItemAddedResult.NO_CONTAINERS;
|
||||
}
|
||||
@@ -492,7 +580,9 @@ public class BotGeneratorHelper(
|
||||
|
||||
if (value?.Properties?.Grids?.Count == 0)
|
||||
// Container has no slots to hold items
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get x/y grid size of item
|
||||
var itemSize = _inventoryHelper.GetItemSize(rootItemTplId, rootItemId, itemWithChildren);
|
||||
@@ -506,12 +596,16 @@ public class BotGeneratorHelper(
|
||||
if (slotGrid.Props?.CellsH == 0 ||
|
||||
slotGrid.Props?.CellsV == 0 ||
|
||||
itemSize[0] * itemSize[1] > slotGrid.Props?.CellsV * slotGrid.Props?.CellsH)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Can't put item type in grid, skip all grids as we're assuming they have the same rules
|
||||
if (!ItemAllowedInContainer(slotGrid, rootItemTplId))
|
||||
// Multiple containers, maybe next one allows item, only break out of loop for the containers grids
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Get all root items in found container
|
||||
var existingContainerItems = (inventory.Items ?? []).Where(
|
||||
@@ -539,7 +633,7 @@ public class BotGeneratorHelper(
|
||||
// Free slot found, add item
|
||||
if (findSlotResult.Success ?? false)
|
||||
{
|
||||
var parentItem = itemWithChildren.FirstOrDefault((i) => i.Id == rootItemId);
|
||||
var parentItem = itemWithChildren.FirstOrDefault(i => i.Id == rootItemId);
|
||||
|
||||
// Set items parent to container id
|
||||
if (parentItem is not null)
|
||||
@@ -562,24 +656,33 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
// If we've checked all grids in container and reached this point, there's no space for item
|
||||
if (currentGridCount >= totalSlotGridCount) break;
|
||||
if (currentGridCount >= totalSlotGridCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentGridCount++;
|
||||
// No space in this grid, move to next container grid and try again
|
||||
}
|
||||
|
||||
// if we got to this point, the item couldn't be placed on the container
|
||||
if (containersIdFull is null) continue;
|
||||
if (containersIdFull is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the item was a one by one, we know it must be full. Or if the maps cant find a slot for a one by one
|
||||
if (itemSize[0] == 1 && itemSize[1] == 1) containersIdFull.Add(equipmentSlotId.ToString());
|
||||
if (itemSize[0] == 1 && itemSize[1] == 1)
|
||||
{
|
||||
containersIdFull.Add(equipmentSlotId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return ItemAddedResult.NO_SPACE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take a list of items and check if they need children + add them
|
||||
/// Take a list of items and check if they need children + add them
|
||||
/// </summary>
|
||||
/// <param name="containerItems"></param>
|
||||
/// <param name="inventoryItems"></param>
|
||||
@@ -601,7 +704,7 @@ public class BotGeneratorHelper(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the provided item allowed inside a container
|
||||
/// Is the provided item allowed inside a container
|
||||
/// </summary>
|
||||
/// <param name="slotGrid">Items sub-grid we want to place item inside</param>
|
||||
/// <param name="itemTpl">Item tpl being placed</param>
|
||||
@@ -614,19 +717,30 @@ public class BotGeneratorHelper(
|
||||
|
||||
if (propFilters?.Count == 0)
|
||||
// no filters, item is fine to add
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if item base type is excluded
|
||||
var itemDetails = _itemHelper.GetItem(itemTpl).Value;
|
||||
|
||||
// if item to add is found in exclude filter, not allowed
|
||||
if (excludedFilter.Contains(itemDetails?.Parent ?? string.Empty)) return false;
|
||||
if (excludedFilter.Contains(itemDetails?.Parent ?? string.Empty))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Filter array only contains 1 filter and its for basetype 'item', allow it
|
||||
if (filter.Count == 1 && filter.Contains(BaseClasses.ITEM)) return true;
|
||||
if (filter.Count == 1 && filter.Contains(BaseClasses.ITEM))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If allowed filter has something in it + filter doesnt have basetype 'item', not allowed
|
||||
if (filter.Count > 0 && !filter.Contains(itemDetails?.Parent ?? string.Empty)) return false;
|
||||
if (filter.Count > 0 && !filter.Contains(itemDetails?.Parent ?? string.Empty))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user