.NET Format Style Fixes

This commit is contained in:
refringe
2025-06-18 17:09:20 +00:00
committed by Format Bot
parent ca0a7d6345
commit 6e01428b2b
774 changed files with 23507 additions and 40003 deletions
-5
View File
@@ -1,12 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Build.props" /> <Import Project="..\Build.props" />
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" /> <PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="FastCloner" Version="3.3.8" /> <PackageReference Include="FastCloner" Version="3.3.8" />
@@ -15,11 +12,9 @@
<ProjectReference Include="..\Libraries\SPTarkov.Common\SPTarkov.Common.csproj" /> <ProjectReference Include="..\Libraries\SPTarkov.Common\SPTarkov.Common.csproj" />
<ProjectReference Include="..\Libraries\SPTarkov.DI\SPTarkov.DI.csproj" /> <ProjectReference Include="..\Libraries\SPTarkov.DI\SPTarkov.DI.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Assets\**"> <Content Include="Assets\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
</Project> </Project>
+2 -3
View File
@@ -20,9 +20,8 @@ public class ClonerBenchmarks
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
var jsonUtil = new JsonUtil([ new SptJsonConverterRegistrator() ]); var jsonUtil = new JsonUtil([new SptJsonConverterRegistrator()]);
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(), var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(), jsonUtil);
jsonUtil);
var loadTask = importer.LoadRecursiveAsync<Templates>("./Assets/database/templates/"); var loadTask = importer.LoadRecursiveAsync<Templates>("./Assets/database/templates/");
loadTask.Wait(); loadTask.Wait();
_templates = loadTask.Result; _templates = loadTask.Result;
+6 -1
View File
@@ -6,7 +6,12 @@ namespace Benchmarks.Mock;
public class MockLogger<T> : ISptLogger<T> public class MockLogger<T> : ISptLogger<T>
{ {
public void LogWithColor(string data, LogTextColor? textColor = null, LogBackgroundColor? backgroundColor = null, Exception? ex = null) public void LogWithColor(
string data,
LogTextColor? textColor = null,
LogBackgroundColor? backgroundColor = null,
Exception? ex = null
)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
+7 -9
View File
@@ -1,11 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>4.0.0-prerelease2</Version> <Version>4.0.0-prerelease2</Version>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -8,7 +8,10 @@ public static class MemberInfoExtensions
public static string GetJsonName(this MemberInfo memberInfo) public static string GetJsonName(this MemberInfo memberInfo)
{ {
return Attribute.IsDefined(memberInfo, typeof(JsonPropertyNameAttribute)) return Attribute.IsDefined(memberInfo, typeof(JsonPropertyNameAttribute))
? (Attribute.GetCustomAttribute(memberInfo, typeof(JsonPropertyNameAttribute)) as JsonPropertyNameAttribute).Name ? (
Attribute.GetCustomAttribute(memberInfo, typeof(JsonPropertyNameAttribute))
as JsonPropertyNameAttribute
).Name
: memberInfo.Name; : memberInfo.Name;
} }
} }
@@ -6,7 +6,8 @@ namespace SPTarkov.Common.Extensions;
public static class ObjectExtensions public static class ObjectExtensions
{ {
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _indexedProperties = new(); private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _indexedProperties =
new();
private static readonly Lock _indexedPropertiesLockObject = new(); private static readonly Lock _indexedPropertiesLockObject = new();
private static bool TryGetCachedProperty(Type type, string key, out PropertyInfo cachedProperty) private static bool TryGetCachedProperty(Type type, string key, out PropertyInfo cachedProperty)
@@ -15,7 +16,8 @@ public static class ObjectExtensions
{ {
if (!_indexedProperties.TryGetValue(type, out var properties)) if (!_indexedProperties.TryGetValue(type, out var properties))
{ {
properties = type.GetProperties().ToDictionary(prop => prop.GetJsonName(), prop => prop); properties = type.GetProperties()
.ToDictionary(prop => prop.GetJsonName(), prop => prop);
_indexedProperties.Add(type, properties); _indexedProperties.Add(type, properties);
} }
@@ -49,7 +51,7 @@ public static class ObjectExtensions
return default; return default;
} }
return (T?) cachedProperty.GetValue(obj); return (T?)cachedProperty.GetValue(obj);
} }
public static List<object> GetAllPropValuesAsList(this object? obj) public static List<object> GetAllPropValuesAsList(this object? obj)
@@ -8,7 +8,11 @@ public static class StringExtensions
private static readonly Dictionary<string, Regex> _regexCache = new(); private static readonly Dictionary<string, Regex> _regexCache = new();
private static readonly Lock _regexCacheLock = new(); private static readonly Lock _regexCacheLock = new();
public static string RegexReplace(this string source, [StringSyntax(StringSyntaxAttribute.Regex)] string regexString, string newValue) public static string RegexReplace(
this string source,
[StringSyntax(StringSyntaxAttribute.Regex)] string regexString,
string newValue
)
{ {
Regex regex; Regex regex;
lock (_regexCacheLock) lock (_regexCacheLock)
@@ -23,7 +27,11 @@ public static class StringExtensions
return regex.Replace(source, newValue); return regex.Replace(source, newValue);
} }
public static bool RegexMatch(this string source, [StringSyntax(StringSyntaxAttribute.Regex)] string regexString, out Match? matchedString) public static bool RegexMatch(
this string source,
[StringSyntax(StringSyntaxAttribute.Regex)] string regexString,
out Match? matchedString
)
{ {
Regex regex; Regex regex;
lock (_regexCacheLock) lock (_regexCacheLock)
@@ -1,7 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\Build.props" />
<Import Project="..\..\Build.props"/>
<PropertyGroup> <PropertyGroup>
<PackageId>SPTarkov.Common</PackageId> <PackageId>SPTarkov.Common</PackageId>
<Authors>Single Player Tarkov</Authors> <Authors>Single Player Tarkov</Authors>
@@ -14,13 +12,10 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SemanticVersioning" Version="3.0.0"/> <PackageReference Include="SemanticVersioning" Version="3.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath=""/> <None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,30 +1,22 @@
namespace SPTarkov.DI.Annotations; namespace SPTarkov.DI.Annotations;
[AttributeUsage(AttributeTargets.Class, Inherited = false)] [AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class Injectable(InjectionType injectionType = InjectionType.Scoped, Type? typeOverride = null, int typePriority = int.MaxValue) : Attribute public class Injectable(
InjectionType injectionType = InjectionType.Scoped,
Type? typeOverride = null,
int typePriority = int.MaxValue
) : Attribute
{ {
public InjectionType InjectionType public InjectionType InjectionType { get; set; } = injectionType;
{
get;
set;
} = injectionType;
public int TypePriority public int TypePriority { get; set; } = typePriority;
{
get;
set;
} = typePriority;
public Type? TypeOverride public Type? TypeOverride { get; set; } = typeOverride;
{
get;
set;
} = typeOverride;
} }
public enum InjectionType public enum InjectionType
{ {
Singleton, Singleton,
Transient, Transient,
Scoped Scoped,
} }
@@ -16,7 +16,6 @@ public class DependencyInjectionHandler
private bool _oneTimeUseFlag; private bool _oneTimeUseFlag;
public DependencyInjectionHandler(IServiceCollection serviceCollection) public DependencyInjectionHandler(IServiceCollection serviceCollection)
{ {
_serviceCollection = serviceCollection; _serviceCollection = serviceCollection;
@@ -43,8 +42,9 @@ public class DependencyInjectionHandler
public void AddInjectableTypesFromTypeList(IEnumerable<Type> types) public void AddInjectableTypesFromTypeList(IEnumerable<Type> types)
{ {
var typesToInject = types.Where(type => var typesToInject = types.Where(type =>
Attribute.IsDefined(type, typeof(Injectable)) && Attribute.IsDefined(type, typeof(Injectable))
!_injectedTypeNames.ContainsKey($"{type.Namespace}.{type.Name}")); && !_injectedTypeNames.ContainsKey($"{type.Namespace}.{type.Name}")
);
if (typesToInject.Any()) if (typesToInject.Any())
{ {
foreach (var type in typesToInject) foreach (var type in typesToInject)
@@ -58,16 +58,24 @@ public class DependencyInjectionHandler
{ {
if (_oneTimeUseFlag) if (_oneTimeUseFlag)
{ {
throw new Exception("Invalid usage of DependencyInjectionHandler, this is a one time use service!"); throw new Exception(
"Invalid usage of DependencyInjectionHandler, this is a one time use service!"
);
} }
_oneTimeUseFlag = true; _oneTimeUseFlag = true;
var typeRefValues = _injectedTypeNames.Values var typeRefValues = _injectedTypeNames.Values.Select(t => new TypeRefContainer(
.Select(t => new TypeRefContainer(((Injectable[]) Attribute.GetCustomAttributes(t, typeof(Injectable)))[0], t, t)); ((Injectable[])Attribute.GetCustomAttributes(t, typeof(Injectable)))[0],
t,
t
));
// All the components that have a type override, we need to find them and remove them before injecting everything // All the components that have a type override, we need to find them and remove them before injecting everything
var componentsToRemove = typeRefValues.Where(tr => tr.InjectableAttribute.TypeOverride != null).Select(tr => var componentsToRemove = typeRefValues
string.IsNullOrEmpty(tr.InjectableAttribute.TypeOverride!.FullName) .Where(tr => tr.InjectableAttribute.TypeOverride != null)
? $"{tr.InjectableAttribute.TypeOverride.Namespace}.{tr.InjectableAttribute.TypeOverride.Name}" .Select(tr =>
: tr.InjectableAttribute.TypeOverride.FullName!) string.IsNullOrEmpty(tr.InjectableAttribute.TypeOverride!.FullName)
? $"{tr.InjectableAttribute.TypeOverride.Namespace}.{tr.InjectableAttribute.TypeOverride.Name}"
: tr.InjectableAttribute.TypeOverride.FullName!
)
.ToHashSet(); .ToHashSet();
// All the components without the removed overrides // All the components without the removed overrides
var cleanedComponents = typeRefValues.Where(tr => var cleanedComponents = typeRefValues.Where(tr =>
@@ -78,17 +86,27 @@ public class DependencyInjectionHandler
return !componentsToRemove.Contains(name); return !componentsToRemove.Contains(name);
}); });
// All the components sorted and ready to be inserted into the DI container // All the components sorted and ready to be inserted into the DI container
var sortedInjectableTypes = cleanedComponents.OrderBy(tRef => tRef.InjectableAttribute.TypePriority); var sortedInjectableTypes = cleanedComponents.OrderBy(tRef =>
tRef.InjectableAttribute.TypePriority
);
foreach (var typeRefToInject in sortedInjectableTypes) foreach (var typeRefToInject in sortedInjectableTypes)
{ {
var nodes = new Queue<TypeRefContainer>(); var nodes = new Queue<TypeRefContainer>();
nodes.Enqueue(typeRefToInject); nodes.Enqueue(typeRefToInject);
foreach (var implementedInterface in typeRefToInject.Type.GetInterfaces() foreach (
.Where(t => !t.Namespace.StartsWith("System"))) var implementedInterface in typeRefToInject
.Type.GetInterfaces()
.Where(t => !t.Namespace.StartsWith("System"))
)
{ {
nodes.Enqueue(new TypeRefContainer(typeRefToInject.InjectableAttribute, typeRefToInject.Type, nodes.Enqueue(
implementedInterface)); new TypeRefContainer(
typeRefToInject.InjectableAttribute,
typeRefToInject.Type,
implementedInterface
)
);
} }
while (nodes.Any()) while (nodes.Any())
@@ -96,8 +114,13 @@ public class DependencyInjectionHandler
var node = nodes.Dequeue(); var node = nodes.Dequeue();
if (node.Type.BaseType != null && node.Type.BaseType != typeof(object)) if (node.Type.BaseType != null && node.Type.BaseType != typeof(object))
{ {
nodes.Enqueue(new TypeRefContainer(node.InjectableAttribute, typeRefToInject.Type, nodes.Enqueue(
node.Type.BaseType)); new TypeRefContainer(
node.InjectableAttribute,
typeRefToInject.Type,
node.Type.BaseType
)
);
} }
if (node.Type.IsGenericType) if (node.Type.IsGenericType)
@@ -106,9 +129,11 @@ public class DependencyInjectionHandler
} }
else else
{ {
RegisterComponent(node.InjectableAttribute.InjectionType, RegisterComponent(
node.InjectableAttribute.InjectionType,
node.Type, node.Type,
node.ParentType); node.ParentType
);
} }
} }
} }
@@ -118,7 +143,10 @@ public class DependencyInjectionHandler
{ {
try try
{ {
_allLoadedTypes ??= AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()).ToList(); _allLoadedTypes ??= AppDomain
.CurrentDomain.GetAssemblies()
.SelectMany(t => t.GetTypes())
.ToList();
} }
catch (ReflectionTypeLoadException ex) catch (ReflectionTypeLoadException ex)
{ {
@@ -130,10 +158,12 @@ public class DependencyInjectionHandler
var typeName = $"{typeRef.Type.Namespace}.{typeRef.Type.Name}"; var typeName = $"{typeRef.Type.Namespace}.{typeRef.Type.Name}";
try try
{ {
var matchedConstructors = _allConstructors.Where(c => c.GetParameters() var matchedConstructors = _allConstructors.Where(c =>
.Any(p => p.ParameterType.IsGenericType && c.GetParameters()
p.ParameterType.GetGenericTypeDefinition().FullName == typeName .Any(p =>
) p.ParameterType.IsGenericType
&& p.ParameterType.GetGenericTypeDefinition().FullName == typeName
)
); );
var constructorInfos = matchedConstructors.ToList(); var constructorInfos = matchedConstructors.ToList();
@@ -145,7 +175,11 @@ public class DependencyInjectionHandler
foreach (var matchedConstructor in constructorInfos) foreach (var matchedConstructor in constructorInfos)
{ {
var constructorParams = matchedConstructor.GetParameters(); var constructorParams = matchedConstructor.GetParameters();
foreach (var parameterInfo in constructorParams.Where(x => IsMatchingGenericType(x, typeName))) foreach (
var parameterInfo in constructorParams.Where(x =>
IsMatchingGenericType(x, typeName)
)
)
{ {
var parameters = parameterInfo.ParameterType.GetGenericArguments(); var parameters = parameterInfo.ParameterType.GetGenericArguments();
var typedGeneric = typeRef.ParentType.MakeGenericType(parameters); var typedGeneric = typeRef.ParentType.MakeGenericType(parameters);
@@ -166,8 +200,8 @@ public class DependencyInjectionHandler
private static bool IsMatchingGenericType(ParameterInfo paramInfo, string typeName) private static bool IsMatchingGenericType(ParameterInfo paramInfo, string typeName)
{ {
return paramInfo.ParameterType.IsGenericType && return paramInfo.ParameterType.IsGenericType
paramInfo.ParameterType.GetGenericTypeDefinition().FullName == typeName; && paramInfo.ParameterType.GetGenericTypeDefinition().FullName == typeName;
} }
private void RegisterComponent( private void RegisterComponent(
@@ -188,7 +222,10 @@ public class DependencyInjectionHandler
_serviceCollection.AddScoped(registrableInterface, implementationType); _serviceCollection.AddScoped(registrableInterface, implementationType);
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(injectionType), $"Unknown injection type on {implementationType.Namespace}.{implementationType.Name}"); throw new ArgumentOutOfRangeException(
nameof(injectionType),
$"Unknown injection type on {implementationType.Namespace}.{implementationType.Name}"
);
} }
} }
@@ -197,20 +234,23 @@ public class DependencyInjectionHandler
var serviceKey = $"{implementationType.Namespace}.{implementationType.Name}"; var serviceKey = $"{implementationType.Namespace}.{implementationType.Name}";
if (registrableInterface != implementationType) if (registrableInterface != implementationType)
{ {
_serviceCollection.AddSingleton(registrableInterface, (serviceProvider) => _serviceCollection.AddSingleton(
{ registrableInterface,
object service; (serviceProvider) =>
lock (_injectedValuesLock)
{ {
if (!_injectedValues.TryGetValue(serviceKey, out service)) object service;
lock (_injectedValuesLock)
{ {
service = serviceProvider.GetService(implementationType); if (!_injectedValues.TryGetValue(serviceKey, out service))
_injectedValues.Add(serviceKey, service); {
service = serviceProvider.GetService(implementationType);
_injectedValues.Add(serviceKey, service);
}
} }
}
return service; return service;
}); }
);
} }
else else
{ {
+4 -7
View File
@@ -1,7 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\Build.props" /> <Import Project="..\..\Build.props" />
<PropertyGroup> <PropertyGroup>
<PackageId>SPTarkov.DI</PackageId> <PackageId>SPTarkov.DI</PackageId>
<Authors>Single Player Tarkov</Authors> <Authors>Single Player Tarkov</Authors>
@@ -14,17 +12,16 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.5" /> <PackageReference
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
Version="9.0.5"
/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\SPTarkov.Common\SPTarkov.Common.csproj" /> <ProjectReference Include="..\SPTarkov.Common\SPTarkov.Common.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="" /> <None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -2,10 +2,7 @@
public class SingletonStateHolder<T> public class SingletonStateHolder<T>
{ {
public T State public T State { get; }
{
get;
}
public SingletonStateHolder(T state) public SingletonStateHolder(T state)
{ {
@@ -27,35 +27,76 @@ public class CodeGenerator
return new CodeInstruction(code.OpCode) { labels = GetLabelList(code) }; return new CodeInstruction(code.OpCode) { labels = GetLabelList(code) };
} }
if (code.OpCode == OpCodes.Ldfld || code.OpCode == OpCodes.Ldflda || code.OpCode == OpCodes.Stfld) if (
code.OpCode == OpCodes.Ldfld
|| code.OpCode == OpCodes.Ldflda
|| code.OpCode == OpCodes.Stfld
)
{ {
return new CodeInstruction(code.OpCode, AccessTools.Field(code.CallerType, code.OperandTarget as string)) { labels = GetLabelList(code) }; return new CodeInstruction(
code.OpCode,
AccessTools.Field(code.CallerType, code.OperandTarget as string)
)
{
labels = GetLabelList(code),
};
} }
if (code.OpCode == OpCodes.Call || code.OpCode == OpCodes.Callvirt) if (code.OpCode == OpCodes.Call || code.OpCode == OpCodes.Callvirt)
{ {
return new CodeInstruction(code.OpCode, AccessTools.Method(code.CallerType, code.OperandTarget as string, code.Parameters)) { labels = GetLabelList(code) }; return new CodeInstruction(
code.OpCode,
AccessTools.Method(code.CallerType, code.OperandTarget as string, code.Parameters)
)
{
labels = GetLabelList(code),
};
} }
if (code.OpCode == OpCodes.Box) if (code.OpCode == OpCodes.Box)
{ {
return new CodeInstruction(code.OpCode, code.CallerType) { labels = GetLabelList(code) }; return new CodeInstruction(code.OpCode, code.CallerType)
{
labels = GetLabelList(code),
};
} }
if (code.OpCode == OpCodes.Br || code.OpCode == OpCodes.Brfalse || code.OpCode == OpCodes.Brtrue || code.OpCode == OpCodes.Brtrue_S if (
|| code.OpCode == OpCodes.Brfalse_S || code.OpCode == OpCodes.Br_S) code.OpCode == OpCodes.Br
|| code.OpCode == OpCodes.Brfalse
|| code.OpCode == OpCodes.Brtrue
|| code.OpCode == OpCodes.Brtrue_S
|| code.OpCode == OpCodes.Brfalse_S
|| code.OpCode == OpCodes.Br_S
)
{ {
return new CodeInstruction(code.OpCode, code.OperandTarget) { labels = GetLabelList(code) }; return new CodeInstruction(code.OpCode, code.OperandTarget)
{
labels = GetLabelList(code),
};
} }
if (code.OpCode == OpCodes.Ldftn) if (code.OpCode == OpCodes.Ldftn)
{ {
return new CodeInstruction(code.OpCode, AccessTools.Method(code.CallerType, code.OperandTarget as string, code.Parameters)) { labels = GetLabelList(code) }; return new CodeInstruction(
code.OpCode,
AccessTools.Method(code.CallerType, code.OperandTarget as string, code.Parameters)
)
{
labels = GetLabelList(code),
};
} }
if (code.OpCode == OpCodes.Newobj) if (code.OpCode == OpCodes.Newobj)
{ {
return new CodeInstruction(code.OpCode, code.CallerType.GetConstructors().FirstOrDefault(x => x.GetParameters().Length == code.Parameters.Length)) { labels = GetLabelList(code) }; return new CodeInstruction(
code.OpCode,
code.CallerType.GetConstructors()
.FirstOrDefault(x => x.GetParameters().Length == code.Parameters.Length)
)
{
labels = GetLabelList(code),
};
} }
throw new ArgumentException($"Code with OpCode {code.OpCode.ToString()} is not supported."); throw new ArgumentException($"Code with OpCode {code.OpCode.ToString()} is not supported.");
@@ -68,6 +109,6 @@ public class CodeGenerator
return []; return [];
} }
return [ (Label)code.GetLabel() ]; return [(Label)code.GetLabel()];
} }
} }
@@ -6,22 +6,32 @@ public class CodeWithLabel : Code
{ {
public Label Label { get; } public Label Label { get; }
public CodeWithLabel(OpCode opCode, Label label) : base(opCode) public CodeWithLabel(OpCode opCode, Label label)
: base(opCode)
{ {
Label = label; Label = label;
} }
public CodeWithLabel(OpCode opCode, Label label, object operandTarget) : base(opCode, operandTarget) public CodeWithLabel(OpCode opCode, Label label, object operandTarget)
: base(opCode, operandTarget)
{ {
Label = label; Label = label;
} }
public CodeWithLabel(OpCode opCode, Label label, Type callerType) : base(opCode, callerType) public CodeWithLabel(OpCode opCode, Label label, Type callerType)
: base(opCode, callerType)
{ {
Label = label; Label = label;
} }
public CodeWithLabel(OpCode opCode, Label label, Type callerType, object operandTarget, Type[] parameters = null) : base(opCode, callerType, operandTarget, parameters) public CodeWithLabel(
OpCode opCode,
Label label,
Type callerType,
object operandTarget,
Type[] parameters = null
)
: base(opCode, callerType, operandTarget, parameters)
{ {
Label = label; Label = label;
} }
@@ -25,13 +25,17 @@ public abstract class AbstractPatch
_finalizerList = GetPatchMethods(typeof(PatchFinalizerAttribute)); _finalizerList = GetPatchMethods(typeof(PatchFinalizerAttribute));
_ilManipulatorList = GetPatchMethods(typeof(PatchIlManipulatorAttribute)); _ilManipulatorList = GetPatchMethods(typeof(PatchIlManipulatorAttribute));
if (_prefixList.Count == 0 if (
_prefixList.Count == 0
&& _postfixList.Count == 0 && _postfixList.Count == 0
&& _transpilerList.Count == 0 && _transpilerList.Count == 0
&& _finalizerList.Count == 0 && _finalizerList.Count == 0
&& _ilManipulatorList.Count == 0) && _ilManipulatorList.Count == 0
)
{ {
throw new Exception($"{_harmony.Id}: At least one of the patch methods must be specified"); throw new Exception(
$"{_harmony.Id}: At least one of the patch methods must be specified"
);
} }
} }
@@ -51,8 +55,14 @@ public abstract class AbstractPatch
var T = GetType(); var T = GetType();
var methods = new List<HarmonyMethod>(); var methods = new List<HarmonyMethod>();
foreach (var method in T.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | foreach (
BindingFlags.DeclaredOnly)) var method in T.GetMethods(
BindingFlags.Static
| BindingFlags.NonPublic
| BindingFlags.Public
| BindingFlags.DeclaredOnly
)
)
{ {
if (method.GetCustomAttribute(attributeType) != null) if (method.GetCustomAttribute(attributeType) != null)
{ {
@@ -1,27 +1,17 @@
namespace SPTarkov.Reflection.Patching namespace SPTarkov.Reflection.Patching
{ {
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class PatchPrefixAttribute : Attribute public class PatchPrefixAttribute : Attribute { }
{
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class PatchPostfixAttribute : Attribute public class PatchPostfixAttribute : Attribute { }
{
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class PatchTranspilerAttribute : Attribute public class PatchTranspilerAttribute : Attribute { }
{
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class PatchFinalizerAttribute : Attribute public class PatchFinalizerAttribute : Attribute { }
{
}
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public class PatchIlManipulatorAttribute : Attribute public class PatchIlManipulatorAttribute : Attribute { }
{
}
} }
@@ -1,26 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Build.props" />
<Import Project="..\..\Build.props"/> <PropertyGroup>
<PackageId>SPTarkov.Reflection</PackageId>
<PropertyGroup> <Authors>Single Player Tarkov</Authors>
<PackageId>SPTarkov.Reflection</PackageId> <Description>Reflection library for the Single Player Tarkov server.</Description>
<Authors>Single Player Tarkov</Authors> <Copyright>Copyright (c) Single Player Tarkov 2025</Copyright>
<Description>Reflection library for the Single Player Tarkov server.</Description> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<Copyright>Copyright (c) Single Player Tarkov 2025</Copyright> <PackageProjectUrl>https://sp-tarkov.com</PackageProjectUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sp-tarkov/server-csharp</RepositoryUrl>
<PackageProjectUrl>https://sp-tarkov.com</PackageProjectUrl> <ImplicitUsings>enable</ImplicitUsings>
<RepositoryUrl>https://github.com/sp-tarkov/server-csharp</RepositoryUrl> <OutputType>Library</OutputType>
<ImplicitUsings>enable</ImplicitUsings> <IsPackable>true</IsPackable>
<OutputType>Library</OutputType> </PropertyGroup>
<IsPackable>true</IsPackable> <ItemGroup>
</PropertyGroup> <None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath=""/> <PackageReference Include="HarmonyX" Version="2.14.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="HarmonyX" Version="2.14.0"/>
</ItemGroup>
</Project> </Project>
@@ -1,7 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Build.props" />
<Import Project="..\..\Build.props"/>
<PropertyGroup> <PropertyGroup>
<PackageId>SPTarkov.Server.Assets</PackageId> <PackageId>SPTarkov.Server.Assets</PackageId>
<Authors>Single Player Tarkov</Authors> <Authors>Single Player Tarkov</Authors>
@@ -16,21 +14,16 @@
<ContentTargetFolders>content</ContentTargetFolders> <ContentTargetFolders>content</ContentTargetFolders>
<RootNamespace>SptAssets</RootNamespace> <RootNamespace>SptAssets</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="SPT_Data\**"> <Content Include="SPT_Data\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
<ExcludeFromSingleFile>True</ExcludeFromSingleFile> <ExcludeFromSingleFile>True</ExcludeFromSingleFile>
</Content> </Content>
</ItemGroup> </ItemGroup>
<Target Name="PostBuildHashFile" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'"> <Target Name="PostBuildHashFile" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<Exec Command="pwsh -NoProfile -ExecutionPolicy Bypass -File &quot;$(ProjectDir)PostBuild.ps1&quot;" /> <Exec Command="pwsh -NoProfile -ExecutionPolicy Bypass -File &quot;$(ProjectDir)PostBuild.ps1&quot;" />
</Target> </Target>
<ItemGroup> <ItemGroup>
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath=""/> <None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -17,7 +17,9 @@ public class AchievementCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetAchievements(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetAchievements(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_achievementController.GetAchievements(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_achievementController.GetAchievements(sessionID))
);
} }
/// <summary> /// <summary>
@@ -26,6 +28,8 @@ public class AchievementCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> Statistic(string url, EmptyRequestData _, string sessionID) public ValueTask<string> Statistic(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_achievementController.GetAchievementStatics(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_achievementController.GetAchievementStatics(sessionID))
);
} }
} }
@@ -7,10 +7,7 @@ using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable] [Injectable]
public class BotCallbacks( public class BotCallbacks(BotController _botController, HttpResponseUtil _httpResponseUtil)
BotController _botController,
HttpResponseUtil _httpResponseUtil
)
{ {
/// <summary> /// <summary>
/// Handle singleplayer/settings/bot/limit /// Handle singleplayer/settings/bot/limit
@@ -21,7 +18,9 @@ public class BotCallbacks(
{ {
var splitUrl = url.Split('/'); var splitUrl = url.Split('/');
var type = splitUrl[^1]; var type = splitUrl[^1];
return new ValueTask<string>(_httpResponseUtil.NoBody(_botController.GetBotPresetGenerationLimit(type))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_botController.GetBotPresetGenerationLimit(type))
);
} }
/// <summary> /// <summary>
@@ -35,10 +34,14 @@ public class BotCallbacks(
var difficulty = splitUrl[^1]; var difficulty = splitUrl[^1];
if (difficulty == "core") if (difficulty == "core")
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_botController.GetBotCoreDifficulty())); return new ValueTask<string>(
_httpResponseUtil.NoBody(_botController.GetBotCoreDifficulty())
);
} }
return new ValueTask<string>(_httpResponseUtil.NoBody(_botController.GetBotDifficulty(sessionID, type, difficulty))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_botController.GetBotDifficulty(sessionID, type, difficulty))
);
} }
/// <summary> /// <summary>
@@ -47,16 +50,24 @@ public class BotCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetAllBotDifficulties(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetAllBotDifficulties(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_botController.GetAllBotDifficulties())); return new ValueTask<string>(
_httpResponseUtil.NoBody(_botController.GetAllBotDifficulties())
);
} }
/// <summary> /// <summary>
/// Handle client/game/bot/generate /// Handle client/game/bot/generate
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GenerateBots(string url, GenerateBotsRequestData info, string sessionID) public ValueTask<string> GenerateBots(
string url,
GenerateBotsRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_botController.Generate(sessionID, info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_botController.Generate(sessionID, info))
);
} }
/// <summary> /// <summary>
@@ -20,10 +20,10 @@ public class BtrDeliveryCallbacks(
SaveServer _saveServer, SaveServer _saveServer,
HashUtil _hashUtil, HashUtil _hashUtil,
ItemHelper _itemHelper ItemHelper _itemHelper
) ) : IOnUpdate
: IOnUpdate
{ {
private readonly BtrDeliveryConfig _btrDeliveryConfig = _configServer.GetConfig<BtrDeliveryConfig>(); private readonly BtrDeliveryConfig _btrDeliveryConfig =
_configServer.GetConfig<BtrDeliveryConfig>();
public Task<bool> OnUpdate(long secondsSinceLastRun) public Task<bool> OnUpdate(long secondsSinceLastRun)
{ {
@@ -81,9 +81,13 @@ public class BtrDeliveryCallbacks(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Found {deliveryList.Count} BTR delivery package(s) in profile {sessionId}"); _logger.Debug(
$"Found {deliveryList.Count} BTR delivery package(s) in profile {sessionId}"
);
} }
return deliveryList.Where(toBeDelivered => currentTime >= toBeDelivered.ScheduledTime).ToList(); return deliveryList
.Where(toBeDelivered => currentTime >= toBeDelivered.ScheduledTime)
.ToList();
} }
return []; return [];
@@ -8,10 +8,7 @@ using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable] [Injectable]
public class BuildsCallbacks( public class BuildsCallbacks(HttpResponseUtil _httpResponseUtil, BuildController _buildController)
HttpResponseUtil _httpResponseUtil,
BuildController _buildController
)
{ {
/// <summary> /// <summary>
/// Handle client/builds/list /// Handle client/builds/list
@@ -19,7 +16,9 @@ public class BuildsCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetBuilds(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetBuilds(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_buildController.GetUserBuilds(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_buildController.GetUserBuilds(sessionID))
);
} }
/// <summary> /// <summary>
@@ -29,7 +28,11 @@ public class BuildsCallbacks(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> CreateMagazineTemplate(string url, SetMagazineRequest request, string sessionID) public ValueTask<string> CreateMagazineTemplate(
string url,
SetMagazineRequest request,
string sessionID
)
{ {
_buildController.CreateMagazineTemplate(sessionID, request); _buildController.CreateMagazineTemplate(sessionID, request);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -39,7 +42,11 @@ public class BuildsCallbacks(
/// Handle client/builds/weapon/save /// Handle client/builds/weapon/save
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SetWeapon(string url, PresetBuildActionRequestData request, string sessionID) public ValueTask<string> SetWeapon(
string url,
PresetBuildActionRequestData request,
string sessionID
)
{ {
_buildController.SaveWeaponBuild(sessionID, request); _buildController.SaveWeaponBuild(sessionID, request);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -49,7 +56,11 @@ public class BuildsCallbacks(
/// Handle client/builds/equipment/save /// Handle client/builds/equipment/save
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SetEquipment(string url, PresetBuildActionRequestData request, string sessionID) public ValueTask<string> SetEquipment(
string url,
PresetBuildActionRequestData request,
string sessionID
)
{ {
_buildController.SaveEquipmentBuild(sessionID, request); _buildController.SaveEquipmentBuild(sessionID, request);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -59,7 +70,11 @@ public class BuildsCallbacks(
/// Handle client/builds/delete /// Handle client/builds/delete
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> DeleteBuild(string url, RemoveBuildRequestData request, string sessionID) public ValueTask<string> DeleteBuild(
string url,
RemoveBuildRequestData request,
string sessionID
)
{ {
_buildController.RemoveBuild(sessionID, request); _buildController.RemoveBuild(sessionID, request);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -6,9 +6,7 @@ using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable] [Injectable]
public class BundleCallbacks( public class BundleCallbacks(HttpResponseUtil _httpResponseUtil, BundleLoader _bundleLoader)
HttpResponseUtil _httpResponseUtil,
BundleLoader _bundleLoader)
{ {
/// <summary> /// <summary>
/// Handle singleplayer/bundles /// Handle singleplayer/bundles
@@ -41,15 +41,26 @@ public class ClientLogCallbacks(
? _localisationService.GetText("release-beta-disclaimer-mods-enabled") ? _localisationService.GetText("release-beta-disclaimer-mods-enabled")
: _localisationService.GetText("release-beta-disclaimer"); : _localisationService.GetText("release-beta-disclaimer");
data.BetaDisclaimerAcceptText = _localisationService.GetText("release-beta-disclaimer-accept"); data.BetaDisclaimerAcceptText = _localisationService.GetText(
"release-beta-disclaimer-accept"
);
data.ServerModsLoadedText = _localisationService.GetText("release-server-mods-loaded"); data.ServerModsLoadedText = _localisationService.GetText("release-server-mods-loaded");
data.ServerModsLoadedDebugText = _localisationService.GetText("release-server-mods-debug-message"); data.ServerModsLoadedDebugText = _localisationService.GetText(
"release-server-mods-debug-message"
);
data.ClientModsLoadedText = _localisationService.GetText("release-plugins-loaded"); data.ClientModsLoadedText = _localisationService.GetText("release-plugins-loaded");
data.ClientModsLoadedDebugText = _localisationService.GetText("release-plugins-loaded-debug-message"); data.ClientModsLoadedDebugText = _localisationService.GetText(
data.IllegalPluginsLoadedText = _localisationService.GetText("release-illegal-plugins-loaded"); "release-plugins-loaded-debug-message"
data.IllegalPluginsExceptionText = _localisationService.GetText("release-illegal-plugins-exception"); );
data.IllegalPluginsLoadedText = _localisationService.GetText(
"release-illegal-plugins-loaded"
);
data.IllegalPluginsExceptionText = _localisationService.GetText(
"release-illegal-plugins-exception"
);
data.ReleaseSummaryText = _localisationService.GetText("release-summary"); data.ReleaseSummaryText = _localisationService.GetText("release-summary");
data.IsBeta = ProgramStatics.ENTRY_TYPE() is EntryType.BLEEDING_EDGE or EntryType.BLEEDING_EDGE_MODS; data.IsBeta =
ProgramStatics.ENTRY_TYPE() is EntryType.BLEEDING_EDGE or EntryType.BLEEDING_EDGE_MODS;
data.IsModdable = ProgramStatics.MODS(); data.IsModdable = ProgramStatics.MODS();
data.IsModded = _loadedMods.Count > 0; data.IsModded = _loadedMods.Count > 0;
@@ -19,9 +19,15 @@ public class CustomizationCallbacks(
/// Handle client/trading/customization/storage /// Handle client/trading/customization/storage
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetCustomisationUnlocks(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetCustomisationUnlocks(
string url,
EmptyRequestData _,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_saveServer.GetProfile(sessionID).CustomisationUnlocks)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_saveServer.GetProfile(sessionID).CustomisationUnlocks)
);
} }
/// <summary> /// <summary>
@@ -33,14 +39,20 @@ public class CustomizationCallbacks(
var splitUrl = url.Split('/'); var splitUrl = url.Split('/');
var traderId = splitUrl[^3]; var traderId = splitUrl[^3];
return new ValueTask<string>(_httpResponseUtil.GetBody(_customizationController.GetTraderSuits(traderId, sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_customizationController.GetTraderSuits(traderId, sessionID))
);
} }
/// <summary> /// <summary>
/// Handle CustomizationBuy event /// Handle CustomizationBuy event
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse BuyCustomisation(PmcData pmcData, BuyClothingRequestData request, string sessionID) public ItemEventRouterResponse BuyCustomisation(
PmcData pmcData,
BuyClothingRequestData request,
string sessionID
)
{ {
return _customizationController.BuyCustomisation(pmcData, request, sessionID); return _customizationController.BuyCustomisation(pmcData, request, sessionID);
} }
@@ -49,9 +61,15 @@ public class CustomizationCallbacks(
/// Handle client/hideout/customization/offer/list /// Handle client/hideout/customization/offer/list
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetHideoutCustomisation(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetHideoutCustomisation(
string url,
EmptyRequestData _,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_customizationController.GetHideoutCustomisation(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_customizationController.GetHideoutCustomisation(sessionID))
);
} }
/// <summary> /// <summary>
@@ -60,14 +78,20 @@ public class CustomizationCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetStorage(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetStorage(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_customizationController.GetCustomisationStorage(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_customizationController.GetCustomisationStorage(sessionID))
);
} }
/// <summary> /// <summary>
/// Handle CustomizationSet /// Handle CustomizationSet
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SetCustomisation(PmcData pmcData, CustomizationSetRequest request, string sessionID) public ItemEventRouterResponse SetCustomisation(
PmcData pmcData,
CustomizationSetRequest request,
string sessionID
)
{ {
return _customizationController.SetCustomisation(sessionID, request, pmcData); return _customizationController.SetCustomisation(sessionID, request, pmcData);
} }
@@ -43,7 +43,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetTemplateItems(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetTemplateItems(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetUnclearedBody(_databaseService.GetItems())); return new ValueTask<string>(
_httpResponseUtil.GetUnclearedBody(_databaseService.GetItems())
);
} }
/// <summary> /// <summary>
@@ -61,7 +63,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetTemplateSuits(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetTemplateSuits(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetTemplates().Customization)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetTemplates().Customization)
);
} }
/// <summary> /// <summary>
@@ -70,7 +74,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetTemplateCharacter(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetTemplateCharacter(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetTemplates().Character)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetTemplates().Character)
);
} }
/// <summary> /// <summary>
@@ -79,7 +85,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetHideoutSettings(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetHideoutSettings(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetHideout().Settings)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetHideout().Settings)
);
} }
/// <summary> /// <summary>
@@ -88,7 +96,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetHideoutAreas(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetHideoutAreas(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetHideout().Areas)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetHideout().Areas)
);
} }
/// <summary> /// <summary>
@@ -97,7 +107,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetHideoutProduction(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetHideoutProduction(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetHideout().Production)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetHideout().Production)
);
} }
/// <summary> /// <summary>
@@ -106,7 +118,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetLocalesLanguages(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetLocalesLanguages(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetLocales().Languages)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetLocales().Languages)
);
} }
/// <summary> /// <summary>
@@ -117,7 +131,8 @@ public class DataCallbacks(
{ {
var localeId = url.Replace("/client/menu/locale/", ""); var localeId = url.Replace("/client/menu/locale/", "");
var locales = _databaseService.GetLocales(); var locales = _databaseService.GetLocales();
var result = locales.Menu?[localeId] ?? locales.Menu?.FirstOrDefault(m => m.Key == "en").Value; var result =
locales.Menu?[localeId] ?? locales.Menu?.FirstOrDefault(m => m.Key == "en").Value;
if (result == null) if (result == null)
{ {
@@ -145,7 +160,9 @@ public class DataCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetQteList(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetQteList(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetUnclearedBody(_hideoutController.GetQteList(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetUnclearedBody(_hideoutController.GetQteList(sessionID))
);
} }
/// <summary> /// <summary>
@@ -156,6 +173,8 @@ public class DataCallbacks(
{ {
var traderId = url.Replace("/client/items/prices/", ""); var traderId = url.Replace("/client/items/prices/", "");
return new ValueTask<string>(_httpResponseUtil.GetBody(_traderController.GetItemPrices(sessionID, traderId))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_traderController.GetItemPrices(sessionID, traderId))
);
} }
} }
@@ -14,8 +14,7 @@ public class DialogueCallbacks(
TimeUtil _timeUtil, TimeUtil _timeUtil,
HttpResponseUtil _httpResponseUtil, HttpResponseUtil _httpResponseUtil,
DialogueController _dialogueController DialogueController _dialogueController
) ) : IOnUpdate
: IOnUpdate
{ {
public Task<bool> OnUpdate(long timeSinceLastRun) public Task<bool> OnUpdate(long timeSinceLastRun)
{ {
@@ -29,14 +28,20 @@ public class DialogueCallbacks(
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> GetFriendList(string url, EmptyRequestData _, string sessionID) public virtual ValueTask<string> GetFriendList(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.GetFriendList(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_dialogueController.GetFriendList(sessionID))
);
} }
/// <summary> /// <summary>
/// Handle client/chatServer/list /// Handle client/chatServer/list
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> GetChatServerList(string url, GetChatServerListRequestData request, string sessionID) public virtual ValueTask<string> GetChatServerList(
string url,
GetChatServerListRequestData request,
string sessionID
)
{ {
var chatServer = new List<ChatServer> var chatServer = new List<ChatServer>
{ {
@@ -50,15 +55,8 @@ public class DialogueCallbacks(
VersionId = "bgkidft87ddd", VersionId = "bgkidft87ddd",
Ip = "", Ip = "",
Port = 0, Port = 0,
Chats = Chats = [new Chat { Id = "0", Members = 0 }],
[ },
new Chat
{
Id = "0",
Members = 0
}
]
}
}; };
return new ValueTask<string>(_httpResponseUtil.GetBody(chatServer)); return new ValueTask<string>(_httpResponseUtil.GetBody(chatServer));
@@ -69,9 +67,20 @@ public class DialogueCallbacks(
/// TODO: request properties are not handled /// TODO: request properties are not handled
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> GetMailDialogList(string url, GetMailDialogListRequestData request, string sessionID) public virtual ValueTask<string> GetMailDialogList(
string url,
GetMailDialogListRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.GenerateDialogueList(sessionID), 0, null, false)); return new ValueTask<string>(
_httpResponseUtil.GetBody(
_dialogueController.GenerateDialogueList(sessionID),
0,
null,
false
)
);
} }
/// <summary> /// <summary>
@@ -81,25 +90,48 @@ public class DialogueCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> GetMailDialogView(string url, GetMailDialogViewRequestData request, string sessionID) public virtual ValueTask<string> GetMailDialogView(
string url,
GetMailDialogViewRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.GenerateDialogueView(request, sessionID), 0, null, false)); return new ValueTask<string>(
_httpResponseUtil.GetBody(
_dialogueController.GenerateDialogueView(request, sessionID),
0,
null,
false
)
);
} }
/// <summary> /// <summary>
/// Handle client/mail/dialog/info /// Handle client/mail/dialog/info
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> GetMailDialogInfo(string url, GetMailDialogInfoRequestData request, string sessionID) public virtual ValueTask<string> GetMailDialogInfo(
string url,
GetMailDialogInfoRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.GetDialogueInfo(request.DialogId, sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(
_dialogueController.GetDialogueInfo(request.DialogId, sessionID)
)
);
} }
/// <summary> /// <summary>
/// Handle client/mail/dialog/remove /// Handle client/mail/dialog/remove
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> RemoveDialog(string url, RemoveDialogRequestData request, string sessionID) public virtual ValueTask<string> RemoveDialog(
string url,
RemoveDialogRequestData request,
string sessionID
)
{ {
_dialogueController.RemoveDialogue(request.DialogId, sessionID); _dialogueController.RemoveDialogue(request.DialogId, sessionID);
return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse());
@@ -109,7 +141,11 @@ public class DialogueCallbacks(
/// Handle client/mail/dialog/pin /// Handle client/mail/dialog/pin
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> PinDialog(string url, PinDialogRequestData request, string sessionID) public virtual ValueTask<string> PinDialog(
string url,
PinDialogRequestData request,
string sessionID
)
{ {
_dialogueController.SetDialoguePin(request.DialogId, true, sessionID); _dialogueController.SetDialoguePin(request.DialogId, true, sessionID);
return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse());
@@ -119,7 +155,11 @@ public class DialogueCallbacks(
/// Handle client/mail/dialog/unpin /// Handle client/mail/dialog/unpin
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> UnpinDialog(string url, PinDialogRequestData request, string sessionID) public virtual ValueTask<string> UnpinDialog(
string url,
PinDialogRequestData request,
string sessionID
)
{ {
_dialogueController.SetDialoguePin(request.DialogId, false, sessionID); _dialogueController.SetDialoguePin(request.DialogId, false, sessionID);
return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse());
@@ -129,7 +169,11 @@ public class DialogueCallbacks(
/// Handle client/mail/dialog/read /// Handle client/mail/dialog/read
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> SetRead(string url, SetDialogReadRequestData request, string sessionID) public virtual ValueTask<string> SetRead(
string url,
SetDialogReadRequestData request,
string sessionID
)
{ {
_dialogueController.SetRead(request.Dialogs, sessionID); _dialogueController.SetRead(request.Dialogs, sessionID);
return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse());
@@ -139,18 +183,32 @@ public class DialogueCallbacks(
/// Handle client/mail/dialog/getAllAttachments /// Handle client/mail/dialog/getAllAttachments
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> GetAllAttachments(string url, GetAllAttachmentsRequestData request, string sessionID) public virtual ValueTask<string> GetAllAttachments(
string url,
GetAllAttachmentsRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.GetAllAttachments(request.DialogId, sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(
_dialogueController.GetAllAttachments(request.DialogId, sessionID)
)
);
} }
/// <summary> /// <summary>
/// Handle client/mail/msg/send /// Handle client/mail/msg/send
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> SendMessage(string url, SendMessageRequest request, string sessionID) public virtual ValueTask<string> SendMessage(
string url,
SendMessageRequest request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.SendMessage(sessionID, request))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_dialogueController.SendMessage(sessionID, request))
);
} }
/// <summary> /// <summary>
@@ -175,16 +233,26 @@ public class DialogueCallbacks(
/// Handle client/friend/request/send /// Handle client/friend/request/send
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> SendFriendRequest(string url, FriendRequestData request, string sessionID) public virtual ValueTask<string> SendFriendRequest(
string url,
FriendRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_dialogueController.SendFriendRequest(sessionID, request))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_dialogueController.SendFriendRequest(sessionID, request))
);
} }
/// <summary> /// <summary>
/// Handle client/friend/request/accept-all /// Handle client/friend/request/accept-all
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> AcceptAllFriendRequests(string url, EmptyRequestData _, string sessionID) public virtual ValueTask<string> AcceptAllFriendRequests(
string url,
EmptyRequestData _,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -193,7 +261,11 @@ public class DialogueCallbacks(
/// Handle client/friend/request/accept /// Handle client/friend/request/accept
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> AcceptFriendRequest(string url, AcceptFriendRequestData request, string sessionID) public virtual ValueTask<string> AcceptFriendRequest(
string url,
AcceptFriendRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(true)); return new ValueTask<string>(_httpResponseUtil.GetBody(true));
} }
@@ -202,7 +274,11 @@ public class DialogueCallbacks(
/// Handle client/friend/request/decline /// Handle client/friend/request/decline
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> DeclineFriendRequest(string url, DeclineFriendRequestData request, string sessionID) public virtual ValueTask<string> DeclineFriendRequest(
string url,
DeclineFriendRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(true)); return new ValueTask<string>(_httpResponseUtil.GetBody(true));
} }
@@ -211,7 +287,11 @@ public class DialogueCallbacks(
/// Handle client/friend/request/cancel /// Handle client/friend/request/cancel
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> CancelFriendRequest(string url, CancelFriendRequestData request, string sessionID) public virtual ValueTask<string> CancelFriendRequest(
string url,
CancelFriendRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(true)); return new ValueTask<string>(_httpResponseUtil.GetBody(true));
} }
@@ -220,7 +300,11 @@ public class DialogueCallbacks(
/// Handle client/friend/delete /// Handle client/friend/delete
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> DeleteFriend(string url, DeleteFriendRequest request, string sessionID) public virtual ValueTask<string> DeleteFriend(
string url,
DeleteFriendRequest request,
string sessionID
)
{ {
_dialogueController.DeleteFriend(sessionID, request); _dialogueController.DeleteFriend(sessionID, request);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -230,7 +314,11 @@ public class DialogueCallbacks(
/// Handle client/friend/ignore/set /// Handle client/friend/ignore/set
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> IgnoreFriend(string url, UIDRequestData request, string sessionID) public virtual ValueTask<string> IgnoreFriend(
string url,
UIDRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -239,34 +327,58 @@ public class DialogueCallbacks(
/// Handle client/friend/ignore/remove /// Handle client/friend/ignore/remove
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual ValueTask<string> UnIgnoreFriend(string url, UIDRequestData request, string sessionID) public virtual ValueTask<string> UnIgnoreFriend(
string url,
UIDRequestData request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
public virtual ValueTask<string> ClearMail(string url, ClearMailMessageRequest request, string sessionID) public virtual ValueTask<string> ClearMail(
string url,
ClearMailMessageRequest request,
string sessionID
)
{ {
_dialogueController.ClearMessages(sessionID, request); _dialogueController.ClearMessages(sessionID, request);
return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse());
} }
public virtual ValueTask<string> CreateGroupMail(string url, CreateGroupMailRequest request, string sessionID) public virtual ValueTask<string> CreateGroupMail(
string url,
CreateGroupMailRequest request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyArrayResponse());
} }
public virtual ValueTask<string> ChangeMailGroupOwner(string url, ChangeGroupMailOwnerRequest request, string sessionID) public virtual ValueTask<string> ChangeMailGroupOwner(
string url,
ChangeGroupMailOwnerRequest request,
string sessionID
)
{ {
return new ValueTask<string>("Not Implemented!"); // Not implemented in Node return new ValueTask<string>("Not Implemented!"); // Not implemented in Node
} }
public virtual ValueTask<string> AddUserToMail(string url, AddUserGroupMailRequest request, string sessionID) public virtual ValueTask<string> AddUserToMail(
string url,
AddUserGroupMailRequest request,
string sessionID
)
{ {
return new ValueTask<string>("Not Implemented!"); // Not implemented in Node return new ValueTask<string>("Not Implemented!"); // Not implemented in Node
} }
public virtual ValueTask<string> RemoveUserFromMail(string url, RemoveUserGroupMailRequest request, string sessionID) public virtual ValueTask<string> RemoveUserFromMail(
string url,
RemoveUserGroupMailRequest request,
string sessionID
)
{ {
return new ValueTask<string>("Not Implemented!"); // Not implemented in Node return new ValueTask<string>("Not Implemented!"); // Not implemented in Node
} }
@@ -28,7 +28,11 @@ public class GameCallbacks(
/// Handle client/game/version/validate /// Handle client/game/version/validate
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> VersionValidate(string url, VersionValidateRequestData info, string sessionID) public ValueTask<string> VersionValidate(
string url,
VersionValidateRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -41,12 +45,9 @@ public class GameCallbacks(
{ {
var startTimestampSec = _timeUtil.GetTimeStamp(); var startTimestampSec = _timeUtil.GetTimeStamp();
_gameController.GameStart(url, sessionID, startTimestampSec); _gameController.GameStart(url, sessionID, startTimestampSec);
return new ValueTask<string>(_httpResponseUtil.GetBody( return new ValueTask<string>(
new GameStartResponse _httpResponseUtil.GetBody(new GameStartResponse { UtcTime = startTimestampSec })
{ );
UtcTime = startTimestampSec
}
));
} }
/// <summary> /// <summary>
@@ -57,21 +58,22 @@ public class GameCallbacks(
public async ValueTask<string> GameLogout(string url, EmptyRequestData _, string sessionID) public async ValueTask<string> GameLogout(string url, EmptyRequestData _, string sessionID)
{ {
await _saveServer.SaveProfileAsync(sessionID); await _saveServer.SaveProfileAsync(sessionID);
return _httpResponseUtil.GetBody( return _httpResponseUtil.GetBody(new GameLogoutResponseData { Status = "ok" });
new GameLogoutResponseData
{
Status = "ok"
}
);
} }
/// <summary> /// <summary>
/// Handle client/game/config /// Handle client/game/config
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetGameConfig(string url, GameEmptyCrcRequestData info, string sessionID) public ValueTask<string> GetGameConfig(
string url,
GameEmptyCrcRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetGameConfig(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetGameConfig(sessionID))
);
} }
/// <summary> /// <summary>
@@ -80,7 +82,9 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetGameMode(string url, GameModeRequestData info, string sessionID) public ValueTask<string> GetGameMode(string url, GameModeRequestData info, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetGameMode(sessionID, info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetGameMode(sessionID, info))
);
} }
/// <summary> /// <summary>
@@ -89,7 +93,9 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetServer(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetServer(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetServer(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetServer(sessionID))
);
} }
/// <summary> /// <summary>
@@ -98,7 +104,9 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetCurrentGroup(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetCurrentGroup(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetCurrentGroup(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetCurrentGroup(sessionID))
);
} }
/// <summary> /// <summary>
@@ -107,7 +115,9 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> ValidateGameVersion(string url, EmptyRequestData _, string sessionID) public ValueTask<string> ValidateGameVersion(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetValidGameVersion(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetValidGameVersion(sessionID))
);
} }
/// <summary> /// <summary>
@@ -116,7 +126,9 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GameKeepalive(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GameKeepalive(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetKeepAlive(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetKeepAlive(sessionID))
);
} }
/// <summary> /// <summary>
@@ -126,12 +138,9 @@ public class GameCallbacks(
public ValueTask<string> GetVersion(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetVersion(string url, EmptyRequestData _, string sessionID)
{ {
// change to be a proper type // change to be a proper type
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new _httpResponseUtil.NoBody(new { Version = _watermark.GetInGameVersionLabel() })
{ );
Version = _watermark.GetInGameVersionLabel()
}
));
} }
/// <summary> /// <summary>
@@ -149,7 +158,9 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetRaidTime(string url, GetRaidTimeRequest request, string sessionID) public ValueTask<string> GetRaidTime(string url, GetRaidTimeRequest request, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_gameController.GetRaidTime(sessionID, request))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_gameController.GetRaidTime(sessionID, request))
);
} }
/// <summary> /// <summary>
@@ -158,14 +169,20 @@ public class GameCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetSurvey(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetSurvey(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_gameController.GetSurvey(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_gameController.GetSurvey(sessionID))
);
} }
/// <summary> /// <summary>
/// Handle client/survey/view /// Handle client/survey/view
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetSurveyView(string url, SendSurveyOpinionRequest request, string sessionID) public ValueTask<string> GetSurveyView(
string url,
SendSurveyOpinionRequest request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -174,7 +191,11 @@ public class GameCallbacks(
/// Handle client/survey/opinion /// Handle client/survey/opinion
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SendSurveyOpinion(string url, SendSurveyOpinionRequest request, string sessionID) public ValueTask<string> SendSurveyOpinion(
string url,
SendSurveyOpinionRequest request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -24,7 +24,11 @@ public class HealthCallbacks(
/// <returns>empty response, no data sent back to client</returns> /// <returns>empty response, no data sent back to client</returns>
public ValueTask<string> HandleWorkoutEffects(string url, WorkoutData info, string sessionID) public ValueTask<string> HandleWorkoutEffects(string url, WorkoutData info, string sessionID)
{ {
_healthController.ApplyWorkoutChanges(_profileHelper.GetPmcProfile(sessionID), info, sessionID); _healthController.ApplyWorkoutChanges(
_profileHelper.GetPmcProfile(sessionID),
info,
sessionID
);
return new ValueTask<string>(_httpResponseUtil.EmptyResponse()); return new ValueTask<string>(_httpResponseUtil.EmptyResponse());
} }
@@ -35,7 +39,11 @@ public class HealthCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse OffraidEat(PmcData pmcData, OffraidEatRequestData info, string sessionID) public ItemEventRouterResponse OffraidEat(
PmcData pmcData,
OffraidEatRequestData info,
string sessionID
)
{ {
return _healthController.OffRaidEat(pmcData, info, sessionID); return _healthController.OffRaidEat(pmcData, info, sessionID);
} }
@@ -47,7 +55,11 @@ public class HealthCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse OffraidHeal(PmcData pmcData, OffraidHealRequestData info, string sessionID) public ItemEventRouterResponse OffraidHeal(
PmcData pmcData,
OffraidHealRequestData info,
string sessionID
)
{ {
return _healthController.OffRaidHeal(pmcData, info, sessionID); return _healthController.OffRaidHeal(pmcData, info, sessionID);
} }
@@ -59,7 +71,11 @@ public class HealthCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse HealthTreatment(PmcData pmcData, HealthTreatmentRequestData info, string sessionID) public ItemEventRouterResponse HealthTreatment(
PmcData pmcData,
HealthTreatmentRequestData info,
string sessionID
)
{ {
return _healthController.HealthTreatment(pmcData, info, sessionID); return _healthController.HealthTreatment(pmcData, info, sessionID);
} }
@@ -10,10 +10,8 @@ using SPTarkov.Server.Core.Servers;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable(TypePriority = OnUpdateOrder.HideoutCallbacks)] [Injectable(TypePriority = OnUpdateOrder.HideoutCallbacks)]
public class HideoutCallbacks( public class HideoutCallbacks(HideoutController _hideoutController, ConfigServer _configServer)
HideoutController _hideoutController, : IOnUpdate
ConfigServer _configServer
) : IOnUpdate
{ {
private readonly HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>(); private readonly HideoutConfig _hideoutConfig = _configServer.GetConfig<HideoutConfig>();
@@ -33,7 +31,12 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutUpgrade event /// Handle HideoutUpgrade event
/// </summary> /// </summary>
public ItemEventRouterResponse Upgrade(PmcData pmcData, HideoutUpgradeRequestData request, string sessionID, ItemEventRouterResponse output) public ItemEventRouterResponse Upgrade(
PmcData pmcData,
HideoutUpgradeRequestData request,
string sessionID,
ItemEventRouterResponse output
)
{ {
_hideoutController.StartUpgrade(pmcData, request, sessionID, output); _hideoutController.StartUpgrade(pmcData, request, sessionID, output);
@@ -43,7 +46,12 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutUpgradeComplete event /// Handle HideoutUpgradeComplete event
/// </summary> /// </summary>
public ItemEventRouterResponse UpgradeComplete(PmcData pmcData, HideoutUpgradeCompleteRequestData request, string sessionID, ItemEventRouterResponse output) public ItemEventRouterResponse UpgradeComplete(
PmcData pmcData,
HideoutUpgradeCompleteRequestData request,
string sessionID,
ItemEventRouterResponse output
)
{ {
_hideoutController.UpgradeComplete(pmcData, request, sessionID, output); _hideoutController.UpgradeComplete(pmcData, request, sessionID, output);
@@ -53,7 +61,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutPutItemsInAreaSlots /// Handle HideoutPutItemsInAreaSlots
/// </summary> /// </summary>
public ItemEventRouterResponse PutItemsInAreaSlots(PmcData pmcData, HideoutPutItemInRequestData request, string sessionID) public ItemEventRouterResponse PutItemsInAreaSlots(
PmcData pmcData,
HideoutPutItemInRequestData request,
string sessionID
)
{ {
return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID); return _hideoutController.PutItemsInAreaSlots(pmcData, request, sessionID);
} }
@@ -61,7 +73,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutTakeItemsFromAreaSlots event /// Handle HideoutTakeItemsFromAreaSlots event
/// </summary> /// </summary>
public ItemEventRouterResponse TakeItemsFromAreaSlots(PmcData pmcData, HideoutTakeItemOutRequestData request, string sessionID) public ItemEventRouterResponse TakeItemsFromAreaSlots(
PmcData pmcData,
HideoutTakeItemOutRequestData request,
string sessionID
)
{ {
return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID); return _hideoutController.TakeItemsFromAreaSlots(pmcData, request, sessionID);
} }
@@ -69,7 +85,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutToggleArea event /// Handle HideoutToggleArea event
/// </summary> /// </summary>
public ItemEventRouterResponse ToggleArea(PmcData pmcData, HideoutToggleAreaRequestData request, string sessionID) public ItemEventRouterResponse ToggleArea(
PmcData pmcData,
HideoutToggleAreaRequestData request,
string sessionID
)
{ {
return _hideoutController.ToggleArea(pmcData, request, sessionID); return _hideoutController.ToggleArea(pmcData, request, sessionID);
} }
@@ -77,7 +97,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutSingleProductionStart event /// Handle HideoutSingleProductionStart event
/// </summary> /// </summary>
public ItemEventRouterResponse SingleProductionStart(PmcData pmcData, HideoutSingleProductionStartRequestData request, string sessionID) public ItemEventRouterResponse SingleProductionStart(
PmcData pmcData,
HideoutSingleProductionStartRequestData request,
string sessionID
)
{ {
return _hideoutController.SingleProductionStart(pmcData, request, sessionID); return _hideoutController.SingleProductionStart(pmcData, request, sessionID);
} }
@@ -85,7 +109,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutScavCaseProductionStart event /// Handle HideoutScavCaseProductionStart event
/// </summary> /// </summary>
public ItemEventRouterResponse ScavCaseProductionStart(PmcData pmcData, HideoutScavCaseStartRequestData request, string sessionID) public ItemEventRouterResponse ScavCaseProductionStart(
PmcData pmcData,
HideoutScavCaseStartRequestData request,
string sessionID
)
{ {
return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID); return _hideoutController.ScavCaseProductionStart(pmcData, request, sessionID);
} }
@@ -93,7 +121,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutContinuousProductionStart /// Handle HideoutContinuousProductionStart
/// </summary> /// </summary>
public ItemEventRouterResponse ContinuousProductionStart(PmcData pmcData, HideoutContinuousProductionStartRequestData request, string sessionID) public ItemEventRouterResponse ContinuousProductionStart(
PmcData pmcData,
HideoutContinuousProductionStartRequestData request,
string sessionID
)
{ {
return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID); return _hideoutController.ContinuousProductionStart(pmcData, request, sessionID);
} }
@@ -101,7 +133,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutTakeProduction event /// Handle HideoutTakeProduction event
/// </summary> /// </summary>
public ItemEventRouterResponse TakeProduction(PmcData pmcData, HideoutTakeProductionRequestData request, string sessionID) public ItemEventRouterResponse TakeProduction(
PmcData pmcData,
HideoutTakeProductionRequestData request,
string sessionID
)
{ {
return _hideoutController.TakeProduction(pmcData, request, sessionID); return _hideoutController.TakeProduction(pmcData, request, sessionID);
} }
@@ -109,7 +145,12 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle HideoutQuickTimeEvent /// Handle HideoutQuickTimeEvent
/// </summary> /// </summary>
public ItemEventRouterResponse HandleQTEEvent(PmcData pmcData, HandleQTEEventRequestData request, string sessionID, ItemEventRouterResponse output) public ItemEventRouterResponse HandleQTEEvent(
PmcData pmcData,
HandleQTEEventRequestData request,
string sessionID,
ItemEventRouterResponse output
)
{ {
_hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output); _hideoutController.HandleQTEEventOutcome(sessionID, pmcData, request, output);
@@ -119,8 +160,12 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle client/game/profile/items/moving - RecordShootingRangePoints /// Handle client/game/profile/items/moving - RecordShootingRangePoints
/// </summary> /// </summary>
public ItemEventRouterResponse RecordShootingRangePoints(PmcData pmcData, RecordShootingRangePoints request, string sessionID, public ItemEventRouterResponse RecordShootingRangePoints(
ItemEventRouterResponse output) PmcData pmcData,
RecordShootingRangePoints request,
string sessionID,
ItemEventRouterResponse output
)
{ {
_hideoutController.RecordShootingRangePoints(sessionID, pmcData, request); _hideoutController.RecordShootingRangePoints(sessionID, pmcData, request);
@@ -130,7 +175,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle client/game/profile/items/moving - RecordShootingRangePoints /// Handle client/game/profile/items/moving - RecordShootingRangePoints
/// </summary> /// </summary>
public ItemEventRouterResponse ImproveArea(PmcData pmcData, HideoutImproveAreaRequestData request, string sessionID) public ItemEventRouterResponse ImproveArea(
PmcData pmcData,
HideoutImproveAreaRequestData request,
string sessionID
)
{ {
return _hideoutController.ImproveArea(sessionID, pmcData, request); return _hideoutController.ImproveArea(sessionID, pmcData, request);
} }
@@ -138,7 +187,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle client/game/profile/items/moving - HideoutCancelProductionCommand /// Handle client/game/profile/items/moving - HideoutCancelProductionCommand
/// </summary> /// </summary>
public ItemEventRouterResponse CancelProduction(PmcData pmcData, HideoutCancelProductionRequestData request, string sessionID) public ItemEventRouterResponse CancelProduction(
PmcData pmcData,
HideoutCancelProductionRequestData request,
string sessionID
)
{ {
return _hideoutController.CancelProduction(sessionID, pmcData, request); return _hideoutController.CancelProduction(sessionID, pmcData, request);
} }
@@ -146,7 +199,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle client/game/profile/items/moving - HideoutCircleOfCultistProductionStart /// Handle client/game/profile/items/moving - HideoutCircleOfCultistProductionStart
/// </summary> /// </summary>
public ItemEventRouterResponse CicleOfCultistProductionStart(PmcData pmcData, HideoutCircleOfCultistProductionStartRequestData request, string sessionID) public ItemEventRouterResponse CicleOfCultistProductionStart(
PmcData pmcData,
HideoutCircleOfCultistProductionStartRequestData request,
string sessionID
)
{ {
return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request); return _hideoutController.CicleOfCultistProductionStart(sessionID, pmcData, request);
} }
@@ -154,7 +211,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle client/game/profile/items/moving - HideoutDeleteProductionCommand /// Handle client/game/profile/items/moving - HideoutDeleteProductionCommand
/// </summary> /// </summary>
public ItemEventRouterResponse HideoutDeleteProductionCommand(PmcData pmcData, HideoutDeleteProductionRequestData request, string sessionID) public ItemEventRouterResponse HideoutDeleteProductionCommand(
PmcData pmcData,
HideoutDeleteProductionRequestData request,
string sessionID
)
{ {
return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request); return _hideoutController.HideoutDeleteProductionCommand(sessionID, pmcData, request);
} }
@@ -162,7 +223,11 @@ public class HideoutCallbacks(
/// <summary> /// <summary>
/// Handle client/game/profile/items/moving - HideoutCustomizationApply /// Handle client/game/profile/items/moving - HideoutCustomizationApply
/// </summary> /// </summary>
public ItemEventRouterResponse HideoutCustomizationApplyCommand(PmcData pmcData, HideoutCustomizationApplyRequestData request, string sessionID) public ItemEventRouterResponse HideoutCustomizationApplyCommand(
PmcData pmcData,
HideoutCustomizationApplyRequestData request,
string sessionID
)
{ {
return _hideoutController.HideoutCustomizationApply(sessionID, pmcData, request); return _hideoutController.HideoutCustomizationApply(sessionID, pmcData, request);
} }
@@ -171,7 +236,11 @@ public class HideoutCallbacks(
/// Handle client/game/profile/items/moving - hideoutCustomizationSetMannequinPose /// Handle client/game/profile/items/moving - hideoutCustomizationSetMannequinPose
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse HideoutCustomizationSetMannequinPose(PmcData pmcData, HideoutCustomizationSetMannequinPoseRequest request, string sessionId) public ItemEventRouterResponse HideoutCustomizationSetMannequinPose(
PmcData pmcData,
HideoutCustomizationSetMannequinPoseRequest request,
string sessionId
)
{ {
return _hideoutController.HideoutCustomizationSetMannequinPose(sessionId, pmcData, request); return _hideoutController.HideoutCustomizationSetMannequinPose(sessionId, pmcData, request);
} }
@@ -3,6 +3,7 @@ using SPTarkov.Server.Core.DI;
using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Servers;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable(InjectionType.Singleton, TypePriority = OnLoadOrder.HttpCallbacks)] [Injectable(InjectionType.Singleton, TypePriority = OnLoadOrder.HttpCallbacks)]
public class HttpCallbacks(HttpServer _httpServer) : IOnLoad public class HttpCallbacks(HttpServer _httpServer) : IOnLoad
{ {
@@ -7,10 +7,7 @@ using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable] [Injectable]
public class InraidCallbacks( public class InraidCallbacks(InRaidController _inRaidController, HttpResponseUtil _httpResponseUtil)
InRaidController _inRaidController,
HttpResponseUtil _httpResponseUtil
)
{ {
/// <summary> /// <summary>
/// Handle client/location/getLocalloot /// Handle client/location/getLocalloot
@@ -20,7 +17,11 @@ public class InraidCallbacks(
/// <param name="info">register player request</param> /// <param name="info">register player request</param>
/// <param name="sessionID">Session id</param> /// <param name="sessionID">Session id</param>
/// <returns>Null http response</returns> /// <returns>Null http response</returns>
public ValueTask<string> RegisterPlayer(string url, RegisterPlayerRequestData info, string sessionID) public ValueTask<string> RegisterPlayer(
string url,
RegisterPlayerRequestData info,
string sessionID
)
{ {
_inRaidController.AddPlayer(sessionID, info); _inRaidController.AddPlayer(sessionID, info);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -45,16 +46,24 @@ public class InraidCallbacks(
/// <returns>JSON as string</returns> /// <returns>JSON as string</returns>
public ValueTask<string> GetRaidMenuSettings() public ValueTask<string> GetRaidMenuSettings()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_inRaidController.GetInRaidConfig().RaidMenuSettings)); return new ValueTask<string>(
_httpResponseUtil.NoBody(_inRaidController.GetInRaidConfig().RaidMenuSettings)
);
} }
/// <summary> /// <summary>
/// Handle singleplayer/scav/traitorscavhostile /// Handle singleplayer/scav/traitorscavhostile
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetTraitorScavHostileChance(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetTraitorScavHostileChance(
string url,
EmptyRequestData _,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_inRaidController.GetTraitorScavHostileChance(url, sessionID))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_inRaidController.GetTraitorScavHostileChance(url, sessionID))
);
} }
/// <summary> /// <summary>
@@ -63,6 +72,8 @@ public class InraidCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetBossTypes(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetBossTypes(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_inRaidController.GetBossTypes(url, sessionID))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_inRaidController.GetBossTypes(url, sessionID))
);
} }
} }
@@ -17,8 +17,7 @@ public class InsuranceCallbacks(
InsuranceService _insuranceService, InsuranceService _insuranceService,
HttpResponseUtil _httpResponseUtil, HttpResponseUtil _httpResponseUtil,
ConfigServer _configServer ConfigServer _configServer
) ) : IOnUpdate
: IOnUpdate
{ {
private readonly InsuranceConfig _insuranceConfig = _configServer.GetConfig<InsuranceConfig>(); private readonly InsuranceConfig _insuranceConfig = _configServer.GetConfig<InsuranceConfig>();
@@ -41,9 +40,15 @@ public class InsuranceCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetInsuranceCost(string url, GetInsuranceCostRequestData info, string sessionID) public ValueTask<string> GetInsuranceCost(
string url,
GetInsuranceCostRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_insuranceController.Cost(info, sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_insuranceController.Cost(info, sessionID))
);
} }
/// <summary> /// <summary>
@@ -21,8 +21,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse MoveItem(PmcData pmcData, InventoryMoveRequestData info, string sessionID, public ItemEventRouterResponse MoveItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryMoveRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.MoveItem(pmcData, info, sessionID, output); _inventoryController.MoveItem(pmcData, info, sessionID, output);
return output; return output;
@@ -36,8 +40,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse RemoveItem(PmcData pmcData, InventoryRemoveRequestData info, string sessionID, public ItemEventRouterResponse RemoveItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryRemoveRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.DiscardItem(pmcData, info, sessionID, output); _inventoryController.DiscardItem(pmcData, info, sessionID, output);
return output; return output;
@@ -51,8 +59,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SplitItem(PmcData pmcData, InventorySplitRequestData info, string sessionID, public ItemEventRouterResponse SplitItem(
ItemEventRouterResponse output) PmcData pmcData,
InventorySplitRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.SplitItem(pmcData, info, sessionID, output); _inventoryController.SplitItem(pmcData, info, sessionID, output);
return output; return output;
@@ -65,8 +77,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse MergeItem(PmcData pmcData, InventoryMergeRequestData info, string sessionID, public ItemEventRouterResponse MergeItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryMergeRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.MergeItem(pmcData, info, sessionID, output); _inventoryController.MergeItem(pmcData, info, sessionID, output);
return output; return output;
@@ -79,8 +95,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse TransferItem(PmcData pmcData, InventoryTransferRequestData info, string sessionID, public ItemEventRouterResponse TransferItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryTransferRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.TransferItem(pmcData, info, sessionID, output); _inventoryController.TransferItem(pmcData, info, sessionID, output);
return output; return output;
@@ -93,7 +113,11 @@ public class InventoryCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SwapItem(PmcData pmcData, InventorySwapRequestData info, string sessionID) public ItemEventRouterResponse SwapItem(
PmcData pmcData,
InventorySwapRequestData info,
string sessionID
)
{ {
return _inventoryController.SwapItem(pmcData, info, sessionID); return _inventoryController.SwapItem(pmcData, info, sessionID);
} }
@@ -104,7 +128,11 @@ public class InventoryCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData info, string sessionID) public ItemEventRouterResponse FoldItem(
PmcData pmcData,
InventoryFoldRequestData info,
string sessionID
)
{ {
return _inventoryController.FoldItem(pmcData, info, sessionID); return _inventoryController.FoldItem(pmcData, info, sessionID);
} }
@@ -115,7 +143,11 @@ public class InventoryCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ToggleItem(PmcData pmcData, InventoryToggleRequestData info, string sessionID) public ItemEventRouterResponse ToggleItem(
PmcData pmcData,
InventoryToggleRequestData info,
string sessionID
)
{ {
return _inventoryController.ToggleItem(pmcData, info, sessionID); return _inventoryController.ToggleItem(pmcData, info, sessionID);
} }
@@ -126,7 +158,11 @@ public class InventoryCallbacks(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData request, string sessionId) public ItemEventRouterResponse TagItem(
PmcData pmcData,
InventoryTagRequestData request,
string sessionId
)
{ {
return _inventoryController.TagItem(pmcData, request, sessionId); return _inventoryController.TagItem(pmcData, request, sessionId);
} }
@@ -138,8 +174,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse BindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID, public ItemEventRouterResponse BindItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryBindRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.BindItem(pmcData, info, sessionID, output); _inventoryController.BindItem(pmcData, info, sessionID, output);
return output; return output;
@@ -152,8 +192,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse UnBindItem(PmcData pmcData, InventoryBindRequestData info, string sessionID, public ItemEventRouterResponse UnBindItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryBindRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.UnBindItem(pmcData, info, sessionID, output); _inventoryController.UnBindItem(pmcData, info, sessionID, output);
return output; return output;
@@ -166,8 +210,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ExamineItem(PmcData pmcData, InventoryExamineRequestData info, string sessionID, public ItemEventRouterResponse ExamineItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryExamineRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.ExamineItem(pmcData, info, sessionID, output); _inventoryController.ExamineItem(pmcData, info, sessionID, output);
return output; return output;
@@ -180,8 +228,11 @@ public class InventoryCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData info, public ItemEventRouterResponse ReadEncyclopedia(
string sessionID) PmcData pmcData,
InventoryReadEncyclopediaRequestData info,
string sessionID
)
{ {
return _inventoryController.ReadEncyclopedia(pmcData, info, sessionID); return _inventoryController.ReadEncyclopedia(pmcData, info, sessionID);
} }
@@ -194,8 +245,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SortInventory(PmcData pmcData, InventorySortRequestData info, string sessionID, public ItemEventRouterResponse SortInventory(
ItemEventRouterResponse output) PmcData pmcData,
InventorySortRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.SortInventory(pmcData, info, sessionID, output); _inventoryController.SortInventory(pmcData, info, sessionID, output);
return output; return output;
@@ -208,8 +263,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData info, public ItemEventRouterResponse CreateMapMarker(
string sessionID, ItemEventRouterResponse output) PmcData pmcData,
InventoryCreateMarkerRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.CreateMapMarker(pmcData, info, sessionID, output); _inventoryController.CreateMapMarker(pmcData, info, sessionID, output);
return output; return output;
@@ -222,8 +281,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData info, public ItemEventRouterResponse DeleteMapMarker(
string sessionID, ItemEventRouterResponse output) PmcData pmcData,
InventoryDeleteMarkerRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.DeleteMapMarker(pmcData, info, sessionID, output); _inventoryController.DeleteMapMarker(pmcData, info, sessionID, output);
return output; return output;
@@ -236,8 +299,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData info, string sessionID, public ItemEventRouterResponse EditMapMarker(
ItemEventRouterResponse output) PmcData pmcData,
InventoryEditMarkerRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.EditMapMarker(pmcData, info, sessionID, output); _inventoryController.EditMapMarker(pmcData, info, sessionID, output);
return output; return output;
@@ -251,9 +318,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData info, public ItemEventRouterResponse OpenRandomLootContainer(
PmcData pmcData,
OpenRandomLootContainerRequestData info,
string sessionID, string sessionID,
ItemEventRouterResponse output) ItemEventRouterResponse output
)
{ {
_inventoryController.OpenRandomLootContainer(pmcData, info, sessionID, output); _inventoryController.OpenRandomLootContainer(pmcData, info, sessionID, output);
return output; return output;
@@ -266,8 +336,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData info, string sessionID, public ItemEventRouterResponse RedeemProfileReward(
ItemEventRouterResponse output) PmcData pmcData,
RedeemProfileRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.RedeemProfileReward(pmcData, info, sessionID); _inventoryController.RedeemProfileReward(pmcData, info, sessionID);
return output; return output;
@@ -281,8 +355,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SetFavoriteItem(PmcData pmcData, SetFavoriteItems info, string sessionID, public ItemEventRouterResponse SetFavoriteItem(
ItemEventRouterResponse output) PmcData pmcData,
SetFavoriteItems info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.SetFavoriteItem(pmcData, info, sessionID); _inventoryController.SetFavoriteItem(pmcData, info, sessionID);
return output; return output;
@@ -297,8 +375,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse FailQuest(PmcData pmcData, FailQuestRequestData info, string sessionID, public ItemEventRouterResponse FailQuest(
ItemEventRouterResponse output) PmcData pmcData,
FailQuestRequestData info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_questController.FailQuest(pmcData, info, sessionID, output); _questController.FailQuest(pmcData, info, sessionID, output);
return output; return output;
@@ -311,8 +393,12 @@ public class InventoryCallbacks(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse PinOrLock(PmcData pmcData, PinOrLockItemRequest info, string sessionID, public ItemEventRouterResponse PinOrLock(
ItemEventRouterResponse output) PmcData pmcData,
PinOrLockItemRequest info,
string sessionID,
ItemEventRouterResponse output
)
{ {
_inventoryController.PinOrLock(pmcData, info, sessionID, output); _inventoryController.PinOrLock(pmcData, info, sessionID, output);
return output; return output;
@@ -7,13 +7,24 @@ using SPTarkov.Server.Core.Utils;
namespace SPTarkov.Server.Core.Callbacks; namespace SPTarkov.Server.Core.Callbacks;
[Injectable] [Injectable]
public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRouter _itemEventRouter) public class ItemEventCallbacks(
HttpResponseUtil _httpResponseUtil,
ItemEventRouter _itemEventRouter
)
{ {
public async ValueTask<string> HandleEvents(string url, ItemEventRouterRequest info, string sessionID) public async ValueTask<string> HandleEvents(
string url,
ItemEventRouterRequest info,
string sessionID
)
{ {
var eventResponse = await _itemEventRouter.HandleEvents(info, sessionID); var eventResponse = await _itemEventRouter.HandleEvents(info, sessionID);
var result = IsCriticalError(eventResponse.Warnings) var result = IsCriticalError(eventResponse.Warnings)
? _httpResponseUtil.GetBody(eventResponse, GetErrorCode(eventResponse.Warnings), eventResponse.Warnings[0].ErrorMessage) ? _httpResponseUtil.GetBody(
eventResponse,
GetErrorCode(eventResponse.Warnings),
eventResponse.Warnings[0].ErrorMessage
)
: _httpResponseUtil.GetBody(eventResponse); : _httpResponseUtil.GetBody(eventResponse);
return result; return result;
@@ -34,7 +45,7 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
// List of non-critical error codes, we return true if any error NOT included is passed in // List of non-critical error codes, we return true if any error NOT included is passed in
var nonCriticalErrorCodes = new HashSet<BackendErrorCodes> var nonCriticalErrorCodes = new HashSet<BackendErrorCodes>
{ {
BackendErrorCodes.NotEnoughSpace BackendErrorCodes.NotEnoughSpace,
}; };
foreach (var warning in warnings) foreach (var warning in warnings)
@@ -51,6 +62,8 @@ public class ItemEventCallbacks(HttpResponseUtil _httpResponseUtil, ItemEventRou
public static BackendErrorCodes GetErrorCode(List<Warning> warnings) public static BackendErrorCodes GetErrorCode(List<Warning> warnings)
{ {
// Cast int to string to get the error code of 220 for Unknown Error. // 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; return warnings.FirstOrDefault()?.Code is null
? BackendErrorCodes.UnknownError
: warnings.FirstOrDefault()?.Code ?? BackendErrorCodes.UnknownError;
} }
} }
@@ -68,21 +68,33 @@ public class LauncherCallbacks(
public ValueTask<string> RemoveProfile(string url, RemoveProfileData info, string sessionID) public ValueTask<string> RemoveProfile(string url, RemoveProfileData info, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_saveServer.RemoveProfile(sessionID))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_saveServer.RemoveProfile(sessionID))
);
} }
public ValueTask<string> GetCompatibleTarkovVersion() public ValueTask<string> GetCompatibleTarkovVersion()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_launcherController.GetCompatibleTarkovVersion())); return new ValueTask<string>(
_httpResponseUtil.NoBody(_launcherController.GetCompatibleTarkovVersion())
);
} }
public ValueTask<string> GetLoadedServerMods() public ValueTask<string> GetLoadedServerMods()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_launcherController.GetLoadedServerMods())); return new ValueTask<string>(
_httpResponseUtil.NoBody(_launcherController.GetLoadedServerMods())
);
} }
public ValueTask<string> GetServerModsProfileUsed(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetServerModsProfileUsed(
string url,
EmptyRequestData _,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody(_launcherController.GetServerModsProfileUsed(sessionID))); return new ValueTask<string>(
_httpResponseUtil.NoBody(_launcherController.GetServerModsProfileUsed(sessionID))
);
} }
} }
@@ -15,32 +15,29 @@ public class LauncherV2Callbacks(
{ {
public ValueTask<string> Ping() public ValueTask<string> Ping()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2PingResponse _httpResponseUtil.NoBody(
{ new LauncherV2PingResponse { Response = _launcherV2Controller.Ping() }
Response = _launcherV2Controller.Ping() )
} );
));
} }
public ValueTask<string> Types() public ValueTask<string> Types()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2TypesResponse _httpResponseUtil.NoBody(
{ new LauncherV2TypesResponse { Response = _launcherV2Controller.Types() }
Response = _launcherV2Controller.Types() )
} );
));
} }
public ValueTask<string> Login(LoginRequestData info) public ValueTask<string> Login(LoginRequestData info)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2LoginResponse _httpResponseUtil.NoBody(
{ new LauncherV2LoginResponse { Response = _launcherV2Controller.Login(info) }
Response = _launcherV2Controller.Login(info) )
} );
));
} }
public async ValueTask<string> Register(RegisterData info) public async ValueTask<string> Register(RegisterData info)
@@ -49,7 +46,7 @@ public class LauncherV2Callbacks(
new LauncherV2RegisterResponse new LauncherV2RegisterResponse
{ {
Response = await _launcherV2Controller.Register(info), Response = await _launcherV2Controller.Register(info),
Profiles = _profileController.GetMiniProfiles() Profiles = _profileController.GetMiniProfiles(),
} }
); );
} }
@@ -60,63 +57,67 @@ public class LauncherV2Callbacks(
new LauncherV2PasswordChangeResponse new LauncherV2PasswordChangeResponse
{ {
Response = await _launcherV2Controller.PasswordChange(info), Response = await _launcherV2Controller.PasswordChange(info),
Profiles = _profileController.GetMiniProfiles() Profiles = _profileController.GetMiniProfiles(),
} }
); );
} }
public ValueTask<string> Remove(LoginRequestData info) public ValueTask<string> Remove(LoginRequestData info)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2RemoveResponse _httpResponseUtil.NoBody(
{ new LauncherV2RemoveResponse
Response = _launcherV2Controller.Remove(info), {
Profiles = _profileController.GetMiniProfiles() Response = _launcherV2Controller.Remove(info),
} Profiles = _profileController.GetMiniProfiles(),
)); }
)
);
} }
public ValueTask<string> CompatibleVersion() public ValueTask<string> CompatibleVersion()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2VersionResponse _httpResponseUtil.NoBody(
{ new LauncherV2VersionResponse
Response = new LauncherV2CompatibleVersion
{ {
SptVersion = _launcherV2Controller.SptVersion(), Response = new LauncherV2CompatibleVersion
EftVersion = _launcherV2Controller.EftVersion() {
SptVersion = _launcherV2Controller.SptVersion(),
EftVersion = _launcherV2Controller.EftVersion(),
},
} }
} )
)); );
} }
public ValueTask<string> Mods() public ValueTask<string> Mods()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2ModsResponse _httpResponseUtil.NoBody(
{ new LauncherV2ModsResponse { Response = _launcherV2Controller.LoadedMods() }
Response = _launcherV2Controller.LoadedMods() )
} );
));
} }
public ValueTask<string> Profiles() public ValueTask<string> Profiles()
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2ProfilesResponse _httpResponseUtil.NoBody(
{ new LauncherV2ProfilesResponse { Response = _profileController.GetMiniProfiles() }
Response = _profileController.GetMiniProfiles() )
} );
));
} }
public ValueTask<string> Profile(string? sessionId) public ValueTask<string> Profile(string? sessionId)
{ {
return new ValueTask<string>(_httpResponseUtil.NoBody( return new ValueTask<string>(
new LauncherV2ProfileResponse _httpResponseUtil.NoBody(
{ new LauncherV2ProfileResponse
Response = _launcherV2Controller.GetProfile(sessionId) {
} Response = _launcherV2Controller.GetProfile(sessionId),
)); }
)
);
} }
} }
@@ -18,15 +18,23 @@ public class LocationCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetLocationData(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetLocationData(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_locationController.GenerateAll(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_locationController.GenerateAll(sessionID))
);
} }
/// <summary> /// <summary>
/// Handle client/airdrop/loot /// Handle client/airdrop/loot
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetAirdropLoot(string url, GetAirdropLootRequest info, string sessionID) public ValueTask<string> GetAirdropLoot(
string url,
GetAirdropLootRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_locationController.GetAirDropLoot(info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_locationController.GetAirDropLoot(info))
);
} }
} }
@@ -54,12 +54,9 @@ public class MatchCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GroupCurrent(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GroupCurrent(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody( return new ValueTask<string>(
new MatchGroupCurrentResponse _httpResponseUtil.GetBody(new MatchGroupCurrentResponse { Squad = [] })
{ );
Squad = []
}
));
} }
/// <summary> /// <summary>
@@ -84,7 +81,11 @@ public class MatchCallbacks(
/// Handle client/match/group/invite/send /// Handle client/match/group/invite/send
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SendGroupInvite(string url, MatchGroupInviteSendRequest info, string sessionID) public ValueTask<string> SendGroupInvite(
string url,
MatchGroupInviteSendRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody("2427943f23698ay9f2863735")); return new ValueTask<string>(_httpResponseUtil.GetBody("2427943f23698ay9f2863735"));
} }
@@ -95,12 +96,7 @@ public class MatchCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> AcceptGroupInvite(string url, RequestIdRequest info, string sessionID) public ValueTask<string> AcceptGroupInvite(string url, RequestIdRequest info, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody( return new ValueTask<string>(_httpResponseUtil.GetBody(new List<GroupCharacter> { new() }));
new List<GroupCharacter>
{
new()
}
));
} }
/// <summary> /// <summary>
@@ -134,7 +130,11 @@ public class MatchCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> TransferGroup(string url, MatchGroupTransferRequest info, string sessionID) public ValueTask<string> TransferGroup(
string url,
MatchGroupTransferRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(true)); return new ValueTask<string>(_httpResponseUtil.GetBody(true));
} }
@@ -161,7 +161,11 @@ public class MatchCallbacks(
/// Handle client/analytics/event-disconnect /// Handle client/analytics/event-disconnect
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> EventDisconnect(string url, PutMetricsRequestData info, string sessionID) public ValueTask<string> EventDisconnect(
string url,
PutMetricsRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -179,9 +183,15 @@ public class MatchCallbacks(
/// Handle match/group/start_game /// Handle match/group/start_game
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> JoinMatch(string url, MatchGroupStartGameRequest info, string sessionID) public ValueTask<string> JoinMatch(
string url,
MatchGroupStartGameRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_matchController.JoinMatch(info, sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_matchController.JoinMatch(info, sessionID))
);
} }
/// <summary> /// <summary>
@@ -190,7 +200,9 @@ public class MatchCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetMetrics(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetMetrics(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_databaseService.GetMatch().Metrics)); return new ValueTask<string>(
_httpResponseUtil.GetBody(_databaseService.GetMatch().Metrics)
);
} }
/// <summary> /// <summary>
@@ -198,9 +210,15 @@ public class MatchCallbacks(
/// Handle client/match/group/status /// Handle client/match/group/status
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetGroupStatus(string url, MatchGroupStatusRequest info, string sessionID) public ValueTask<string> GetGroupStatus(
string url,
MatchGroupStatusRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_matchController.GetGroupStatus(info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_matchController.GetGroupStatus(info))
);
} }
/// <summary> /// <summary>
@@ -226,7 +244,11 @@ public class MatchCallbacks(
/// Handle client/match/group/player/remove /// Handle client/match/group/player/remove
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> RemovePlayerFromGroup(string url, MatchGroupPlayerRemoveRequest info, string sessionID) public ValueTask<string> RemovePlayerFromGroup(
string url,
MatchGroupPlayerRemoveRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(true)); return new ValueTask<string>(_httpResponseUtil.GetBody(true));
} }
@@ -235,16 +257,26 @@ public class MatchCallbacks(
/// Handle client/match/local/start /// Handle client/match/local/start
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> StartLocalRaid(string url, StartLocalRaidRequestData info, string sessionID) public ValueTask<string> StartLocalRaid(
string url,
StartLocalRaidRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_matchController.StartLocalRaid(sessionID, info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_matchController.StartLocalRaid(sessionID, info))
);
} }
/// <summary> /// <summary>
/// Handle client/match/local/end /// Handle client/match/local/end
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> EndLocalRaid(string url, EndLocalRaidRequestData info, string sessionID) public ValueTask<string> EndLocalRaid(
string url,
EndLocalRaidRequestData info,
string sessionID
)
{ {
_matchController.EndLocalRaid(sessionID, info); _matchController.EndLocalRaid(sessionID, info);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -254,7 +286,11 @@ public class MatchCallbacks(
/// Handle client/raid/configuration /// Handle client/raid/configuration
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetRaidConfiguration(string url, GetRaidConfigurationRequestData info, string sessionID) public ValueTask<string> GetRaidConfiguration(
string url,
GetRaidConfigurationRequestData info,
string sessionID
)
{ {
_matchController.ConfigureOfflineRaid(info, sessionID); _matchController.ConfigureOfflineRaid(info, sessionID);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -267,7 +303,11 @@ public class MatchCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetConfigurationByProfile(string url, GetRaidConfigurationRequestData info, string sessionID) public ValueTask<string> GetConfigurationByProfile(
string url,
GetRaidConfigurationRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
@@ -16,7 +16,11 @@ public class NoteCallbacks(NoteController _noteController)
/// <param name="request">Add note request</param> /// <param name="request">Add note request</param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse AddNote(PmcData pmcData, NoteActionRequest request, string sessionID) public ItemEventRouterResponse AddNote(
PmcData pmcData,
NoteActionRequest request,
string sessionID
)
{ {
return _noteController.AddNote(pmcData, request, sessionID); return _noteController.AddNote(pmcData, request, sessionID);
} }
@@ -28,7 +32,11 @@ public class NoteCallbacks(NoteController _noteController)
/// <param name="request">Edit note request</param> /// <param name="request">Edit note request</param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse EditNote(PmcData pmcData, NoteActionRequest request, string sessionID) public ItemEventRouterResponse EditNote(
PmcData pmcData,
NoteActionRequest request,
string sessionID
)
{ {
return _noteController.EditNote(pmcData, request, sessionID); return _noteController.EditNote(pmcData, request, sessionID);
} }
@@ -40,7 +48,11 @@ public class NoteCallbacks(NoteController _noteController)
/// <param name="request">Delete note request</param> /// <param name="request">Delete note request</param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse DeleteNote(PmcData pmcData, NoteActionRequest request, string sessionID) public ItemEventRouterResponse DeleteNote(
PmcData pmcData,
NoteActionRequest request,
string sessionID
)
{ {
return _noteController.DeleteNote(pmcData, request, sessionID); return _noteController.DeleteNote(pmcData, request, sessionID);
} }
@@ -32,12 +32,14 @@ public class NotifierCallbacks(
* Take our array of JSON message objects and cast them to JSON strings, so that they can then * Take our array of JSON message objects and cast them to JSON strings, so that they can then
* be sent to client as NEWLINE separated strings... yup. * be sent to client as NEWLINE separated strings... yup.
*/ */
_notifierController.NotifyAsync(tmpSessionID) _notifierController
.ContinueWith(messages => messages.Result.Select(message => string.Join("\n", jsonUtil.Serialize(message)))) .NotifyAsync(tmpSessionID)
.ContinueWith(messages =>
messages.Result.Select(message => string.Join("\n", jsonUtil.Serialize(message)))
)
.ContinueWith(text => httpServerHelper.SendTextJson(resp, text.Result)); .ContinueWith(text => httpServerHelper.SendTextJson(resp, text.Result));
} }
/// <summary> /// <summary>
/// TODO: removed from client? /// TODO: removed from client?
/// Handle push/notifier/get /// Handle push/notifier/get
@@ -55,7 +57,9 @@ public class NotifierCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> CreateNotifierChannel(string url, EmptyRequestData _, string sessionID) public ValueTask<string> CreateNotifierChannel(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_notifierController.GetChannel(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_notifierController.GetChannel(sessionID))
);
} }
/// <summary> /// <summary>
@@ -64,12 +68,9 @@ public class NotifierCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SelectProfile(string url, UIDRequestData info, string sessionID) public ValueTask<string> SelectProfile(string url, UIDRequestData info, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody( return new ValueTask<string>(
new SelectProfileResponse _httpResponseUtil.GetBody(new SelectProfileResponse { Status = "ok" })
{ );
Status = "ok"
}
));
} }
/// <summary> /// <summary>
@@ -21,7 +21,9 @@ public class PrestigeCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetPrestige(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetPrestige(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_prestigeController.GetPrestige(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_prestigeController.GetPrestige(sessionID))
);
} }
/// <summary> /// <summary>
@@ -31,7 +33,11 @@ public class PrestigeCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public async ValueTask<string> ObtainPrestige(string url, ObtainPrestigeRequestList info, string sessionID) public async ValueTask<string> ObtainPrestige(
string url,
ObtainPrestigeRequestList info,
string sessionID
)
{ {
await _prestigeController.ObtainPrestige(sessionID, info); await _prestigeController.ObtainPrestige(sessionID, info);
@@ -22,15 +22,14 @@ public class ProfileCallbacks(
/// Handle client/game/profile/create /// Handle client/game/profile/create
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async ValueTask<string> CreateProfile(string url, ProfileCreateRequestData info, string sessionID) public async ValueTask<string> CreateProfile(
string url,
ProfileCreateRequestData info,
string sessionID
)
{ {
var id = await _profileController.CreateProfile(info, sessionID); var id = await _profileController.CreateProfile(info, sessionID);
return _httpResponse.GetBody( return _httpResponse.GetBody(new CreateProfileResponse { UserId = id });
new CreateProfileResponse
{
UserId = id
}
);
} }
/// <summary> /// <summary>
@@ -40,7 +39,9 @@ public class ProfileCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetProfileData(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetProfileData(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponse.GetBody(_profileController.GetCompleteProfile(sessionID))); return new ValueTask<string>(
_httpResponse.GetBody(_profileController.GetCompleteProfile(sessionID))
);
} }
/// <summary> /// <summary>
@@ -51,19 +52,22 @@ public class ProfileCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> RegenerateScav(string url, EmptyRequestData _, string sessionID) public ValueTask<string> RegenerateScav(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponse.GetBody( return new ValueTask<string>(
new List<PmcData> _httpResponse.GetBody(
{ new List<PmcData> { _profileController.GeneratePlayerScav(sessionID) }
_profileController.GeneratePlayerScav(sessionID) )
} );
));
} }
/// <summary> /// <summary>
/// Handle client/game/profile/voice/change event /// Handle client/game/profile/voice/change event
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> ChangeVoice(string url, ProfileChangeVoiceRequestData info, string sessionID) public ValueTask<string> ChangeVoice(
string url,
ProfileChangeVoiceRequestData info,
string sessionID
)
{ {
_profileController.ChangeVoice(info, sessionID); _profileController.ChangeVoice(info, sessionID);
return new ValueTask<string>(_httpResponse.NullResponse()); return new ValueTask<string>(_httpResponse.NullResponse());
@@ -74,21 +78,35 @@ public class ProfileCallbacks(
/// Client allows player to adjust their profile name /// Client allows player to adjust their profile name
/// </summary> /// </summary>
/// <returns>Client response as string</returns> /// <returns>Client response as string</returns>
public ValueTask<string> ChangeNickname(string url, ProfileChangeNicknameRequestData info, string sessionId) public ValueTask<string> ChangeNickname(
string url,
ProfileChangeNicknameRequestData info,
string sessionId
)
{ {
var output = _profileController.ChangeNickname(info, sessionId); var output = _profileController.ChangeNickname(info, sessionId);
return output switch return output switch
{ {
NicknameValidationResult.Taken => new ValueTask<string>(_httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotUnique, $"{BackendErrorCodes.NicknameNotUnique} - ")), NicknameValidationResult.Taken => new ValueTask<string>(
NicknameValidationResult.Short => new ValueTask<string>(_httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotValid, $"{BackendErrorCodes.NicknameNotValid} - ")), _httpResponse.GetBody<object?>(
_ => new ValueTask<string>(_httpResponse.GetBody<object>( null,
new BackendErrorCodes.NicknameNotUnique,
{ $"{BackendErrorCodes.NicknameNotUnique} - "
status = 0, )
NicknameChangeDate = _timeUtil.GetTimeStamp() ),
} NicknameValidationResult.Short => new ValueTask<string>(
)) _httpResponse.GetBody<object?>(
null,
BackendErrorCodes.NicknameNotValid,
$"{BackendErrorCodes.NicknameNotValid} - "
)
),
_ => new ValueTask<string>(
_httpResponse.GetBody<object>(
new { status = 0, NicknameChangeDate = _timeUtil.GetTimeStamp() }
)
),
}; };
} }
@@ -96,18 +114,29 @@ public class ProfileCallbacks(
/// Handle client/game/profile/nickname/validate /// Handle client/game/profile/nickname/validate
/// </summary> /// </summary>
/// <returns>Client response as string</returns> /// <returns>Client response as string</returns>
public ValueTask<string> ValidateNickname(string url, ValidateNicknameRequestData info, string sessionId) public ValueTask<string> ValidateNickname(
string url,
ValidateNicknameRequestData info,
string sessionId
)
{ {
return _profileController.ValidateNickname(info, sessionId) switch return _profileController.ValidateNickname(info, sessionId) switch
{ {
NicknameValidationResult.Taken => new ValueTask<string>(_httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotUnique, $"{BackendErrorCodes.NicknameNotUnique} - ")), NicknameValidationResult.Taken => new ValueTask<string>(
NicknameValidationResult.Short => new ValueTask<string>(_httpResponse.GetBody<object?>(null, BackendErrorCodes.NicknameNotValid, $"{BackendErrorCodes.NicknameNotValid} - ")), _httpResponse.GetBody<object?>(
_ => new ValueTask<string>(_httpResponse.GetBody( null,
new BackendErrorCodes.NicknameNotUnique,
{ $"{BackendErrorCodes.NicknameNotUnique} - "
status = "ok" )
} ),
)) NicknameValidationResult.Short => new ValueTask<string>(
_httpResponse.GetBody<object?>(
null,
BackendErrorCodes.NicknameNotValid,
$"{BackendErrorCodes.NicknameNotValid} - "
)
),
_ => new ValueTask<string>(_httpResponse.GetBody(new { status = "ok" })),
}; };
} }
@@ -134,7 +163,9 @@ public class ProfileCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetProfileStatus(string url, EmptyRequestData _, string sessionId) public ValueTask<string> GetProfileStatus(string url, EmptyRequestData _, string sessionId)
{ {
return new ValueTask<string>(_httpResponse.GetBody(_profileController.GetProfileStatus(sessionId))); return new ValueTask<string>(
_httpResponse.GetBody(_profileController.GetProfileStatus(sessionId))
);
} }
/// <summary> /// <summary>
@@ -142,36 +173,60 @@ public class ProfileCallbacks(
/// Called when viewing another players profile /// Called when viewing another players profile
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetOtherProfile(string url, GetOtherProfileRequest request, string sessionID) public ValueTask<string> GetOtherProfile(
string url,
GetOtherProfileRequest request,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponse.GetBody(_profileController.GetOtherProfile(sessionID, request))); return new ValueTask<string>(
_httpResponse.GetBody(_profileController.GetOtherProfile(sessionID, request))
);
} }
/// <summary> /// <summary>
/// Handle client/profile/settings /// Handle client/profile/settings
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetProfileSettings(string url, GetProfileSettingsRequest info, string sessionID) public ValueTask<string> GetProfileSettings(
string url,
GetProfileSettingsRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponse.GetBody(_profileController.SetChosenProfileIcon(sessionID, info))); return new ValueTask<string>(
_httpResponse.GetBody(_profileController.SetChosenProfileIcon(sessionID, info))
);
} }
/// <summary> /// <summary>
/// Handle client/game/profile/search /// Handle client/game/profile/search
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SearchProfiles(string url, SearchProfilesRequestData info, string sessionID) public ValueTask<string> SearchProfiles(
string url,
SearchProfilesRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponse.GetBody(_profileController.SearchProfiles(info, sessionID))); return new ValueTask<string>(
_httpResponse.GetBody(_profileController.SearchProfiles(info, sessionID))
);
} }
/// <summary> /// <summary>
/// Handle launcher/profile/info /// Handle launcher/profile/info
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetMiniProfile(string url, GetMiniProfileRequestData info, string sessionID) public ValueTask<string> GetMiniProfile(
string url,
GetMiniProfileRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponse.NoBody(_profileController.GetMiniProfile(sessionID))); return new ValueTask<string>(
_httpResponse.NoBody(_profileController.GetMiniProfile(sessionID))
);
} }
/// <summary> /// <summary>
@@ -21,7 +21,11 @@ public class QuestCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ChangeRepeatableQuest(PmcData pmcData, RepeatableQuestChangeRequest info, string sessionID) public ItemEventRouterResponse ChangeRepeatableQuest(
PmcData pmcData,
RepeatableQuestChangeRequest info,
string sessionID
)
{ {
return _repeatableQuestController.ChangeRepeatableQuest(pmcData, info, sessionID); return _repeatableQuestController.ChangeRepeatableQuest(pmcData, info, sessionID);
} }
@@ -33,7 +37,11 @@ public class QuestCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse AcceptQuest(PmcData pmcData, AcceptQuestRequestData info, string sessionID) public ItemEventRouterResponse AcceptQuest(
PmcData pmcData,
AcceptQuestRequestData info,
string sessionID
)
{ {
if (info.Type == "repeatable") if (info.Type == "repeatable")
{ {
@@ -50,7 +58,11 @@ public class QuestCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse CompleteQuest(PmcData pmcData, CompleteQuestRequestData info, string sessionID) public ItemEventRouterResponse CompleteQuest(
PmcData pmcData,
CompleteQuestRequestData info,
string sessionID
)
{ {
return _questController.CompleteQuest(pmcData, info, sessionID); return _questController.CompleteQuest(pmcData, info, sessionID);
} }
@@ -62,7 +74,11 @@ public class QuestCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse HandoverQuest(PmcData pmcData, HandoverQuestRequestData info, string sessionID) public ItemEventRouterResponse HandoverQuest(
PmcData pmcData,
HandoverQuestRequestData info,
string sessionID
)
{ {
return _questController.HandoverQuest(pmcData, info, sessionID); return _questController.HandoverQuest(pmcData, info, sessionID);
} }
@@ -76,7 +92,9 @@ public class QuestCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> ListQuests(string url, ListQuestsRequestData info, string sessionID) public ValueTask<string> ListQuests(string url, ListQuestsRequestData info, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_questController.GetClientQuests(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_questController.GetClientQuests(sessionID))
);
} }
/// <summary> /// <summary>
@@ -88,6 +106,10 @@ public class QuestCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> ActivityPeriods(string url, EmptyRequestData _, string sessionID) public ValueTask<string> ActivityPeriods(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_repeatableQuestController.GetClientRepeatableQuests(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(
_repeatableQuestController.GetClientRepeatableQuests(sessionID)
)
);
} }
} }
@@ -61,7 +61,9 @@ public class RagfairCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> Search(string url, SearchRequestData info, string sessionID) public ValueTask<string> Search(string url, SearchRequestData info, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_ragfairController.GetOffers(sessionID, info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_ragfairController.GetOffers(sessionID, info))
);
} }
/// <summary> /// <summary>
@@ -71,9 +73,15 @@ public class RagfairCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetMarketPrice(string url, GetMarketPriceRequestData info, string sessionID) public ValueTask<string> GetMarketPrice(
string url,
GetMarketPriceRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_ragfairController.GetItemMinAvgMaxFleaPriceValues(info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_ragfairController.GetItemMinAvgMaxFleaPriceValues(info))
);
} }
/// <summary> /// <summary>
@@ -83,7 +91,11 @@ public class RagfairCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse AddOffer(PmcData pmcData, AddOfferRequestData info, string sessionID) public ItemEventRouterResponse AddOffer(
PmcData pmcData,
AddOfferRequestData info,
string sessionID
)
{ {
return _ragfairController.AddPlayerOffer(pmcData, info, sessionID); return _ragfairController.AddPlayerOffer(pmcData, info, sessionID);
} }
@@ -95,7 +107,11 @@ public class RagfairCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse RemoveOffer(PmcData pmcData, RemoveOfferRequestData info, string sessionID) public ItemEventRouterResponse RemoveOffer(
PmcData pmcData,
RemoveOfferRequestData info,
string sessionID
)
{ {
return _ragfairController.FlagOfferForRemoval(info.OfferId, sessionID); return _ragfairController.FlagOfferForRemoval(info.OfferId, sessionID);
} }
@@ -107,7 +123,11 @@ public class RagfairCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ExtendOffer(PmcData pmcData, ExtendOfferRequestData info, string sessionID) public ItemEventRouterResponse ExtendOffer(
PmcData pmcData,
ExtendOfferRequestData info,
string sessionID
)
{ {
return _ragfairController.ExtendOffer(info, sessionID); return _ragfairController.ExtendOffer(info, sessionID);
} }
@@ -122,7 +142,9 @@ public class RagfairCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetFleaPrices(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetFleaPrices(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_ragfairController.GetAllFleaPrices())); return new ValueTask<string>(
_httpResponseUtil.GetBody(_ragfairController.GetAllFleaPrices())
);
} }
/// <summary> /// <summary>
@@ -132,12 +154,20 @@ public class RagfairCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> SendReport(string url, SendRagfairReportRequestData info, string sessionID) public ValueTask<string> SendReport(
string url,
SendRagfairReportRequestData info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
} }
public ValueTask<string> StorePlayerOfferTaxAmount(string url, StorePlayerOfferTaxAmountRequestData info, string sessionID) public ValueTask<string> StorePlayerOfferTaxAmount(
string url,
StorePlayerOfferTaxAmountRequestData info,
string sessionID
)
{ {
_ragfairTaxService.StoreClientOfferTaxValue(sessionID, info); _ragfairTaxService.StoreClientOfferTaxValue(sessionID, info);
return new ValueTask<string>(_httpResponseUtil.NullResponse()); return new ValueTask<string>(_httpResponseUtil.NullResponse());
@@ -150,8 +180,14 @@ public class RagfairCallbacks(
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetFleaOfferById(string url, GetRagfairOfferByIdRequest info, string sessionID) public ValueTask<string> GetFleaOfferById(
string url,
GetRagfairOfferByIdRequest info,
string sessionID
)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_ragfairController.GetOfferByInternalId(sessionID, info))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_ragfairController.GetOfferByInternalId(sessionID, info))
);
} }
} }
@@ -17,7 +17,11 @@ public class RepairCallbacks(RepairController _repairController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse TraderRepair(PmcData pmcData, TraderRepairActionDataRequest info, string sessionID) public ItemEventRouterResponse TraderRepair(
PmcData pmcData,
TraderRepairActionDataRequest info,
string sessionID
)
{ {
return _repairController.TraderRepair(sessionID, info, pmcData); return _repairController.TraderRepair(sessionID, info, pmcData);
} }
@@ -30,7 +34,11 @@ public class RepairCallbacks(RepairController _repairController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse Repair(PmcData pmcData, RepairActionDataRequest info, string sessionID) public ItemEventRouterResponse Repair(
PmcData pmcData,
RepairActionDataRequest info,
string sessionID
)
{ {
return _repairController.RepairWithKit(sessionID, info, pmcData); return _repairController.RepairWithKit(sessionID, info, pmcData);
} }
@@ -11,8 +11,7 @@ public class SaveCallbacks(
SaveServer _saveServer, SaveServer _saveServer,
ConfigServer _configServer, ConfigServer _configServer,
BackupService _backupService BackupService _backupService
) ) : IOnLoad, IOnUpdate
: IOnLoad, IOnUpdate
{ {
private readonly CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>(); private readonly CoreConfig _coreConfig = _configServer.GetConfig<CoreConfig>();
@@ -16,7 +16,11 @@ public class TradeCallbacks(TradeController _tradeController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ProcessTrade(PmcData pmcData, ProcessBaseTradeRequestData info, string sessionID) public ItemEventRouterResponse ProcessTrade(
PmcData pmcData,
ProcessBaseTradeRequestData info,
string sessionID
)
{ {
return _tradeController.ConfirmTrading(pmcData, info, sessionID); return _tradeController.ConfirmTrading(pmcData, info, sessionID);
} }
@@ -28,7 +32,11 @@ public class TradeCallbacks(TradeController _tradeController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ProcessRagfairTrade(PmcData pmcData, ProcessRagfairTradeRequestData info, string sessionID) public ItemEventRouterResponse ProcessRagfairTrade(
PmcData pmcData,
ProcessRagfairTradeRequestData info,
string sessionID
)
{ {
return _tradeController.ConfirmRagfairTrading(pmcData, info, sessionID); return _tradeController.ConfirmRagfairTrading(pmcData, info, sessionID);
} }
@@ -40,7 +48,11 @@ public class TradeCallbacks(TradeController _tradeController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SellAllFromSavage(PmcData pmcData, SellScavItemsToFenceRequestData info, string sessionID) public ItemEventRouterResponse SellAllFromSavage(
PmcData pmcData,
SellScavItemsToFenceRequestData info,
string sessionID
)
{ {
return _tradeController.SellScavItemsToFence(pmcData, info, sessionID); return _tradeController.SellScavItemsToFence(pmcData, info, sessionID);
} }
@@ -35,7 +35,9 @@ public class TraderCallbacks(
/// </summary> /// </summary>
public ValueTask<string> GetTraderSettings(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetTraderSettings(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_traderController.GetAllTraders(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_traderController.GetAllTraders(sessionID))
);
} }
/// <summary> /// <summary>
@@ -44,7 +46,9 @@ public class TraderCallbacks(
public ValueTask<string> GetTrader(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetTrader(string url, EmptyRequestData _, string sessionID)
{ {
var traderID = url.Replace("/client/trading/api/getTrader/", ""); var traderID = url.Replace("/client/trading/api/getTrader/", "");
return new ValueTask<string>(_httpResponseUtil.GetBody(_traderController.GetTrader(sessionID, traderID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_traderController.GetTrader(sessionID, traderID))
);
} }
/// <summary> /// <summary>
@@ -54,7 +58,9 @@ public class TraderCallbacks(
public ValueTask<string> GetAssort(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetAssort(string url, EmptyRequestData _, string sessionID)
{ {
var traderID = url.Replace("/client/trading/api/getTraderAssort/", ""); var traderID = url.Replace("/client/trading/api/getTraderAssort/", "");
return new ValueTask<string>(_httpResponseUtil.GetBody(_traderController.GetAssort(sessionID, traderID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_traderController.GetAssort(sessionID, traderID))
);
} }
/// <summary> /// <summary>
@@ -32,6 +32,8 @@ public class WeatherCallbacks(
/// <returns></returns> /// <returns></returns>
public ValueTask<string> GetLocalWeather(string url, EmptyRequestData _, string sessionID) public ValueTask<string> GetLocalWeather(string url, EmptyRequestData _, string sessionID)
{ {
return new ValueTask<string>(_httpResponseUtil.GetBody(_weatherController.GenerateLocal(sessionID))); return new ValueTask<string>(
_httpResponseUtil.GetBody(_weatherController.GenerateLocal(sessionID))
);
} }
} }
@@ -16,7 +16,11 @@ public class WishlistCallbacks(WishlistController _wishlistController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse AddToWishlist(PmcData pmcData, AddToWishlistRequest info, string sessionID) public ItemEventRouterResponse AddToWishlist(
PmcData pmcData,
AddToWishlistRequest info,
string sessionID
)
{ {
return _wishlistController.AddToWishList(pmcData, info, sessionID); return _wishlistController.AddToWishList(pmcData, info, sessionID);
} }
@@ -28,7 +32,11 @@ public class WishlistCallbacks(WishlistController _wishlistController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse RemoveFromWishlist(PmcData pmcData, RemoveFromWishlistRequest info, string sessionID) public ItemEventRouterResponse RemoveFromWishlist(
PmcData pmcData,
RemoveFromWishlistRequest info,
string sessionID
)
{ {
return _wishlistController.RemoveFromWishList(pmcData, info, sessionID); return _wishlistController.RemoveFromWishList(pmcData, info, sessionID);
} }
@@ -40,7 +48,11 @@ public class WishlistCallbacks(WishlistController _wishlistController)
/// <param name="info"></param> /// <param name="info"></param>
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ChangeWishlistItemCategory(PmcData pmcData, ChangeWishlistItemCategoryRequest info, string sessionID) public ItemEventRouterResponse ChangeWishlistItemCategory(
PmcData pmcData,
ChangeWishlistItemCategoryRequest info,
string sessionID
)
{ {
return _wishlistController.ChangeWishListItemCategory(pmcData, info, sessionID); return _wishlistController.ChangeWishListItemCategory(pmcData, info, sessionID);
} }
@@ -23,10 +23,7 @@ public class AchievementController(
/// <returns></returns> /// <returns></returns>
public virtual GetAchievementsResponse GetAchievements(string sessionID) public virtual GetAchievementsResponse GetAchievements(string sessionID)
{ {
return new GetAchievementsResponse return new GetAchievementsResponse { Elements = databaseService.GetAchievements() };
{
Elements = databaseService.GetAchievements()
};
} }
/// <summary> /// <summary>
@@ -37,12 +34,21 @@ public class AchievementController(
public virtual CompletedAchievementsResponse GetAchievementStatics(string sessionId) public virtual CompletedAchievementsResponse GetAchievementStatics(string sessionId)
{ {
var stats = new Dictionary<string, int>(); var stats = new Dictionary<string, int>();
var profiles = profileHelper.GetProfiles() var profiles = profileHelper
.Where(kvp => !coreConfig.Features.AchievementProfileIdBlacklist.Contains(kvp.Value.ProfileInfo.ProfileId)) .GetProfiles()
.Where(kvp =>
!coreConfig.Features.AchievementProfileIdBlacklist.Contains(
kvp.Value.ProfileInfo.ProfileId
)
)
.ToDictionary(); .ToDictionary();
var achievements = databaseService.GetAchievements(); var achievements = databaseService.GetAchievements();
foreach (var achievementId in achievements.Select(achievement => achievement.Id).Where(achievementId => !string.IsNullOrEmpty(achievementId))) foreach (
var achievementId in achievements
.Select(achievement => achievement.Id)
.Where(achievementId => !string.IsNullOrEmpty(achievementId))
)
{ {
var profilesHaveAchievement = 0; var profilesHaveAchievement = 0;
foreach (var (profileId, profile) in profiles) foreach (var (profileId, profile) in profiles)
@@ -63,15 +69,13 @@ public class AchievementController(
var percentage = 0; var percentage = 0;
if (profiles.Count > 0) if (profiles.Count > 0)
{ {
percentage = (int)Math.Round((double)profilesHaveAchievement / profiles.Count * 100); percentage = (int)
Math.Round((double)profilesHaveAchievement / profiles.Count * 100);
} }
stats.Add(achievementId, percentage); stats.Add(achievementId, percentage);
} }
return new CompletedAchievementsResponse return new CompletedAchievementsResponse { Elements = stats };
{
Elements = stats
};
} }
} }
@@ -1,7 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using SPTarkov.Server.Core.Constants;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Constants;
using SPTarkov.Server.Core.Generators; using SPTarkov.Server.Core.Generators;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Common;
@@ -50,7 +50,9 @@ public class BotController(
{ {
if (!_botConfig.PresetBatch.TryGetValue(type, out var limit)) if (!_botConfig.PresetBatch.TryGetValue(type, out var limit))
{ {
_logger.Warning(_localisationService.GetText("bot-bot_preset_count_value_missing", type)); _logger.Warning(
_localisationService.GetText("bot-bot_preset_count_value_missing", type)
);
return 10; return 10;
} }
@@ -78,23 +80,38 @@ public class BotController(
/// <param name="raidConfig">OPTIONAL - applicationContext Data stored at start of raid</param> /// <param name="raidConfig">OPTIONAL - applicationContext Data stored at start of raid</param>
/// <param name="ignoreRaidSettings">OPTIONAL - should raid settings chosen pre-raid be ignored</param> /// <param name="ignoreRaidSettings">OPTIONAL - should raid settings chosen pre-raid be ignored</param>
/// <returns>Difficulty object</returns> /// <returns>Difficulty object</returns>
public DifficultyCategories GetBotDifficulty(string sessionId, string type, string diffLevel, bool ignoreRaidSettings = false) public DifficultyCategories GetBotDifficulty(
string sessionId,
string type,
string diffLevel,
bool ignoreRaidSettings = false
)
{ {
var difficulty = diffLevel.ToLower(); var difficulty = diffLevel.ToLower();
var raidConfig = _profileActivityService.GetProfileActivityRaidData(sessionId)?.RaidConfiguration; var raidConfig = _profileActivityService
.GetProfileActivityRaidData(sessionId)
?.RaidConfiguration;
if (!(raidConfig != null || ignoreRaidSettings)) if (!(raidConfig != null || ignoreRaidSettings))
{ {
_logger.Error(_localisationService.GetText("bot-missing_application_context", "RAID_CONFIGURATION")); _logger.Error(
_localisationService.GetText(
"bot-missing_application_context",
"RAID_CONFIGURATION"
)
);
} }
// Check value chosen in pre-raid difficulty dropdown // Check value chosen in pre-raid difficulty dropdown
// If value is not 'asonline', change requested difficulty to be what was chosen in dropdown // If value is not 'asonline', change requested difficulty to be what was chosen in dropdown
var botDifficultyDropDownValue = raidConfig?.WavesSettings?.BotDifficulty?.ToString().ToLower() ?? "asonline"; var botDifficultyDropDownValue =
raidConfig?.WavesSettings?.BotDifficulty?.ToString().ToLower() ?? "asonline";
if (botDifficultyDropDownValue != "asonline") if (botDifficultyDropDownValue != "asonline")
{ {
difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(botDifficultyDropDownValue); difficulty = _botDifficultyHelper.ConvertBotDifficultyDropdownToBotDifficulty(
botDifficultyDropDownValue
);
} }
var botDb = _databaseService.GetBots(); var botDb = _databaseService.GetBots();
@@ -130,7 +147,9 @@ public class BotController(
result[botTypeLower] = result[Roles.Assault]; result[botTypeLower] = result[Roles.Assault];
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Unable to find bot: {botTypeLower} in db, copying '{Roles.Assault}'"); _logger.Debug(
$"Unable to find bot: {botTypeLower} in db, copying '{Roles.Assault}'"
);
} }
continue; continue;
@@ -139,7 +158,9 @@ public class BotController(
if (botDetails?.BotDifficulty is null) if (botDetails?.BotDifficulty is null)
{ {
// Bot has no difficulty values, skip // Bot has no difficulty values, skip
_logger.Warning($"Unable to find bot: {botTypeLower} difficulty values in db, skipping"); _logger.Warning(
$"Unable to find bot: {botTypeLower} difficulty values in db, skipping"
);
continue; continue;
} }
@@ -153,7 +174,11 @@ public class BotController(
} }
// Store all difficulty values in dict keyed by difficulty type e.g. easy/normal/impossible // Store all difficulty values in dict keyed by difficulty type e.g. easy/normal/impossible
result[botNameKey].Add(difficultyName, GetBotDifficulty(string.Empty, botNameKey, difficultyName, true)); result[botNameKey]
.Add(
difficultyName,
GetBotDifficulty(string.Empty, botNameKey, difficultyName, true)
);
} }
} }
@@ -180,7 +205,11 @@ public class BotController(
/// <param name="pmcProfile">Player generating bots</param> /// <param name="pmcProfile">Player generating bots</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>List of generated bots</returns> /// <returns>List of generated bots</returns>
protected List<BotBase> GenerateBotWaves(GenerateBotsRequestData request, PmcData? pmcProfile, string sessionId) protected List<BotBase> GenerateBotWaves(
GenerateBotsRequestData request,
PmcData? pmcProfile,
string sessionId
)
{ {
var generatedBotList = new List<BotBase>(); var generatedBotList = new List<BotBase>();
var raidSettings = GetMostRecentRaidSettings(sessionId); var raidSettings = GetMostRecentRaidSettings(sessionId);
@@ -191,23 +220,36 @@ public class BotController(
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
// Map conditions to promises for bot generation // Map conditions to promises for bot generation
Task.WaitAll((request.Conditions ?? []) Task.WaitAll(
.Select(condition => Task.Factory.StartNew(() => (request.Conditions ?? [])
{ .Select(condition =>
var botWaveGenerationDetails = GetBotGenerationDetailsForWave( Task.Factory.StartNew(() =>
condition, {
pmcProfile, var botWaveGenerationDetails = GetBotGenerationDetailsForWave(
allPmcsHaveSameNameAsPlayer, condition,
raidSettings); pmcProfile,
allPmcsHaveSameNameAsPlayer,
raidSettings
);
GenerateBotWave(condition, botWaveGenerationDetails, generatedBotList, sessionId); GenerateBotWave(
})).ToArray()); condition,
botWaveGenerationDetails,
generatedBotList,
sessionId
);
})
)
.ToArray()
);
stopwatch.Stop(); stopwatch.Stop();
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache()"); _logger.Debug(
$"Took {stopwatch.ElapsedMilliseconds}ms to GenerateMultipleBotsAndCache()"
);
} }
return generatedBotList; return generatedBotList;
@@ -225,9 +267,13 @@ public class BotController(
GenerateCondition generateRequest, GenerateCondition generateRequest,
BotGenerationDetails botGenerationDetails, BotGenerationDetails botGenerationDetails,
List<BotBase> botList, List<BotBase> botList,
string sessionId) string sessionId
)
{ {
var isEventBot = generateRequest.Role?.Contains("event", StringComparison.OrdinalIgnoreCase); var isEventBot = generateRequest.Role?.Contains(
"event",
StringComparison.OrdinalIgnoreCase
);
if (isEventBot.GetValueOrDefault(false)) if (isEventBot.GetValueOrDefault(false))
{ {
// Add eventRole data + reassign role property to be base type // Add eventRole data + reassign role property to be base type
@@ -241,45 +287,55 @@ public class BotController(
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Generating wave of: {botGenerationDetails.BotCountToGenerate} bots of type: {role} {botGenerationDetails.BotDifficulty}"); _logger.Debug(
$"Generating wave of: {botGenerationDetails.BotCountToGenerate} bots of type: {role} {botGenerationDetails.BotDifficulty}"
);
} }
Parallel.For(0, botGenerationDetails.BotCountToGenerate.Value, (i) => Parallel.For(
{ 0,
BotBase bot = null; botGenerationDetails.BotCountToGenerate.Value,
(i) =>
try
{ {
bot = _botGenerator.PrepareAndGenerateBot(sessionId, _cloner.Clone(botGenerationDetails)); BotBase bot = null;
}
catch (Exception e)
{
_logger.Error(
$"Failed to generate bot: {botGenerationDetails.Role} #{i + 1}: {e.Message} {e.StackTrace}");
return;
}
// The client expects the Side for PMCs to be `Savage` try
// We do this here so it's after we cache the bot in the match details lookup, as when you die, they will have the right side {
if (bot.Info.Side is Sides.Bear or Sides.Usec) bot = _botGenerator.PrepareAndGenerateBot(
{ sessionId,
bot.Info.Side = Sides.Savage; _cloner.Clone(botGenerationDetails)
} );
}
catch (Exception e)
{
_logger.Error(
$"Failed to generate bot: {botGenerationDetails.Role} #{i + 1}: {e.Message} {e.StackTrace}"
);
return;
}
lock (_botListLock) // The client expects the Side for PMCs to be `Savage`
{ // We do this here so it's after we cache the bot in the match details lookup, as when you die, they will have the right side
botList.Add(bot); if (bot.Info.Side is Sides.Bear or Sides.Usec)
} {
bot.Info.Side = Sides.Savage;
}
// Store bot details in cache so post-raid PMC messages can use data lock (_botListLock)
_matchBotDetailsCacheService.CacheBot(bot); {
}); botList.Add(bot);
}
// Store bot details in cache so post-raid PMC messages can use data
_matchBotDetailsCacheService.CacheBot(bot);
}
);
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug( _logger.Debug(
$"Generated: {botGenerationDetails.BotCountToGenerate} {botGenerationDetails.Role}" + $"Generated: {botGenerationDetails.BotCountToGenerate} {botGenerationDetails.Role}"
$"({botGenerationDetails.EventRole ?? botGenerationDetails.Role ?? ""}) {botGenerationDetails.BotDifficulty} bots" + $"({botGenerationDetails.EventRole ?? botGenerationDetails.Role ?? ""}) {botGenerationDetails.BotDifficulty} bots"
); );
} }
} }
@@ -290,11 +346,15 @@ public class BotController(
/// <returns>GetRaidConfigurationRequestData if it exists</returns> /// <returns>GetRaidConfigurationRequestData if it exists</returns>
protected GetRaidConfigurationRequestData? GetMostRecentRaidSettings(string sessionId) protected GetRaidConfigurationRequestData? GetMostRecentRaidSettings(string sessionId)
{ {
var raidConfiguration = _profileActivityService.GetProfileActivityRaidData(sessionId)?.RaidConfiguration; var raidConfiguration = _profileActivityService
.GetProfileActivityRaidData(sessionId)
?.RaidConfiguration;
if (raidConfiguration is null) if (raidConfiguration is null)
{ {
_logger.Warning(_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext")); _logger.Warning(
_localisationService.GetText("bot-unable_to_load_raid_settings_from_appcontext")
);
} }
return raidConfiguration; return raidConfiguration;
@@ -307,7 +367,10 @@ public class BotController(
/// <returns>MinMax values</returns> /// <returns>MinMax values</returns>
protected MinMax<int> GetPmcLevelRangeForMap(string? location) protected MinMax<int> GetPmcLevelRangeForMap(string? location)
{ {
return _pmcConfig.LocationSpecificPmcLevelOverride!.GetValueOrDefault(location?.ToLower() ?? "", null); return _pmcConfig.LocationSpecificPmcLevelOverride!.GetValueOrDefault(
location?.ToLower() ?? "",
null
);
} }
/// <summary> /// <summary>
@@ -322,24 +385,30 @@ public class BotController(
GenerateCondition condition, GenerateCondition condition,
PmcData? pmcProfile, PmcData? pmcProfile,
bool allPmcsHaveSameNameAsPlayer, bool allPmcsHaveSameNameAsPlayer,
GetRaidConfigurationRequestData? raidSettings) GetRaidConfigurationRequestData? raidSettings
)
{ {
var generateAsPmc = _botHelper.IsBotPmc(condition.Role); var generateAsPmc = _botHelper.IsBotPmc(condition.Role);
return new BotGenerationDetails return new BotGenerationDetails
{ {
IsPmc = generateAsPmc, IsPmc = generateAsPmc,
Side = generateAsPmc ? _botHelper.GetPmcSideByRole(condition.Role ?? string.Empty) : "Savage", Side = generateAsPmc
? _botHelper.GetPmcSideByRole(condition.Role ?? string.Empty)
: "Savage",
Role = condition.Role, Role = condition.Role,
PlayerLevel = pmcProfile?.Info?.Level ?? 1, PlayerLevel = pmcProfile?.Info?.Level ?? 1,
PlayerName = pmcProfile?.Info?.Nickname, PlayerName = pmcProfile?.Info?.Nickname,
BotRelativeLevelDeltaMax = _pmcConfig.BotRelativeLevelDeltaMax, BotRelativeLevelDeltaMax = _pmcConfig.BotRelativeLevelDeltaMax,
BotRelativeLevelDeltaMin = _pmcConfig.BotRelativeLevelDeltaMin, BotRelativeLevelDeltaMin = _pmcConfig.BotRelativeLevelDeltaMin,
BotCountToGenerate = Math.Max(GetBotPresetGenerationLimit(condition.Role), condition.Limit), // Choose largest between value passed in from request vs what's in bot.config BotCountToGenerate = Math.Max(
GetBotPresetGenerationLimit(condition.Role),
condition.Limit
), // Choose largest between value passed in from request vs what's in bot.config
BotDifficulty = condition.Difficulty, BotDifficulty = condition.Difficulty,
LocationSpecificPmcLevelOverride = GetPmcLevelRangeForMap(raidSettings?.Location), // Min/max levels for PMCs to generate within LocationSpecificPmcLevelOverride = GetPmcLevelRangeForMap(raidSettings?.Location), // Min/max levels for PMCs to generate within
IsPlayerScav = false, IsPlayerScav = false,
AllPmcsHaveSameNameAsPlayer = allPmcsHaveSameNameAsPlayer AllPmcsHaveSameNameAsPlayer = allPmcsHaveSameNameAsPlayer,
}; };
} }
@@ -359,7 +428,10 @@ public class BotController(
if (location == "default") if (location == "default")
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText("bot-no_bot_cap_found_for_location", location.ToLower()) _localisationService.GetText(
"bot-no_bot_cap_found_for_location",
location.ToLower()
)
); );
} }
@@ -376,7 +448,7 @@ public class BotController(
{ {
PmcType = _pmcConfig.PmcType, PmcType = _pmcConfig.PmcType,
Assault = _botConfig.AssaultBrainType, Assault = _botConfig.AssaultBrainType,
PlayerScav = _botConfig.PlayerScavBrainType PlayerScav = _botConfig.PlayerScavBrainType,
}; };
} }
} }
@@ -384,23 +456,11 @@ public class BotController(
public record AiBotBrainTypes public record AiBotBrainTypes
{ {
[JsonPropertyName("pmc")] [JsonPropertyName("pmc")]
public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType public Dictionary<string, Dictionary<string, Dictionary<string, double>>> PmcType { get; set; }
{
get;
set;
}
[JsonPropertyName("assault")] [JsonPropertyName("assault")]
public Dictionary<string, Dictionary<string, int>> Assault public Dictionary<string, Dictionary<string, int>> Assault { get; set; }
{
get;
set;
}
[JsonPropertyName("playerScav")] [JsonPropertyName("playerScav")]
public Dictionary<string, Dictionary<string, int>> PlayerScav public Dictionary<string, Dictionary<string, int>> PlayerScav { get; set; }
{
get;
set;
}
} }
@@ -42,30 +42,37 @@ public class BuildController(
{ {
EquipmentBuilds = [], EquipmentBuilds = [],
WeaponBuilds = [], WeaponBuilds = [],
MagazineBuilds = [] MagazineBuilds = [],
}; };
} }
// Ensure the secure container in the default presets match what the player has equipped // Ensure the secure container in the default presets match what the player has equipped
var defaultEquipmentPresetsClone = _cloner.Clone(_databaseService.GetTemplates().DefaultEquipmentPresets) var defaultEquipmentPresetsClone = _cloner
.Clone(_databaseService.GetTemplates().DefaultEquipmentPresets)
.ToList(); .ToList();
// Get players secure container // Get players secure container
var playerSecureContainer = profile?.CharacterData?.PmcData?.Inventory?.Items?.FirstOrDefault(x => x.SlotId == secureContainerSlotId var playerSecureContainer =
); profile?.CharacterData?.PmcData?.Inventory?.Items?.FirstOrDefault(x =>
x.SlotId == secureContainerSlotId
);
var firstDefaultItemsSecureContainer = defaultEquipmentPresetsClone? var firstDefaultItemsSecureContainer = defaultEquipmentPresetsClone
.FirstOrDefault() ?.FirstOrDefault()
?.Items? ?.Items?.FirstOrDefault(x => x.SlotId == secureContainerSlotId);
.FirstOrDefault(x => x.SlotId == secureContainerSlotId);
if (playerSecureContainer is not null && playerSecureContainer.Template != firstDefaultItemsSecureContainer?.Template) if (
// Default equipment presets' secure container tpl doesn't match players secure container tpl 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) foreach (var defaultPreset in defaultEquipmentPresetsClone)
{ {
// Find presets secure container // Find presets secure container
var secureContainer = defaultPreset.Items?.FirstOrDefault(item => item.SlotId == secureContainerSlotId); var secureContainer = defaultPreset.Items?.FirstOrDefault(item =>
item.SlotId == secureContainerSlotId
);
if (secureContainer is not null) if (secureContainer is not null)
{ {
secureContainer.Template = playerSecureContainer.Template; secureContainer.Template = playerSecureContainer.Template;
@@ -102,7 +109,7 @@ public class BuildController(
Id = body.Id, Id = body.Id,
Name = body.Name, Name = body.Name,
Root = body.Root, Root = body.Root,
Items = body.Items Items = body.Items,
}; };
var profile = _profileHelper.GetFullProfile(sessionId); var profile = _profileHelper.GetFullProfile(sessionId);
@@ -132,8 +139,9 @@ public class BuildController(
var profile = _profileHelper.GetFullProfile(sessionID); var profile = _profileHelper.GetFullProfile(sessionID);
var pmcData = profile.CharacterData.PmcData; var pmcData = profile.CharacterData.PmcData;
var existingSavedEquipmentBuilds = var existingSavedEquipmentBuilds = _saveServer
_saveServer.GetProfile(sessionID).UserBuildData.EquipmentBuilds; .GetProfile(sessionID)
.UserBuildData.EquipmentBuilds;
// Replace duplicate ID's. The first item is the base item. // Replace duplicate ID's. The first item is the base item.
// Root ID and the base item ID need to match. // Root ID and the base item ID need to match.
@@ -145,10 +153,11 @@ public class BuildController(
Name = request.Name, Name = request.Name,
BuildType = EquipmentBuildType.Custom, BuildType = EquipmentBuildType.Custom,
Root = request.Items[0].Id, Root = request.Items[0].Id,
Items = request.Items Items = request.Items,
}; };
var existingBuild = existingSavedEquipmentBuilds?.FirstOrDefault(build => build.Name == request.Name || build.Id == request.Id var existingBuild = existingSavedEquipmentBuilds?.FirstOrDefault(build =>
build.Name == request.Name || build.Id == request.Id
); );
if (existingBuild is not null) if (existingBuild is not null)
{ {
@@ -190,7 +199,7 @@ public class BuildController(
Caliber = request.Caliber, Caliber = request.Caliber,
TopCount = request.TopCount, TopCount = request.TopCount,
BottomCount = request.BottomCount, BottomCount = request.BottomCount,
Items = request.Items Items = request.Items,
}; };
var profile = _profileHelper.GetFullProfile(sessionId); var profile = _profileHelper.GetFullProfile(sessionId);
@@ -198,7 +207,9 @@ public class BuildController(
profile.UserBuildData.MagazineBuilds ??= []; profile.UserBuildData.MagazineBuilds ??= [];
// Check if template with desired name already exists and remove it // Check if template with desired name already exists and remove it
var magazineBuildToRemove = profile.UserBuildData.MagazineBuilds.FirstOrDefault(item => item.Name == request.Name); var magazineBuildToRemove = profile.UserBuildData.MagazineBuilds.FirstOrDefault(item =>
item.Name == request.Name
);
if (magazineBuildToRemove is not null) if (magazineBuildToRemove is not null)
{ {
profile.UserBuildData.MagazineBuilds.Remove(magazineBuildToRemove); profile.UserBuildData.MagazineBuilds.Remove(magazineBuildToRemove);
@@ -222,7 +233,9 @@ public class BuildController(
var magazineBuilds = profile.UserBuildData.MagazineBuilds; var magazineBuilds = profile.UserBuildData.MagazineBuilds;
// Check for id in weapon array first // Check for id in weapon array first
var matchingWeaponBuild = weaponBuilds.FirstOrDefault(weaponBuild => weaponBuild.Id == idToRemove); var matchingWeaponBuild = weaponBuilds.FirstOrDefault(weaponBuild =>
weaponBuild.Id == idToRemove
);
if (matchingWeaponBuild is not null) if (matchingWeaponBuild is not null)
{ {
weaponBuilds.Remove(matchingWeaponBuild); weaponBuilds.Remove(matchingWeaponBuild);
@@ -231,7 +244,9 @@ public class BuildController(
} }
// Id not found in weapons, try equipment // Id not found in weapons, try equipment
var matchingEquipmentBuild = equipmentBuilds.FirstOrDefault(equipmentBuild => equipmentBuild.Id == idToRemove); var matchingEquipmentBuild = equipmentBuilds.FirstOrDefault(equipmentBuild =>
equipmentBuild.Id == idToRemove
);
if (matchingEquipmentBuild is not null) if (matchingEquipmentBuild is not null)
{ {
equipmentBuilds.Remove(matchingEquipmentBuild); equipmentBuilds.Remove(matchingEquipmentBuild);
@@ -240,7 +255,9 @@ public class BuildController(
} }
// Id not found in weapons/equipment, try mags // Id not found in weapons/equipment, try mags
var matchingMagazineBuild = magazineBuilds.FirstOrDefault(magBuild => magBuild.Id == idToRemove); var matchingMagazineBuild = magazineBuilds.FirstOrDefault(magBuild =>
magBuild.Id == idToRemove
);
if (matchingMagazineBuild is not null) if (matchingMagazineBuild is not null)
{ {
magazineBuilds.Remove(matchingMagazineBuild); magazineBuilds.Remove(matchingMagazineBuild);
@@ -7,9 +7,7 @@ using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
public class ClientLogController( public class ClientLogController(ISptLogger<ClientLogController> _logger)
ISptLogger<ClientLogController> _logger
)
{ {
/// <summary> /// <summary>
/// Handle /singleplayer/log /// Handle /singleplayer/log
@@ -44,16 +44,18 @@ public class CustomizationController(
var suits = _databaseService.GetTrader(traderId).Suits; var suits = _databaseService.GetTrader(traderId).Suits;
var matchingSuits = suits?.Where(s => clothing.ContainsKey(s.SuiteId!)).ToList(); var matchingSuits = suits?.Where(s => clothing.ContainsKey(s.SuiteId!)).ToList();
matchingSuits = matchingSuits?.Where(s => clothing[s.SuiteId ?? string.Empty] matchingSuits = matchingSuits
?.Properties?.Side? ?.Where(s =>
.Contains(pmcData?.Info?.Side ?? string.Empty) ?? clothing[s.SuiteId ?? string.Empty]
false ?.Properties?.Side?.Contains(pmcData?.Info?.Side ?? string.Empty) ?? false
) )
.ToList(); .ToList();
if (matchingSuits == null) if (matchingSuits == null)
{ {
throw new Exception(_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId)); throw new Exception(
_localisationService.GetText("customisation-unable_to_get_trader_suits", traderId)
);
} }
return matchingSuits; return matchingSuits;
@@ -70,7 +72,8 @@ public class CustomizationController(
public ItemEventRouterResponse BuyCustomisation( public ItemEventRouterResponse BuyCustomisation(
PmcData pmcData, PmcData pmcData,
BuyClothingRequestData buyClothingRequest, BuyClothingRequestData buyClothingRequest,
string sessionId) string sessionId
)
{ {
var output = _eventOutputHolder.GetOutput(sessionId); var output = _eventOutputHolder.GetOutput(sessionId);
@@ -78,7 +81,10 @@ public class CustomizationController(
if (traderOffer is null) if (traderOffer is null)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText("customisation-unable_to_find_suit_by_id", buyClothingRequest.Offer) _localisationService.GetText(
"customisation-unable_to_find_suit_by_id",
buyClothingRequest.Offer
)
); );
return output; return output;
} }
@@ -90,11 +96,7 @@ public class CustomizationController(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"customisation-item_already_purchased", "customisation-item_already_purchased",
new new { itemId = suitDetails?.Id, itemName = suitDetails?.Name }
{
itemId = suitDetails?.Id,
itemName = suitDetails?.Name
}
) )
); );
@@ -111,7 +113,7 @@ public class CustomizationController(
{ {
Id = suitId, Id = suitId,
Source = CustomisationSource.UNLOCKED_IN_GAME, Source = CustomisationSource.UNLOCKED_IN_GAME,
Type = CustomisationType.SUITE Type = CustomisationType.SUITE,
}; };
profile.CustomisationUnlocks.Add(rewardToStore); profile.CustomisationUnlocks.Add(rewardToStore);
@@ -130,7 +132,9 @@ public class CustomizationController(
var fullProfile = _profileHelper.GetFullProfile(sessionId); var fullProfile = _profileHelper.GetFullProfile(sessionId);
// Check if clothing can be found by id // Check if clothing can be found by id
return fullProfile.CustomisationUnlocks.Exists(customisation => Equals(customisation.Id, suitId)); return fullProfile.CustomisationUnlocks.Exists(customisation =>
Equals(customisation.Id, suitId)
);
} }
/// <summary> /// <summary>
@@ -144,7 +148,9 @@ public class CustomizationController(
var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId); var foundSuit = GetAllTraderSuits(sessionId).FirstOrDefault(s => s.Id == offerId);
if (foundSuit is null) if (foundSuit is null)
{ {
_logger.Error(_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId)); _logger.Error(
_localisationService.GetText("customisation-unable_to_find_suit_with_id", offerId)
);
} }
return foundSuit; return foundSuit;
@@ -157,9 +163,12 @@ public class CustomizationController(
/// <param name="pmcData">Player profile</param> /// <param name="pmcData">Player profile</param>
/// <param name="itemsToPayForClothingWith">Clothing purchased</param> /// <param name="itemsToPayForClothingWith">Clothing purchased</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
protected void PayForClothingItems(string sessionId, PmcData pmcData, protected void PayForClothingItems(
string sessionId,
PmcData pmcData,
List<PaymentItemForClothing>? itemsToPayForClothingWith, List<PaymentItemForClothing>? itemsToPayForClothingWith,
ItemEventRouterResponse output) ItemEventRouterResponse output
)
{ {
if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0) if (itemsToPayForClothingWith is null || itemsToPayForClothingWith.Count == 0)
{ {
@@ -175,15 +184,15 @@ public class CustomizationController(
new IdWithCount new IdWithCount
{ {
Count = inventoryItemToProcess.Count.Value, Count = inventoryItemToProcess.Count.Value,
Id = inventoryItemToProcess.Id Id = inventoryItemToProcess.Id,
} },
], ],
TransactionId = Traders.RAGMAN, TransactionId = Traders.RAGMAN,
Action = "BuyCustomization", Action = "BuyCustomization",
Type = "", Type = "",
ItemId = "", ItemId = "",
Count = 0, Count = 0,
SchemeId = 0 SchemeId = 0,
}; };
_paymentService.PayMoney(pmcData, options, sessionId, output); _paymentService.PayMoney(pmcData, options, sessionId, output);
@@ -202,7 +211,10 @@ public class CustomizationController(
foreach (var trader in traders) foreach (var trader in traders)
{ {
if (trader.Value.Base?.CustomizationSeller is not null && trader.Value.Base.CustomizationSeller.Value) if (
trader.Value.Base?.CustomizationSeller is not null
&& trader.Value.Base.CustomizationSeller.Value
)
{ {
result.AddRange(GetTraderSuits(trader.Key, sessionId)); result.AddRange(GetTraderSuits(trader.Key, sessionId));
} }
@@ -226,10 +238,11 @@ public class CustomizationController(
/// </summary> /// </summary>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns></returns> /// <returns></returns>
public List<CustomisationStorage> GetCustomisationStorage( public List<CustomisationStorage> GetCustomisationStorage(string sessionId)
string sessionId)
{ {
var customisationResultsClone = _cloner.Clone(_databaseService.GetTemplates().CustomisationStorage); var customisationResultsClone = _cloner.Clone(
_databaseService.GetTemplates().CustomisationStorage
);
var profile = _profileHelper.GetFullProfile(sessionId); var profile = _profileHelper.GetFullProfile(sessionId);
if (profile is null) if (profile is null)
@@ -249,7 +262,11 @@ public class CustomizationController(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse SetCustomisation(string sessionId, CustomizationSetRequest request, PmcData pmcData) public ItemEventRouterResponse SetCustomisation(
string sessionId,
CustomizationSetRequest request,
PmcData pmcData
)
{ {
foreach (var customisation in request.Customizations) foreach (var customisation in request.Customizations)
{ {
@@ -37,13 +37,17 @@ public class DialogueController(
{ {
if (_dialogueChatBots.Any(cb => cb.GetChatBot().Id == chatBot.GetChatBot().Id)) if (_dialogueChatBots.Any(cb => cb.GetChatBot().Id == chatBot.GetChatBot().Id))
{ {
_logger.Error(_localisationService.GetText("dialog-chatbot_id_already_exists", chatBot.GetChatBot().Id)); _logger.Error(
_localisationService.GetText(
"dialog-chatbot_id_already_exists",
chatBot.GetChatBot().Id
)
);
} }
_dialogueChatBots.Add(chatBot); _dialogueChatBots.Add(chatBot);
} }
/// <summary> /// <summary>
/// Handle onUpdate spt event /// Handle onUpdate spt event
/// </summary> /// </summary>
@@ -80,7 +84,7 @@ public class DialogueController(
{ {
Id = friendProfile.Id, Id = friendProfile.Id,
Aid = friendProfile.Aid, Aid = friendProfile.Aid,
Info = friendProfile.Info Info = friendProfile.Info,
} }
); );
} }
@@ -91,7 +95,7 @@ public class DialogueController(
{ {
Friends = friends, Friends = friends,
Ignore = [], Ignore = [],
InIgnoreList = [] InIgnoreList = [],
}; };
} }
@@ -144,9 +148,7 @@ public class DialogueController(
/// <param name="dialogueId">Dialog id</param> /// <param name="dialogueId">Dialog id</param>
/// <param name="sessionId">Session Id</param> /// <param name="sessionId">Session Id</param>
/// <returns>DialogueInfo</returns> /// <returns>DialogueInfo</returns>
public virtual DialogueInfo? GetDialogueInfo( public virtual DialogueInfo? GetDialogueInfo(string? dialogueId, string sessionId)
string? dialogueId,
string sessionId)
{ {
var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId); var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId);
var dialogue = dialogs!.GetValueOrDefault(dialogueId); var dialogue = dialogs!.GetValueOrDefault(dialogueId);
@@ -164,7 +166,7 @@ public class DialogueController(
New = dialogue?.New, New = dialogue?.New,
AttachmentsNew = dialogue?.AttachmentsNew, AttachmentsNew = dialogue?.AttachmentsNew,
Pinned = dialogue?.Pinned, Pinned = dialogue?.Pinned,
Users = GetDialogueUsers(dialogue, dialogue?.Type, sessionId) Users = GetDialogueUsers(dialogue, dialogue?.Type, sessionId),
}; };
return result; return result;
@@ -180,14 +182,19 @@ public class DialogueController(
public virtual List<UserDialogInfo> GetDialogueUsers( public virtual List<UserDialogInfo> GetDialogueUsers(
Dialogue? dialog, Dialogue? dialog,
MessageType? messageType, MessageType? messageType,
string sessionId) string sessionId
)
{ {
var profile = _saveServer.GetProfile(sessionId); var profile = _saveServer.GetProfile(sessionId);
// User to user messages are special in that they need the player to exist in them, add if they don't // User to user messages are special in that they need the player to exist in them, add if they don't
if (messageType == MessageType.UserMessage && if (
dialog?.Users is not null && messageType == MessageType.UserMessage
dialog.Users.All(userDialog => userDialog.Id != profile.CharacterData?.PmcData?.SessionId)) && dialog?.Users is not null
&& dialog.Users.All(userDialog =>
userDialog.Id != profile.CharacterData?.PmcData?.SessionId
)
)
{ {
dialog.Users.Add( dialog.Users.Add(
new UserDialogInfo new UserDialogInfo
@@ -200,8 +207,12 @@ public class DialogueController(
Nickname = profile.CharacterData?.PmcData?.Info?.Nickname, Nickname = profile.CharacterData?.PmcData?.Info?.Nickname,
Side = profile.CharacterData?.PmcData?.Info?.Side, Side = profile.CharacterData?.PmcData?.Info?.Side,
MemberCategory = profile.CharacterData?.PmcData?.Info?.MemberCategory, MemberCategory = profile.CharacterData?.PmcData?.Info?.MemberCategory,
SelectedMemberCategory = profile.CharacterData?.PmcData?.Info?.SelectedMemberCategory SelectedMemberCategory = profile
} .CharacterData
?.PmcData
?.Info
?.SelectedMemberCategory,
},
} }
); );
} }
@@ -220,7 +231,8 @@ public class DialogueController(
/// <returns>GetMailDialogViewResponseData object</returns> /// <returns>GetMailDialogViewResponseData object</returns>
public virtual GetMailDialogViewResponseData GenerateDialogueView( public virtual GetMailDialogViewResponseData GenerateDialogueView(
GetMailDialogViewRequestData request, GetMailDialogViewRequestData request,
string sessionId) string sessionId
)
{ {
var dialogueId = request.DialogId; var dialogueId = request.DialogId;
var fullProfile = _saveServer.GetProfile(sessionId); var fullProfile = _saveServer.GetProfile(sessionId);
@@ -232,7 +244,7 @@ public class DialogueController(
{ {
Messages = [], Messages = [],
Profiles = [], Profiles = [],
HasMessagesWithRewards = false HasMessagesWithRewards = false,
}; };
} }
@@ -246,7 +258,7 @@ public class DialogueController(
{ {
Messages = dialogue.Messages, Messages = dialogue.Messages,
Profiles = GetProfilesForMail(fullProfile, dialogue.Users), Profiles = GetProfilesForMail(fullProfile, dialogue.Users),
HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages!) HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages!),
}; };
} }
@@ -258,11 +270,16 @@ public class DialogueController(
/// <returns>Dialogue</returns> /// <returns>Dialogue</returns>
protected Dialogue GetDialogByIdFromProfile( protected Dialogue GetDialogByIdFromProfile(
SptProfile profile, SptProfile profile,
GetMailDialogViewRequestData request) GetMailDialogViewRequestData request
)
{ {
if (profile.DialogueRecords is null || profile.DialogueRecords.ContainsKey(request.DialogId!)) if (
profile.DialogueRecords is null
|| profile.DialogueRecords.ContainsKey(request.DialogId!)
)
{ {
return profile.DialogueRecords?[request.DialogId!] ?? throw new NullReferenceException(); return profile.DialogueRecords?[request.DialogId!]
?? throw new NullReferenceException();
} }
profile.DialogueRecords[request.DialogId!] = new Dialogue profile.DialogueRecords[request.DialogId!] = new Dialogue
@@ -272,7 +289,7 @@ public class DialogueController(
Pinned = false, Pinned = false,
Messages = [], Messages = [],
New = 0, New = 0,
Type = request.Type Type = request.Type,
}; };
if (request.Type != MessageType.UserMessage) if (request.Type != MessageType.UserMessage)
@@ -282,7 +299,9 @@ public class DialogueController(
var dialogue = profile.DialogueRecords[request.DialogId!]; var dialogue = profile.DialogueRecords[request.DialogId!];
dialogue.Users = []; dialogue.Users = [];
var chatBot = _dialogueChatBots.FirstOrDefault(cb => cb.GetChatBot().Id == request.DialogId); var chatBot = _dialogueChatBots.FirstOrDefault(cb =>
cb.GetChatBot().Id == request.DialogId
);
if (chatBot is null) if (chatBot is null)
{ {
@@ -301,11 +320,14 @@ public class DialogueController(
/// <param name="fullProfile">Player profile</param> /// <param name="fullProfile">Player profile</param>
/// <param name="userDialogs">The participants of the mail</param> /// <param name="userDialogs">The participants of the mail</param>
/// <returns>UserDialogInfo list</returns> /// <returns>UserDialogInfo list</returns>
protected List<UserDialogInfo> GetProfilesForMail(SptProfile fullProfile, List<UserDialogInfo>? userDialogs) protected List<UserDialogInfo> GetProfilesForMail(
SptProfile fullProfile,
List<UserDialogInfo>? userDialogs
)
{ {
List<UserDialogInfo> result = []; List<UserDialogInfo> result = [];
if (userDialogs is null) if (userDialogs is null)
// Nothing to add // Nothing to add
{ {
return result; return result;
} }
@@ -330,8 +352,8 @@ public class DialogueController(
Side = pmcProfile?.Info?.Side, Side = pmcProfile?.Info?.Side,
Level = pmcProfile?.Info?.Level, Level = pmcProfile?.Info?.Level,
MemberCategory = pmcProfile?.Info?.MemberCategory, MemberCategory = pmcProfile?.Info?.MemberCategory,
SelectedMemberCategory = pmcProfile?.Info?.SelectedMemberCategory SelectedMemberCategory = pmcProfile?.Info?.SelectedMemberCategory,
} },
} }
); );
@@ -344,15 +366,16 @@ public class DialogueController(
/// <param name="sessionId">Session id</param> /// <param name="sessionId">Session id</param>
/// <param name="dialogueId">Dialog id</param> /// <param name="dialogueId">Dialog id</param>
/// <returns>Count of messages with attachments</returns> /// <returns>Count of messages with attachments</returns>
protected int GetUnreadMessagesWithAttachmentsCount( protected int GetUnreadMessagesWithAttachmentsCount(string sessionId, string dialogueId)
string sessionId,
string dialogueId)
{ {
var newAttachmentCount = 0; var newAttachmentCount = 0;
var activeMessages = GetActiveMessagesFromDialog(sessionId, dialogueId); var activeMessages = GetActiveMessagesFromDialog(sessionId, dialogueId);
foreach (var message in activeMessages) foreach (var message in activeMessages)
{ {
if (message.HasRewards.GetValueOrDefault(false) && !message.RewardCollected.GetValueOrDefault(false)) if (
message.HasRewards.GetValueOrDefault(false)
&& !message.RewardCollected.GetValueOrDefault(false)
)
{ {
newAttachmentCount++; newAttachmentCount++;
} }
@@ -372,11 +395,13 @@ public class DialogueController(
var timeNow = _timeUtil.GetTimeStamp(); var timeNow = _timeUtil.GetTimeStamp();
var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId); var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId);
return dialogs[dialogueId].Messages?.Where(message => return dialogs[dialogueId]
{ .Messages?.Where(message =>
var checkTime = message.DateTime + (message.MaxStorageTime ?? 0); {
return timeNow < checkTime; var checkTime = message.DateTime + (message.MaxStorageTime ?? 0);
}).ToList() ?? []; return timeNow < checkTime;
})
.ToList() ?? [];
} }
/// <summary> /// <summary>
@@ -395,9 +420,7 @@ public class DialogueController(
/// </summary> /// </summary>
/// <param name="dialogueId">id of the dialog to remove</param> /// <param name="dialogueId">id of the dialog to remove</param>
/// <param name="sessionId">Player id</param> /// <param name="sessionId">Player id</param>
public virtual void RemoveDialogue( public virtual void RemoveDialogue(string? dialogueId, string sessionId)
string? dialogueId,
string sessionId)
{ {
var profile = _saveServer.GetProfile(sessionId); var profile = _saveServer.GetProfile(sessionId);
if (!profile.DialogueRecords.ContainsKey(dialogueId)) if (!profile.DialogueRecords.ContainsKey(dialogueId))
@@ -405,11 +428,7 @@ public class DialogueController(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"dialogue-unable_to_find_in_profile", "dialogue-unable_to_find_in_profile",
new new { sessionId, dialogueId }
{
sessionId,
dialogueId
}
) )
); );
@@ -433,11 +452,7 @@ public class DialogueController(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"dialogue-unable_to_find_in_profile", "dialogue-unable_to_find_in_profile",
new new { sessionId, dialogueId }
{
sessionId,
dialogueId
}
) )
); );
@@ -461,10 +476,7 @@ public class DialogueController(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"dialogue-unable_to_find_dialogs_in_profile", "dialogue-unable_to_find_dialogs_in_profile",
new new { sessionId }
{
sessionId
}
) )
); );
@@ -491,9 +503,7 @@ public class DialogueController(
var dialog = dialogs.TryGetValue(dialogueId, out var dialogInfo); var dialog = dialogs.TryGetValue(dialogueId, out var dialogInfo);
if (!dialog) if (!dialog)
{ {
_logger.Error( _logger.Error(_localisationService.GetText("dialogue-unable_to_find_in_profile"));
_localisationService.GetText("dialogue-unable_to_find_in_profile")
);
return null; return null;
} }
@@ -508,7 +518,7 @@ public class DialogueController(
{ {
Messages = messagesWithAttachments, Messages = messagesWithAttachments,
Profiles = [], Profiles = [],
HasMessagesWithRewards = MessagesHaveUncollectedRewards(messagesWithAttachments) HasMessagesWithRewards = MessagesHaveUncollectedRewards(messagesWithAttachments),
}; };
} }
@@ -518,18 +528,15 @@ public class DialogueController(
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="request"></param> /// <param name="request"></param>
/// <returns></returns> /// <returns></returns>
public virtual string SendMessage( public virtual string SendMessage(string sessionId, SendMessageRequest request)
string sessionId,
SendMessageRequest request)
{ {
_mailSendService.SendPlayerMessageToNpc(sessionId, request.DialogId!, request.Text!); _mailSendService.SendPlayerMessageToNpc(sessionId, request.DialogId!, request.Text!);
return (_dialogueChatBots.FirstOrDefault(cb => return (
cb.GetChatBot().Id == request.DialogId _dialogueChatBots
) .FirstOrDefault(cb => cb.GetChatBot().Id == request.DialogId)
?.HandleMessage(sessionId, request) ?? ?.HandleMessage(sessionId, request) ?? request.DialogId
request.DialogId) ?? ) ?? string.Empty;
string.Empty;
} }
/// <summary> /// <summary>
@@ -592,7 +599,10 @@ public class DialogueController(
/// <param name="sessionID">Session/player id</param> /// <param name="sessionID">Session/player id</param>
/// <param name="request">Sent friend request</param> /// <param name="request">Sent friend request</param>
/// <returns></returns> /// <returns></returns>
public virtual FriendRequestSendResponse SendFriendRequest(string sessionID, FriendRequestData request) public virtual FriendRequestSendResponse SendFriendRequest(
string sessionID,
FriendRequestData request
)
{ {
// To avoid needing to jump between profiles, auto-accept all friend requests // To avoid needing to jump between profiles, auto-accept all friend requests
var friendProfile = _profileHelper.GetFullProfile(request.To); var friendProfile = _profileHelper.GetFullProfile(request.To);
@@ -602,7 +612,7 @@ public class DialogueController(
{ {
Status = BackendErrorCodes.PlayerProfileNotFound, Status = BackendErrorCodes.PlayerProfileNotFound,
RequestId = "", // Unused in an error state RequestId = "", // Unused in an error state
RetryAfter = 600 RetryAfter = 600,
}; };
} }
@@ -620,7 +630,9 @@ public class DialogueController(
var notification = new WsFriendsListAccept var notification = new WsFriendsListAccept
{ {
EventType = NotificationEventType.friendListRequestAccept, EventType = NotificationEventType.friendListRequestAccept,
Profile = _profileHelper.GetChatRoomMemberFromPmcProfile(friendProfile.CharacterData.PmcData) Profile = _profileHelper.GetChatRoomMemberFromPmcProfile(
friendProfile.CharacterData.PmcData
),
}; };
_notificationSendHelper.SendMessage(sessionID, notification); _notificationSendHelper.SendMessage(sessionID, notification);
}, },
@@ -633,7 +645,7 @@ public class DialogueController(
{ {
Status = BackendErrorCodes.None, Status = BackendErrorCodes.None,
RequestId = friendProfile.ProfileInfo.Aid.ToString(), RequestId = friendProfile.ProfileInfo.Aid.ToString(),
RetryAfter = 600 RetryAfter = 600,
}; };
} }
@@ -662,7 +674,9 @@ public class DialogueController(
var profile = _saveServer.GetProfile(sessionId); var profile = _saveServer.GetProfile(sessionId);
if (!profile.DialogueRecords.TryGetValue(request.DialogId, out var dialogToClear)) if (!profile.DialogueRecords.TryGetValue(request.DialogId, out var dialogToClear))
{ {
_logger.Warning($"unable to clear messages from dialog: {request.DialogId} as it cannot be found in profile: {sessionId}"); _logger.Warning(
$"unable to clear messages from dialog: {request.DialogId} as it cannot be found in profile: {sessionId}"
);
return; return;
} }
@@ -15,7 +15,6 @@ using SPTarkov.Server.Core.Utils.Cloners;
using SPTarkov.Server.Core.Utils.Json; using SPTarkov.Server.Core.Utils.Json;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
@@ -81,7 +80,7 @@ public class GameController(
fullProfile.SptData ??= new Spt fullProfile.SptData ??= new Spt
{ {
//TODO: complete //TODO: complete
Version = "Replace_me" Version = "Replace_me",
}; };
fullProfile.SptData.Migrations ??= new Dictionary<string, long>(); fullProfile.SptData.Migrations ??= new Dictionary<string, long>();
fullProfile.FriendProfileIds ??= []; fullProfile.FriendProfileIds ??= [];
@@ -91,8 +90,14 @@ public class GameController(
return; return;
} }
fullProfile.CharacterData!.PmcData!.WishList ??= new DictionaryOrList<string, int>(new Dictionary<string, int>(), []); fullProfile.CharacterData!.PmcData!.WishList ??= new DictionaryOrList<string, int>(
fullProfile.CharacterData.ScavData!.WishList ??= new DictionaryOrList<string, int>(new Dictionary<string, int>(), []); new Dictionary<string, int>(),
[]
);
fullProfile.CharacterData.ScavData!.WishList ??= new DictionaryOrList<string, int>(
new Dictionary<string, int>(),
[]
);
if (fullProfile.DialogueRecords is not null) if (fullProfile.DialogueRecords is not null)
{ {
@@ -101,7 +106,9 @@ public class GameController(
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Started game with session {sessionId} {fullProfile.ProfileInfo?.Username}"); _logger.Debug(
$"Started game with session {sessionId} {fullProfile.ProfileInfo?.Username}"
);
} }
var pmcProfile = fullProfile.CharacterData.PmcData; var pmcProfile = fullProfile.CharacterData.PmcData;
@@ -133,7 +140,12 @@ public class GameController(
_hideoutHelper.UnlockHideoutWallInProfile(pmcProfile); _hideoutHelper.UnlockHideoutWallInProfile(pmcProfile);
// Handle if player has been inactive for a long time, catch up on hideout update before the user goes to his hideout // Handle if player has been inactive for a long time, catch up on hideout update before the user goes to his hideout
if (!_profileActivityService.ActiveWithinLastMinutes(sessionId, _hideoutConfig.UpdateProfileHideoutWhenActiveWithinMinutes)) if (
!_profileActivityService.ActiveWithinLastMinutes(
sessionId,
_hideoutConfig.UpdateProfileHideoutWhenActiveWithinMinutes
)
)
{ {
_hideoutHelper.UpdatePlayerHideout(sessionId); _hideoutHelper.UpdatePlayerHideout(sessionId);
} }
@@ -167,10 +179,12 @@ public class GameController(
public GameConfigResponse GetGameConfig(string sessionId) public GameConfigResponse GetGameConfig(string sessionId)
{ {
var profile = _profileHelper.GetPmcProfile(sessionId); var profile = _profileHelper.GetPmcProfile(sessionId);
var gameTime = profile?.Stats?.Eft?.OverallCounters?.Items? var gameTime =
.FirstOrDefault(c => c.Key!.Contains("LifeTime") && c.Key.Contains("Pmc")) profile
?.Value ?? ?.Stats?.Eft?.OverallCounters?.Items?.FirstOrDefault(c =>
0D; c.Key!.Contains("LifeTime") && c.Key.Contains("Pmc")
)
?.Value ?? 0D;
var config = new GameConfigResponse var config = new GameConfigResponse
{ {
@@ -188,18 +202,14 @@ public class GameController(
Trading = _httpServerHelper.GetBackendUrl(), Trading = _httpServerHelper.GetBackendUrl(),
Messaging = _httpServerHelper.GetBackendUrl(), Messaging = _httpServerHelper.GetBackendUrl(),
Main = _httpServerHelper.GetBackendUrl(), Main = _httpServerHelper.GetBackendUrl(),
RagFair = _httpServerHelper.GetBackendUrl() RagFair = _httpServerHelper.GetBackendUrl(),
}, },
UseProtobuf = false, UseProtobuf = false,
UtcTime = _timeUtil.GetTimeStamp(), UtcTime = _timeUtil.GetTimeStamp(),
TotalInGame = gameTime, TotalInGame = gameTime,
SessionMode = "pve", SessionMode = "pve",
PurchasedGames = new PurchasedGames PurchasedGames = new PurchasedGames { IsEftPurchased = true, IsArenaPurchased = false },
{ IsGameSynced = true,
IsEftPurchased = true,
IsArenaPurchased = false
},
IsGameSynced = true
}; };
return config; return config;
@@ -211,14 +221,12 @@ public class GameController(
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="requestData"></param> /// <param name="requestData"></param>
/// <returns></returns> /// <returns></returns>
public GameModeResponse GetGameMode( public GameModeResponse GetGameMode(string sessionId, GameModeRequestData requestData)
string sessionId,
GameModeRequestData requestData)
{ {
return new GameModeResponse return new GameModeResponse
{ {
GameMode = "pve", GameMode = "pve",
BackendUrl = _httpServerHelper.GetBackendUrl() BackendUrl = _httpServerHelper.GetBackendUrl(),
}; };
} }
@@ -229,14 +237,7 @@ public class GameController(
/// <returns></returns> /// <returns></returns>
public List<ServerDetails> GetServer(string sessionId) public List<ServerDetails> GetServer(string sessionId)
{ {
return return [new ServerDetails { Ip = _httpConfig.BackendIp, Port = _httpConfig.BackendPort }];
[
new ServerDetails
{
Ip = _httpConfig.BackendIp,
Port = _httpConfig.BackendPort
}
];
} }
/// <summary> /// <summary>
@@ -246,13 +247,9 @@ public class GameController(
/// <returns></returns> /// <returns></returns>
public CurrentGroupResponse GetCurrentGroup(string sessionId) public CurrentGroupResponse GetCurrentGroup(string sessionId)
{ {
return new CurrentGroupResponse return new CurrentGroupResponse { Squad = [] };
{
Squad = []
};
} }
/// <summary> /// <summary>
/// Handle client/checkVersion /// Handle client/checkVersion
/// </summary> /// </summary>
@@ -263,7 +260,7 @@ public class GameController(
return new CheckVersionResponse return new CheckVersionResponse
{ {
IsValid = true, IsValid = true,
LatestVersion = _coreConfig.CompatibleTarkovVersion LatestVersion = _coreConfig.CompatibleTarkovVersion,
}; };
} }
@@ -275,11 +272,7 @@ public class GameController(
public GameKeepAliveResponse GetKeepAlive(string sessionId) public GameKeepAliveResponse GetKeepAlive(string sessionId)
{ {
_profileActivityService.SetActivityTimestamp(sessionId); _profileActivityService.SetActivityTimestamp(sessionId);
return new GameKeepAliveResponse return new GameKeepAliveResponse { Message = "OK", UtcTime = _timeUtil.GetTimeStamp() };
{
Message = "OK",
UtcTime = _timeUtil.GetTimeStamp()
};
} }
/// <summary> /// <summary>
@@ -311,7 +304,9 @@ public class GameController(
var botReloadSkill = _profileHelper.GetSkillFromProfile(pmcProfile, SkillTypes.BotReload); var botReloadSkill = _profileHelper.GetSkillFromProfile(pmcProfile, SkillTypes.BotReload);
if (botReloadSkill?.Progress > 0) if (botReloadSkill?.Progress > 0)
{ {
_logger.Warning(_localisationService.GetText("server_start_player_active_botreload_skill")); _logger.Warning(
_localisationService.GetText("server_start_player_active_botreload_skill")
);
} }
} }
@@ -337,23 +332,25 @@ public class GameController(
var hpRegenPerHour = 456.6; var hpRegenPerHour = 456.6;
// Set new values, whatever is smallest // Set new values, whatever is smallest
energyRegenPerHour += pmcProfile.Bonuses! energyRegenPerHour += pmcProfile
.Where(bonus => bonus.Type == BonusType.EnergyRegeneration) .Bonuses!.Where(bonus => bonus.Type == BonusType.EnergyRegeneration)
.Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value); .Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value);
hydrationRegenPerHour += pmcProfile.Bonuses! hydrationRegenPerHour += pmcProfile
.Where(bonus => bonus.Type == BonusType.HydrationRegeneration) .Bonuses!.Where(bonus => bonus.Type == BonusType.HydrationRegeneration)
.Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value); .Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value);
hpRegenPerHour += pmcProfile.Bonuses! hpRegenPerHour += pmcProfile
.Where(bonus => bonus.Type == BonusType.HealthRegeneration) .Bonuses!.Where(bonus => bonus.Type == BonusType.HealthRegeneration)
.Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value); .Aggregate(0d, (sum, bonus) => sum + bonus.Value!.Value);
// Player has energy deficit // Player has energy deficit
if (pmcProfile.Health?.Energy?.Current - pmcProfile.Health?.Energy?.Maximum <= _deviation) if (pmcProfile.Health?.Energy?.Current - pmcProfile.Health?.Energy?.Maximum <= _deviation)
{ {
// Set new value, whatever is smallest // Set new value, whatever is smallest
pmcProfile.Health!.Energy!.Current += Math.Round(energyRegenPerHour * (diffSeconds!.Value / 3600)); pmcProfile.Health!.Energy!.Current += Math.Round(
energyRegenPerHour * (diffSeconds!.Value / 3600)
);
if (pmcProfile.Health.Energy.Current > pmcProfile.Health.Energy.Maximum) if (pmcProfile.Health.Energy.Current > pmcProfile.Health.Energy.Maximum)
{ {
pmcProfile.Health.Energy.Current = pmcProfile.Health.Energy.Maximum; pmcProfile.Health.Energy.Current = pmcProfile.Health.Energy.Maximum;
@@ -361,9 +358,14 @@ public class GameController(
} }
// Player has hydration deficit // Player has hydration deficit
if (pmcProfile.Health?.Hydration?.Current - pmcProfile.Health?.Hydration?.Maximum <= _deviation) if (
pmcProfile.Health?.Hydration?.Current - pmcProfile.Health?.Hydration?.Maximum
<= _deviation
)
{ {
pmcProfile.Health!.Hydration!.Current += Math.Round(hydrationRegenPerHour * (diffSeconds!.Value / 3600)); pmcProfile.Health!.Hydration!.Current += Math.Round(
hydrationRegenPerHour * (diffSeconds!.Value / 3600)
);
if (pmcProfile.Health.Hydration.Current > pmcProfile.Health.Hydration.Maximum) if (pmcProfile.Health.Hydration.Current > pmcProfile.Health.Hydration.Maximum)
{ {
pmcProfile.Health.Hydration.Current = pmcProfile.Health.Hydration.Maximum; pmcProfile.Health.Hydration.Current = pmcProfile.Health.Hydration.Maximum;
@@ -383,10 +385,15 @@ public class GameController(
/// <param name="pmcProfile">Player</param> /// <param name="pmcProfile">Player</param>
/// <param name="hpRegenPerHour"></param> /// <param name="hpRegenPerHour"></param>
/// <param name="diffSeconds"></param> /// <param name="diffSeconds"></param>
protected void DecreaseBodyPartEffectTimes(PmcData pmcProfile, double hpRegenPerHour, double diffSeconds) protected void DecreaseBodyPartEffectTimes(
PmcData pmcProfile,
double hpRegenPerHour,
double diffSeconds
)
{ {
foreach (var bodyPart in pmcProfile.Health!.BodyParts! foreach (
.Select(bodyPartKvP => bodyPartKvP.Value)) var bodyPart in pmcProfile.Health!.BodyParts!.Select(bodyPartKvP => bodyPartKvP.Value)
)
{ {
// Check part hp // Check part hp
if (bodyPart.Health!.Current < bodyPart.Health.Maximum) if (bodyPart.Health!.Current < bodyPart.Health.Maximum)
@@ -399,7 +406,6 @@ public class GameController(
bodyPart.Health.Current = bodyPart.Health.Maximum; bodyPart.Health.Current = bodyPart.Health.Maximum;
} }
if (bodyPart.Effects is null || bodyPart.Effects.Count == 0) if (bodyPart.Effects is null || bodyPart.Effects.Count == 0)
{ {
continue; continue;
@@ -423,7 +429,7 @@ public class GameController(
// Decrement effect time value by difference between current time and time health was last updated // Decrement effect time value by difference between current time and time health was last updated
effectKvP.Value.Time -= diffSeconds; effectKvP.Value.Time -= diffSeconds;
if (effectKvP.Value.Time < 1) if (effectKvP.Value.Time < 1)
// Effect time was sub 1, set floor it can be // Effect time was sub 1, set floor it can be
{ {
effectKvP.Value.Time = 1; effectKvP.Value.Time = 1;
} }
@@ -475,7 +481,9 @@ public class GameController(
{ {
if ( if (
fullProfile.SptData.Mods.Any(m => fullProfile.SptData.Mods.Any(m =>
m.Author == mod.ModMetadata.Author && m.Version == mod.ModMetadata.Version && m.Name == mod.ModMetadata.Name m.Author == mod.ModMetadata.Author
&& m.Version == mod.ModMetadata.Version
&& m.Name == mod.ModMetadata.Name
) )
) )
{ {
@@ -490,7 +498,7 @@ public class GameController(
Version = mod.ModMetadata.Version, Version = mod.ModMetadata.Version,
Name = mod.ModMetadata.Name, Name = mod.ModMetadata.Name,
Url = mod.ModMetadata.Url, Url = mod.ModMetadata.Url,
DateAdded = _timeUtil.GetTimeStamp() DateAdded = _timeUtil.GetTimeStamp(),
} }
); );
} }
@@ -552,7 +560,9 @@ public class GameController(
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Profile made with: {fullProfile.SptData?.Version}"); _logger.Debug($"Profile made with: {fullProfile.SptData?.Version}");
_logger.Debug($"Server version: {ProgramStatics.SPT_VERSION() ?? _coreConfig.SptVersion} {ProgramStatics.COMMIT()}"); _logger.Debug(
$"Server version: {ProgramStatics.SPT_VERSION() ?? _coreConfig.SptVersion} {ProgramStatics.COMMIT()}"
);
_logger.Debug($"Debug enabled: {ProgramStatics.DEBUG()}"); _logger.Debug($"Debug enabled: {ProgramStatics.DEBUG()}");
_logger.Debug($"Mods enabled: {ProgramStatics.MODS()}"); _logger.Debug($"Mods enabled: {ProgramStatics.MODS()}");
} }
@@ -25,7 +25,8 @@ public class HealthController(
LocalisationService _localisationService, LocalisationService _localisationService,
HttpResponseUtil _httpResponseUtil, HttpResponseUtil _httpResponseUtil,
HealthHelper _healthHelper, HealthHelper _healthHelper,
ICloner _cloner) ICloner _cloner
)
{ {
/// <summary> /// <summary>
/// When healing in menu /// When healing in menu
@@ -37,15 +38,21 @@ public class HealthController(
public ItemEventRouterResponse OffRaidHeal( public ItemEventRouterResponse OffRaidHeal(
PmcData pmcData, PmcData pmcData,
OffraidHealRequestData request, OffraidHealRequestData request,
string sessionID) string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
// Update medkit used (hpresource) // Update medkit used (hpresource)
var healingItemToUse = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item); var healingItemToUse = pmcData.Inventory.Items.FirstOrDefault(item =>
item.Id == request.Item
);
if (healingItemToUse is null) if (healingItemToUse is null)
{ {
var errorMessage = _localisationService.GetText("health-healing_item_not_found", request.Item); var errorMessage = _localisationService.GetText(
"health-healing_item_not_found",
request.Item
);
_logger.Error(errorMessage); _logger.Error(errorMessage);
return _httpResponseUtil.AppendErrorToOutput(output, errorMessage); return _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
@@ -61,11 +68,10 @@ public class HealthController(
else else
{ {
// Get max healing from db // Get max healing from db
var maxHp = _itemHelper.GetItem(healingItemToUse.Template).Value.Properties.MaxHpResource; var maxHp = _itemHelper
healingItemToUse.Upd.MedKit = new UpdMedKit .GetItem(healingItemToUse.Template)
{ .Value.Properties.MaxHpResource;
HpResource = maxHp - request.Count healingItemToUse.Upd.MedKit = new UpdMedKit { HpResource = maxHp - request.Count }; // Subtract amout used from max
}; // Subtract amout used from max
// request.count appears to take into account healing effects removed, e.g. bleeds // 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) // Salewa heals limb for 20 and fixes light bleed = (20+45 = 65)
} }
@@ -82,7 +88,9 @@ public class HealthController(
var bodyPartToHeal = pmcData.Health.BodyParts.GetValueOrDefault(request.Part); var bodyPartToHeal = pmcData.Health.BodyParts.GetValueOrDefault(request.Part);
if (bodyPartToHeal is null) if (bodyPartToHeal is null)
{ {
_logger.Warning($"Player: {sessionID} Tried to heal a non-existent body part: {request.Part}"); _logger.Warning(
$"Player: {sessionID} Tried to heal a non-existent body part: {request.Part}"
);
return output; return output;
} }
@@ -99,20 +107,25 @@ public class HealthController(
{ {
// Check enum has effectType // Check enum has effectType
if (!Enum.TryParse<DamageEffectType>(effectKvP.Key, out var effect)) if (!Enum.TryParse<DamageEffectType>(effectKvP.Key, out var effect))
// Enum doesnt contain this key // Enum doesnt contain this key
{ {
continue; continue;
} }
// Check if healing item removes the effect on limb // Check if healing item removes the effect on limb
if (!healItemEffectDetails.TryGetValue(effect, out var matchingEffectFromHealingItem)) if (
// Healing item doesn't have matching effect, it doesn't remove the effect !healItemEffectDetails.TryGetValue(
effect,
out var matchingEffectFromHealingItem
)
)
// Healing item doesn't have matching effect, it doesn't remove the effect
{ {
continue; continue;
} }
// Adjust limb heal amount based on if it's fixing an effect (request.count is TOTAL cost of hp resource on heal item, NOT amount to heal limb) // Adjust limb heal amount based on if it's 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(effectKvP.Key); bodyPartToHeal.Effects.Remove(effectKvP.Key);
} }
} }
@@ -140,14 +153,15 @@ public class HealthController(
public ItemEventRouterResponse OffRaidEat( public ItemEventRouterResponse OffRaidEat(
PmcData pmcData, PmcData pmcData,
OffraidEatRequestData request, OffraidEatRequestData request,
string sessionID) string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
var resourceLeft = 0d; var resourceLeft = 0d;
var itemToConsume = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item); var itemToConsume = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
if (itemToConsume is null) if (itemToConsume is null)
// Item not found, very bad // Item not found, very bad
{ {
return _httpResponseUtil.AppendErrorToOutput( return _httpResponseUtil.AppendErrorToOutput(
output, output,
@@ -155,7 +169,9 @@ public class HealthController(
); );
} }
var consumedItemMaxResource = _itemHelper.GetItem(itemToConsume.Template).Value.Properties.MaxResource; var consumedItemMaxResource = _itemHelper
.GetItem(itemToConsume.Template)
.Value.Properties.MaxResource;
if (consumedItemMaxResource > 1) if (consumedItemMaxResource > 1)
{ {
// Ensure item has a upd object // Ensure item has a upd object
@@ -165,7 +181,7 @@ public class HealthController(
{ {
itemToConsume.Upd.FoodDrink = new UpdFoodDrink itemToConsume.Upd.FoodDrink = new UpdFoodDrink
{ {
HpPercent = consumedItemMaxResource - request.Count HpPercent = consumedItemMaxResource - request.Count,
}; };
} }
else else
@@ -192,14 +208,21 @@ public class HealthController(
switch (key) switch (key)
{ {
case HealthFactor.Hydration: case HealthFactor.Hydration:
ApplyEdibleEffect(pmcData.Health.Hydration, effectProps, foodIsSingleUse, request); ApplyEdibleEffect(
pmcData.Health.Hydration,
effectProps,
foodIsSingleUse,
request
);
break; break;
case HealthFactor.Energy: case HealthFactor.Energy:
ApplyEdibleEffect(pmcData.Health.Energy, effectProps, foodIsSingleUse, request); ApplyEdibleEffect(pmcData.Health.Energy, effectProps, foodIsSingleUse, request);
break; break;
default: default:
_logger.Warning($"Unhandled effect after consuming: {itemToConsume.Template}, {key}"); _logger.Warning(
$"Unhandled effect after consuming: {itemToConsume.Template}, {key}"
);
break; break;
} }
} }
@@ -214,11 +237,15 @@ public class HealthController(
/// <param name="consumptionDetails">Properties of consumed item</param> /// <param name="consumptionDetails">Properties of consumed item</param>
/// <param name="foodIsSingleUse">Single use item</param> /// <param name="foodIsSingleUse">Single use item</param>
/// <param name="request">Client request</param> /// <param name="request">Client request</param>
protected void ApplyEdibleEffect(CurrentMinMax bodyValue, EffectsHealthProps consumptionDetails, bool foodIsSingleUse, protected void ApplyEdibleEffect(
OffraidEatRequestData request) CurrentMinMax bodyValue,
EffectsHealthProps consumptionDetails,
bool foodIsSingleUse,
OffraidEatRequestData request
)
{ {
if (foodIsSingleUse) if (foodIsSingleUse)
// Apply whole value from passed in parameter // Apply whole value from passed in parameter
{ {
bodyValue.Current += consumptionDetails.Value; bodyValue.Current += consumptionDetails.Value;
} }
@@ -253,7 +280,8 @@ public class HealthController(
public ItemEventRouterResponse HealthTreatment( public ItemEventRouterResponse HealthTreatment(
PmcData pmcData, PmcData pmcData,
HealthTreatmentRequestData healthTreatmentRequest, HealthTreatmentRequestData healthTreatmentRequest,
string sessionID) string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
var payMoneyRequest = new ProcessBuyTradeRequestData var payMoneyRequest = new ProcessBuyTradeRequestData
@@ -264,7 +292,7 @@ public class HealthController(
Type = "", Type = "",
ItemId = "", ItemId = "",
Count = 0, Count = 0,
SchemeId = 0 SchemeId = 0,
}; };
_paymentService.PayMoney(pmcData, payMoneyRequest, sessionID, output); _paymentService.PayMoney(pmcData, payMoneyRequest, sessionID, output);
@@ -276,12 +304,12 @@ public class HealthController(
foreach (var bodyPartKvP in healthTreatmentRequest.Difference.BodyParts.GetAllPropsAsDict()) foreach (var bodyPartKvP in healthTreatmentRequest.Difference.BodyParts.GetAllPropsAsDict())
{ {
// Get body part from request + from pmc profile // 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]; var profilePart = pmcData.Health.BodyParts[bodyPartKvP.Key];
// Bodypart healing is chosen when part request hp is above 0 // Bodypart healing is chosen when part request hp is above 0
if (partRequest.Health > 0) if (partRequest.Health > 0)
// Heal bodypart // Heal bodypart
{ {
profilePart.Health.Current = profilePart.Health.Maximum; profilePart.Health.Current = profilePart.Health.Maximum;
} }
@@ -315,10 +343,7 @@ public class HealthController(
/// <param name="pmcData">Player profile</param> /// <param name="pmcData">Player profile</param>
/// <param name="request">Request data</param> /// <param name="request">Request data</param>
/// <param name="sessionId">session id</param> /// <param name="sessionId">session id</param>
public void ApplyWorkoutChanges( public void ApplyWorkoutChanges(PmcData? pmcData, WorkoutData request, string sessionId)
PmcData? pmcData,
WorkoutData request,
string sessionId)
{ {
pmcData.Skills.Common = request.Skills.Common; pmcData.Skills.Common = request.Skills.Common;
} }
File diff suppressed because it is too large Load Diff
@@ -25,7 +25,7 @@ public class InRaidController(
/// <param name="info">Register player request</param> /// <param name="info">Register player request</param>
public void AddPlayer(string sessionId, RegisterPlayerRequestData info) public void AddPlayer(string sessionId, RegisterPlayerRequestData info)
{ {
// _applicationContext.AddValue(ContextVariableType.REGISTER_PLAYER_REQUEST, info); // _applicationContext.AddValue(ContextVariableType.REGISTER_PLAYER_REQUEST, info);
} }
/// <summary> /// <summary>
@@ -91,11 +91,15 @@ public class InsuranceController(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Found {profileInsuranceDetails.Count} insurance packages in profile {sessionId}"); _logger.Debug(
$"Found {profileInsuranceDetails.Count} insurance packages in profile {sessionId}"
);
} }
} }
return profileInsuranceDetails.Where(insured => insuranceTime >= insured.ScheduledTime).ToList(); return profileInsuranceDetails
.Where(insured => insuranceTime >= insured.ScheduledTime)
.ToList();
} }
/// <summary> /// <summary>
@@ -159,17 +163,20 @@ public class InsuranceController(
protected void RemoveInsurancePackageFromProfile(string sessionId, Insurance insPackage) protected void RemoveInsurancePackageFromProfile(string sessionId, Insurance insPackage)
{ {
var profile = _saveServer.GetProfile(sessionId); var profile = _saveServer.GetProfile(sessionId);
profile.InsuranceList = profile.InsuranceList.Where(insurance => profile.InsuranceList = profile
insurance.TraderId != insPackage.TraderId || .InsuranceList.Where(insurance =>
insurance.SystemData?.Date != insPackage.SystemData?.Date || insurance.TraderId != insPackage.TraderId
insurance.SystemData?.Time != insPackage.SystemData?.Time || || insurance.SystemData?.Date != insPackage.SystemData?.Date
insurance.SystemData?.Location != insPackage.SystemData?.Location || insurance.SystemData?.Time != insPackage.SystemData?.Time
|| insurance.SystemData?.Location != insPackage.SystemData?.Location
) )
.ToList(); .ToList();
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Removed processed insurance package. Remaining packages: {profile.InsuranceList.Count}"); _logger.Debug(
$"Removed processed insurance package. Remaining packages: {profile.InsuranceList.Count}"
);
} }
} }
@@ -186,11 +193,14 @@ public class InsuranceController(
// Populate a Map object of items for quick lookup by their ID and use it to populate a Map of main-parent items // Populate a Map object of items for quick lookup by their ID and use it to populate a Map of main-parent items
// and each of their attachments. For example, a gun mapped to each of its attachments. // and each of their attachments. For example, a gun mapped to each of its attachments.
var itemsMap = _itemHelper.GenerateItemsMap(insured.Items); var itemsMap = _itemHelper.GenerateItemsMap(insured.Items);
var parentAttachmentsMap = PopulateParentAttachmentsMap(rootItemParentId, insured, itemsMap); var parentAttachmentsMap = PopulateParentAttachmentsMap(
rootItemParentId,
insured,
itemsMap
);
// Check to see if any regular items are present. // Check to see if any regular items are present.
var hasRegularItems = itemsMap.Values.Any(item => !_itemHelper.IsAttachmentAttached(item) var hasRegularItems = itemsMap.Values.Any(item => !_itemHelper.IsAttachmentAttached(item));
);
// Process all items that are not attached, attachments; those are handled separately, by value. // Process all items that are not attached, attachments; those are handled separately, by value.
if (hasRegularItems) if (hasRegularItems)
@@ -229,7 +239,11 @@ public class InsuranceController(
/// <param name="insured">The insurance object containing the items to evaluate</param> /// <param name="insured">The insurance object containing the items to evaluate</param>
/// <param name="itemsMap">A Dictionary for quick item look-up by item ID</param> /// <param name="itemsMap">A Dictionary for quick item look-up by item ID</param>
/// <returns>A dictionary containing parent item IDs to arrays of their attachment items</returns> /// <returns>A dictionary containing parent item IDs to arrays of their attachment items</returns>
protected Dictionary<string, List<Item>> PopulateParentAttachmentsMap(string rootItemParentID, Insurance insured, Dictionary<string, Item> itemsMap) protected Dictionary<string, List<Item>> PopulateParentAttachmentsMap(
string rootItemParentID,
Insurance insured,
Dictionary<string, Item> itemsMap
)
{ {
var mainParentToAttachmentsMap = new Dictionary<string, List<Item>>(); var mainParentToAttachmentsMap = new Dictionary<string, List<Item>>();
foreach (var insuredItem in insured.Items) foreach (var insuredItem in insured.Items)
@@ -247,7 +261,7 @@ public class InsuranceController(
{ {
insuredItemId = insuredItem.Id, insuredItemId = insuredItem.Id,
insuredItemTpl = insuredItem.Template, insuredItemTpl = insuredItem.Template,
parentId = insuredItem.ParentId parentId = insuredItem.ParentId,
} }
) )
); );
@@ -270,7 +284,7 @@ public class InsuranceController(
new new
{ {
insuredItemId = insuredItem.Id, insuredItemId = insuredItem.Id,
insuredItemTpl = insuredItem.Template insuredItemTpl = insuredItem.Template,
} }
) )
); );
@@ -290,7 +304,7 @@ public class InsuranceController(
{ {
insuredItemId = insuredItem.Id, insuredItemId = insuredItem.Id,
insuredItemTpl = insuredItem.Template, insuredItemTpl = insuredItem.Template,
parentId = insuredItem.ParentId parentId = insuredItem.ParentId,
} }
) )
); );
@@ -322,8 +336,10 @@ public class InsuranceController(
/// <param name="parentAttachmentsMap">Dictionary containing parent item IDs to arrays of their attachment items</param> /// <param name="parentAttachmentsMap">Dictionary containing parent item IDs to arrays of their attachment items</param>
/// <param name="itemsMap">Hashset containing parent item IDs to arrays of their attachment items which are not moddable in-raid</param> /// <param name="itemsMap">Hashset containing parent item IDs to arrays of their attachment items which are not moddable in-raid</param>
/// <returns></returns> /// <returns></returns>
protected Dictionary<string, List<Item>> RemoveNonModdableAttachments(Dictionary<string, List<Item>> parentAttachmentsMap, protected Dictionary<string, List<Item>> RemoveNonModdableAttachments(
Dictionary<string, Item> itemsMap) Dictionary<string, List<Item>> parentAttachmentsMap,
Dictionary<string, Item> itemsMap
)
{ {
var updatedMap = new Dictionary<string, List<Item>>(); var updatedMap = new Dictionary<string, List<Item>>();
@@ -371,7 +387,11 @@ public class InsuranceController(
/// <param name="insured">Insurance object containing the items to evaluate</param> /// <param name="insured">Insurance object containing the items to evaluate</param>
/// <param name="toDelete">Hashset to keep track of items marked for deletion</param> /// <param name="toDelete">Hashset to keep track of items marked for deletion</param>
/// <param name="parentAttachmentsMap">Dictionary containing parent item IDs to arrays of their attachment items</param> /// <param name="parentAttachmentsMap">Dictionary containing parent item IDs to arrays of their attachment items</param>
protected void ProcessRegularItems(Insurance insured, HashSet<string> toDelete, Dictionary<string, List<Item>> parentAttachmentsMap) protected void ProcessRegularItems(
Insurance insured,
HashSet<string> toDelete,
Dictionary<string, List<Item>> parentAttachmentsMap
)
{ {
foreach (var insuredItem in insured.Items) foreach (var insuredItem in insured.Items)
{ {
@@ -419,8 +439,12 @@ public class InsuranceController(
/// <param name="itemsMap">Dictionary for quick item look-up by item ID</param> /// <param name="itemsMap">Dictionary for quick item look-up by item ID</param>
/// <param name="insuredTraderId">Trader ID from the Insurance object</param> /// <param name="insuredTraderId">Trader ID from the Insurance object</param>
/// <param name="toDelete">Tracked attachment ids to be removed</param> /// <param name="toDelete">Tracked attachment ids to be removed</param>
protected void ProcessAttachments(Dictionary<string, List<Item>> mainParentToAttachmentsMap, Dictionary<string, Item> itemsMap, string? insuredTraderId, protected void ProcessAttachments(
HashSet<string> toDelete) Dictionary<string, List<Item>> mainParentToAttachmentsMap,
Dictionary<string, Item> itemsMap,
string? insuredTraderId,
HashSet<string> toDelete
)
{ {
foreach (var parentObj in mainParentToAttachmentsMap) foreach (var parentObj in mainParentToAttachmentsMap)
{ {
@@ -444,7 +468,6 @@ public class InsuranceController(
} }
} }
/// <summary> /// <summary>
/// Takes an array of attachment items that belong to the same main-parent item, sorts them in descending order by /// Takes an array of attachment items that belong to the same main-parent item, sorts them in descending order by
/// their maximum price. For each attachment, a roll is made to determine if a deletion should be made. Once the /// their maximum price. For each attachment, a roll is made to determine if a deletion should be made. Once the
@@ -454,16 +477,26 @@ public class InsuranceController(
/// <param name="attachments">Array of attachment items to sort, filter, and roll</param> /// <param name="attachments">Array of attachment items to sort, filter, and roll</param>
/// <param name="traderId">ID of the trader to that has ensured these items</param> /// <param name="traderId">ID of the trader to that has ensured these items</param>
/// <param name="toDelete">array that accumulates the IDs of the items to be deleted</param> /// <param name="toDelete">array that accumulates the IDs of the items to be deleted</param>
protected void ProcessAttachmentByParent(List<Item> attachments, string? traderId, HashSet<string> toDelete) protected void ProcessAttachmentByParent(
List<Item> attachments,
string? traderId,
HashSet<string> toDelete
)
{ {
// Create dict of item ids + their flea/handbook price (highest is chosen) // Create dict of item ids + their flea/handbook price (highest is chosen)
var weightedAttachmentByPrice = WeightAttachmentsByPrice(attachments); var weightedAttachmentByPrice = WeightAttachmentsByPrice(attachments);
// Get how many attachments we want to pull off parent // Get how many attachments we want to pull off parent
var countOfAttachmentsToRemove = GetAttachmentCountToRemove(weightedAttachmentByPrice, traderId); var countOfAttachmentsToRemove = GetAttachmentCountToRemove(
weightedAttachmentByPrice,
traderId
);
// Create prob array and add all attachments with rouble price as the weight // Create prob array and add all attachments with rouble price as the weight
var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(_mathUtil, _cloner); var attachmentsProbabilityArray = new ProbabilityObjectArray<string, double?>(
_mathUtil,
_cloner
);
foreach (var attachmentTpl in weightedAttachmentByPrice) foreach (var attachmentTpl in weightedAttachmentByPrice)
{ {
attachmentsProbabilityArray.Add( attachmentsProbabilityArray.Add(
@@ -472,7 +505,10 @@ public class InsuranceController(
} }
// Draw x attachments from weighted array to remove from parent, remove from pool after being picked // Draw x attachments from weighted array to remove from parent, remove from pool after being picked
var attachmentIdsToRemove = attachmentsProbabilityArray.Draw((int) countOfAttachmentsToRemove, false); var attachmentIdsToRemove = attachmentsProbabilityArray.Draw(
(int)countOfAttachmentsToRemove,
false
);
foreach (var attachmentId in attachmentIdsToRemove) foreach (var attachmentId in attachmentIdsToRemove)
{ {
toDelete.Add(attachmentId); toDelete.Add(attachmentId);
@@ -492,7 +528,11 @@ public class InsuranceController(
/// <param name="attachmentIdsToRemove"></param> /// <param name="attachmentIdsToRemove"></param>
/// <param name="attachments"></param> /// <param name="attachments"></param>
/// <param name="attachmentPrices"></param> /// <param name="attachmentPrices"></param>
protected void LogAttachmentsBeingRemoved(List<string> attachmentIdsToRemove, List<Item> attachments, Dictionary<string, double> attachmentPrices) protected void LogAttachmentsBeingRemoved(
List<string> attachmentIdsToRemove,
List<Item> attachments,
Dictionary<string, double> attachmentPrices
)
{ {
var index = 1; var index = 1;
foreach (var attachmentId in attachmentIdsToRemove) foreach (var attachmentId in attachmentIdsToRemove)
@@ -500,8 +540,8 @@ public class InsuranceController(
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.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]}" + $"Price: {attachmentPrices[attachmentId]}"
); );
} }
@@ -521,7 +561,10 @@ public class InsuranceController(
// Get a dictionary of item tpls + their rouble price // Get a dictionary of item tpls + their rouble price
foreach (var attachment in attachments) foreach (var attachment in attachments)
{ {
var price = _ragfairPriceService.GetDynamicItemPrice(attachment.Template, Money.ROUBLES); var price = _ragfairPriceService.GetDynamicItemPrice(
attachment.Template,
Money.ROUBLES
);
if (price is not null) if (price is not null)
{ {
result[attachment.Id] = Math.Round(price ?? 0); result[attachment.Id] = Math.Round(price ?? 0);
@@ -539,7 +582,10 @@ public class InsuranceController(
/// <param name="weightedAttachmentByPrice">Dict of item Tpls and their rouble price</param> /// <param name="weightedAttachmentByPrice">Dict of item Tpls and their rouble price</param>
/// <param name="traderId">Trader the attachment is insured against</param> /// <param name="traderId">Trader the attachment is insured against</param>
/// <returns>Attachment count to remove</returns> /// <returns>Attachment count to remove</returns>
protected double GetAttachmentCountToRemove(Dictionary<string, double> weightedAttachmentByPrice, string? traderId) protected double GetAttachmentCountToRemove(
Dictionary<string, double> weightedAttachmentByPrice,
string? traderId
)
{ {
const int removeCount = 0; const int removeCount = 0;
@@ -550,7 +596,9 @@ public class InsuranceController(
// Get attachments count above or equal to price set in config // Get attachments count above or equal to price set in config
return weightedAttachmentByPrice return weightedAttachmentByPrice
.Where(attachment => attachment.Value >= _insuranceConfig.MinAttachmentRoublePriceToBeTaken) .Where(attachment =>
attachment.Value >= _insuranceConfig.MinAttachmentRoublePriceToBeTaken
)
.Count(_ => RollForDelete(traderId) ?? false); .Count(_ => RollForDelete(traderId) ?? false);
} }
@@ -577,8 +625,8 @@ public class InsuranceController(
// Map is labs + insurance is disabled in base.json // Map is labs + insurance is disabled in base.json
if (IsMapLabsAndInsuranceDisabled(insurance)) if (IsMapLabsAndInsuranceDisabled(insurance))
// Trader has labs-specific messages // Trader has labs-specific messages
// Wipe out returnable items // Wipe out returnable items
{ {
HandleLabsInsurance(traderDialogMessages, insurance); HandleLabsInsurance(traderDialogMessages, insurance);
} }
@@ -587,9 +635,14 @@ public class InsuranceController(
HandleLabyrinthInsurance(traderDialogMessages, insurance); HandleLabyrinthInsurance(traderDialogMessages, insurance);
} }
else if (insurance.Items?.Count == 0) else if (insurance.Items?.Count == 0)
// Not labs and no items to return // Not labs and no items to return
{ {
if (traderDialogMessages.TryGetValue("insuranceFailed", out var insuranceFailedTemplates)) if (
traderDialogMessages.TryGetValue(
"insuranceFailed",
out var insuranceFailedTemplates
)
)
{ {
insurance.MessageTemplateId = _randomUtil.GetArrayValue(insuranceFailedTemplates); insurance.MessageTemplateId = _randomUtil.GetArrayValue(insuranceFailedTemplates);
} }
@@ -615,8 +668,15 @@ public class InsuranceController(
/// <returns></returns> /// <returns></returns>
protected bool IsMapLabsAndInsuranceDisabled(Insurance insurance, string labsId = "laboratory") protected bool IsMapLabsAndInsuranceDisabled(Insurance insurance, string labsId = "laboratory")
{ {
return string.Equals(insurance.SystemData?.Location, labsId, StringComparison.OrdinalIgnoreCase) && return string.Equals(
!(_databaseService.GetLocation(labsId)?.Base?.Insurance.GetValueOrDefault(false) ?? false); insurance.SystemData?.Location,
labsId,
StringComparison.OrdinalIgnoreCase
)
&& !(
_databaseService.GetLocation(labsId)?.Base?.Insurance.GetValueOrDefault(false)
?? false
);
} }
/// <summary> /// <summary>
@@ -625,10 +685,20 @@ public class InsuranceController(
/// <param name="insurance">The insured items to process</param> /// <param name="insurance">The insured items to process</param>
/// <param name="labsId">OPTIONAL - id of labs location</param> /// <param name="labsId">OPTIONAL - id of labs location</param>
/// <returns></returns> /// <returns></returns>
protected bool IsMapLabyrinthAndInsuranceDisabled(Insurance insurance, string labyrinthId = "labyrinth") protected bool IsMapLabyrinthAndInsuranceDisabled(
Insurance insurance,
string labyrinthId = "labyrinth"
)
{ {
return string.Equals(insurance.SystemData?.Location, labyrinthId, StringComparison.OrdinalIgnoreCase) && return string.Equals(
!(_databaseService.GetLocation(labyrinthId)?.Base?.Insurance.GetValueOrDefault(false) ?? false); insurance.SystemData?.Location,
labyrinthId,
StringComparison.OrdinalIgnoreCase
)
&& !(
_databaseService.GetLocation(labyrinthId)?.Base?.Insurance.GetValueOrDefault(false)
?? false
);
} }
/// <summary> /// <summary>
@@ -636,7 +706,10 @@ public class InsuranceController(
/// </summary> /// </summary>
/// <param name="traderDialogMessages"></param> /// <param name="traderDialogMessages"></param>
/// <param name="insurance"></param> /// <param name="insurance"></param>
protected void HandleLabsInsurance(Dictionary<string, List<string>?>? traderDialogMessages, Insurance insurance) protected void HandleLabsInsurance(
Dictionary<string, List<string>?>? traderDialogMessages,
Insurance insurance
)
{ {
// Use labs specific messages if available, otherwise use default // Use labs specific messages if available, otherwise use default
var responseMesageIds = var responseMesageIds =
@@ -655,10 +728,13 @@ public class InsuranceController(
/// </summary> /// </summary>
/// <param name="traderDialogMessages"></param> /// <param name="traderDialogMessages"></param>
/// <param name="insurance"></param> /// <param name="insurance"></param>
protected void HandleLabyrinthInsurance(Dictionary<string, List<string>?>? traderDialogMessages, Insurance insurance) protected void HandleLabyrinthInsurance(
Dictionary<string, List<string>?>? traderDialogMessages,
Insurance insurance
)
{ {
// Use labs specific messages if available, otherwise use default // Use labs specific messages if available, otherwise use default
var responseMessageIds = var responseMessageIds =
traderDialogMessages["insuranceFailedLabyrinth"]?.Count > 0 traderDialogMessages["insuranceFailedLabyrinth"]?.Count > 0
? traderDialogMessages["insuranceFailedLabyrinth"] ? traderDialogMessages["insuranceFailedLabyrinth"]
: traderDialogMessages["insuranceFailed"]; : traderDialogMessages["insuranceFailed"];
@@ -669,7 +745,6 @@ public class InsuranceController(
insurance.Items = []; insurance.Items = [];
} }
/// <summary> /// <summary>
/// Roll for chance of item being 'lost' /// Roll for chance of item being 'lost'
/// </summary> /// </summary>
@@ -692,11 +767,15 @@ public class InsuranceController(
var roll = returnChance >= traderReturnChance; var roll = returnChance >= traderReturnChance;
// Log the roll with as much detail as possible. // Log the roll with as much detail as possible.
var itemName = insuredItem is not null ? $"{_itemHelper.GetItemName(insuredItem.Template)}" : ""; var itemName = insuredItem is not null
? $"{_itemHelper.GetItemName(insuredItem.Template)}"
: "";
var status = roll ? "Delete" : "Keep"; var status = roll ? "Delete" : "Keep";
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Rolling {itemName} with {trader} - Return {traderReturnChance}% - Roll: {returnChance} - Status: {status}"); _logger.Debug(
$"Rolling {itemName} with {trader} - Return {traderReturnChance}% - Roll: {returnChance} - Status: {status}"
);
} }
return roll; return roll;
@@ -709,7 +788,11 @@ public class InsuranceController(
/// <param name="request">Insurance request</param> /// <param name="request">Insurance request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse object to send to client</returns> /// <returns>ItemEventRouterResponse object to send to client</returns>
public ItemEventRouterResponse Insure(PmcData pmcData, InsureRequestData request, string sessionId) public ItemEventRouterResponse Insure(
PmcData pmcData,
InsureRequestData request,
string sessionId
)
{ {
var output = _eventOutputHolder.GetOutput(sessionId); var output = _eventOutputHolder.GetOutput(sessionId);
var itemsToInsureCount = request.Items.Count; var itemsToInsureCount = request.Items.Count;
@@ -729,7 +812,7 @@ public class InsuranceController(
pmcData, pmcData,
inventoryItemsHash[key], inventoryItemsHash[key],
request.TransactionId request.TransactionId
) ),
} }
); );
} }
@@ -742,7 +825,7 @@ public class InsuranceController(
Type = "", Type = "",
ItemId = "", ItemId = "",
Count = 0, Count = 0,
SchemeId = 0 SchemeId = 0,
}; };
// pay for the item insurance // pay for the item insurance
@@ -757,20 +840,22 @@ public class InsuranceController(
foreach (var key in request.Items) foreach (var key in request.Items)
{ {
pmcData.InsuredItems.Add( pmcData.InsuredItems.Add(
new InsuredItem new InsuredItem { TId = request.TransactionId, ItemId = inventoryItemsHash[key].Id }
{
TId = request.TransactionId,
ItemId = inventoryItemsHash[key].Id
}
); );
// If Item is Helmet or Body Armour -> Handle insurance of soft inserts // If Item is Helmet or Body Armour -> Handle insurance of soft inserts
if (_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(inventoryItemsHash[key].Template)) if (
_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(inventoryItemsHash[key].Template)
)
{ {
InsureSoftInserts(inventoryItemsHash[key], pmcData, request); InsureSoftInserts(inventoryItemsHash[key], pmcData, request);
} }
} }
_profileHelper.AddSkillPointsToPlayer(pmcData, SkillTypes.Charisma, itemsToInsureCount * 0.01); _profileHelper.AddSkillPointsToPlayer(
pmcData,
SkillTypes.Charisma,
itemsToInsureCount * 0.01
);
return output; return output;
} }
@@ -781,9 +866,15 @@ public class InsuranceController(
/// <param name="itemWithSoftInserts">Armor item to be insured</param> /// <param name="itemWithSoftInserts">Armor item to be insured</param>
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <param name="request">Insurance request data</param> /// <param name="request">Insurance request data</param>
public void InsureSoftInserts(Item itemWithSoftInserts, PmcData pmcData, InsureRequestData request) public void InsureSoftInserts(
Item itemWithSoftInserts,
PmcData pmcData,
InsureRequestData request
)
{ {
var softInsertSlots = pmcData.Inventory.Items.Where(item => item.ParentId == itemWithSoftInserts.Id && _itemHelper.IsSoftInsertId(item.SlotId.ToLower()) var softInsertSlots = pmcData.Inventory.Items.Where(item =>
item.ParentId == itemWithSoftInserts.Id
&& _itemHelper.IsSoftInsertId(item.SlotId.ToLower())
); );
foreach (var softInsertSlot in softInsertSlots) foreach (var softInsertSlot in softInsertSlots)
@@ -794,11 +885,7 @@ public class InsuranceController(
} }
pmcData.InsuredItems.Add( pmcData.InsuredItems.Add(
new InsuredItem new InsuredItem { TId = request.TransactionId, ItemId = softInsertSlot.Id }
{
TId = request.TransactionId,
ItemId = softInsertSlot.Id
}
); );
} }
} }
@@ -830,7 +917,9 @@ public class InsuranceController(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Item with id: {itemId} missing from player inventory, skipping"); _logger.Debug(
$"Item with id: {itemId} missing from player inventory, skipping"
);
} }
continue; continue;
@@ -838,7 +927,11 @@ public class InsuranceController(
items.TryAdd( items.TryAdd(
inventoryItemsHash[itemId].Template, inventoryItemsHash[itemId].Template,
_insuranceService.GetRoublePriceToInsureItemWithTrader(pmcData, inventoryItemsHash[itemId], trader) _insuranceService.GetRoublePriceToInsureItemWithTrader(
pmcData,
inventoryItemsHash[itemId],
trader
)
); );
} }
@@ -48,8 +48,12 @@ public class InventoryController(
/// <param name="moveRequest">Move request data</param> /// <param name="moveRequest">Move request data</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void MoveItem(PmcData pmcData, InventoryMoveRequestData moveRequest, string sessionId, public void MoveItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryMoveRequestData moveRequest,
string sessionId,
ItemEventRouterResponse output
)
{ {
if (output.Warnings?.Count > 0) if (output.Warnings?.Count > 0)
{ {
@@ -57,18 +61,27 @@ public class InventoryController(
} }
// Changes made to result apply to character inventory // Changes made to result apply to character inventory
var ownerInventoryItems = _inventoryHelper.GetOwnerInventoryItems(moveRequest, moveRequest.Item, sessionId); var ownerInventoryItems = _inventoryHelper.GetOwnerInventoryItems(
moveRequest,
moveRequest.Item,
sessionId
);
if (ownerInventoryItems.SameInventory.GetValueOrDefault(false)) if (ownerInventoryItems.SameInventory.GetValueOrDefault(false))
{ {
// Don't move items from trader to profile, this can happen when editing a traders preset weapons // Don't move items from trader to profile, this can happen when editing a traders preset weapons
if (moveRequest.FromOwner?.Type == "Trader" && !ownerInventoryItems.IsMail.GetValueOrDefault(false)) if (
moveRequest.FromOwner?.Type == "Trader"
&& !ownerInventoryItems.IsMail.GetValueOrDefault(false)
)
{ {
AppendTraderExploitErrorResponse(output); AppendTraderExploitErrorResponse(output);
return; return;
} }
// Check for item in inventory before allowing internal transfer // Check for item in inventory before allowing internal transfer
var originalItemLocation = ownerInventoryItems.From?.FirstOrDefault(item => item.Id == moveRequest.Item); var originalItemLocation = ownerInventoryItems.From?.FirstOrDefault(item =>
item.Id == moveRequest.Item
);
if (originalItemLocation is null) if (originalItemLocation is null)
{ {
// Internal item move but item never existed, possible dupe glitch // Internal item move but item never existed, possible dupe glitch
@@ -91,9 +104,19 @@ public class InventoryController(
} }
// Item is moving into or out of place of fame dog tag slot // Item is moving into or out of place of fame dog tag slot
if (moveRequest.To?.Container != null && if (
(moveRequest.To.Container.StartsWith("dogtag", StringComparison.OrdinalIgnoreCase) || moveRequest.To?.Container != null
originalLocationSlotId.StartsWith("dogtag", StringComparison.OrdinalIgnoreCase))) && (
moveRequest.To.Container.StartsWith(
"dogtag",
StringComparison.OrdinalIgnoreCase
)
|| originalLocationSlotId.StartsWith(
"dogtag",
StringComparison.OrdinalIgnoreCase
)
)
)
{ {
_hideoutHelper.ApplyPlaceOfFameDogtagBonus(pmcData); _hideoutHelper.ApplyPlaceOfFameDogtagBonus(pmcData);
} }
@@ -117,7 +140,7 @@ public class InventoryController(
_httpResponseUtil.AppendErrorToOutput( _httpResponseUtil.AppendErrorToOutput(
output, output,
_localisationService.GetText("inventory-edit_trader_item"), _localisationService.GetText("inventory-edit_trader_item"),
(BackendErrorCodes) 228 (BackendErrorCodes)228
); );
} }
@@ -129,13 +152,21 @@ public class InventoryController(
/// <param name="request">Pin/Lock request data</param> /// <param name="request">Pin/Lock request data</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void PinOrLock(PmcData pmcData, PinOrLockItemRequest request, string sessionId, public void PinOrLock(
ItemEventRouterResponse output) PmcData pmcData,
PinOrLockItemRequest request,
string sessionId,
ItemEventRouterResponse output
)
{ {
var itemToAdjust = pmcData.Inventory!.Items!.FirstOrDefault(item => item.Id == request.Item); var itemToAdjust = pmcData.Inventory!.Items!.FirstOrDefault(item =>
item.Id == request.Item
);
if (itemToAdjust is null) if (itemToAdjust is null)
{ {
_logger.Error($"Unable find item: {request.Item} to: {request.State} on player {sessionId}to: "); _logger.Error(
$"Unable find item: {request.Item} to: {request.State} on player {sessionId}to: "
);
return; return;
} }
@@ -165,7 +196,11 @@ public class InventoryController(
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
public void RedeemProfileReward(PmcData pmcData, RedeemProfileRequestData request, string sessionId) public void RedeemProfileReward(
PmcData pmcData,
RedeemProfileRequestData request,
string sessionId
)
{ {
var fullProfile = _profileHelper.GetFullProfile(sessionId); var fullProfile = _profileHelper.GetFullProfile(sessionId);
foreach (var rewardEvent in request.Events) foreach (var rewardEvent in request.Events)
@@ -173,49 +208,58 @@ public class InventoryController(
// Hard coded to `SYSTEM` for now // Hard coded to `SYSTEM` for now
// TODO: make this dynamic // TODO: make this dynamic
var dialog = fullProfile.DialogueRecords["59e7125688a45068a6249071"]; var dialog = fullProfile.DialogueRecords["59e7125688a45068a6249071"];
var mail = dialog.Messages.FirstOrDefault(message => message.Id == rewardEvent.MessageId); var mail = dialog.Messages.FirstOrDefault(message =>
var mailEvent = message.Id == rewardEvent.MessageId
mail.ProfileChangeEvents.FirstOrDefault(changeEvent => changeEvent.Id == rewardEvent.EventId); );
var mailEvent = mail.ProfileChangeEvents.FirstOrDefault(changeEvent =>
changeEvent.Id == rewardEvent.EventId
);
switch (mailEvent.Type) switch (mailEvent.Type)
{ {
case "TraderSalesSum": case "TraderSalesSum":
pmcData.TradersInfo[mailEvent.Entity].SalesSum = mailEvent.Value; pmcData.TradersInfo[mailEvent.Entity].SalesSum = mailEvent.Value;
_traderHelper.LevelUp(mailEvent.Entity, pmcData); _traderHelper.LevelUp(mailEvent.Entity, pmcData);
_logger.Success($"Set trader {mailEvent.Entity}: Sales Sum to: {mailEvent.Value}"); _logger.Success(
$"Set trader {mailEvent.Entity}: Sales Sum to: {mailEvent.Value}"
);
break; break;
case "TraderStanding": case "TraderStanding":
pmcData.TradersInfo[mailEvent.Entity].Standing = mailEvent.Value; pmcData.TradersInfo[mailEvent.Entity].Standing = mailEvent.Value;
_traderHelper.LevelUp(mailEvent.Entity, pmcData); _traderHelper.LevelUp(mailEvent.Entity, pmcData);
_logger.Success($"Set trader {mailEvent.Entity}: Standing to: {mailEvent.Value}"); _logger.Success(
$"Set trader {mailEvent.Entity}: Standing to: {mailEvent.Value}"
);
break; break;
case "ProfileLevel": case "ProfileLevel":
pmcData.Info.Experience = (int) mailEvent.Value.Value; pmcData.Info.Experience = (int)mailEvent.Value.Value;
// Will calculate level below // Will calculate level below
_traderHelper.ValidateTraderStandingsAndPlayerLevelForProfile(sessionId); _traderHelper.ValidateTraderStandingsAndPlayerLevelForProfile(sessionId);
_logger.Success($"Set profile xp to: {mailEvent.Value}"); _logger.Success($"Set profile xp to: {mailEvent.Value}");
break; break;
case "SkillPoints": case "SkillPoints":
{
var profileSkill = pmcData.Skills.Common.FirstOrDefault(x =>
x.Id == Enum.Parse<SkillTypes>(mailEvent.Entity)
);
if (profileSkill is null)
{ {
var profileSkill = pmcData.Skills.Common.FirstOrDefault(x => x.Id == Enum.Parse<SkillTypes>(mailEvent.Entity)); _logger.Warning($"Unable to find skill with name: {mailEvent.Entity}");
if (profileSkill is null) continue;
{
_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 "ExamineAllItems": case "ExamineAllItems":
{ {
var itemsToInspect = _itemHelper.GetItems().Where(x => x.Type != "Node"); var itemsToInspect = _itemHelper.GetItems().Where(x => x.Type != "Node");
FlagItemsAsInspectedAndRewardXp(itemsToInspect.Select(x => x.Id), fullProfile); FlagItemsAsInspectedAndRewardXp(itemsToInspect.Select(x => x.Id), fullProfile);
_logger.Success($"Flagged {itemsToInspect.Count()} items as examined"); _logger.Success($"Flagged {itemsToInspect.Count()} items as examined");
break; break;
} }
case "UnlockTrader": case "UnlockTrader":
pmcData.TradersInfo[mailEvent.Entity].Unlocked = true; pmcData.TradersInfo[mailEvent.Entity].Unlocked = true;
_logger.Success($"Trader {mailEvent.Entity} Unlocked"); _logger.Success($"Trader {mailEvent.Entity} Unlocked");
@@ -228,19 +272,21 @@ public class InventoryController(
break; break;
case "HideoutAreaLevel": case "HideoutAreaLevel":
{
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)
{ {
var areaName = mailEvent.Entity; desiredArea.Level = (int?)newValue;
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 = (int?) newValue;
}
break;
} }
break;
}
default: default:
_logger.Warning($"Unhandled profile reward event: {mailEvent.Type}"); _logger.Warning($"Unhandled profile reward event: {mailEvent.Type}");
@@ -254,7 +300,10 @@ public class InventoryController(
/// </summary> /// </summary>
/// <param name="itemTpls">Inspected item tpls</param> /// <param name="itemTpls">Inspected item tpls</param>
/// <param name="fullProfile">Profile to add xp to</param> /// <param name="fullProfile">Profile to add xp to</param>
protected void FlagItemsAsInspectedAndRewardXp(IEnumerable<string> itemTpls, SptProfile fullProfile) protected void FlagItemsAsInspectedAndRewardXp(
IEnumerable<string> itemTpls,
SptProfile fullProfile
)
{ {
foreach (var itemTpl in itemTpls) foreach (var itemTpl in itemTpls)
{ {
@@ -262,16 +311,23 @@ public class InventoryController(
if (!item.Key) if (!item.Key)
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText("inventory-unable_to_inspect_item_not_in_db", itemTpl) _localisationService.GetText(
"inventory-unable_to_inspect_item_not_in_db",
itemTpl
)
); );
return; return;
} }
fullProfile.CharacterData.PmcData.Info.Experience += item.Value.Properties.ExamineExperience; fullProfile.CharacterData.PmcData.Info.Experience += item.Value
.Properties
.ExamineExperience;
fullProfile.CharacterData.PmcData.Encyclopedia[itemTpl] = false; fullProfile.CharacterData.PmcData.Encyclopedia[itemTpl] = false;
fullProfile.CharacterData.ScavData.Info.Experience += item.Value.Properties.ExamineExperience; fullProfile.CharacterData.ScavData.Info.Experience += item.Value
.Properties
.ExamineExperience;
fullProfile.CharacterData.ScavData.Encyclopedia[itemTpl] = false; fullProfile.CharacterData.ScavData.Encyclopedia[itemTpl] = false;
} }
@@ -291,8 +347,12 @@ public class InventoryController(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void OpenRandomLootContainer(PmcData pmcData, OpenRandomLootContainerRequestData request, string sessionId, public void OpenRandomLootContainer(
ItemEventRouterResponse output) PmcData pmcData,
OpenRandomLootContainerRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
// Container player opened in their inventory // Container player opened in their inventory
var openedItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item); var openedItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
@@ -305,7 +365,7 @@ public class InventoryController(
{ {
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_VIOLET_OPEN, ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_VIOLET_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_BLUE_OPEN, ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_BLUE_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_GREEN_OPEN ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_GREEN_OPEN,
}; };
// Temp fix for unlocked weapon crate hideout craft // Temp fix for unlocked weapon crate hideout craft
if (isSealedWeaponBox || unlockedWeaponCrates.Contains(containerDetailsDb.Value.Id)) if (isSealedWeaponBox || unlockedWeaponCrates.Contains(containerDetailsDb.Value.Id))
@@ -320,10 +380,14 @@ public class InventoryController(
} }
else else
{ {
var rewardContainerDetails = _inventoryHelper.GetRandomLootContainerRewardDetails(openedItem.Template); var rewardContainerDetails = _inventoryHelper.GetRandomLootContainerRewardDetails(
openedItem.Template
);
if (rewardContainerDetails?.RewardCount == null) if (rewardContainerDetails?.RewardCount == null)
{ {
_logger.Error($"Unable to add loot to container: {openedItem.Template}, no rewards found"); _logger.Error(
$"Unable to add loot to container: {openedItem.Template}, no rewards found"
);
} }
else else
{ {
@@ -344,7 +408,7 @@ public class InventoryController(
ItemsWithModsToAdd = rewards, ItemsWithModsToAdd = rewards,
FoundInRaid = foundInRaid, FoundInRaid = foundInRaid,
Callback = null, Callback = null,
UseSortingTable = true UseSortingTable = true,
}; };
_inventoryHelper.AddItemsToStash(sessionId, addItemsRequest, pmcData, output); _inventoryHelper.AddItemsToStash(sessionId, addItemsRequest, pmcData, output);
if (output.Warnings?.Count > 0) if (output.Warnings?.Count > 0)
@@ -364,8 +428,12 @@ public class InventoryController(
/// <param name="request">Edit marker request</param> /// <param name="request">Edit marker request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void EditMapMarker(PmcData pmcData, InventoryEditMarkerRequestData request, string sessionId, public void EditMapMarker(
ItemEventRouterResponse output) PmcData pmcData,
InventoryEditMarkerRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
var mapItem = _mapMarkerService.EditMarkerOnMap(pmcData, request); var mapItem = _mapMarkerService.EditMarkerOnMap(pmcData, request);
@@ -380,8 +448,12 @@ public class InventoryController(
/// <param name="request">Delete marker request</param> /// <param name="request">Delete marker request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void DeleteMapMarker(PmcData pmcData, InventoryDeleteMarkerRequestData request, string sessionId, public void DeleteMapMarker(
ItemEventRouterResponse output) PmcData pmcData,
InventoryDeleteMarkerRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
var mapItem = _mapMarkerService.DeleteMarkerFromMap(pmcData, request); var mapItem = _mapMarkerService.DeleteMarkerFromMap(pmcData, request);
@@ -389,8 +461,12 @@ public class InventoryController(
output.ProfileChanges[sessionId].Items.ChangedItems.Add(mapItem); output.ProfileChanges[sessionId].Items.ChangedItems.Add(mapItem);
} }
public void CreateMapMarker(PmcData pmcData, InventoryCreateMarkerRequestData request, string sessionId, public void CreateMapMarker(
ItemEventRouterResponse output) PmcData pmcData,
InventoryCreateMarkerRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
var adjustedMapItem = _mapMarkerService.CreateMarkerOnMap(pmcData, request); var adjustedMapItem = _mapMarkerService.CreateMarkerOnMap(pmcData, request);
@@ -405,16 +481,25 @@ public class InventoryController(
/// <param name="request">Add marker request</param> /// <param name="request">Add marker request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void SortInventory(PmcData pmcData, InventorySortRequestData request, string sessionId, public void SortInventory(
ItemEventRouterResponse output) PmcData pmcData,
InventorySortRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
foreach (var change in request.ChangedItems) foreach (var change in request.ChangedItems)
{ {
var inventoryItem = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == change.Id); var inventoryItem = pmcData.Inventory.Items.FirstOrDefault(item =>
item.Id == change.Id
);
if (inventoryItem is null) if (inventoryItem is null)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText("inventory-unable_to_sort_inventory_restart_game", change.Id) _localisationService.GetText(
"inventory-unable_to_sort_inventory_restart_game",
change.Id
)
); );
continue; continue;
@@ -440,8 +525,11 @@ public class InventoryController(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ReadEncyclopedia(PmcData pmcData, InventoryReadEncyclopediaRequestData request, public ItemEventRouterResponse ReadEncyclopedia(
string sessionId) PmcData pmcData,
InventoryReadEncyclopediaRequestData request,
string sessionId
)
{ {
foreach (var id in request.Ids) foreach (var id in request.Ids)
{ {
@@ -458,8 +546,12 @@ public class InventoryController(
/// <param name="request">Examine item request</param> /// <param name="request">Examine item request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void ExamineItem(PmcData pmcData, InventoryExamineRequestData request, string sessionId, public void ExamineItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryExamineRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
string? itemId = null; string? itemId = null;
if (request.FromOwner is not null) if (request.FromOwner is not null)
@@ -470,12 +562,17 @@ public class InventoryController(
} }
catch catch
{ {
_logger.Error(_localisationService.GetText("inventory-examine_item_does_not_exist", request.Item)); _logger.Error(
_localisationService.GetText(
"inventory-examine_item_does_not_exist",
request.Item
)
);
} }
} }
if (itemId is null) if (itemId is null)
// item template // item template
{ {
if (_databaseService.GetItems().ContainsKey(request.Item)) if (_databaseService.GetItems().ContainsKey(request.Item))
{ {
@@ -514,14 +611,17 @@ public class InventoryController(
} }
if (request.FromOwner.Id == Traders.FENCE) if (request.FromOwner.Id == Traders.FENCE)
// Get tpl from fence assorts // Get tpl from fence assorts
{ {
return _fenceService.GetRawFenceAssorts().Items.FirstOrDefault(x => x.Id == request.Item)?.Template; return _fenceService
.GetRawFenceAssorts()
.Items.FirstOrDefault(x => x.Id == request.Item)
?.Template;
} }
if (request.FromOwner.Type == "Trader") if (request.FromOwner.Type == "Trader")
// Not fence // Not fence
// get tpl from trader assort // get tpl from trader assort
{ {
return _databaseService return _databaseService
.GetTrader(request.FromOwner.Id) .GetTrader(request.FromOwner.Id)
@@ -539,18 +639,23 @@ public class InventoryController(
} }
// Try alternate way of getting offer if first approach fails // Try alternate way of getting offer if first approach fails
var offer = _ragfairOfferService.GetOfferByOfferId(request.Item) ?? var offer =
_ragfairOfferService.GetOfferByOfferId(request.FromOwner.Id); _ragfairOfferService.GetOfferByOfferId(request.Item)
?? _ragfairOfferService.GetOfferByOfferId(request.FromOwner.Id);
// Try find examine item inside offer items array // Try find examine item inside offer items array
var matchingItem = offer.Items.FirstOrDefault(offerItem => offerItem.Id == request.Item); var matchingItem = offer.Items.FirstOrDefault(offerItem =>
offerItem.Id == request.Item
);
if (matchingItem is not null) if (matchingItem is not null)
{ {
return matchingItem.Template; return matchingItem.Template;
} }
// Unable to find item in database or ragfair // Unable to find item in database or ragfair
_logger.Warning(_localisationService.GetText("inventory-unable_to_find_item", request.Item)); _logger.Warning(
_localisationService.GetText("inventory-unable_to_find_item", request.Item)
);
} }
// get hideout item // get hideout item
@@ -565,7 +670,9 @@ public class InventoryController(
// all mail the player has // all mail the player has
var mail = _profileHelper.GetFullProfile(sessionId).DialogueRecords; var mail = _profileHelper.GetFullProfile(sessionId).DialogueRecords;
// per trader/person mail // per trader/person mail
var dialogue = mail.FirstOrDefault(x => x.Value.Messages.Any(m => m.Id == request.FromOwner.Id)); var dialogue = mail.FirstOrDefault(x =>
x.Value.Messages.Any(m => m.Id == request.FromOwner.Id)
);
// check each message from that trader/person for messages that match the ID we got // check each message from that trader/person for messages that match the ID we got
var message = dialogue.Value.Messages.FirstOrDefault(m => m.Id == request.FromOwner.Id); var message = dialogue.Value.Messages.FirstOrDefault(m => m.Id == request.FromOwner.Id);
// get the Id given and get the Template ID from that // get the Id given and get the Template ID from that
@@ -590,8 +697,12 @@ public class InventoryController(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void UnBindItem(PmcData pmcData, InventoryBindRequestData request, string sessionId, public void UnBindItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryBindRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
// Remove kvp from requested fast panel index // Remove kvp from requested fast panel index
@@ -607,8 +718,12 @@ public class InventoryController(
/// <param name="bindRequest"></param> /// <param name="bindRequest"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void BindItem(PmcData pmcData, InventoryBindRequestData bindRequest, string sessionId, public void BindItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryBindRequestData bindRequest,
string sessionId,
ItemEventRouterResponse output
)
{ {
// Remove link // Remove link
if (pmcData.Inventory.FastPanel.ContainsKey(bindRequest.Index)) if (pmcData.Inventory.FastPanel.ContainsKey(bindRequest.Index))
@@ -627,7 +742,11 @@ public class InventoryController(
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse TagItem(PmcData pmcData, InventoryTagRequestData request, string sessionId) public ItemEventRouterResponse TagItem(
PmcData pmcData,
InventoryTagRequestData request,
string sessionId
)
{ {
var itemToTag = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item); var itemToTag = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == request.Item);
if (itemToTag is null) if (itemToTag is null)
@@ -636,20 +755,13 @@ public class InventoryController(
$"Unable to tag item: {request.Item} as it cannot be found in player {sessionId} inventory" $"Unable to tag item: {request.Item} as it cannot be found in player {sessionId} inventory"
); );
return new ItemEventRouterResponse return new ItemEventRouterResponse { Warnings = [] };
{
Warnings = []
};
} }
// Null guard // Null guard
itemToTag.Upd ??= new Upd(); itemToTag.Upd ??= new Upd();
itemToTag.Upd.Tag = new UpdTag itemToTag.Upd.Tag = new UpdTag { Color = request.TagColor, Name = request.TagName };
{
Color = request.TagColor,
Name = request.TagName
};
return _eventOutputHolder.GetOutput(sessionId); return _eventOutputHolder.GetOutput(sessionId);
} }
@@ -661,7 +773,11 @@ public class InventoryController(
/// <param name="request">Toggle request</param> /// <param name="request">Toggle request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse ToggleItem(PmcData pmcData, InventoryToggleRequestData request, string sessionId) public ItemEventRouterResponse ToggleItem(
PmcData pmcData,
InventoryToggleRequestData request,
string sessionId
)
{ {
// May need to reassign to scav profile // May need to reassign to scav profile
var playerData = pmcData; var playerData = pmcData;
@@ -677,23 +793,22 @@ public class InventoryController(
{ {
_itemHelper.AddUpdObjectToItem( _itemHelper.AddUpdObjectToItem(
itemToToggle, itemToToggle,
_localisationService.GetText("inventory-item_to_toggle_missing_upd", itemToToggle.Id) _localisationService.GetText(
"inventory-item_to_toggle_missing_upd",
itemToToggle.Id
)
); );
itemToToggle.Upd.Togglable = new UpdTogglable itemToToggle.Upd.Togglable = new UpdTogglable { On = request.Value };
{
On = request.Value
};
return _eventOutputHolder.GetOutput(sessionId); return _eventOutputHolder.GetOutput(sessionId);
} }
_logger.Warning(_localisationService.GetText("inventory-unable_to_toggle_item_not_found", request.Item)); _logger.Warning(
_localisationService.GetText("inventory-unable_to_toggle_item_not_found", request.Item)
);
return new ItemEventRouterResponse return new ItemEventRouterResponse { Warnings = [] };
{
Warnings = []
};
} }
/// <summary> /// <summary>
@@ -703,7 +818,11 @@ public class InventoryController(
/// <param name="request">Fold item request</param> /// <param name="request">Fold item request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse FoldItem(PmcData pmcData, InventoryFoldRequestData request, string sessionId) public ItemEventRouterResponse FoldItem(
PmcData pmcData,
InventoryFoldRequestData request,
string sessionId
)
{ {
// May need to reassign to scav profile // May need to reassign to scav profile
var playerData = pmcData; var playerData = pmcData;
@@ -714,27 +833,26 @@ public class InventoryController(
playerData = _profileHelper.GetScavProfile(sessionId); playerData = _profileHelper.GetScavProfile(sessionId);
} }
var itemToFold = playerData.Inventory.Items.FirstOrDefault(item => item?.Id == request.Item); var itemToFold = playerData.Inventory.Items.FirstOrDefault(item =>
item?.Id == request.Item
);
if (itemToFold is null) if (itemToFold is null)
{ {
// Item not found // Item not found
_logger.Warning( _logger.Warning(
_localisationService.GetText("inventory-unable_to_fold_item_not_found_in_inventory", request.Item) _localisationService.GetText(
"inventory-unable_to_fold_item_not_found_in_inventory",
request.Item
)
); );
return new ItemEventRouterResponse return new ItemEventRouterResponse { Warnings = [] };
{
Warnings = []
};
} }
// Item may not have upd object // Item may not have upd object
_itemHelper.AddUpdObjectToItem(itemToFold); _itemHelper.AddUpdObjectToItem(itemToFold);
itemToFold.Upd.Foldable = new UpdFoldable itemToFold.Upd.Foldable = new UpdFoldable { Folded = request.Value };
{
Folded = request.Value
};
return _eventOutputHolder.GetOutput(sessionId); return _eventOutputHolder.GetOutput(sessionId);
} }
@@ -748,7 +866,11 @@ public class InventoryController(
/// <param name="request">Swap item request</param> /// <param name="request">Swap item request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse SwapItem(PmcData pmcData, InventorySwapRequestData request, string sessionId) public ItemEventRouterResponse SwapItem(
PmcData pmcData,
InventorySwapRequestData request,
string sessionId
)
{ {
// During post-raid scav transfer, the swap may be in the scav inventory // During post-raid scav transfer, the swap may be in the scav inventory
var playerData = pmcData; var playerData = pmcData;
@@ -763,11 +885,7 @@ public class InventoryController(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"inventory-unable_to_find_item_to_swap", "inventory-unable_to_find_item_to_swap",
new new { item1Id = request.Item, item2Id = request.Item2 }
{
item1Id = request.Item,
item2Id = request.Item2
}
) )
); );
} }
@@ -778,11 +896,7 @@ public class InventoryController(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"inventory-unable_to_find_item_to_swap", "inventory-unable_to_find_item_to_swap",
new new { item1Id = request.Item2, item2Id = request.Item }
{
item1Id = request.Item2,
item2Id = request.Item
}
) )
); );
} }
@@ -827,11 +941,19 @@ public class InventoryController(
/// <param name="request">Transfer item request</param> /// <param name="request">Transfer item request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void TransferItem(PmcData pmcData, InventoryTransferRequestData request, string sessionId, public void TransferItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryTransferRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
// TODO - check GetOwnerInventoryItems() call still works // TODO - check GetOwnerInventoryItems() call still works
var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(request, request.Item, sessionId); var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(
request,
request.Item,
sessionId
);
var sourceItem = inventoryItems.From.FirstOrDefault(item => item.Id == request.Item); var sourceItem = inventoryItems.From.FirstOrDefault(item => item.Id == request.Item);
var destinationItem = inventoryItems.To.FirstOrDefault(item => item.Id == request.With); var destinationItem = inventoryItems.To.FirstOrDefault(item => item.Id == request.With);
@@ -855,27 +977,21 @@ public class InventoryController(
return; return;
} }
sourceItem.Upd ??= new Upd sourceItem.Upd ??= new Upd { StackObjectsCount = 1 };
{
StackObjectsCount = 1
};
var sourceStackCount = sourceItem.Upd.StackObjectsCount; var sourceStackCount = sourceItem.Upd.StackObjectsCount;
if (sourceStackCount > request.Count) if (sourceStackCount > request.Count)
// Source items stack count greater than new desired count // Source items stack count greater than new desired count
{ {
sourceItem.Upd.StackObjectsCount = sourceStackCount - request.Count; sourceItem.Upd.StackObjectsCount = sourceStackCount - request.Count;
} }
else else
// Moving a full stack onto a smaller stack // Moving a full stack onto a smaller stack
{ {
sourceItem.Upd.StackObjectsCount = sourceStackCount - 1; sourceItem.Upd.StackObjectsCount = sourceStackCount - 1;
} }
destinationItem.Upd ??= new Upd destinationItem.Upd ??= new Upd { StackObjectsCount = 1 };
{
StackObjectsCount = 1
};
var destinationStackCount = destinationItem.Upd.StackObjectsCount; var destinationStackCount = destinationItem.Upd.StackObjectsCount;
destinationItem.Upd.StackObjectsCount = destinationStackCount + request.Count; destinationItem.Upd.StackObjectsCount = destinationStackCount + request.Count;
@@ -889,17 +1005,26 @@ public class InventoryController(
/// <param name="request">Merge stacks request</param> /// <param name="request">Merge stacks request</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void MergeItem(PmcData pmcData, InventoryMergeRequestData request, string sessionID, public void MergeItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryMergeRequestData request,
string sessionID,
ItemEventRouterResponse output
)
{ {
// Changes made to result apply to character inventory // Changes made to result apply to character inventory
var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(request, request.Item, sessionID); var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(
request,
request.Item,
sessionID
);
// Get source item (can be from player or trader or mail) // Get source item (can be from player or trader or mail)
var sourceItem = inventoryItems.From.FirstOrDefault(x => x.Id == request.Item); var sourceItem = inventoryItems.From.FirstOrDefault(x => x.Id == request.Item);
if (sourceItem is null) if (sourceItem is null)
{ {
var errorMessage = $"Unable to merge stacks as source item: {request.With} cannot be found"; var errorMessage =
$"Unable to merge stacks as source item: {request.With} cannot be found";
_logger.Error(errorMessage); _logger.Error(errorMessage);
_httpResponseUtil.AppendErrorToOutput(output, errorMessage); _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
@@ -911,7 +1036,8 @@ public class InventoryController(
var destinationItem = inventoryItems.To.FirstOrDefault(x => x.Id == request.With); var destinationItem = inventoryItems.To.FirstOrDefault(x => x.Id == request.With);
if (destinationItem is null) if (destinationItem is null)
{ {
var errorMessage = $"Unable to merge stacks as destination item: {request.With} cannot be found"; var errorMessage =
$"Unable to merge stacks as destination item: {request.With} cannot be found";
_logger.Error(errorMessage); _logger.Error(errorMessage);
_httpResponseUtil.AppendErrorToOutput(output, errorMessage); _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
@@ -920,48 +1046,38 @@ public class InventoryController(
} }
if (destinationItem.Upd?.StackObjectsCount is null) if (destinationItem.Upd?.StackObjectsCount is null)
// No stackcount on destination, add one // No stackcount on destination, add one
{ {
destinationItem.Upd = new Upd destinationItem.Upd = new Upd { StackObjectsCount = 1 };
{
StackObjectsCount = 1
};
} }
if (sourceItem.Upd is null) if (sourceItem.Upd is null)
{ {
sourceItem.Upd = new Upd sourceItem.Upd = new Upd { StackObjectsCount = 1 };
{
StackObjectsCount = 1
};
} }
else if (sourceItem.Upd.StackObjectsCount is null) else if (sourceItem.Upd.StackObjectsCount is null)
// Items pulled out of raid can have no stack count if the stack should be 1 // Items pulled out of raid can have no stack count if the stack should be 1
{ {
sourceItem.Upd.StackObjectsCount = 1; sourceItem.Upd.StackObjectsCount = 1;
} }
// Remove FiR status from destination stack when source stack has no FiR but destination does // Remove FiR status from destination stack when source stack has no FiR but destination does
if (!sourceItem.Upd.SpawnedInSession.GetValueOrDefault(false) && if (
destinationItem.Upd.SpawnedInSession.GetValueOrDefault(false)) !sourceItem.Upd.SpawnedInSession.GetValueOrDefault(false)
&& destinationItem.Upd.SpawnedInSession.GetValueOrDefault(false)
)
{ {
destinationItem.Upd.SpawnedInSession = false; destinationItem.Upd.SpawnedInSession = false;
} }
destinationItem.Upd.StackObjectsCount += destinationItem.Upd.StackObjectsCount += sourceItem.Upd.StackObjectsCount; // Add source stackcount to destination
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
output.ProfileChanges[sessionID]
.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) if (indexOfItemToRemove == -1)
{ {
var errorMessage = $"Unable to find item: {sourceItem.Id} to remove from sender inventory"; var errorMessage =
$"Unable to find item: {sourceItem.Id} to remove from sender inventory";
_logger.Error(errorMessage); _logger.Error(errorMessage);
_httpResponseUtil.AppendErrorToOutput(output, errorMessage); _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
@@ -979,11 +1095,19 @@ public class InventoryController(
/// <param name="request">Split stack request</param> /// <param name="request">Split stack request</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void SplitItem(PmcData pmcData, InventorySplitRequestData request, string sessionID, public void SplitItem(
ItemEventRouterResponse output) PmcData pmcData,
InventorySplitRequestData request,
string sessionID,
ItemEventRouterResponse output
)
{ {
// Changes made to result apply to character inventory // Changes made to result apply to character inventory
var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(request, request.NewItem, sessionID); var inventoryItems = _inventoryHelper.GetOwnerInventoryItems(
request,
request.NewItem,
sessionID
);
// Handle cartridge edge-case // Handle cartridge edge-case
if (request.Container.Location is null && request.Container.ContainerName == "cartridges") if (request.Container.Location is null && request.Container.ContainerName == "cartridges")
@@ -996,7 +1120,8 @@ public class InventoryController(
var itemToSplit = inventoryItems.From.FirstOrDefault(x => x.Id == request.SplitItem); var itemToSplit = inventoryItems.From.FirstOrDefault(x => x.Id == request.SplitItem);
if (itemToSplit is null) if (itemToSplit is null)
{ {
var errorMessage = $"Unable to split stack as source item: {request.SplitItem} cannot be found"; var errorMessage =
$"Unable to split stack as source item: {request.SplitItem} cannot be found";
_logger.Error(errorMessage); _logger.Error(errorMessage);
_httpResponseUtil.AppendErrorToOutput(output, errorMessage); _httpResponseUtil.AppendErrorToOutput(output, errorMessage);
@@ -1012,13 +1137,14 @@ public class InventoryController(
itemToSplit.Upd.StackObjectsCount -= request.Count; itemToSplit.Upd.StackObjectsCount -= request.Count;
// Inform client of change // Inform client of change
output.ProfileChanges[sessionID] output
.ProfileChanges[sessionID]
.Items.NewItems.Add( .Items.NewItems.Add(
new Item new Item
{ {
Id = request.NewItem, Id = request.NewItem,
Template = itemToSplit.Template, Template = itemToSplit.Template,
Upd = updatedUpd Upd = updatedUpd,
} }
); );
@@ -1031,7 +1157,7 @@ public class InventoryController(
ParentId = request.Container.Id, ParentId = request.Container.Id,
SlotId = request.Container.ContainerName, SlotId = request.Container.ContainerName,
Location = request.Container.Location, Location = request.Container.Location,
Upd = updatedUpd Upd = updatedUpd,
} }
); );
} }
@@ -1044,8 +1170,12 @@ public class InventoryController(
/// <param name="request">Discard item request</param> /// <param name="request">Discard item request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
public void DiscardItem(PmcData pmcData, InventoryRemoveRequestData request, string sessionId, public void DiscardItem(
ItemEventRouterResponse output) PmcData pmcData,
InventoryRemoveRequestData request,
string sessionId,
ItemEventRouterResponse output
)
{ {
if (request.FromOwner?.Type == "Mail") if (request.FromOwner?.Type == "Mail")
{ {
@@ -1054,9 +1184,10 @@ public class InventoryController(
return; return;
} }
var profileToRemoveItemFrom = request.FromOwner is null || request.FromOwner?.Id == pmcData.Id var profileToRemoveItemFrom =
? pmcData request.FromOwner is null || request.FromOwner?.Id == pmcData.Id
: _profileHelper.GetFullProfile(sessionId).CharacterData.ScavData; ? pmcData
: _profileHelper.GetFullProfile(sessionId).CharacterData.ScavData;
_inventoryHelper.RemoveItem(profileToRemoveItemFrom, request.Item, sessionId, output); _inventoryHelper.RemoveItem(profileToRemoveItemFrom, request.Item, sessionId, output);
} }
@@ -37,8 +37,11 @@ public class LauncherController(
public ConnectResponse Connect() public ConnectResponse Connect()
{ {
// Get all possible profile types + filter out any that are blacklisted // Get all possible profile types + filter out any that are blacklisted
var profileTemplates = _databaseService.GetProfileTemplates() var profileTemplates = _databaseService
.Where(profile => !_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profile.Key)) .GetProfileTemplates()
.Where(profile =>
!_coreConfig.Features.CreateNewProfileTypesBlacklist.Contains(profile.Key)
)
.ToDictionary(); .ToDictionary();
return new ConnectResponse return new ConnectResponse
@@ -46,7 +49,7 @@ public class LauncherController(
BackendUrl = _httpServerHelper.GetBackendUrl(), BackendUrl = _httpServerHelper.GetBackendUrl(),
Name = _coreConfig.ServerName, Name = _coreConfig.ServerName,
Editions = profileTemplates.Select(x => x.Key).ToList(), Editions = profileTemplates.Select(x => x.Key).ToList(),
ProfileDescriptions = GetProfileDescriptions(profileTemplates) ProfileDescriptions = GetProfileDescriptions(profileTemplates),
}; };
} }
@@ -55,7 +58,9 @@ public class LauncherController(
/// </summary> /// </summary>
/// <param name="profileTemplates">Profiles to get descriptions of</param> /// <param name="profileTemplates">Profiles to get descriptions of</param>
/// <returns>Dictionary of profile types with related descriptive text</returns> /// <returns>Dictionary of profile types with related descriptive text</returns>
protected Dictionary<string, string> GetProfileDescriptions(Dictionary<string, ProfileSides> profileTemplates) protected Dictionary<string, string> GetProfileDescriptions(
Dictionary<string, ProfileSides> profileTemplates
)
{ {
var result = new Dictionary<string, string>(); var result = new Dictionary<string, string>();
foreach (var (profileKey, profile) in profileTemplates) foreach (var (profileKey, profile) in profileTemplates)
@@ -72,7 +77,11 @@ public class LauncherController(
/// <returns></returns> /// <returns></returns>
public Info? Find(string? sessionId) public Info? Find(string? sessionId)
{ {
return sessionId is not null && _saveServer.GetProfiles().TryGetValue(sessionId, out var profile) ? profile.ProfileInfo : null; return
sessionId is not null
&& _saveServer.GetProfiles().TryGetValue(sessionId, out var profile)
? profile.ProfileInfo
: null;
} }
/// <summary> /// <summary>
@@ -126,7 +135,7 @@ public class LauncherController(
Username = info.Username, Username = info.Username,
Password = info.Password, Password = info.Password,
IsWiped = true, IsWiped = true,
Edition = info.Edition Edition = info.Edition,
}; };
_saveServer.CreateProfile(newProfileDetails); _saveServer.CreateProfile(newProfileDetails);
@@ -229,7 +238,10 @@ public class LauncherController(
/// <returns>Dictionary of mod name and mod details</returns> /// <returns>Dictionary of mod name and mod details</returns>
public Dictionary<string, AbstractModMetadata> GetLoadedServerMods() public Dictionary<string, AbstractModMetadata> GetLoadedServerMods()
{ {
return _loadedMods.ToDictionary(sptMod => sptMod.ModMetadata?.Name ?? "UNKNOWN MOD", sptMod => sptMod.ModMetadata); return _loadedMods.ToDictionary(
sptMod => sptMod.ModMetadata?.Name ?? "UNKNOWN MOD",
sptMod => sptMod.ModMetadata
);
} }
/// <summary> /// <summary>
@@ -48,7 +48,10 @@ public class LauncherV2Controller(
foreach (var profileKvP in dbProfiles) foreach (var profileKvP in dbProfiles)
{ {
result.TryAdd(profileKvP.Key, _localisationService.GetText(profileKvP.Value.DescriptionLocaleKey)); result.TryAdd(
profileKvP.Key,
_localisationService.GetText(profileKvP.Value.DescriptionLocaleKey)
);
} }
return result; return result;
@@ -174,7 +177,7 @@ public class LauncherV2Controller(
Username = info.Username, Username = info.Username,
Password = info.Password, Password = info.Password,
IsWiped = true, IsWiped = true,
Edition = info.Edition Edition = info.Edition,
}; };
_saveServer.CreateProfile(newProfileDetails); _saveServer.CreateProfile(newProfileDetails);
@@ -204,7 +207,10 @@ public class LauncherV2Controller(
{ {
foreach (var profile in _saveServer.GetProfiles()) foreach (var profile in _saveServer.GetProfiles())
{ {
if (info.Username == profile.Value.ProfileInfo!.Username && info.Password == profile.Value.ProfileInfo.Password) if (
info.Username == profile.Value.ProfileInfo!.Username
&& info.Password == profile.Value.ProfileInfo.Password
)
{ {
return profile.Key; return profile.Key;
} }
@@ -7,7 +7,6 @@ using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils.Cloners; using SPTarkov.Server.Core.Utils.Cloners;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
@@ -54,7 +53,7 @@ public class LocationController(
return new LocationsGenerateAllResponse return new LocationsGenerateAllResponse
{ {
Locations = locationResult, Locations = locationResult,
Paths = locationsFromDb.Base!.Paths Paths = locationsFromDb.Base!.Paths,
}; };
} }
@@ -70,9 +70,9 @@ public class MatchController(
RaidMode = "Online", RaidMode = "Online",
Mode = "deathmatch", Mode = "deathmatch",
ShortId = null, ShortId = null,
AdditionalInfo = null AdditionalInfo = null,
} },
] ],
}; };
return output; return output;
@@ -85,11 +85,7 @@ public class MatchController(
/// <returns>MatchGroupStatusResponse</returns> /// <returns>MatchGroupStatusResponse</returns>
public MatchGroupStatusResponse GetGroupStatus(MatchGroupStatusRequest request) public MatchGroupStatusResponse GetGroupStatus(MatchGroupStatusRequest request)
{ {
return new MatchGroupStatusResponse return new MatchGroupStatusResponse { Players = [], MaxPveCountExceeded = false };
{
Players = [],
MaxPveCountExceeded = false
};
} }
/// <summary> /// <summary>
@@ -138,7 +134,10 @@ public class MatchController(
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="request">Start raid request</param> /// <param name="request">Start raid request</param>
/// <returns>StartLocalRaidResponseData</returns> /// <returns>StartLocalRaidResponseData</returns>
public StartLocalRaidResponseData StartLocalRaid(string sessionId, StartLocalRaidRequestData request) public StartLocalRaidResponseData StartLocalRaid(
string sessionId,
StartLocalRaidRequestData request
)
{ {
return _locationLifecycleService.StartLocalRaid(sessionId, request); return _locationLifecycleService.StartLocalRaid(sessionId, request);
} }
@@ -7,9 +7,7 @@ using SPTarkov.Server.Core.Routers;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
public class NoteController( public class NoteController(EventOutputHolder _eventOutputHolder)
EventOutputHolder _eventOutputHolder
)
{ {
/// <summary> /// <summary>
/// </summary> /// </summary>
@@ -20,13 +18,10 @@ public class NoteController(
public ItemEventRouterResponse AddNote( public ItemEventRouterResponse AddNote(
PmcData pmcData, PmcData pmcData,
NoteActionRequest request, NoteActionRequest request,
string sessionId) string sessionId
)
{ {
var newNote = new Note var newNote = new Note { Time = request.Note.Time, Text = request.Note.Text };
{
Time = request.Note.Time,
Text = request.Note.Text
};
pmcData.Notes.DataNotes.Add(newNote); pmcData.Notes.DataNotes.Add(newNote);
return _eventOutputHolder.GetOutput(sessionId); return _eventOutputHolder.GetOutput(sessionId);
@@ -41,7 +36,8 @@ public class NoteController(
public ItemEventRouterResponse EditNote( public ItemEventRouterResponse EditNote(
PmcData pmcData, PmcData pmcData,
NoteActionRequest request, NoteActionRequest request,
string sessionId) string sessionId
)
{ {
var noteToEdit = pmcData.Notes.DataNotes[request.Index!.Value]; var noteToEdit = pmcData.Notes.DataNotes[request.Index!.Value];
noteToEdit.Time = request.Note.Time; noteToEdit.Time = request.Note.Time;
@@ -59,7 +55,8 @@ public class NoteController(
public ItemEventRouterResponse DeleteNote( public ItemEventRouterResponse DeleteNote(
PmcData pmcData, PmcData pmcData,
NoteActionRequest request, NoteActionRequest request,
string sessionId) string sessionId
)
{ {
pmcData.Notes?.DataNotes?.RemoveAt(request.Index!.Value); pmcData.Notes?.DataNotes?.RemoveAt(request.Index!.Value);
@@ -63,7 +63,7 @@ public class NotifierController(
ChannelId = sessionId, ChannelId = sessionId,
Url = "", Url = "",
NotifierServer = GetServer(sessionId), NotifierServer = GetServer(sessionId),
WebSocket = _notifierHelper.GetWebSocketServer(sessionId) WebSocket = _notifierHelper.GetWebSocketServer(sessionId),
}; };
} }
@@ -33,10 +33,7 @@ public class PresetController(
// Get root items tpl // Get root items tpl
var tpl = preset.Items.FirstOrDefault()?.Template; var tpl = preset.Items.FirstOrDefault()?.Template;
result.TryAdd(tpl, new PresetCacheDetails result.TryAdd(tpl, new PresetCacheDetails { PresetIds = [] });
{
PresetIds = []
});
result.TryGetValue(tpl, out var details); result.TryGetValue(tpl, out var details);
details.PresetIds.Add(presetId); details.PresetIds.Add(presetId);
@@ -23,8 +23,7 @@ public class PrestigeController(
/// </summary> /// </summary>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>Prestige</returns> /// <returns>Prestige</returns>
public Prestige GetPrestige( public Prestige GetPrestige(string sessionId)
string sessionId)
{ {
return _databaseService.GetTemplates().Prestige; return _databaseService.GetTemplates().Prestige;
} }
@@ -59,9 +58,7 @@ public class PrestigeController(
/// </list> /// </list>
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task ObtainPrestige( public async Task ObtainPrestige(string sessionId, ObtainPrestigeRequestList request)
string sessionId,
ObtainPrestigeRequestList request)
{ {
var profile = _profileHelper.GetFullProfile(sessionId); var profile = _profileHelper.GetFullProfile(sessionId);
if (profile is not null) if (profile is not null)
@@ -69,7 +66,7 @@ public class PrestigeController(
var pendingPrestige = new PendingPrestige var pendingPrestige = new PendingPrestige
{ {
PrestigeLevel = profile.CharacterData.PmcData.Info.PrestigeLevel + 1, PrestigeLevel = profile.CharacterData.PmcData.Info.PrestigeLevel + 1,
Items = request Items = request,
}; };
profile.SptData.PendingPrestige = pendingPrestige; profile.SptData.PendingPrestige = pendingPrestige;
@@ -57,7 +57,9 @@ public class ProfileController(
var profile = _saveServer.GetProfile(sessionId); var profile = _saveServer.GetProfile(sessionId);
if (profile?.CharacterData == null) if (profile?.CharacterData == null)
{ {
throw new Exception($"Unable to find character data for id: {sessionId}. Profile may be corrupt"); throw new Exception(
$"Unable to find character data for id: {sessionId}. Profile may be corrupt"
);
} }
var pmc = profile.CharacterData.PmcData; var pmc = profile.CharacterData.PmcData;
@@ -81,7 +83,7 @@ public class ProfileController(
MaxLevel = maxLvl, MaxLevel = maxLvl,
Edition = profile.ProfileInfo?.Edition ?? "", Edition = profile.ProfileInfo?.Edition ?? "",
ProfileId = profile.ProfileInfo?.ProfileId ?? "", ProfileId = profile.ProfileInfo?.ProfileId ?? "",
SptData = _profileHelper.GetDefaultSptDataObject() SptData = _profileHelper.GetDefaultSptDataObject(),
}; };
} }
@@ -93,12 +95,13 @@ public class ProfileController(
Side = pmc.Info.Side, Side = pmc.Info.Side,
CurrentLevel = pmc.Info.Level, CurrentLevel = pmc.Info.Level,
CurrentExperience = pmc.Info.Experience ?? 0, CurrentExperience = pmc.Info.Experience ?? 0,
PreviousExperience = currentLevel == 0 ? 0 : _profileHelper.GetExperience(currentLevel.Value), PreviousExperience =
currentLevel == 0 ? 0 : _profileHelper.GetExperience(currentLevel.Value),
NextLevel = xpToNextLevel, NextLevel = xpToNextLevel,
MaxLevel = maxLvl, MaxLevel = maxLvl,
Edition = profile.ProfileInfo?.Edition ?? "", Edition = profile.ProfileInfo?.Edition ?? "",
ProfileId = profile.ProfileInfo?.ProfileId ?? "", ProfileId = profile.ProfileInfo?.ProfileId ?? "",
SptData = profile.SptData SptData = profile.SptData,
}; };
} }
@@ -118,7 +121,10 @@ public class ProfileController(
/// <param name="request">Create profile request</param> /// <param name="request">Create profile request</param>
/// <param name="sessionId">Player id</param> /// <param name="sessionId">Player id</param>
/// <returns>Player id</returns> /// <returns>Player id</returns>
public virtual async ValueTask<string> CreateProfile(ProfileCreateRequestData request, string sessionId) public virtual async ValueTask<string> CreateProfile(
ProfileCreateRequestData request,
string sessionId
)
{ {
return await _createProfileService.CreateProfile(sessionId, request); return await _createProfileService.CreateProfile(sessionId, request);
} }
@@ -140,7 +146,10 @@ public class ProfileController(
/// <param name="request">Validate nickname request</param> /// <param name="request">Validate nickname request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns></returns> /// <returns></returns>
public virtual NicknameValidationResult ValidateNickname(ValidateNicknameRequestData request, string sessionId) public virtual NicknameValidationResult ValidateNickname(
ValidateNicknameRequestData request,
string sessionId
)
{ {
if (request.Nickname?.Length < 3) if (request.Nickname?.Length < 3)
{ {
@@ -162,13 +171,13 @@ public class ProfileController(
/// <param name="request">Change nickname request</param> /// <param name="request">Change nickname request</param>
/// <param name="sessionId">Player id</param> /// <param name="sessionId">Player id</param>
/// <returns></returns> /// <returns></returns>
public virtual NicknameValidationResult ChangeNickname(ProfileChangeNicknameRequestData request, string sessionId) public virtual NicknameValidationResult ChangeNickname(
ProfileChangeNicknameRequestData request,
string sessionId
)
{ {
var output = ValidateNickname( var output = ValidateNickname(
new ValidateNicknameRequestData new ValidateNicknameRequestData { Nickname = request.Nickname },
{
Nickname = request.Nickname
},
sessionId sessionId
); );
@@ -200,7 +209,10 @@ public class ProfileController(
/// <param name="request">Search profiles request</param> /// <param name="request">Search profiles request</param>
/// <param name="sessionID">Player id</param> /// <param name="sessionID">Player id</param>
/// <returns>Found profiles</returns> /// <returns>Found profiles</returns>
public virtual List<SearchFriendResponse> SearchProfiles(SearchProfilesRequestData request, string sessionID) public virtual List<SearchFriendResponse> SearchProfiles(
SearchProfilesRequestData request,
string sessionID
)
{ {
var result = new List<SearchFriendResponse>(); var result = new List<SearchFriendResponse>();
@@ -241,7 +253,7 @@ public class ProfileController(
Status = "Free", Status = "Free",
Sid = "", Sid = "",
Ip = "", Ip = "",
Port = 0 Port = 0,
}, },
new ProfileStatusData new ProfileStatusData
{ {
@@ -250,9 +262,9 @@ public class ProfileController(
Status = "Free", Status = "Free",
Sid = "", Sid = "",
Ip = "", Ip = "",
Port = 0 Port = 0,
} },
] ],
}; };
return response; return response;
@@ -264,13 +276,21 @@ public class ProfileController(
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <param name="request">Get other profile request</param> /// <param name="request">Get other profile request</param>
/// <returns>GetOtherProfileResponse</returns> /// <returns>GetOtherProfileResponse</returns>
public virtual GetOtherProfileResponse GetOtherProfile(string sessionId, GetOtherProfileRequest request) public virtual GetOtherProfileResponse GetOtherProfile(
string sessionId,
GetOtherProfileRequest request
)
{ {
// Find the profile by the account ID, fall back to the current player if we can't find the account // Find the profile by the account ID, fall back to the current player if we can't find the account
var profileToView = _profileHelper.GetFullProfileByAccountId(request.AccountId); var profileToView = _profileHelper.GetFullProfileByAccountId(request.AccountId);
if (profileToView?.CharacterData?.PmcData is null || profileToView.CharacterData.ScavData is null) if (
profileToView?.CharacterData?.PmcData is null
|| profileToView.CharacterData.ScavData is null
)
{ {
_logger.Warning($"Unable to get profile: {request.AccountId} to show, falling back to own profile"); _logger.Warning(
$"Unable to get profile: {request.AccountId} to show, falling back to own profile"
);
profileToView = _profileHelper.GetFullProfile(sessionId); profileToView = _profileHelper.GetFullProfile(sessionId);
} }
@@ -283,12 +303,17 @@ public class ProfileController(
hideoutKeys.Add(profileToViewPmc.Inventory.HideoutCustomizationStashId); hideoutKeys.Add(profileToViewPmc.Inventory.HideoutCustomizationStashId);
// Find hideout items e.g. posters // Find hideout items e.g. posters
var hideoutRootItems = profileToViewPmc.Inventory.Items.Where(x => hideoutKeys.Contains(x.Id)); var hideoutRootItems = profileToViewPmc.Inventory.Items.Where(x =>
hideoutKeys.Contains(x.Id)
);
var itemsToReturn = new List<Item>(); var itemsToReturn = new List<Item>();
foreach (var rootItems in hideoutRootItems) foreach (var rootItems in hideoutRootItems)
{ {
// Check each root items for children and add // Check each root items for children and add
var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(profileToViewPmc.Inventory.Items, rootItems.Id); var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(
profileToViewPmc.Inventory.Items,
rootItems.Id
);
itemsToReturn.AddRange(itemWithChildren); itemsToReturn.AddRange(itemWithChildren);
} }
@@ -301,10 +326,12 @@ public class ProfileController(
Nickname = profileToViewPmc.Info.Nickname, Nickname = profileToViewPmc.Info.Nickname,
Side = profileToViewPmc.Info.Side, Side = profileToViewPmc.Info.Side,
Experience = profileToViewPmc.Info.Experience, Experience = profileToViewPmc.Info.Experience,
MemberCategory = (int)(profileToViewPmc.Info.MemberCategory ?? MemberCategory.Default), MemberCategory = (int)(
profileToViewPmc.Info.MemberCategory ?? MemberCategory.Default
),
BannedState = profileToViewPmc.Info.BannedState, BannedState = profileToViewPmc.Info.BannedState,
BannedUntil = profileToViewPmc.Info.BannedUntil, BannedUntil = profileToViewPmc.Info.BannedUntil,
RegistrationDate = profileToViewPmc.Info.RegistrationDate RegistrationDate = profileToViewPmc.Info.RegistrationDate,
}, },
Customization = new OtherProfileCustomization Customization = new OtherProfileCustomization
{ {
@@ -312,13 +339,13 @@ public class ProfileController(
Body = profileToViewPmc.Customization.Body, Body = profileToViewPmc.Customization.Body,
Feet = profileToViewPmc.Customization.Feet, Feet = profileToViewPmc.Customization.Feet,
Hands = profileToViewPmc.Customization.Hands, Hands = profileToViewPmc.Customization.Hands,
Dogtag = profileToViewPmc.Customization.DogTag Dogtag = profileToViewPmc.Customization.DogTag,
}, },
Skills = profileToViewPmc.Skills, Skills = profileToViewPmc.Skills,
Equipment = new OtherProfileEquipment Equipment = new OtherProfileEquipment
{ {
Id = profileToViewPmc.Inventory.Equipment, Id = profileToViewPmc.Inventory.Equipment,
Items = profileToViewPmc.Inventory.Items Items = profileToViewPmc.Inventory.Items,
}, },
Achievements = profileToViewPmc.Achievements, Achievements = profileToViewPmc.Achievements,
FavoriteItems = _profileHelper.GetOtherProfileFavorites(profileToViewPmc), FavoriteItems = _profileHelper.GetOtherProfileFavorites(profileToViewPmc),
@@ -327,21 +354,21 @@ public class ProfileController(
Eft = new OtherProfileSubStats Eft = new OtherProfileSubStats
{ {
TotalInGameTime = profileToViewPmc.Stats.Eft.TotalInGameTime, TotalInGameTime = profileToViewPmc.Stats.Eft.TotalInGameTime,
OverAllCounters = profileToViewPmc.Stats.Eft.OverallCounters OverAllCounters = profileToViewPmc.Stats.Eft.OverallCounters,
} },
}, },
ScavStats = new OtherProfileStats ScavStats = new OtherProfileStats
{ {
Eft = new OtherProfileSubStats Eft = new OtherProfileSubStats
{ {
TotalInGameTime = profileToViewScav.Stats.Eft.TotalInGameTime, TotalInGameTime = profileToViewScav.Stats.Eft.TotalInGameTime,
OverAllCounters = profileToViewScav.Stats.Eft.OverallCounters OverAllCounters = profileToViewScav.Stats.Eft.OverallCounters,
} },
}, },
Hideout = profileToViewPmc.Hideout, Hideout = profileToViewPmc.Hideout,
CustomizationStash = profileToViewPmc.Inventory.HideoutCustomizationStashId, CustomizationStash = profileToViewPmc.Inventory.HideoutCustomizationStashId,
HideoutAreaStashes = profileToViewPmc.Inventory.HideoutAreaStashes, HideoutAreaStashes = profileToViewPmc.Inventory.HideoutAreaStashes,
Items = itemsToReturn Items = itemsToReturn,
}; };
return profile; return profile;
@@ -14,7 +14,6 @@ using SPTarkov.Server.Core.Utils;
using SPTarkov.Server.Core.Utils.Cloners; using SPTarkov.Server.Core.Utils.Cloners;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
@@ -63,13 +62,19 @@ public class QuestController(
/// <param name="acceptedQuest">Quest accepted</param> /// <param name="acceptedQuest">Quest accepted</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse AcceptQuest(PmcData pmcData, AcceptQuestRequestData acceptedQuest, string sessionID) public ItemEventRouterResponse AcceptQuest(
PmcData pmcData,
AcceptQuestRequestData acceptedQuest,
string sessionID
)
{ {
var acceptQuestResponse = _eventOutputHolder.GetOutput(sessionID); var acceptQuestResponse = _eventOutputHolder.GetOutput(sessionID);
// Does quest exist in profile // Does quest exist in profile
// Restarting a failed quest can mean quest exists 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) if (existingQuestStatus is not null)
{ {
// Update existing // Update existing
@@ -81,7 +86,11 @@ public class QuestController(
else else
{ {
// Add new quest to server profile // Add new quest to server profile
var newQuest = _questHelper.GetQuestReadyForProfile(pmcData, QuestStatusEnum.Started, acceptedQuest); var newQuest = _questHelper.GetQuestReadyForProfile(
pmcData,
QuestStatusEnum.Started,
acceptedQuest
);
pmcData.Quests.Add(newQuest); pmcData.Quests.Add(newQuest);
} }
@@ -94,10 +103,10 @@ public class QuestController(
AddTaskConditionCountersToProfile( AddTaskConditionCountersToProfile(
questFromDb.Conditions.AvailableForFinish, questFromDb.Conditions.AvailableForFinish,
pmcData, pmcData,
acceptedQuest.QuestId); acceptedQuest.QuestId
);
} }
// Get messageId of text to send to player as text message in game // Get messageId of text to send to player as text message in game
var messageId = _questHelper.GetMessageIdForQuestStart( var messageId = _questHelper.GetMessageIdForQuestStart(
questFromDb.StartedMessageText, questFromDb.StartedMessageText,
@@ -120,7 +129,9 @@ public class QuestController(
MessageType.QuestStart, MessageType.QuestStart,
messageId, messageId,
startedQuestRewardItems.ToList(), 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 // Having accepted new quest, look for newly unlocked quests and inform client of them
@@ -142,7 +153,11 @@ public class QuestController(
/// <param name="questConditions">Conditions to iterate over and possibly add to profile</param> /// <param name="questConditions">Conditions to iterate over and possibly add to profile</param>
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <param name="questId">Quest where conditions originated</param> /// <param name="questId">Quest where conditions originated</param>
protected void AddTaskConditionCountersToProfile(List<QuestCondition> questConditions, PmcData pmcData, string questId) protected void AddTaskConditionCountersToProfile(
List<QuestCondition> questConditions,
PmcData pmcData,
string questId
)
{ {
foreach (var condition in questConditions) foreach (var condition in questConditions)
{ {
@@ -161,7 +176,7 @@ public class QuestController(
Id = condition.Id, Id = condition.Id,
SourceId = questId, SourceId = questId,
Type = condition.ConditionType, Type = condition.ConditionType,
Value = 0 Value = 0,
}; };
break; break;
} }
@@ -178,7 +193,11 @@ public class QuestController(
/// <param name="request">Complete quest request</param> /// <param name="request">Complete quest request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse CompleteQuest(PmcData pmcData, CompleteQuestRequestData request, string sessionId) public ItemEventRouterResponse CompleteQuest(
PmcData pmcData,
CompleteQuestRequestData request,
string sessionId
)
{ {
return _questHelper.CompleteQuest(pmcData, request, sessionId); return _questHelper.CompleteQuest(pmcData, request, sessionId);
} }
@@ -191,7 +210,11 @@ public class QuestController(
/// <param name="request">Handover request</param> /// <param name="request">Handover request</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse HandoverQuest(PmcData pmcData, HandoverQuestRequestData request, string sessionID) public ItemEventRouterResponse HandoverQuest(
PmcData pmcData,
HandoverQuestRequestData request,
string sessionID
)
{ {
var quest = _questHelper.GetQuestFromDb(request.QuestId, pmcData); var quest = _questHelper.GetQuestFromDb(request.QuestId, pmcData);
List<string> handoverQuestTypes = ["HandoverItem", "WeaponAssembly"]; List<string> handoverQuestTypes = ["HandoverItem", "WeaponAssembly"];
@@ -202,7 +225,11 @@ public class QuestController(
// Decrement number of items handed in // Decrement number of items handed in
QuestCondition? handoverRequirements = null; QuestCondition? handoverRequirements = null;
foreach (var condition in quest.Conditions.AvailableForFinish.Where(condition => condition.Id == request.ConditionId)) foreach (
var condition in quest.Conditions.AvailableForFinish.Where(condition =>
condition.Id == request.ConditionId
)
)
{ {
// Not a handover quest type, skip // Not a handover quest type, skip
if (!handoverQuestTypes.Contains(condition.ConditionType)) if (!handoverQuestTypes.Contains(condition.ConditionType))
@@ -216,7 +243,7 @@ public class QuestController(
if (pmcData.TaskConditionCounters.TryGetValue("ConditionId", out var counter)) if (pmcData.TaskConditionCounters.TryGetValue("ConditionId", out var counter))
{ {
handedInCount -= (int) (counter.Value ?? 0); handedInCount -= (int)(counter.Value ?? 0);
if (handedInCount <= 0) if (handedInCount <= 0)
{ {
@@ -228,7 +255,7 @@ public class QuestController(
questId = request.QuestId, questId = request.QuestId,
conditionId = request.ConditionId, conditionId = request.ConditionId,
profileCounter = counter.Value, profileCounter = counter.Value,
value = handedInCount value = handedInCount,
} }
) )
); );
@@ -242,15 +269,26 @@ public class QuestController(
if (isItemHandoverQuest && handedInCount == 0) if (isItemHandoverQuest && handedInCount == 0)
{ {
return ShowRepeatableQuestInvalidConditionError(request.QuestId, request.ConditionId, output); return ShowRepeatableQuestInvalidConditionError(
request.QuestId,
request.ConditionId,
output
);
} }
var totalItemCountToRemove = 0d; var totalItemCountToRemove = 0d;
foreach (var itemHandover in request.Items) foreach (var itemHandover in request.Items)
{ {
var matchingItemInProfile = pmcData.Inventory.Items.FirstOrDefault(item => item.Id == itemHandover.Id); var matchingItemInProfile = pmcData.Inventory.Items.FirstOrDefault(item =>
if (!(matchingItemInProfile is not null && handoverRequirements.Target.List.Contains(matchingItemInProfile.Template))) item.Id == itemHandover.Id
// Item handed in by player doesn't match what was requested );
if (
!(
matchingItemInProfile is not null
&& handoverRequirements.Target.List.Contains(matchingItemInProfile.Template)
)
)
// Item handed in by player doesn't match what was requested
{ {
return ShowQuestItemHandoverMatchError( return ShowQuestItemHandoverMatchError(
request, request,
@@ -261,7 +299,10 @@ public class QuestController(
} }
// Remove the right quantity of given items // Remove the right quantity of given items
var itemCountToRemove = Math.Min(itemHandover.Count ?? 0, handedInCount - totalItemCountToRemove); var itemCountToRemove = Math.Min(
itemHandover.Count ?? 0,
handedInCount - totalItemCountToRemove
);
totalItemCountToRemove += itemCountToRemove; totalItemCountToRemove += itemCountToRemove;
if (itemHandover.Count - itemCountToRemove > 0) if (itemHandover.Count - itemCountToRemove > 0)
{ {
@@ -269,7 +310,7 @@ public class QuestController(
_questHelper.ChangeItemStack( _questHelper.ChangeItemStack(
pmcData, pmcData,
itemHandover.Id, itemHandover.Id,
(int) (itemHandover.Count - itemCountToRemove), (int)(itemHandover.Count - itemCountToRemove),
sessionID, sessionID,
output output
); );
@@ -283,17 +324,16 @@ public class QuestController(
else else
{ {
// Remove item with children // Remove item with children
var toRemove = _itemHelper.FindAndReturnChildrenByItems(pmcData.Inventory.Items, itemHandover.Id); var toRemove = _itemHelper.FindAndReturnChildrenByItems(
pmcData.Inventory.Items,
itemHandover.Id
);
var index = pmcData.Inventory.Items.Count; var index = pmcData.Inventory.Items.Count;
// Important: don't tell the client to remove the attachments, it will handle it // Important: don't tell the client to remove the attachments, it will handle it
output.ProfileChanges[sessionID] output
.Items.DeletedItems.Add( .ProfileChanges[sessionID]
new Item .Items.DeletedItems.Add(new Item { Id = itemHandover.Id });
{
Id = itemHandover.Id
}
);
// Important: loop backward when removing items from the array we're looping on // Important: loop backward when removing items from the array we're looping on
while (index-- > 0) while (index-- > 0)
@@ -315,7 +355,7 @@ public class QuestController(
childItems.RemoveAt(0); // Remove the parent childItems.RemoveAt(0); // Remove the parent
// Sort by the current `location` and update // 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++) for (var i = 0; i < childItems.Count; i++)
{ {
@@ -344,15 +384,15 @@ public class QuestController(
/// <param name="conditionId">Relevant condition id that failed</param> /// <param name="conditionId">Relevant condition id that failed</param>
/// <param name="output">Client response</param> /// <param name="output">Client response</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
protected ItemEventRouterResponse ShowRepeatableQuestInvalidConditionError(string questId, string conditionId, ItemEventRouterResponse output) protected ItemEventRouterResponse ShowRepeatableQuestInvalidConditionError(
string questId,
string conditionId,
ItemEventRouterResponse output
)
{ {
var errorMessage = _localisationService.GetText( var errorMessage = _localisationService.GetText(
"repeatable-quest_handover_failed_condition_invalid", "repeatable-quest_handover_failed_condition_invalid",
new new { questId, conditionId }
{
questId,
conditionId
}
); );
_logger.Error(errorMessage); _logger.Error(errorMessage);
@@ -367,8 +407,12 @@ public class QuestController(
/// <param name="handoverRequirements">Quest handover requirements</param> /// <param name="handoverRequirements">Quest handover requirements</param>
/// <param name="output">Response to send to user</param> /// <param name="output">Response to send to user</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
protected ItemEventRouterResponse ShowQuestItemHandoverMatchError(HandoverQuestRequestData handoverQuestRequest, Item? itemHandedOver, protected ItemEventRouterResponse ShowQuestItemHandoverMatchError(
QuestCondition? handoverRequirements, ItemEventRouterResponse output) HandoverQuestRequestData handoverQuestRequest,
Item? itemHandedOver,
QuestCondition? handoverRequirements,
ItemEventRouterResponse output
)
{ {
var errorMessage = _localisationService.GetText( var errorMessage = _localisationService.GetText(
"quest-handover_wrong_item", "quest-handover_wrong_item",
@@ -376,7 +420,7 @@ public class QuestController(
{ {
questId = handoverQuestRequest.QuestId, questId = handoverQuestRequest.QuestId,
handedInTpl = itemHandedOver?.Template ?? "UNKNOWN", handedInTpl = itemHandedOver?.Template ?? "UNKNOWN",
requiredTpl = handoverRequirements.Target.List.FirstOrDefault() requiredTpl = handoverRequirements.Target.List.FirstOrDefault(),
} }
); );
_logger.Error(errorMessage); _logger.Error(errorMessage);
@@ -392,7 +436,12 @@ public class QuestController(
/// <param name="conditionId">Backend counter id to update</param> /// <param name="conditionId">Backend counter id to update</param>
/// <param name="questId">Quest id counter is associated with</param> /// <param name="questId">Quest id counter is associated with</param>
/// <param name="counterValue">Value to increment the backend counter with</param> /// <param name="counterValue">Value to increment the backend counter with</param>
protected void UpdateProfileTaskConditionCounterValue(PmcData pmcData, string conditionId, string questId, double counterValue) protected void UpdateProfileTaskConditionCounterValue(
PmcData pmcData,
string conditionId,
string questId,
double counterValue
)
{ {
if (pmcData.TaskConditionCounters.GetValueOrDefault(conditionId) != null) if (pmcData.TaskConditionCounters.GetValueOrDefault(conditionId) != null)
{ {
@@ -401,13 +450,16 @@ public class QuestController(
return; return;
} }
pmcData.TaskConditionCounters.Add(conditionId, new TaskConditionCounter pmcData.TaskConditionCounters.Add(
{ conditionId,
Id = conditionId, new TaskConditionCounter
SourceId = questId, {
Type = "HandoverItem", Id = conditionId,
Value = counterValue SourceId = questId,
}); Type = "HandoverItem",
Value = counterValue,
}
);
} }
/// <summary> /// <summary>
@@ -418,7 +470,12 @@ public class QuestController(
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <param name="output"></param> /// <param name="output"></param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse FailQuest(PmcData pmcData, FailQuestRequestData request, string sessionID, ItemEventRouterResponse output) public ItemEventRouterResponse FailQuest(
PmcData pmcData,
FailQuestRequestData request,
string sessionID,
ItemEventRouterResponse output
)
{ {
_questHelper.FailQuest(pmcData, request, sessionID, output); _questHelper.FailQuest(pmcData, request, sessionID, output);
@@ -114,8 +114,9 @@ public class RagfairController
// Check profile is capable of creating offers // Check profile is capable of creating offers
var pmcProfile = profile.CharacterData.PmcData; var pmcProfile = profile.CharacterData.PmcData;
if ( if (
pmcProfile.RagfairInfo is not null && pmcProfile.RagfairInfo is not null
pmcProfile.Info.Level >= _databaseService.GetGlobals().Configuration.RagFair.MinUserLevel && pmcProfile.Info.Level
>= _databaseService.GetGlobals().Configuration.RagFair.MinUserLevel
) )
{ {
_ragfairOfferHelper.ProcessOffersOnProfile(sessionId); _ragfairOfferHelper.ProcessOffersOnProfile(sessionId);
@@ -139,19 +140,28 @@ public class RagfairController
{ {
Offers = [], Offers = [],
OffersCount = searchRequest.Limit, OffersCount = searchRequest.Limit,
SelectedCategory = searchRequest.HandbookId SelectedCategory = searchRequest.HandbookId,
}; };
// Get all offers ready for sorting/filtering below // Get all offers ready for sorting/filtering below
result.Offers = GetOffersForSearchType(searchRequest, itemsToAdd, traderAssorts, profile.CharacterData.PmcData); result.Offers = GetOffersForSearchType(
searchRequest,
itemsToAdd,
traderAssorts,
profile.CharacterData.PmcData
);
// Client requested a category refresh // Client requested a category refresh
if (searchRequest.UpdateOfferCount.GetValueOrDefault(false)) if (searchRequest.UpdateOfferCount.GetValueOrDefault(false))
{ {
result.Categories = GetSpecificCategories(profile.CharacterData.PmcData, searchRequest, result.Offers); result.Categories = GetSpecificCategories(
profile.CharacterData.PmcData,
searchRequest,
result.Offers
);
} }
// Adjust index value of offers found to start at 0 // Adjust index value of offers found to start at 0
AddIndexValueToOffers(result.Offers); AddIndexValueToOffers(result.Offers);
// Sort offers // Sort offers
@@ -171,7 +181,11 @@ public class RagfairController
} }
// Update trader offers' values, Lock quest-linked offers + adjust offer buy limits // Update trader offers' values, Lock quest-linked offers + adjust offer buy limits
foreach (var traderOffer in result.Offers.Where(offer => _ragfairOfferHelper.OfferIsFromTrader(offer))) foreach (
var traderOffer in result.Offers.Where(offer =>
_ragfairOfferHelper.OfferIsFromTrader(offer)
)
)
{ {
// For the items, check the barter schemes. The method getDisplayableAssorts sets a flag sptQuestLocked // For the items, check the barter schemes. The method getDisplayableAssorts sets a flag sptQuestLocked
// to true if the quest is not completed yet // to true if the quest is not completed yet
@@ -239,11 +253,7 @@ public class RagfairController
_logger.Warning( _logger.Warning(
_localisationService.GetText( _localisationService.GetText(
"ragfair-unable_to_adjust_stack_count_assort_not_found", "ragfair-unable_to_adjust_stack_count_assort_not_found",
new new { offerId = offer.Items.First().Id, traderId = offer.User.Id }
{
offerId = offer.Items.First().Id,
traderId = offer.User.Id
}
) )
); );
@@ -288,7 +298,7 @@ public class RagfairController
// Get specific assort purchase data and set current purchase buy value // Get specific assort purchase data and set current purchase buy value
traderPurchases.TryGetValue(assortId, out var assortTraderPurchaseData); traderPurchases.TryGetValue(assortId, out var assortTraderPurchaseData);
offer.BuyRestrictionCurrent = (int?) assortTraderPurchaseData?.PurchaseCount ?? 0; offer.BuyRestrictionCurrent = (int?)assortTraderPurchaseData?.PurchaseCount ?? 0;
offer.BuyRestrictionMax = offerRootItem.Upd.BuyRestrictionMax; offer.BuyRestrictionMax = offerRootItem.Upd.BuyRestrictionMax;
} }
@@ -313,12 +323,16 @@ public class RagfairController
/// <param name="searchRequest">Client search request data</param> /// <param name="searchRequest">Client search request data</param>
/// <param name="offers">Ragfair offers to get categories for</param> /// <param name="offers">Ragfair offers to get categories for</param>
/// <returns>Record with templates + counts</returns> /// <returns>Record with templates + counts</returns>
protected Dictionary<string, int> GetSpecificCategories(PmcData pmcProfile, SearchRequestData searchRequest, protected Dictionary<string, int> GetSpecificCategories(
List<RagfairOffer> offers) PmcData pmcProfile,
SearchRequestData searchRequest,
List<RagfairOffer> offers
)
{ {
// Linked/required search categories // Linked/required search categories
var playerHasFleaUnlocked = var playerHasFleaUnlocked =
pmcProfile.Info.Level >= _databaseService.GetGlobals().Configuration.RagFair.MinUserLevel; pmcProfile.Info.Level
>= _databaseService.GetGlobals().Configuration.RagFair.MinUserLevel;
List<RagfairOffer> offerPool = []; List<RagfairOffer> offerPool = [];
if (IsLinkedSearch(searchRequest) || IsRequiredSearch(searchRequest)) if (IsLinkedSearch(searchRequest) || IsRequiredSearch(searchRequest))
{ {
@@ -340,7 +354,11 @@ public class RagfairController
return new Dictionary<string, int>(); return new Dictionary<string, int>();
} }
return _ragfairServer.GetAllActiveCategories(playerHasFleaUnlocked, searchRequest, offerPool); return _ragfairServer.GetAllActiveCategories(
playerHasFleaUnlocked,
searchRequest,
offerPool
);
} }
/// <summary> /// <summary>
@@ -371,14 +389,22 @@ public class RagfairController
/// <param name="traderAssorts">Trader assorts</param> /// <param name="traderAssorts">Trader assorts</param>
/// <param name="pmcProfile"></param> /// <param name="pmcProfile"></param>
/// <returns>Array of offers</returns> /// <returns>Array of offers</returns>
protected List<RagfairOffer> GetOffersForSearchType(SearchRequestData searchRequest, List<string> itemsToAdd, protected List<RagfairOffer> GetOffersForSearchType(
SearchRequestData searchRequest,
List<string> itemsToAdd,
Dictionary<string, TraderAssort> traderAssorts, Dictionary<string, TraderAssort> traderAssorts,
PmcData pmcProfile) PmcData pmcProfile
)
{ {
// Searching for items in preset menu // Searching for items in preset menu
if (searchRequest.BuildCount > 0) if (searchRequest.BuildCount > 0)
{ {
return _ragfairOfferHelper.GetOffersForBuild(searchRequest, itemsToAdd, traderAssorts, pmcProfile); return _ragfairOfferHelper.GetOffersForBuild(
searchRequest,
itemsToAdd,
traderAssorts,
pmcProfile
);
} }
if (searchRequest.NeededSearchId?.Length > 0) if (searchRequest.NeededSearchId?.Length > 0)
@@ -387,7 +413,12 @@ public class RagfairController
} }
// Searching for general items // Searching for general items
return _ragfairOfferHelper.GetValidOffers(searchRequest, itemsToAdd, traderAssorts, pmcProfile); return _ragfairOfferHelper.GetValidOffers(
searchRequest,
itemsToAdd,
traderAssorts,
pmcProfile
);
} }
/// <summary> /// <summary>
@@ -396,8 +427,10 @@ public class RagfairController
/// <param name="getPriceRequest">Client request object</param> /// <param name="getPriceRequest">Client request object</param>
/// <param name="ignoreTraderOffers">OPTIONAL - Should trader offers be ignored in the calculation</param> /// <param name="ignoreTraderOffers">OPTIONAL - Should trader offers be ignored in the calculation</param>
/// <returns>min/avg/max values for an item based on flea offers available</returns> /// <returns>min/avg/max values for an item based on flea offers available</returns>
public GetItemPriceResult GetItemMinAvgMaxFleaPriceValues(GetMarketPriceRequestData getPriceRequest, public GetItemPriceResult GetItemMinAvgMaxFleaPriceValues(
bool ignoreTraderOffers = true) GetMarketPriceRequestData getPriceRequest,
bool ignoreTraderOffers = true
)
{ {
// Get all items of tpl // Get all items of tpl
var offers = _ragfairOfferService.GetOffersOfType(getPriceRequest.TemplateId); var offers = _ragfairOfferService.GetOffersOfType(getPriceRequest.TemplateId);
@@ -415,7 +448,7 @@ public class RagfairController
{ {
Avg = Math.Round(average), Avg = Math.Round(average),
Min = minMax.Min, Min = minMax.Min,
Max = minMax.Max Max = minMax.Max,
}; };
} }
@@ -431,11 +464,15 @@ public class RagfairController
{ {
Avg = tplPrice, Avg = tplPrice,
Min = tplPrice, Min = tplPrice,
Max = tplPrice Max = tplPrice,
}; };
} }
protected double GetAveragePriceFromOffers(List<RagfairOffer> offers, MinMax<double> minMax, bool ignoreTraderOffers) protected double GetAveragePriceFromOffers(
List<RagfairOffer> offers,
MinMax<double> minMax,
bool ignoreTraderOffers
)
{ {
var sum = 0d; var sum = 0d;
var totalOfferCount = 0; var totalOfferCount = 0;
@@ -488,14 +525,21 @@ public class RagfairController
/// <param name="offerRequest">Flea list creation offer</param> /// <param name="offerRequest">Flea list creation offer</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse AddPlayerOffer(PmcData pmcData, AddOfferRequestData offerRequest, string sessionID) public ItemEventRouterResponse AddPlayerOffer(
PmcData pmcData,
AddOfferRequestData offerRequest,
string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
var fullProfile = _profileHelper.GetFullProfile(sessionID); var fullProfile = _profileHelper.GetFullProfile(sessionID);
if (!IsValidPlayerOfferRequest(offerRequest)) if (!IsValidPlayerOfferRequest(offerRequest))
{ {
return _httpResponseUtil.AppendErrorToOutput(output, "Unable to add offer, check server for error"); return _httpResponseUtil.AppendErrorToOutput(
output,
"Unable to add offer, check server for error"
);
} }
var typeOfOffer = GetOfferType(offerRequest); var typeOfOffer = GetOfferType(offerRequest);
@@ -540,7 +584,9 @@ public class RagfairController
if (offerRequest.Requirements is null) if (offerRequest.Requirements is null)
{ {
_logger.Error(_localisationService.GetText("ragfair-unable_to_place_offer_with_no_requirements")); _logger.Error(
_localisationService.GetText("ragfair-unable_to_place_offer_with_no_requirements")
);
return false; return false;
} }
@@ -588,8 +634,12 @@ public class RagfairController
/// <param name="fullProfile">Full profile of player</param> /// <param name="fullProfile">Full profile of player</param>
/// <param name="output">output Response to send to client</param> /// <param name="output">output Response to send to client</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
protected ItemEventRouterResponse CreateMultiOffer(string sessionID, AddOfferRequestData offerRequest, protected ItemEventRouterResponse CreateMultiOffer(
SptProfile fullProfile, ItemEventRouterResponse output) string sessionID,
AddOfferRequestData offerRequest,
SptProfile fullProfile,
ItemEventRouterResponse output
)
{ {
var pmcData = fullProfile.CharacterData.PmcData; var pmcData = fullProfile.CharacterData.PmcData;
// var itemsToListCount = offerRequest.Items.Count; // Wasn't used to commented out for now // Does not count stack size, only items // var itemsToListCount = offerRequest.Items.Count; // Wasn't used to commented out for now // Does not count stack size, only items
@@ -625,17 +675,18 @@ public class RagfairController
var rootOfferItem = offer.Items.First(x => x.Id == firstOfferItemId); var rootOfferItem = offer.Items.First(x => x.Id == firstOfferItemId);
// Average offer price for single item (or whole weapon) // Average offer price for single item (or whole weapon)
var averages = var averages = GetItemMinAvgMaxFleaPriceValues(
GetItemMinAvgMaxFleaPriceValues( new GetMarketPriceRequestData { TemplateId = offer.Items[0].Template }
new GetMarketPriceRequestData );
{
TemplateId = offer.Items[0].Template
}
);
// Check for and apply item price modifer if it exists in config // Check for and apply item price modifer if it exists in config
var averageOfferPrice = averages.Avg; var averageOfferPrice = averages.Avg;
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(rootOfferItem.Template, out var itemPriceModifer)) if (
_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(
rootOfferItem.Template,
out var itemPriceModifer
)
)
{ {
averageOfferPrice *= itemPriceModifer; averageOfferPrice *= itemPriceModifer;
} }
@@ -657,7 +708,7 @@ public class RagfairController
); );
// Create array of sell times for items listed // 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 // Subtract flea market fee from stash
if (_ragfairConfig.Sell.Fees) if (_ragfairConfig.Sell.Fees)
@@ -667,7 +718,7 @@ public class RagfairController
rootOfferItem, rootOfferItem,
pmcData, pmcData,
playerListedPriceInRub, playerListedPriceInRub,
(int) stackCountTotal, (int)stackCountTotal,
offerRequest, offerRequest,
output output
); );
@@ -700,8 +751,12 @@ public class RagfairController
/// <param name="fullProfile">Full profile of player</param> /// <param name="fullProfile">Full profile of player</param>
/// <param name="output">Response to send to client</param> /// <param name="output">Response to send to client</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
protected ItemEventRouterResponse CreatePackOffer(string sessionID, AddOfferRequestData offerRequest, protected ItemEventRouterResponse CreatePackOffer(
SptProfile fullProfile, ItemEventRouterResponse output) string sessionID,
AddOfferRequestData offerRequest,
SptProfile fullProfile,
ItemEventRouterResponse output
)
{ {
var pmcData = fullProfile.CharacterData.PmcData; var pmcData = fullProfile.CharacterData.PmcData;
// var itemsToListCount = offerRequest.Items.Count; // TODO: Wasn't used so commented out for now // Does not count stack size, only items // var itemsToListCount = offerRequest.Items.Count; // TODO: Wasn't used so commented out for now // Does not count stack size, only items
@@ -730,22 +785,29 @@ public class RagfairController
firstListingRootItem.Upd.StackObjectsCount = stackCountTotal; firstListingRootItem.Upd.StackObjectsCount = stackCountTotal;
// Create flea object // Create flea object
var offer = CreatePlayerOffer(sessionID, offerRequest.Requirements, firstListingAndChildren, true); var offer = CreatePlayerOffer(
sessionID,
offerRequest.Requirements,
firstListingAndChildren,
true
);
// This is the item that will be listed on flea, has merged stackObjectCount // This is the item that will be listed on flea, has merged stackObjectCount
var newRootOfferItem = offer.Items[0]; // TODO: add logic like single/multi offers to find root item var newRootOfferItem = offer.Items[0]; // TODO: add logic like single/multi offers to find root item
// Single price for an item // Single price for an item
var averages = GetItemMinAvgMaxFleaPriceValues( var averages = GetItemMinAvgMaxFleaPriceValues(
new GetMarketPriceRequestData new GetMarketPriceRequestData { TemplateId = firstListingRootItem.Template }
{
TemplateId = firstListingRootItem.Template
}
); );
var singleItemPrice = averages.Avg; var singleItemPrice = averages.Avg;
// Check for and apply item price modifer if it exists in config // Check for and apply item price modifer if it exists in config
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(newRootOfferItem.Template, out var itemPriceModifer)) if (
_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(
newRootOfferItem.Template,
out var itemPriceModifer
)
)
{ {
singleItemPrice *= itemPriceModifer; singleItemPrice *= itemPriceModifer;
} }
@@ -767,7 +829,11 @@ public class RagfairController
); );
// Create array of sell times for items listed + sell all at once as it's a pack // Create array of sell times for items listed + sell all at once as it's a pack
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int) stackCountTotal, true); offer.SellResults = _ragfairSellHelper.RollForSale(
sellChancePercent,
(int)stackCountTotal,
true
);
// Subtract flea market fee from stash // Subtract flea market fee from stash
if (_ragfairConfig.Sell.Fees) if (_ragfairConfig.Sell.Fees)
@@ -777,7 +843,7 @@ public class RagfairController
newRootOfferItem, newRootOfferItem,
pmcData, pmcData,
playerListedPriceInRub, playerListedPriceInRub,
(int) stackCountTotal, (int)stackCountTotal,
offerRequest, offerRequest,
output output
); );
@@ -809,22 +875,30 @@ public class RagfairController
/// <param name="fullProfile">Full profile of player</param> /// <param name="fullProfile">Full profile of player</param>
/// <param name="output">Response to send to client</param> /// <param name="output">Response to send to client</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
protected ItemEventRouterResponse CreateSingleOffer(string sessionID, AddOfferRequestData offerRequest, protected ItemEventRouterResponse CreateSingleOffer(
string sessionID,
AddOfferRequestData offerRequest,
SptProfile fullProfile, SptProfile fullProfile,
ItemEventRouterResponse output) ItemEventRouterResponse output
)
{ {
var pmcData = fullProfile.CharacterData.PmcData; var pmcData = fullProfile.CharacterData.PmcData;
// var itemsToListCount = offerRequest.Items.Count; // Wasn't used so commented out for now // Does not count stack size, only items // var itemsToListCount = offerRequest.Items.Count; // Wasn't used so commented out for now // Does not count stack size, only items
// Find items to be listed on flea from player inventory // Find items to be listed on flea from player inventory
var inventoryItemsToSell = GetItemsToListOnFleaFromInventory(pmcData, offerRequest.Items); var inventoryItemsToSell = GetItemsToListOnFleaFromInventory(pmcData, offerRequest.Items);
if (inventoryItemsToSell.Items is null || !string.IsNullOrEmpty(inventoryItemsToSell.ErrorMessage)) if (
inventoryItemsToSell.Items is null
|| !string.IsNullOrEmpty(inventoryItemsToSell.ErrorMessage)
)
{ {
_httpResponseUtil.AppendErrorToOutput(output, inventoryItemsToSell.ErrorMessage); _httpResponseUtil.AppendErrorToOutput(output, inventoryItemsToSell.ErrorMessage);
} }
// Total count of items summed using their stack counts // Total count of items summed using their stack counts
var stackCountTotal = _ragfairOfferHelper.GetTotalStackCountSize(inventoryItemsToSell.Items); var stackCountTotal = _ragfairOfferHelper.GetTotalStackCountSize(
inventoryItemsToSell.Items
);
// Checks are done, create offer // Checks are done, create offer
var playerListedPriceInRub = CalculateRequirementsPriceInRub(offerRequest.Requirements); var playerListedPriceInRub = CalculateRequirementsPriceInRub(offerRequest.Requirements);
@@ -841,17 +915,18 @@ public class RagfairController
var qualityMultiplier = _itemHelper.GetItemQualityModifierForItems(offer.Items, true); var qualityMultiplier = _itemHelper.GetItemQualityModifierForItems(offer.Items, true);
// Average offer price for single item (or whole weapon) // Average offer price for single item (or whole weapon)
var averages = var averages = GetItemMinAvgMaxFleaPriceValues(
GetItemMinAvgMaxFleaPriceValues( new GetMarketPriceRequestData { TemplateId = offerRootItem.Template }
new GetMarketPriceRequestData );
{
TemplateId = offerRootItem.Template
}
);
var averageOfferPriceSingleItem = averages.Avg; var averageOfferPriceSingleItem = averages.Avg;
// Check for and apply item price modifer if it exists in config // Check for and apply item price modifer if it exists in config
if (_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(offerRootItem.Template, out var itemPriceModifer)) if (
_ragfairConfig.Dynamic.ItemPriceMultiplier.TryGetValue(
offerRootItem.Template,
out var itemPriceModifer
)
)
{ {
averageOfferPriceSingleItem *= itemPriceModifer; averageOfferPriceSingleItem *= itemPriceModifer;
} }
@@ -865,7 +940,7 @@ public class RagfairController
playerListedPriceInRub, playerListedPriceInRub,
qualityMultiplier qualityMultiplier
); );
offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int) stackCountTotal); offer.SellResults = _ragfairSellHelper.RollForSale(sellChancePercent, (int)stackCountTotal);
// Subtract flea market fee from stash // Subtract flea market fee from stash
if (_ragfairConfig.Sell.Fees) if (_ragfairConfig.Sell.Fees)
@@ -875,7 +950,7 @@ public class RagfairController
offerRootItem, offerRootItem,
pmcData, pmcData,
playerListedPriceInRub, playerListedPriceInRub,
(int) stackCountTotal, (int)stackCountTotal,
offerRequest, offerRequest,
output output
); );
@@ -916,10 +991,13 @@ public class RagfairController
double requirementsPriceInRub, double requirementsPriceInRub,
int itemStackCount, int itemStackCount,
AddOfferRequestData offerRequest, AddOfferRequestData offerRequest,
ItemEventRouterResponse output) ItemEventRouterResponse output
)
{ {
// Get tax from cache hydrated earlier by client, if that's missing fall back to server calculation (inaccurate) // Get tax from cache hydrated earlier by client, if that's missing fall back to server calculation (inaccurate)
var storedClientTaxValue = _ragfairTaxService.GetStoredClientOfferTaxValueById(offerRequest.Items[0]); var storedClientTaxValue = _ragfairTaxService.GetStoredClientOfferTaxValueById(
offerRequest.Items[0]
);
var tax = storedClientTaxValue is not null var tax = storedClientTaxValue is not null
? storedClientTaxValue.Fee ? storedClientTaxValue.Fee
: _ragfairTaxService.CalculateTax( : _ragfairTaxService.CalculateTax(
@@ -932,7 +1010,9 @@ public class RagfairController
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Offer tax to charge: {tax}, pulled from client: {storedClientTaxValue.Count is not null}"); _logger.Debug(
$"Offer tax to charge: {tax}, pulled from client: {storedClientTaxValue.Count is not null}"
);
} }
// cleanup of cache now we've used the tax value from it // cleanup of cache now we've used the tax value from it
@@ -960,32 +1040,34 @@ public class RagfairController
/// <param name="items">Item(s) to list on flea (with children)</param> /// <param name="items">Item(s) to list on flea (with children)</param>
/// <param name="sellInOnePiece">Is this a pack offer</param> /// <param name="sellInOnePiece">Is this a pack offer</param>
/// <returns>RagfairOffer</returns> /// <returns>RagfairOffer</returns>
protected RagfairOffer CreatePlayerOffer(string sessionId, List<Requirement> requirements, List<Item> items, protected RagfairOffer CreatePlayerOffer(
bool sellInOnePiece) string sessionId,
List<Requirement> requirements,
List<Item> items,
bool sellInOnePiece
)
{ {
const int loyalLevel = 1; const int loyalLevel = 1;
var formattedItems = items.Select(item => var formattedItems = items.Select(item =>
{ {
var isChild = items.Any(subItem => subItem.Id == item.ParentId); var isChild = items.Any(subItem => subItem.Id == item.ParentId);
return new Item return new Item
{ {
Id = item.Id, Id = item.Id,
Template = item.Template, Template = item.Template,
ParentId = isChild ? item.ParentId : "hideout", ParentId = isChild ? item.ParentId : "hideout",
SlotId = isChild ? item.SlotId : "hideout", SlotId = isChild ? item.SlotId : "hideout",
Upd = item.Upd Upd = item.Upd,
}; };
} });
);
var formattedRequirements = requirements.Select(item => new BarterScheme var formattedRequirements = requirements.Select(item => new BarterScheme
{ {
Template = item.Template, Template = item.Template,
Count = item.Count, Count = item.Count,
OnlyFunctional = item.OnlyFunctional OnlyFunctional = item.OnlyFunctional,
} });
);
return _ragfairOfferGenerator.CreateAndAddFleaOffer( return _ragfairOfferGenerator.CreateAndAddFleaOffer(
sessionId, sessionId,
@@ -993,7 +1075,7 @@ public class RagfairController
formattedItems.ToList(), formattedItems.ToList(),
formattedRequirements.ToList(), formattedRequirements.ToList(),
loyalLevel, loyalLevel,
(int?) items.FirstOrDefault()?.Upd?.StackObjectsCount ?? 1, (int?)items.FirstOrDefault()?.Upd?.StackObjectsCount ?? 1,
sellInOnePiece sellInOnePiece
); );
} }
@@ -1006,17 +1088,21 @@ public class RagfairController
protected double CalculateRequirementsPriceInRub(List<Requirement> requirements) protected double CalculateRequirementsPriceInRub(List<Requirement> requirements)
{ {
return requirements.Sum(requirement => return requirements.Sum(requirement =>
{
if (
string.IsNullOrEmpty(requirement.Template)
|| !requirement.Count.HasValue
|| requirement.Count == 0
)
{ {
if (string.IsNullOrEmpty(requirement.Template) || !requirement.Count.HasValue || requirement.Count == 0) return 0;
{
return 0;
}
return _paymentHelper.IsMoneyTpl(requirement.Template)
? _handbookHelper.InRUB(requirement.Count.Value, requirement.Template)
: _itemHelper.GetDynamicItemPrice(requirement.Template).Value * requirement.Count.Value;
} }
);
return _paymentHelper.IsMoneyTpl(requirement.Template)
? _handbookHelper.InRUB(requirement.Count.Value, requirement.Template)
: _itemHelper.GetDynamicItemPrice(requirement.Template).Value
* requirement.Count.Value;
});
} }
/// <summary> /// <summary>
@@ -1025,8 +1111,10 @@ public class RagfairController
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <param name="itemIdsFromFleaOfferRequest">Request</param> /// <param name="itemIdsFromFleaOfferRequest">Request</param>
/// <returns>GetItemsToListOnFleaFromInventoryResult</returns> /// <returns>GetItemsToListOnFleaFromInventoryResult</returns>
protected GetItemsToListOnFleaFromInventoryResult GetItemsToListOnFleaFromInventory(PmcData pmcData, protected GetItemsToListOnFleaFromInventoryResult GetItemsToListOnFleaFromInventory(
List<string> itemIdsFromFleaOfferRequest) PmcData pmcData,
List<string> itemIdsFromFleaOfferRequest
)
{ {
List<List<Item>> itemsToReturn = []; List<List<Item>> itemsToReturn = [];
var errorMessage = string.Empty; var errorMessage = string.Empty;
@@ -1039,40 +1127,38 @@ public class RagfairController
{ {
errorMessage = _localisationService.GetText( errorMessage = _localisationService.GetText(
"ragfair-unable_to_find_item_in_inventory", "ragfair-unable_to_find_item_in_inventory",
new new { id = itemId }
{
id = itemId
}
); );
_logger.Error(errorMessage); _logger.Error(errorMessage);
return new GetItemsToListOnFleaFromInventoryResult return new GetItemsToListOnFleaFromInventoryResult
{ {
Items = itemsToReturn, Items = itemsToReturn,
ErrorMessage = errorMessage ErrorMessage = errorMessage,
}; };
} }
_itemHelper.FixItemStackCount(rootItem); _itemHelper.FixItemStackCount(rootItem);
itemsToReturn.Add(_itemHelper.FindAndReturnChildrenAsItems(pmcData.Inventory.Items, itemId)); itemsToReturn.Add(
_itemHelper.FindAndReturnChildrenAsItems(pmcData.Inventory.Items, itemId)
);
} }
if (itemsToReturn?.Count == 0) if (itemsToReturn?.Count == 0)
{ {
errorMessage = _localisationService.GetText("ragfair-unable_to_find_requested_items_in_inventory"); errorMessage = _localisationService.GetText(
"ragfair-unable_to_find_requested_items_in_inventory"
);
_logger.Error(errorMessage); _logger.Error(errorMessage);
return new GetItemsToListOnFleaFromInventoryResult return new GetItemsToListOnFleaFromInventoryResult { ErrorMessage = errorMessage };
{
ErrorMessage = errorMessage
};
} }
return new GetItemsToListOnFleaFromInventoryResult return new GetItemsToListOnFleaFromInventoryResult
{ {
Items = itemsToReturn, Items = itemsToReturn,
ErrorMessage = errorMessage ErrorMessage = errorMessage,
}; };
} }
@@ -1094,11 +1180,7 @@ public class RagfairController
_logger.Warning( _logger.Warning(
_localisationService.GetText( _localisationService.GetText(
"ragfair-unable_to_remove_offer_not_found_in_profile", "ragfair-unable_to_remove_offer_not_found_in_profile",
new new { profileId = sessionId, offerId }
{
profileId = sessionId,
offerId
}
) )
); );
@@ -1109,13 +1191,7 @@ public class RagfairController
if (playerOffer is null) if (playerOffer is null)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText("ragfair-offer_not_found_in_profile", new { offerId })
"ragfair-offer_not_found_in_profile",
new
{
offerId
}
)
); );
return _httpResponseUtil.AppendErrorToOutput( return _httpResponseUtil.AppendErrorToOutput(
@@ -1130,10 +1206,12 @@ public class RagfairController
{ {
// `expireSeconds` Default is 71 seconds // `expireSeconds` Default is 71 seconds
var newEndTime = _ragfairConfig.Sell.ExpireSeconds + _timeUtil.GetTimeStamp(); var newEndTime = _ragfairConfig.Sell.ExpireSeconds + _timeUtil.GetTimeStamp();
playerOffer.EndTime = (long?) Math.Round((double) newEndTime); playerOffer.EndTime = (long?)Math.Round((double)newEndTime);
} }
_logger.Debug($"Flagged player offer: {offerId} for expiry in: {TimeSpan.FromTicks(playerOffer.EndTime.Value).ToString()}"); _logger.Debug(
$"Flagged player offer: {offerId} for expiry in: {TimeSpan.FromTicks(playerOffer.EndTime.Value).ToString()}"
);
return output; return output;
} }
@@ -1144,7 +1222,10 @@ public class RagfairController
/// <param name="extendRequest">Extend time request</param> /// <param name="extendRequest">Extend time request</param>
/// <param name="sessionId">Session/Player id</param> /// <param name="sessionId">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse ExtendOffer(ExtendOfferRequestData extendRequest, string sessionId) public ItemEventRouterResponse ExtendOffer(
ExtendOfferRequestData extendRequest,
string sessionId
)
{ {
var output = _eventOutputHolder.GetOutput(sessionId); var output = _eventOutputHolder.GetOutput(sessionId);
@@ -1158,10 +1239,7 @@ public class RagfairController
_logger.Warning( _logger.Warning(
_localisationService.GetText( _localisationService.GetText(
"ragfair-offer_not_found_in_profile", "ragfair-offer_not_found_in_profile",
new new { offerId = extendRequest.OfferId }
{
offerId = extendRequest.OfferId
}
) )
); );
return _httpResponseUtil.AppendErrorToOutput( return _httpResponseUtil.AppendErrorToOutput(
@@ -1179,7 +1257,8 @@ public class RagfairController
var sellInOncePiece = playerOffer.SellInOnePiece.GetValueOrDefault(false); var sellInOncePiece = playerOffer.SellInOnePiece.GetValueOrDefault(false);
if (!sellInOncePiece) 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( var tax = _ragfairTaxService.CalculateTax(
@@ -1202,7 +1281,7 @@ public class RagfairController
} }
// Add extra time to offer // Add extra time to offer
playerOffers[playerOfferIndex].EndTime += (long?) Math.Round((decimal) secondsToAdd); playerOffers[playerOfferIndex].EndTime += (long?)Math.Round((decimal)secondsToAdd);
return output; return output;
} }
@@ -1213,7 +1292,10 @@ public class RagfairController
/// <param name="currency">What currency: RUB, EURO, USD</param> /// <param name="currency">What currency: RUB, EURO, USD</param>
/// <param name="value">Amount of currency</param> /// <param name="value">Amount of currency</param>
/// <returns>ProcessBuyTradeRequestData</returns> /// <returns>ProcessBuyTradeRequestData</returns>
protected ProcessBuyTradeRequestData CreateBuyTradeRequestObject(CurrencyType currency, double value) protected ProcessBuyTradeRequestData CreateBuyTradeRequestObject(
CurrencyType currency,
double value
)
{ {
return new ProcessBuyTradeRequestData return new ProcessBuyTradeRequestData
{ {
@@ -1224,13 +1306,13 @@ public class RagfairController
new IdWithCount new IdWithCount
{ {
Id = _paymentHelper.GetCurrency(currency), Id = _paymentHelper.GetCurrency(currency),
Count = Math.Round(value) Count = Math.Round(value),
} },
], ],
Type = "", Type = "",
ItemId = "", ItemId = "",
Count = 0, Count = 0,
SchemeId = 0 SchemeId = 0,
}; };
} }
@@ -1258,16 +1340,8 @@ public class RagfairController
public record GetItemsToListOnFleaFromInventoryResult public record GetItemsToListOnFleaFromInventoryResult
{ {
public List<List<Item>>? Items public List<List<Item>>? Items { get; set; }
{
get;
set;
}
public string? ErrorMessage public string? ErrorMessage { get; set; }
{
get;
set;
}
} }
} }
@@ -8,10 +8,7 @@ using SPTarkov.Server.Core.Services;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
public class RepairController( public class RepairController(EventOutputHolder _eventOutputHolder, RepairService _repairService)
EventOutputHolder _eventOutputHolder,
RepairService _repairService
)
{ {
/// <summary> /// <summary>
/// Handle TraderRepair event /// Handle TraderRepair event
@@ -24,14 +21,20 @@ public class RepairController(
public ItemEventRouterResponse TraderRepair( public ItemEventRouterResponse TraderRepair(
string sessionID, string sessionID,
TraderRepairActionDataRequest body, TraderRepairActionDataRequest body,
PmcData pmcData) PmcData pmcData
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
// find the item to repair // find the item to repair
foreach (var repairItem in body.RepairItems) foreach (var repairItem in body.RepairItems)
{ {
var repairDetails = _repairService.RepairItemByTrader(sessionID, pmcData, repairItem, body.TId); var repairDetails = _repairService.RepairItemByTrader(
sessionID,
pmcData,
repairItem,
body.TId
);
_repairService.PayForRepair( _repairService.PayForRepair(
sessionID, sessionID,
@@ -68,7 +71,8 @@ public class RepairController(
public ItemEventRouterResponse RepairWithKit( public ItemEventRouterResponse RepairWithKit(
string sessionId, string sessionId,
RepairActionDataRequest body, RepairActionDataRequest body,
PmcData pmcData) PmcData pmcData
)
{ {
var output = _eventOutputHolder.GetOutput(sessionId); var output = _eventOutputHolder.GetOutput(sessionId);
@@ -53,7 +53,11 @@ public class RepeatableQuestController(
/// <param name="acceptedQuest">Repeatable quest accepted</param> /// <param name="acceptedQuest">Repeatable quest accepted</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <returns>ItemEventRouterResponse</returns> /// <returns>ItemEventRouterResponse</returns>
public ItemEventRouterResponse AcceptRepeatableQuest(PmcData pmcData, AcceptQuestRequestData acceptedQuest, string sessionID) public ItemEventRouterResponse AcceptRepeatableQuest(
PmcData pmcData,
AcceptQuestRequestData acceptedQuest,
string sessionID
)
{ {
// Create and store quest status object inside player profile // Create and store quest status object inside player profile
var newRepeatableQuest = _questHelper.GetQuestReadyForProfile( var newRepeatableQuest = _questHelper.GetQuestReadyForProfile(
@@ -74,11 +78,16 @@ public class RepeatableQuestController(
) )
); );
throw new Exception(_localisationService.GetText("repeatable-unable_to_accept_quest_see_log")); throw new Exception(
_localisationService.GetText("repeatable-unable_to_accept_quest_see_log")
);
} }
// Some scav quests need to be added to scav profile for them to show up in-raid // Some scav quests need to be added to scav profile for them to show up in-raid
if (repeatableQuestProfile.Side == "Scav" && _questTypes.Contains(repeatableQuestProfile.Type.ToString())) if (
repeatableQuestProfile.Side == "Scav"
&& _questTypes.Contains(repeatableQuestProfile.Type.ToString())
)
{ {
var fullProfile = _profileHelper.GetFullProfile(sessionID); var fullProfile = _profileHelper.GetFullProfile(sessionID);
@@ -98,8 +107,11 @@ public class RepeatableQuestController(
/// <param name="changeRequest">Change quest request</param> /// <param name="changeRequest">Change quest request</param>
/// <param name="sessionID">Session/Player id</param> /// <param name="sessionID">Session/Player id</param>
/// <returns></returns> /// <returns></returns>
public ItemEventRouterResponse ChangeRepeatableQuest(PmcData pmcData, RepeatableQuestChangeRequest changeRequest, public ItemEventRouterResponse ChangeRepeatableQuest(
string sessionID) PmcData pmcData,
RepeatableQuestChangeRequest changeRequest,
string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
@@ -112,7 +124,9 @@ public class RepeatableQuestController(
if (repeatables.RepeatableType is null || repeatables.Quest is null) if (repeatables.RepeatableType is null || repeatables.Quest is null)
{ {
// Unable to find quest being replaced // Unable to find quest being replaced
var message = _localisationService.GetText("quest-unable_to_find_repeatable_to_replace"); var message = _localisationService.GetText(
"quest-unable_to_find_repeatable_to_replace"
);
_logger.Error(message); _logger.Error(message);
return _httpResponseUtil.AppendErrorToOutput(output, message); return _httpResponseUtil.AppendErrorToOutput(output, message);
@@ -125,8 +139,8 @@ public class RepeatableQuestController(
var replacedQuestTraderId = questToReplace.TraderId; var replacedQuestTraderId = questToReplace.TraderId;
// Update active quests to exclude the quest we're replacing // Update active quests to exclude the quest we're replacing
repeatablesOfTypeInProfile.ActiveQuests = repeatablesOfTypeInProfile.ActiveQuests.Where(quest => quest.Id != changeRequest.QuestId repeatablesOfTypeInProfile.ActiveQuests = repeatablesOfTypeInProfile
) .ActiveQuests.Where(quest => quest.Id != changeRequest.QuestId)
.ToList(); .ToList();
// Save for later cost calculations // Save for later cost calculations
@@ -138,7 +152,8 @@ public class RepeatableQuestController(
repeatablesOfTypeInProfile.ChangeRequirement.Remove(changeRequest.QuestId); repeatablesOfTypeInProfile.ChangeRequirement.Remove(changeRequest.QuestId);
// Get config for this repeatable subtype (daily/weekly/scav) // Get config for this repeatable subtype (daily/weekly/scav)
var repeatableConfig = _questConfig.RepeatableQuests.FirstOrDefault(config => config.Name == repeatablesOfTypeInProfile.Name var repeatableConfig = _questConfig.RepeatableQuests.FirstOrDefault(config =>
config.Name == repeatablesOfTypeInProfile.Name
); );
// If the configuration dictates to replace with the same quest type, adjust the available quest types // If the configuration dictates to replace with the same quest type, adjust the available quest types
@@ -148,7 +163,10 @@ public class RepeatableQuestController(
} }
// Generate meta-data for what type/level range of quests can be generated for player // Generate meta-data for what type/level range of quests can be generated for player
var allowedQuestTypes = GenerateQuestPool(repeatableConfig, pmcData.Info.Level.GetValueOrDefault(1)); var allowedQuestTypes = GenerateQuestPool(
repeatableConfig,
pmcData.Info.Level.GetValueOrDefault(1)
);
var newRepeatableQuest = AttemptToGenerateRepeatableQuest( var newRepeatableQuest = AttemptToGenerateRepeatableQuest(
sessionID, sessionID,
pmcData, pmcData,
@@ -186,7 +204,7 @@ public class RepeatableQuestController(
repeatablesOfTypeInProfile.ChangeRequirement[newRepeatableQuest.Id] = new ChangeRequirement repeatablesOfTypeInProfile.ChangeRequirement[newRepeatableQuest.Id] = new ChangeRequirement
{ {
ChangeCost = newRepeatableQuest.ChangeCost, ChangeCost = newRepeatableQuest.ChangeCost,
ChangeStandingCost = _randomUtil.GetArrayValue(repeatableConfig.StandingChangeCost) ChangeStandingCost = _randomUtil.GetArrayValue(repeatableConfig.StandingChangeCost),
}; };
// Check if we should charge player for replacing quest // Check if we should charge player for replacing quest
@@ -201,12 +219,22 @@ public class RepeatableQuestController(
var traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId]; var traderOfReplacedQuest = pmcData.TradersInfo[replacedQuestTraderId];
traderOfReplacedQuest.Standing -= previousChangeRequirement.ChangeStandingCost; traderOfReplacedQuest.Standing -= previousChangeRequirement.ChangeStandingCost;
var charismaBonus = _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.Charisma)?.Progress ?? 0; var charismaBonus =
_profileHelper.GetSkillFromProfile(pmcData, SkillTypes.Charisma)?.Progress ?? 0;
foreach (var cost in previousChangeRequirement.ChangeCost) foreach (var cost in previousChangeRequirement.ChangeCost)
{ {
// Not free, Charge player + apply charisma bonus to cost of replacement // Not free, Charge player + apply charisma bonus to cost of replacement
cost.Count = (int) Math.Truncate(cost.Count.Value * (1 - Math.Truncate(charismaBonus / 100) * 0.001)); cost.Count = (int)
_paymentService.AddPaymentToOutput(pmcData, cost.TemplateId, cost.Count.Value, sessionID, output); 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) if (output.Warnings.Count > 0)
{ {
return output; return output;
@@ -265,8 +293,11 @@ public class RepeatableQuestController(
/// <param name="repeatableSubType">Can be daily / weekly / scav repeatable</param> /// <param name="repeatableSubType">Can be daily / weekly / scav repeatable</param>
/// <param name="repeatableTypeName">Subtype of repeatable quest: daily / weekly / scav</param> /// <param name="repeatableTypeName">Subtype of repeatable quest: daily / weekly / scav</param>
/// <returns>Is the repeatable being replaced for free</returns> /// <returns>Is the repeatable being replaced for free</returns>
protected bool UseFreeRefreshIfAvailable(SptProfile? fullProfile, PmcDataRepeatableQuest repeatableSubType, protected bool UseFreeRefreshIfAvailable(
string repeatableTypeName) SptProfile? fullProfile,
PmcDataRepeatableQuest repeatableSubType,
string repeatableTypeName
)
{ {
// No free refreshes, exit early // No free refreshes, exit early
if (repeatableSubType.FreeChangesAvailable <= 0) if (repeatableSubType.FreeChangesAvailable <= 0)
@@ -305,12 +336,14 @@ public class RepeatableQuestController(
/// </summary> /// </summary>
/// <param name="repeatablesOfTypeInProfile">repeatables that have the replaced and new quest</param> /// <param name="repeatablesOfTypeInProfile">repeatables that have the replaced and new quest</param>
/// <param name="replacedQuestId">Id of the replaced quest</param> /// <param name="replacedQuestId">Id of the replaced quest</param>
protected void CleanUpRepeatableChangeRequirements(PmcDataRepeatableQuest repeatablesOfTypeInProfile, protected void CleanUpRepeatableChangeRequirements(
string replacedQuestId) PmcDataRepeatableQuest repeatablesOfTypeInProfile,
string replacedQuestId
)
{ {
if (repeatablesOfTypeInProfile.ActiveQuests.Count == 1) 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 // 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 // Will assist in cleanup of existing profiles data
{ {
repeatablesOfTypeInProfile.ChangeRequirement.Clear(); repeatablesOfTypeInProfile.ChangeRequirement.Clear();
@@ -329,8 +362,12 @@ public class RepeatableQuestController(
/// <param name="questTypePool">What type/level range of quests can be generated for player</param> /// <param name="questTypePool">What type/level range of quests can be generated for player</param>
/// <param name="repeatableConfig">Config for the quest type to generate</param> /// <param name="repeatableConfig">Config for the quest type to generate</param>
/// <returns></returns> /// <returns></returns>
protected RepeatableQuest? AttemptToGenerateRepeatableQuest(string sessionId, PmcData pmcData, protected RepeatableQuest? AttemptToGenerateRepeatableQuest(
QuestTypePool questTypePool, RepeatableQuestConfig repeatableConfig) string sessionId,
PmcData pmcData,
QuestTypePool questTypePool,
RepeatableQuestConfig repeatableConfig
)
{ {
const int maxAttempts = 10; const int maxAttempts = 10;
RepeatableQuest? newRepeatableQuest = null; RepeatableQuest? newRepeatableQuest = null;
@@ -346,7 +383,7 @@ public class RepeatableQuestController(
); );
if (newRepeatableQuest is not null) if (newRepeatableQuest is not null)
// Successfully generated a quest, exit loop // Successfully generated a quest, exit loop
{ {
break; break;
} }
@@ -356,7 +393,12 @@ public class RepeatableQuestController(
if (attempts > maxAttempts) if (attempts > maxAttempts)
{ {
_logger.Error(_localisationService.GetText("quest-repeatable_generation_failed_please_report", attempts)); _logger.Error(
_localisationService.GetText(
"quest-repeatable_generation_failed_please_report",
attempts
)
);
} }
return newRepeatableQuest; return newRepeatableQuest;
@@ -370,7 +412,10 @@ public class RepeatableQuestController(
protected void RemoveQuestFromProfile(SptProfile fullProfile, string questToReplaceId) protected void RemoveQuestFromProfile(SptProfile fullProfile, string questToReplaceId)
{ {
// Find quest we're replacing in pmc profile quests array and remove it // Find quest we're replacing in pmc profile quests array and remove it
_questHelper.FindAndRemoveQuestFromArrayIfExists(questToReplaceId, fullProfile.CharacterData.PmcData.Quests); _questHelper.FindAndRemoveQuestFromArrayIfExists(
questToReplaceId,
fullProfile.CharacterData.PmcData.Quests
);
// Look for and remove quest we're replacing in scav profile too // Look for and remove quest we're replacing in scav profile too
if (fullProfile.CharacterData.ScavData is not null) if (fullProfile.CharacterData.ScavData is not null)
@@ -393,10 +438,11 @@ public class RepeatableQuestController(
foreach (var repeatablesInProfile in pmcData.RepeatableQuests) foreach (var repeatablesInProfile in pmcData.RepeatableQuests)
{ {
// Check for existing quest in (daily/weekly/scav arrays) // Check for existing quest in (daily/weekly/scav arrays)
var questToReplace = var questToReplace = repeatablesInProfile.ActiveQuests?.FirstOrDefault(repeatable =>
repeatablesInProfile.ActiveQuests?.FirstOrDefault(repeatable => repeatable.Id == questId); repeatable.Id == questId
);
if (questToReplace is null) if (questToReplace is null)
// Not found, skip to next repeatable subtype // Not found, skip to next repeatable subtype
{ {
continue; continue;
} }
@@ -404,7 +450,7 @@ public class RepeatableQuestController(
return new GetRepeatableByIdResult return new GetRepeatableByIdResult
{ {
Quest = questToReplace, Quest = questToReplace,
RepeatableType = repeatablesInProfile RepeatableType = repeatablesInProfile,
}; };
} }
@@ -443,12 +489,15 @@ public class RepeatableQuestController(
foreach (var repeatableConfig in _questConfig.RepeatableQuests) foreach (var repeatableConfig in _questConfig.RepeatableQuests)
{ {
// Get daily/weekly data from profile, add empty object if missing // Get daily/weekly data from profile, add empty object if missing
var generatedRepeatables = GetRepeatableQuestSubTypeFromProfile(repeatableConfig, pmcData); var generatedRepeatables = GetRepeatableQuestSubTypeFromProfile(
repeatableConfig,
pmcData
);
var repeatableTypeLower = repeatableConfig.Name.ToLower(); var repeatableTypeLower = repeatableConfig.Name.ToLower();
var canAccessRepeatables = CanProfileAccessRepeatableQuests(repeatableConfig, pmcData); var canAccessRepeatables = CanProfileAccessRepeatableQuests(repeatableConfig, pmcData);
if (!canAccessRepeatables) if (!canAccessRepeatables)
// Don't send any repeatables, even existing ones // Don't send any repeatables, even existing ones
{ {
continue; continue;
} }
@@ -483,7 +532,10 @@ public class RepeatableQuestController(
ProcessExpiredQuests(generatedRepeatables, pmcData); ProcessExpiredQuests(generatedRepeatables, pmcData);
// Create dynamic quest pool to avoid generating duplicates // Create dynamic quest pool to avoid generating duplicates
var questTypePool = GenerateQuestPool(repeatableConfig, pmcData.Info.Level.GetValueOrDefault(1)); var questTypePool = GenerateQuestPool(
repeatableConfig,
pmcData.Info.Level.GetValueOrDefault(1)
);
// Add repeatable quests of this loops sub-type (daily/weekly) // Add repeatable quests of this loops sub-type (daily/weekly)
for (var i = 0; i < GetQuestCount(repeatableConfig, fullProfile); i++) for (var i = 0; i < GetQuestCount(repeatableConfig, fullProfile); i++)
@@ -535,7 +587,9 @@ public class RepeatableQuestController(
new ChangeRequirement new ChangeRequirement
{ {
ChangeCost = quest.ChangeCost, ChangeCost = quest.ChangeCost,
ChangeStandingCost = _randomUtil.GetArrayValue(repeatableConfig.StandingChangeCost) // Randomise standing loss to replace ChangeStandingCost = _randomUtil.GetArrayValue(
repeatableConfig.StandingChangeCost
), // Randomise standing loss to replace
} }
); );
} }
@@ -553,7 +607,7 @@ public class RepeatableQuestController(
InactiveQuests = generatedRepeatables.InactiveQuests, InactiveQuests = generatedRepeatables.InactiveQuests,
ChangeRequirement = generatedRepeatables.ChangeRequirement, ChangeRequirement = generatedRepeatables.ChangeRequirement,
FreeChanges = generatedRepeatables.FreeChanges, FreeChanges = generatedRepeatables.FreeChanges,
FreeChangesAvailable = generatedRepeatables.FreeChangesAvailable FreeChangesAvailable = generatedRepeatables.FreeChangesAvailable,
} }
); );
} }
@@ -567,11 +621,14 @@ public class RepeatableQuestController(
/// <param name="repeatableConfig">daily/weekly config</param> /// <param name="repeatableConfig">daily/weekly config</param>
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <returns>PmcDataRepeatableQuest</returns> /// <returns>PmcDataRepeatableQuest</returns>
protected PmcDataRepeatableQuest GetRepeatableQuestSubTypeFromProfile(RepeatableQuestConfig repeatableConfig, protected PmcDataRepeatableQuest GetRepeatableQuestSubTypeFromProfile(
PmcData pmcData) RepeatableQuestConfig repeatableConfig,
PmcData pmcData
)
{ {
// Get from profile, add if missing // Get from profile, add if missing
var repeatableQuestDetails = pmcData.RepeatableQuests.FirstOrDefault(repeatable => repeatable.Name == repeatableConfig.Name var repeatableQuestDetails = pmcData.RepeatableQuests.FirstOrDefault(repeatable =>
repeatable.Name == repeatableConfig.Name
); );
var hasAccess = _profileHelper.HasAccessToRepeatableFreeRefreshSystem(pmcData); var hasAccess = _profileHelper.HasAccessToRepeatableFreeRefreshSystem(pmcData);
@@ -586,7 +643,7 @@ public class RepeatableQuestController(
InactiveQuests = [], InactiveQuests = [],
EndTime = 0, EndTime = 0,
FreeChanges = hasAccess ? repeatableConfig.FreeChanges : 0, FreeChanges = hasAccess ? repeatableConfig.FreeChanges : 0,
FreeChangesAvailable = hasAccess ? repeatableConfig.FreeChangesAvailable : 0 FreeChangesAvailable = hasAccess ? repeatableConfig.FreeChangesAvailable : 0,
}; };
// Add base object that holds repeatable data to profile // Add base object that holds repeatable data to profile
@@ -610,10 +667,16 @@ public class RepeatableQuestController(
/// <param name="repeatableConfig">Repeatable quest config</param> /// <param name="repeatableConfig">Repeatable quest config</param>
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <returns>True if profile has access to repeatables</returns> /// <returns>True if profile has access to repeatables</returns>
protected bool CanProfileAccessRepeatableQuests(RepeatableQuestConfig repeatableConfig, PmcData pmcData) protected bool CanProfileAccessRepeatableQuests(
RepeatableQuestConfig repeatableConfig,
PmcData pmcData
)
{ {
// PMC and daily quests not unlocked yet // PMC and daily quests not unlocked yet
if (repeatableConfig.Side == "Pmc" && !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)) if (
repeatableConfig.Side == "Pmc"
&& !PlayerHasDailyPmcQuestsUnlocked(pmcData, repeatableConfig)
)
{ {
return false; return false;
} }
@@ -638,7 +701,10 @@ public class RepeatableQuestController(
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
/// <param name="repeatableConfig">Config of daily type to check</param> /// <param name="repeatableConfig">Config of daily type to check</param>
/// <returns>True if unlocked</returns> /// <returns>True if unlocked</returns>
protected static bool PlayerHasDailyPmcQuestsUnlocked(PmcData pmcData, RepeatableQuestConfig repeatableConfig) protected static bool PlayerHasDailyPmcQuestsUnlocked(
PmcData pmcData,
RepeatableQuestConfig repeatableConfig
)
{ {
return pmcData.Info.Level >= repeatableConfig.MinPlayerLevel; return pmcData.Info.Level >= repeatableConfig.MinPlayerLevel;
} }
@@ -650,9 +716,11 @@ public class RepeatableQuestController(
/// <returns>True if unlocked</returns> /// <returns>True if unlocked</returns>
protected bool PlayerHasDailyScavQuestsUnlocked(PmcData pmcData) protected bool PlayerHasDailyScavQuestsUnlocked(PmcData pmcData)
{ {
return pmcData?.Hideout?.Areas?.FirstOrDefault(hideoutArea => hideoutArea.Type == HideoutAreas.IntelligenceCenter) return pmcData
?.Level >= ?.Hideout?.Areas?.FirstOrDefault(hideoutArea =>
1; hideoutArea.Type == HideoutAreas.IntelligenceCenter
)
?.Level >= 1;
} }
/// <summary> /// <summary>
@@ -660,12 +728,17 @@ public class RepeatableQuestController(
/// </summary> /// </summary>
/// <param name="generatedRepeatables">Repeatables to process (daily/weekly)</param> /// <param name="generatedRepeatables">Repeatables to process (daily/weekly)</param>
/// <param name="pmcData">Players PMC profile</param> /// <param name="pmcData">Players PMC profile</param>
protected void ProcessExpiredQuests(PmcDataRepeatableQuest generatedRepeatables, PmcData pmcData) protected void ProcessExpiredQuests(
PmcDataRepeatableQuest generatedRepeatables,
PmcData pmcData
)
{ {
var questsToKeep = new List<RepeatableQuest>(); var questsToKeep = new List<RepeatableQuest>();
foreach (var activeQuest in generatedRepeatables.ActiveQuests) foreach (var activeQuest in generatedRepeatables.ActiveQuests)
{ {
var questStatusInProfile = pmcData.Quests.FirstOrDefault(quest => quest.QId == activeQuest.Id); var questStatusInProfile = pmcData.Quests.FirstOrDefault(quest =>
quest.QId == activeQuest.Id
);
if (questStatusInProfile is null) if (questStatusInProfile is null)
{ {
continue; continue;
@@ -723,8 +796,15 @@ public class RepeatableQuestController(
// Add "any" to pickup quest pool // Add "any" to pickup quest pool
questPool.Pool.Pickup.Locations[ELocationName.any] = ["any"]; questPool.Pool.Pickup.Locations[ELocationName.any] = ["any"];
var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(pmcLevel, repeatableConfig); var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(
var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(_mathUtil, _cloner, eliminationConfig.Targets); pmcLevel,
repeatableConfig
);
var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(
_mathUtil,
_cloner,
eliminationConfig.Targets
);
// Populate Elimination quest targets and their locations // Populate Elimination quest targets and their locations
foreach (var target in targetsConfig) foreach (var target in targetsConfig)
@@ -734,10 +814,7 @@ public class RepeatableQuestController(
{ {
questPool.Pool.Elimination.Targets.Add( questPool.Pool.Elimination.Targets.Add(
target.Key, target.Key,
new TargetLocation new TargetLocation { Locations = ["any"] }
{
Locations = ["any"]
}
); );
continue; continue;
@@ -747,16 +824,16 @@ public class RepeatableQuestController(
var possibleLocations = repeatableConfig.Locations.Keys; var possibleLocations = repeatableConfig.Locations.Keys;
var allowedLocations = var allowedLocations =
target.Key == "Savage" target.Key == "Savage"
? possibleLocations.Where(location => location != ELocationName.laboratory ? possibleLocations.Where(location => location != ELocationName.laboratory) // Exclude labs for Savage targets.
) // Exclude labs for Savage targets.
: possibleLocations; : possibleLocations;
questPool.Pool.Elimination.Targets.Add( questPool.Pool.Elimination.Targets.Add(
target.Key, target.Key,
new TargetLocation new TargetLocation
{ {
Locations = allowedLocations.Select(x => x.ToString()).ToList() Locations = allowedLocations.Select(x => x.ToString()).ToList(),
}); }
);
} }
return questPool; return questPool;
@@ -776,17 +853,17 @@ public class RepeatableQuestController(
{ {
Exploration = new ExplorationPool Exploration = new ExplorationPool
{ {
Locations = new Dictionary<ELocationName, List<string>>() Locations = new Dictionary<ELocationName, List<string>>(),
}, },
Elimination = new EliminationPool Elimination = new EliminationPool
{ {
Targets = new Dictionary<string, TargetLocation>() Targets = new Dictionary<string, TargetLocation>(),
}, },
Pickup = new ExplorationPool Pickup = new ExplorationPool
{ {
Locations = new Dictionary<ELocationName, List<string>>() Locations = new Dictionary<ELocationName, List<string>>(),
} },
} },
}; };
} }
@@ -805,23 +882,25 @@ public class RepeatableQuestController(
} }
// Add elite bonus to daily quests // Add elite bonus to daily quests
if (string.Equals(repeatableConfig.Name, "daily", StringComparison.OrdinalIgnoreCase) && if (
_profileHelper.HasEliteSkillLevel(SkillTypes.Charisma, fullProfile.CharacterData.PmcData)) string.Equals(repeatableConfig.Name, "daily", StringComparison.OrdinalIgnoreCase)
// Elite charisma skill gives extra daily quest(s) && _profileHelper.HasEliteSkillLevel(
SkillTypes.Charisma,
fullProfile.CharacterData.PmcData
)
)
// Elite charisma skill gives extra daily quest(s)
{ {
questCount += _databaseService questCount += _databaseService
.GetGlobals() .GetGlobals()
.Configuration .Configuration.SkillsSettings.Charisma.BonusSettings.EliteBonusSettings.RepeatableQuestExtraCount.GetValueOrDefault(
.SkillsSettings 0
.Charisma );
.BonusSettings
.EliteBonusSettings
.RepeatableQuestExtraCount
.GetValueOrDefault(0);
} }
// Add any extra repeatable quests the profile has unlocked // Add any extra repeatable quests the profile has unlocked
questCount += (int) fullProfile.SptData.ExtraRepeatableQuests.GetValueOrDefault(repeatableConfig.Id, 0); questCount += (int)
fullProfile.SptData.ExtraRepeatableQuests.GetValueOrDefault(repeatableConfig.Id, 0);
return questCount; return questCount;
} }
@@ -50,7 +50,8 @@ public class TradeController(
public ItemEventRouterResponse ConfirmTrading( public ItemEventRouterResponse ConfirmTrading(
PmcData pmcData, PmcData pmcData,
ProcessBaseTradeRequestData request, ProcessBaseTradeRequestData request,
string sessionID) string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
@@ -58,7 +59,7 @@ public class TradeController(
if (request.Type == "buy_from_trader") if (request.Type == "buy_from_trader")
{ {
var foundInRaid = _traderConfig.PurchasesAreFoundInRaid; var foundInRaid = _traderConfig.PurchasesAreFoundInRaid;
var buyData = (ProcessBuyTradeRequestData) request; var buyData = (ProcessBuyTradeRequestData)request;
_tradeHelper.BuyItem(pmcData, buyData, sessionID, foundInRaid, output); _tradeHelper.BuyItem(pmcData, buyData, sessionID, foundInRaid, output);
return output; return output;
@@ -67,7 +68,7 @@ public class TradeController(
// Selling // Selling
if (request.Type == "sell_to_trader") if (request.Type == "sell_to_trader")
{ {
var sellData = (ProcessSellTradeRequestData) request; var sellData = (ProcessSellTradeRequestData)request;
_tradeHelper.SellItem(pmcData, pmcData, sellData, sessionID, output); _tradeHelper.SellItem(pmcData, pmcData, sellData, sessionID, output);
return output; return output;
@@ -76,7 +77,11 @@ public class TradeController(
var errorMessage = $"Unhandled trade event: {request.Type}"; var errorMessage = $"Unhandled trade event: {request.Type}";
_logger.Error(errorMessage); _logger.Error(errorMessage);
return _httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.RagfairUnavailable); return _httpResponseUtil.AppendErrorToOutput(
output,
errorMessage,
BackendErrorCodes.RagfairUnavailable
);
} }
/// <summary> /// <summary>
@@ -89,7 +94,8 @@ public class TradeController(
public ItemEventRouterResponse ConfirmRagfairTrading( public ItemEventRouterResponse ConfirmRagfairTrading(
PmcData pmcData, PmcData pmcData,
ProcessRagfairTradeRequestData request, ProcessRagfairTradeRequestData request,
string sessionID) string sessionID
)
{ {
var output = _eventOutputHolder.GetOutput(sessionID); var output = _eventOutputHolder.GetOutput(sessionID);
@@ -111,7 +117,11 @@ public class TradeController(
"ragfair-unable_to_purchase_0_count_item", "ragfair-unable_to_purchase_0_count_item",
_itemHelper.GetItem(fleaOffer.Items[0].Template).Value.Name _itemHelper.GetItem(fleaOffer.Items[0].Template).Value.Name
); );
return _httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.OfferOutOfStock); return _httpResponseUtil.AppendErrorToOutput(
output,
errorMessage,
BackendErrorCodes.OfferOutOfStock
);
} }
if (_ragfairOfferHelper.OfferIsFromTrader(fleaOffer)) if (_ragfairOfferHelper.OfferIsFromTrader(fleaOffer))
@@ -146,18 +156,24 @@ public class TradeController(
PmcData pmcData, PmcData pmcData,
RagfairOffer fleaOffer, RagfairOffer fleaOffer,
OfferRequest requestOffer, OfferRequest requestOffer,
ItemEventRouterResponse output) ItemEventRouterResponse output
)
{ {
// Skip buying items when player doesn't have needed loyalty // Skip buying items when player doesn't have needed loyalty
if (PlayerLacksTraderLoyaltyLevelToBuyOffer(fleaOffer, pmcData)) 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"; 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)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug(errorMessage); _logger.Debug(errorMessage);
} }
_httpResponseUtil.AppendErrorToOutput(output, errorMessage, BackendErrorCodes.RagfairUnavailable); _httpResponseUtil.AppendErrorToOutput(
output,
errorMessage,
BackendErrorCodes.RagfairUnavailable
);
return; return;
} }
@@ -171,9 +187,15 @@ public class TradeController(
ItemId = fleaOffer.Root, ItemId = fleaOffer.Root,
Count = requestOffer.Count, Count = requestOffer.Count,
SchemeId = 0, SchemeId = 0,
SchemeItems = requestOffer.Items SchemeItems = requestOffer.Items,
}; };
_tradeHelper.BuyItem(pmcData, buyData, sessionId, _traderConfig.PurchasesAreFoundInRaid, output); _tradeHelper.BuyItem(
pmcData,
buyData,
sessionId,
_traderConfig.PurchasesAreFoundInRaid,
output
);
// Remove/lower offer quantity of item purchased from trader flea offer // Remove/lower offer quantity of item purchased from trader flea offer
_ragfairServer.ReduceOfferQuantity(fleaOffer.Id, requestOffer.Count ?? 0); _ragfairServer.ReduceOfferQuantity(fleaOffer.Id, requestOffer.Count ?? 0);
@@ -192,7 +214,8 @@ public class TradeController(
PmcData pmcData, PmcData pmcData,
RagfairOffer fleaOffer, RagfairOffer fleaOffer,
OfferRequest requestOffer, OfferRequest requestOffer,
ItemEventRouterResponse output) ItemEventRouterResponse output
)
{ {
var buyData = new ProcessBuyTradeRequestData var buyData = new ProcessBuyTradeRequestData
{ {
@@ -202,11 +225,17 @@ public class TradeController(
ItemId = fleaOffer.Id, // Store ragfair offerId in buyRequestData.item_id ItemId = fleaOffer.Id, // Store ragfair offerId in buyRequestData.item_id
Count = requestOffer.Count, Count = requestOffer.Count,
SchemeId = 0, SchemeId = 0,
SchemeItems = requestOffer.Items SchemeItems = requestOffer.Items,
}; };
// buyItem() must occur prior to removing the offer stack, otherwise item inside offer doesn't exist for confirmTrading() to use // 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); _tradeHelper.BuyItem(
pmcData,
buyData,
sessionId,
_ragfairConfig.Dynamic.PurchasesAreFoundInRaid,
output
);
if (output.Warnings?.Count > 0) if (output.Warnings?.Count > 0)
{ {
return; return;
@@ -234,9 +263,7 @@ public class TradeController(
/// <param name="offerId">id of the offer</param> /// <param name="offerId">id of the offer</param>
/// <param name="offerOwnerId">Owner id</param> /// <param name="offerOwnerId">Owner id</param>
/// <returns>true if offer was made by a player</returns> /// <returns>true if offer was made by a player</returns>
protected bool IsPlayerOffer( protected bool IsPlayerOffer(string offerId, string? offerOwnerId)
string offerId,
string? offerOwnerId)
{ {
// No ownerId, not player offer // No ownerId, not player offer
if (offerOwnerId is null) if (offerOwnerId is null)
@@ -246,7 +273,7 @@ public class TradeController(
var offerCreatorProfile = _profileHelper.GetPmcProfile(offerOwnerId); var offerCreatorProfile = _profileHelper.GetPmcProfile(offerOwnerId);
if (offerCreatorProfile is null || offerCreatorProfile.RagfairInfo.Offers?.Count == 0) if (offerCreatorProfile is null || offerCreatorProfile.RagfairInfo.Offers?.Count == 0)
// No profile or no offers // No profile or no offers
{ {
return false; return false;
} }
@@ -261,9 +288,7 @@ public class TradeController(
/// <param name="fleaOffer">Flea offer being bought</param> /// <param name="fleaOffer">Flea offer being bought</param>
/// <param name="pmcData">Player profile</param> /// <param name="pmcData">Player profile</param>
/// <returns>True if player can buy offer</returns> /// <returns>True if player can buy offer</returns>
protected bool PlayerLacksTraderLoyaltyLevelToBuyOffer( protected bool PlayerLacksTraderLoyaltyLevelToBuyOffer(RagfairOffer fleaOffer, PmcData pmcData)
RagfairOffer fleaOffer,
PmcData pmcData)
{ {
return fleaOffer.LoyaltyLevel > pmcData.TradersInfo[fleaOffer.User.Id].LoyaltyLevel; return fleaOffer.LoyaltyLevel > pmcData.TradersInfo[fleaOffer.User.Id].LoyaltyLevel;
} }
@@ -278,11 +303,12 @@ public class TradeController(
public ItemEventRouterResponse SellScavItemsToFence( public ItemEventRouterResponse SellScavItemsToFence(
PmcData pmcData, PmcData pmcData,
SellScavItemsToFenceRequestData request, SellScavItemsToFenceRequestData request,
string sessionId) string sessionId
)
{ {
var output = _eventOutputHolder.GetOutput(sessionId); var output = _eventOutputHolder.GetOutput(sessionId);
MailMoneyToPlayer(sessionId, (int) request.TotalValue, Traders.FENCE); MailMoneyToPlayer(sessionId, (int)request.TotalValue, Traders.FENCE);
return output; return output;
} }
@@ -293,10 +319,7 @@ public class TradeController(
/// <param name="sessionId">Session id</param> /// <param name="sessionId">Session id</param>
/// <param name="roublesToSend">amount of roubles to send</param> /// <param name="roublesToSend">amount of roubles to send</param>
/// <param name="trader">Trader to sell items to</param> /// <param name="trader">Trader to sell items to</param>
protected void MailMoneyToPlayer( protected void MailMoneyToPlayer(string sessionId, int roublesToSend, string trader)
string sessionId,
int roublesToSend,
string trader)
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
@@ -308,10 +331,7 @@ public class TradeController(
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = Money.ROUBLES, Template = Money.ROUBLES,
Upd = new Upd Upd = new Upd { StackObjectsCount = roublesToSend },
{
StackObjectsCount = roublesToSend
}
}; };
// Ensure money is properly split to follow its max stack size limit // Ensure money is properly split to follow its max stack size limit
@@ -322,7 +342,11 @@ public class TradeController(
sessionId, sessionId,
trader, trader,
MessageType.MessageWithItems, MessageType.MessageWithItems,
_randomUtil.GetArrayValue(_databaseService.GetTrader(trader).Dialogue.TryGetValue("soldItems", out var items) ? items : new List<string>()), _randomUtil.GetArrayValue(
_databaseService.GetTrader(trader).Dialogue.TryGetValue("soldItems", out var items)
? items
: new List<string>()
),
curencyReward.SelectMany(x => x).ToList(), curencyReward.SelectMany(x => x).ToList(),
_timeUtil.GetHoursAsSeconds(72) _timeUtil.GetHoursAsSeconds(72)
); );
@@ -340,7 +364,8 @@ public class TradeController(
string parentItemId, string parentItemId,
List<Item> items, List<Item> items,
Dictionary<string, int?> handbookPrices, Dictionary<string, int?> handbookPrices,
TraderBase traderDetails) TraderBase traderDetails
)
{ {
var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(items, parentItemId); var itemWithChildren = _itemHelper.FindAndReturnChildrenAsItems(items, parentItemId);
@@ -348,14 +373,25 @@ public class TradeController(
foreach (var itemToSell in itemWithChildren) foreach (var itemToSell in itemWithChildren)
{ {
var itemDetails = _itemHelper.GetItem(itemToSell.Template); var itemDetails = _itemHelper.GetItem(itemToSell.Template);
if (!(itemDetails.Key && _itemHelper.IsOfBaseclasses(itemDetails.Value.Id, traderDetails.ItemsBuy.Category))) if (
// Skip if tpl isn't item OR item doesn't fulfil match traders buy categories !(
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; continue;
} }
// Get price of item multiplied by how many are in stack // 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; return totalPrice;
@@ -67,8 +67,9 @@ public class TraderController(
_traderPurchasePersisterService.RemoveStalePurchasesFromProfiles(traderId); _traderPurchasePersisterService.RemoveStalePurchasesFromProfiles(traderId);
// Set to next hour on clock or current time + 60 minutes // Set to next hour on clock or current time + 60 minutes
trader.Base.NextResupply = trader.Base.NextResupply = traderResetStartsWithServer
traderResetStartsWithServer ? (int) _traderHelper.GetNextUpdateTimestamp(trader.Base.Id) : (int) nextHourTimestamp; ? (int)_traderHelper.GetNextUpdateTimestamp(trader.Base.Id)
: (int)nextHourTimestamp;
} }
} }
@@ -85,10 +86,7 @@ public class TraderController(
var barterSchemeItem = kvp.Value?.FirstOrDefault()?.FirstOrDefault(); var barterSchemeItem = kvp.Value?.FirstOrDefault()?.FirstOrDefault();
if (barterSchemeItem != null && _paymentHelper.IsMoneyTpl(barterSchemeItem.Template)) if (barterSchemeItem != null && _paymentHelper.IsMoneyTpl(barterSchemeItem.Template))
{ {
barterSchemeItem.Count += Math.Round( barterSchemeItem.Count += Math.Round(barterSchemeItem?.Count * multiplier ?? 0D, 2);
barterSchemeItem?.Count * multiplier ?? 0D,
2
);
} }
} }
} }
@@ -108,14 +106,14 @@ public class TraderController(
case Traders.LIGHTHOUSEKEEPER: case Traders.LIGHTHOUSEKEEPER:
continue; continue;
case Traders.FENCE: case Traders.FENCE:
{
if (_fenceService.NeedsPartialRefresh())
{ {
if (_fenceService.NeedsPartialRefresh()) _fenceService.GenerateFenceAssorts();
{
_fenceService.GenerateFenceAssorts();
}
continue;
} }
continue;
}
} }
// Trader needs to be refreshed // Trader needs to be refreshed
@@ -203,8 +201,8 @@ public class TraderController(
{ {
{ "5449016a4bdc2d6f028b456f", handbookPrices[Money.ROUBLES] }, { "5449016a4bdc2d6f028b456f", handbookPrices[Money.ROUBLES] },
{ "569668774bdc2da2298b4568", handbookPrices[Money.EUROS] }, { "569668774bdc2da2298b4568", handbookPrices[Money.EUROS] },
{ "5696686a4bdc2da3298b456a", handbookPrices[Money.DOLLARS] } { "5696686a4bdc2da3298b456a", handbookPrices[Money.DOLLARS] },
} },
}; };
} }
} }
@@ -23,7 +23,6 @@ public class WeatherController(
{ {
protected WeatherConfig _weatherConfig = _configServer.GetConfig<WeatherConfig>(); protected WeatherConfig _weatherConfig = _configServer.GetConfig<WeatherConfig>();
/// <summary> /// <summary>
/// Handle client/weather /// Handle client/weather
/// </summary> /// </summary>
@@ -36,7 +35,7 @@ public class WeatherController(
Time = "", Time = "",
Date = "", Date = "",
Weather = null, Weather = null,
Season = Season.AUTUMN Season = Season.AUTUMN,
}; };
_weatherGenerator.CalculateGameTime(result); _weatherGenerator.CalculateGameTime(result);
@@ -55,7 +54,7 @@ public class WeatherController(
var result = new GetLocalWeatherResponseData var result = new GetLocalWeatherResponseData
{ {
Season = _seasonalEventService.GetActiveWeatherSeason(), Season = _seasonalEventService.GetActiveWeatherSeason(),
Weather = [] Weather = [],
}; };
result.Weather.AddRange(_raidWeatherService.GetUpcomingWeather()); result.Weather.AddRange(_raidWeatherService.GetUpcomingWeather());
@@ -7,9 +7,7 @@ using SPTarkov.Server.Core.Routers;
namespace SPTarkov.Server.Core.Controllers; namespace SPTarkov.Server.Core.Controllers;
[Injectable] [Injectable]
public class WishlistController( public class WishlistController(EventOutputHolder _eventOutputHolder)
EventOutputHolder _eventOutputHolder
)
{ {
/// <summary> /// <summary>
/// Handle AddToWishList /// Handle AddToWishList
@@ -21,7 +19,8 @@ public class WishlistController(
public ItemEventRouterResponse AddToWishList( public ItemEventRouterResponse AddToWishList(
PmcData pmcData, PmcData pmcData,
AddToWishlistRequest request, AddToWishlistRequest request,
string sessionId) string sessionId
)
{ {
foreach (var item in request.Items) foreach (var item in request.Items)
{ {
@@ -41,7 +40,8 @@ public class WishlistController(
public ItemEventRouterResponse RemoveFromWishList( public ItemEventRouterResponse RemoveFromWishList(
PmcData pmcData, PmcData pmcData,
RemoveFromWishlistRequest request, RemoveFromWishlistRequest request,
string sessionId) string sessionId
)
{ {
foreach (var itemId in request.Items) foreach (var itemId in request.Items)
{ {
@@ -61,7 +61,8 @@ public class WishlistController(
public ItemEventRouterResponse ChangeWishListItemCategory( public ItemEventRouterResponse ChangeWishListItemCategory(
PmcData pmcData, PmcData pmcData,
ChangeWishlistItemCategoryRequest request, ChangeWishlistItemCategoryRequest request,
string sessionId) string sessionId
)
{ {
pmcData.WishList.Dictionary[request.Item] = request.Category.Value; pmcData.WishList.Dictionary[request.Item] = request.Category.Value;
+20 -12
View File
@@ -32,14 +32,10 @@ public abstract class Router
{ {
if (partialMatch) if (partialMatch)
{ {
return GetInternalHandledRoutes() return GetInternalHandledRoutes().Where(r => r.dynamic).Any(r => url.Contains(r.route));
.Where(r => r.dynamic)
.Any(r => url.Contains(r.route));
} }
return GetInternalHandledRoutes() return GetInternalHandledRoutes().Where(r => !r.dynamic).Any(r => r.route == url);
.Where(r => !r.dynamic)
.Any(r => r.route == url);
} }
} }
@@ -54,14 +50,19 @@ public abstract class StaticRouter : Router
_jsonUtil = jsonUtil; _jsonUtil = jsonUtil;
} }
public async ValueTask<object> HandleStatic(string url, string? body, string sessionID, string output) public async ValueTask<object> HandleStatic(
string url,
string? body,
string sessionID,
string output
)
{ {
var action = _actions.Single(route => route.url == url); var action = _actions.Single(route => route.url == url);
var type = action.bodyType; var type = action.bodyType;
IRequestData? info = null; IRequestData? info = null;
if (type != null && !string.IsNullOrEmpty(body)) if (type != null && !string.IsNullOrEmpty(body))
{ {
info = (IRequestData?) _jsonUtil.Deserialize(body, type); info = (IRequestData?)_jsonUtil.Deserialize(body, type);
} }
return await action.action(url, info, sessionID, output); return await action.action(url, info, sessionID, output);
@@ -84,14 +85,19 @@ public abstract class DynamicRouter : Router
_jsonUtil = jsonUtil; _jsonUtil = jsonUtil;
} }
public async ValueTask<object> HandleDynamic(string url, string? body, string sessionID, string output) public async ValueTask<object> HandleDynamic(
string url,
string? body,
string sessionID,
string output
)
{ {
var action = actions.First(r => url.Contains(r.url)); var action = actions.First(r => url.Contains(r.url));
var type = action.bodyType; var type = action.bodyType;
IRequestData? info = null; IRequestData? info = null;
if (type != null && !string.IsNullOrEmpty(body)) if (type != null && !string.IsNullOrEmpty(body))
{ {
info = (IRequestData?) _jsonUtil.Deserialize(body, type); info = (IRequestData?)_jsonUtil.Deserialize(body, type);
} }
return await action.action(url, info, sessionID, output); return await action.action(url, info, sessionID, output);
@@ -107,11 +113,13 @@ public abstract class DynamicRouter : Router
// So instead I added the definition // So instead I added the definition
public abstract class ItemEventRouterDefinition : Router public abstract class ItemEventRouterDefinition : Router
{ {
public abstract ValueTask<ItemEventRouterResponse> HandleItemEvent(string url, public abstract ValueTask<ItemEventRouterResponse> HandleItemEvent(
string url,
PmcData pmcData, PmcData pmcData,
BaseInteractionRequestData body, BaseInteractionRequestData body,
string sessionID, string sessionID,
ItemEventRouterResponse output); ItemEventRouterResponse output
);
} }
public abstract class SaveLoadRouter : Router public abstract class SaveLoadRouter : Router
@@ -1,18 +1,14 @@
namespace SPTarkov.Server.Core.DI namespace SPTarkov.Server.Core.DI
{ {
/// <summary> /// <summary>
/// A service locator designed specifically for Harmony patches and other /// A service locator designed specifically for Harmony patches and other
/// parts of the application that do not have direct access to the Dependency Injection (DI) system. /// parts of the application that do not have direct access to the Dependency Injection (DI) system.
/// ///
/// This should not be used at all when having direct access to DI. /// This should not be used at all when having direct access to DI.
/// </summary> /// </summary>
public static class ServiceLocator public static class ServiceLocator
{ {
public static IServiceProvider ServiceProvider public static IServiceProvider ServiceProvider { get; private set; }
{
get;
private set;
}
internal static void SetServiceProvider(IServiceProvider provider) internal static void SetServiceProvider(IServiceProvider provider)
{ {
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
using SPTarkov.Server.Core.Constants;
using SPTarkov.DI.Annotations; using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Constants;
using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Helpers;
using SPTarkov.Server.Core.Models.Common; using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common;
@@ -16,7 +16,6 @@ using BodyPart = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPart;
using BodyParts = SPTarkov.Server.Core.Constants.BodyParts; using BodyParts = SPTarkov.Server.Core.Constants.BodyParts;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Generators; namespace SPTarkov.Server.Core.Generators;
[Injectable] [Injectable]
@@ -50,7 +49,13 @@ public class BotGenerator(
/// <param name="botTemplate">base bot template to use (e.g. assault/pmcbot)</param> /// <param name="botTemplate">base bot template to use (e.g. assault/pmcbot)</param>
/// <param name="profile">profile of player generating pscav</param> /// <param name="profile">profile of player generating pscav</param>
/// <returns>BotBase</returns> /// <returns>BotBase</returns>
public PmcData GeneratePlayerScav(string sessionId, string role, string difficulty, BotType botTemplate, PmcData profile) public PmcData GeneratePlayerScav(
string sessionId,
string role,
string difficulty,
BotType botTemplate,
PmcData profile
)
{ {
var bot = GetCloneOfBotBase(); var bot = GetCloneOfBotBase();
bot.Info.Settings.BotDifficulty = difficulty; bot.Info.Settings.BotDifficulty = difficulty;
@@ -66,7 +71,7 @@ public class BotGenerator(
BotRelativeLevelDeltaMin = 0, BotRelativeLevelDeltaMin = 0,
BotCountToGenerate = 1, BotCountToGenerate = 1,
BotDifficulty = difficulty, BotDifficulty = difficulty,
IsPlayerScav = true IsPlayerScav = true,
}; };
bot = GenerateBot(sessionId, bot, botTemplate, botGenDetails); bot = GenerateBot(sessionId, bot, botTemplate, botGenDetails);
@@ -105,7 +110,7 @@ public class BotGenerator(
WishList = bot.WishList, WishList = bot.WishList,
MoneyTransferLimitData = bot.MoneyTransferLimitData, MoneyTransferLimitData = bot.MoneyTransferLimitData,
IsPmc = bot.IsPmc, IsPmc = bot.IsPmc,
Prestige = new Dictionary<string, long>() Prestige = new Dictionary<string, long>(),
}; };
} }
@@ -115,7 +120,10 @@ public class BotGenerator(
/// <param name="sessionId">Session id</param> /// <param name="sessionId">Session id</param>
/// <param name="botGenerationDetails">details on how to generate bots</param> /// <param name="botGenerationDetails">details on how to generate bots</param>
/// <returns>constructed bot</returns> /// <returns>constructed bot</returns>
public BotBase PrepareAndGenerateBot(string sessionId, BotGenerationDetails? botGenerationDetails) public BotBase PrepareAndGenerateBot(
string sessionId,
BotGenerationDetails? botGenerationDetails
)
{ {
var preparedBotBase = GetPreparedBotBase( var preparedBotBase = GetPreparedBotBase(
botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided botGenerationDetails.EventRole ?? botGenerationDetails.Role, // Use eventRole if provided
@@ -124,13 +132,16 @@ public class BotGenerator(
); );
// Get raw json data for bot (Cloned) // Get raw json data for bot (Cloned)
var botRole = botGenerationDetails.IsPmc ?? false var botRole =
? preparedBotBase.Info.Side // Use side to get usec.json or bear.json when bot will be PMC botGenerationDetails.IsPmc ?? false
: botGenerationDetails.Role; ? 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)); var botJsonTemplateClone = _cloner.Clone(_botHelper.GetBotTemplate(botRole));
if (botJsonTemplateClone is null) if (botJsonTemplateClone is null)
{ {
_logger.Error($"Unable to retrieve: {botRole} bot template, cannot generate bot of this type"); _logger.Error(
$"Unable to retrieve: {botRole} bot template, cannot generate bot of this type"
);
} }
return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails); return GenerateBot(sessionId, preparedBotBase, botJsonTemplateClone, botGenerationDetails);
@@ -174,7 +185,8 @@ public class BotGenerator(
string sessionId, string sessionId,
BotBase bot, BotBase bot,
BotType botJsonTemplate, BotType botJsonTemplate,
BotGenerationDetails botGenerationDetails) BotGenerationDetails botGenerationDetails
)
{ {
var botRoleLowercase = botGenerationDetails.Role.ToLower(); var botRoleLowercase = botGenerationDetails.Role.ToLower();
var botLevel = _botLevelGenerator.GenerateBotLevel( var botLevel = _botLevelGenerator.GenerateBotLevel(
@@ -207,14 +219,17 @@ public class BotGenerator(
: string.Empty; : string.Empty;
// Only run when generating a 'fake' playerscav, not actual player scav // Only run when generating a 'fake' playerscav, not actual player scav
if (!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false) && ShouldSimulatePlayerScav(botRoleLowercase)) if (
!botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)
&& ShouldSimulatePlayerScav(botRoleLowercase)
)
{ {
_botNameService.AddRandomPmcNameToBotMainProfileNicknameProperty(bot); _botNameService.AddRandomPmcNameToBotMainProfileNicknameProperty(bot);
SetRandomisedGameVersionAndCategory(bot.Info); SetRandomisedGameVersionAndCategory(bot.Info);
} }
if (!_seasonalEventService.ChristmasEventEnabled()) if (!_seasonalEventService.ChristmasEventEnabled())
// Process all bots EXCEPT gifter, he needs christmas items // Process all bots EXCEPT gifter, he needs christmas items
{ {
if (botGenerationDetails.Role != "gifter") if (botGenerationDetails.Role != "gifter")
{ {
@@ -228,7 +243,12 @@ public class BotGenerator(
RemoveBlacklistedLootFromBotTemplate(botJsonTemplate.BotInventory); RemoveBlacklistedLootFromBotTemplate(botJsonTemplate.BotInventory);
// Remove hideout data if bot is not a PMC or pscav - match what live sends // Remove hideout data if bot is not a PMC or pscav - match what live sends
if (!(botGenerationDetails.IsPmc.GetValueOrDefault(false) || botGenerationDetails.IsPlayerScav.GetValueOrDefault(false))) if (
!(
botGenerationDetails.IsPmc.GetValueOrDefault(false)
|| botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)
)
)
{ {
bot.Hideout = null; bot.Hideout = null;
} }
@@ -250,9 +270,15 @@ public class BotGenerator(
botGenerationDetails.BotDifficulty, botGenerationDetails.BotDifficulty,
botGenerationDetails.Role botGenerationDetails.Role
); );
bot.Info.Settings.UseSimpleAnimator = botJsonTemplate.BotExperience.UseSimpleAnimator ?? false; bot.Info.Settings.UseSimpleAnimator =
bot.Info.Voice = _weightedRandomHelper.GetWeightedValue(botJsonTemplate.BotAppearance.Voice); botJsonTemplate.BotExperience.UseSimpleAnimator ?? false;
bot.Health = GenerateHealth(botJsonTemplate.BotHealth, botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)); bot.Info.Voice = _weightedRandomHelper.GetWeightedValue(
botJsonTemplate.BotAppearance.Voice
);
bot.Health = GenerateHealth(
botJsonTemplate.BotHealth,
botGenerationDetails.IsPlayerScav.GetValueOrDefault(false)
);
bot.Skills = GenerateSkills(botJsonTemplate.BotSkills); bot.Skills = GenerateSkills(botJsonTemplate.BotSkills);
bot.Info.PrestigeLevel = 0; bot.Info.PrestigeLevel = 0;
@@ -308,7 +334,8 @@ public class BotGenerator(
/// <returns>True if name should be simulated pscav</returns> /// <returns>True if name should be simulated pscav</returns>
public bool ShouldSimulatePlayerScav(string botRole) public bool ShouldSimulatePlayerScav(string botRole)
{ {
return botRole == Roles.Assault && _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName); return botRole == Roles.Assault
&& _randomUtil.GetChance100(_botConfig.ChanceAssaultScavHasPlayerScavName);
} }
/// <summary> /// <summary>
@@ -318,13 +345,19 @@ public class BotGenerator(
/// <param name="botDifficulty">the killed bots difficulty</param> /// <param name="botDifficulty">the killed bots difficulty</param>
/// <param name="role">Role of bot (optional, used for error logging)</param> /// <param name="role">Role of bot (optional, used for error logging)</param>
/// <returns>Experience for kill</returns> /// <returns>Experience for kill</returns>
public int GetExperienceRewardForKillByDifficulty(Dictionary<string, MinMax<int>> experiences, string botDifficulty, string role) public int GetExperienceRewardForKillByDifficulty(
Dictionary<string, MinMax<int>> experiences,
string botDifficulty,
string role
)
{ {
if (!experiences.TryGetValue(botDifficulty.ToLower(), out var result)) if (!experiences.TryGetValue(botDifficulty.ToLower(), out var result))
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`"); _logger.Debug(
$"Unable to find experience: {botDifficulty} for {role} bot, falling back to `normal`"
);
} }
return _randomUtil.GetInt(experiences["normal"].Min, experiences["normal"].Max); return _randomUtil.GetInt(experiences["normal"].Min, experiences["normal"].Max);
@@ -332,7 +365,6 @@ public class BotGenerator(
// Some bots have -1/-1, shortcut result // Some bots have -1/-1, shortcut result
if (result.Max == -1) if (result.Max == -1)
{ {
return -1; return -1;
@@ -348,11 +380,17 @@ public class BotGenerator(
/// <param name="botDifficulty">Difficulty of bot to look up</param> /// <param name="botDifficulty">Difficulty of bot to look up</param>
/// <param name="role">Role of bot (optional, used for error logging)</param> /// <param name="role">Role of bot (optional, used for error logging)</param>
/// <returns>Standing change value</returns> /// <returns>Standing change value</returns>
public double GetStandingChangeForKillByDifficulty(Dictionary<string, double> standingsForKill, string botDifficulty, string role) public double GetStandingChangeForKillByDifficulty(
Dictionary<string, double> standingsForKill,
string botDifficulty,
string role
)
{ {
if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result)) if (!standingsForKill.TryGetValue(botDifficulty.ToLower(), out var result))
{ {
_logger.Warning($"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`"); _logger.Warning(
$"Unable to find standing for kill value for: {role} {botDifficulty}, falling back to `normal`"
);
return standingsForKill["normal"]; return standingsForKill["normal"];
} }
@@ -367,11 +405,17 @@ public class BotGenerator(
/// <param name="botDifficulty">Difficulty of bot to look up</param> /// <param name="botDifficulty">Difficulty of bot to look up</param>
/// <param name="role">Role of bot (optional, used for error logging)</param> /// <param name="role">Role of bot (optional, used for error logging)</param>
/// <returns>Standing change value</returns> /// <returns>Standing change value</returns>
public double GetAggressorBonusByDifficulty(Dictionary<string, double> aggressorBonuses, string botDifficulty, string role) public double GetAggressorBonusByDifficulty(
Dictionary<string, double> aggressorBonuses,
string botDifficulty,
string role
)
{ {
if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result)) if (!aggressorBonuses.TryGetValue(botDifficulty.ToLower(), out var result))
{ {
_logger.Warning($"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`"); _logger.Warning(
$"Unable to find aggressor bonus for kill value for: {role} {botDifficulty}, falling back to `normal`"
);
return aggressorBonuses["normal"]; return aggressorBonuses["normal"];
} }
@@ -384,7 +428,10 @@ public class BotGenerator(
/// </summary> /// </summary>
/// <param name="botJsonTemplate">Bot data to adjust</param> /// <param name="botJsonTemplate">Bot data to adjust</param>
/// <param name="botGenerationDetails">Generation details of bot</param> /// <param name="botGenerationDetails">Generation details of bot</param>
public void FilterBlacklistedGear(BotType botJsonTemplate, BotGenerationDetails botGenerationDetails) public void FilterBlacklistedGear(
BotType botJsonTemplate,
BotGenerationDetails botGenerationDetails
)
{ {
var blacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist( var blacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
_botGeneratorHelper.GetBotEquipmentRole(botGenerationDetails.Role), _botGeneratorHelper.GetBotEquipmentRole(botGenerationDetails.Role),
@@ -392,7 +439,7 @@ public class BotGenerator(
); );
if (blacklist?.Gear is null) if (blacklist?.Gear is null)
// Nothing to filter by // Nothing to filter by
{ {
return; return;
} }
@@ -402,7 +449,7 @@ public class BotGenerator(
var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentSlot]; var equipmentDict = botJsonTemplate.BotInventory.Equipment[equipmentSlot];
foreach (var blacklistedTpl in blacklistedTpls) foreach (var blacklistedTpl in blacklistedTpls)
// Set weighting to 0, will never be picked // Set weighting to 0, will never be picked
{ {
equipmentDict[blacklistedTpl] = 0; equipmentDict[blacklistedTpl] = 0;
} }
@@ -433,9 +480,10 @@ public class BotGenerator(
// Remove blacklisted loot from loot containers // Remove blacklisted loot from loot containers
foreach (var lootContainerKey in lootContainersToFilter) foreach (var lootContainerKey in lootContainersToFilter)
{ {
var propInfo = props var propInfo = props.FirstOrDefault(x =>
.FirstOrDefault(x => string.Equals(x.Name, lootContainerKey, StringComparison.CurrentCultureIgnoreCase)); string.Equals(x.Name, lootContainerKey, StringComparison.CurrentCultureIgnoreCase)
var prop = (Dictionary<string, double>?) propInfo.GetValue(botInventory.Items); );
var prop = (Dictionary<string, double>?)propInfo.GetValue(botInventory.Items);
// No container, skip // No container, skip
if (prop is null) if (prop is null)
@@ -444,9 +492,10 @@ public class BotGenerator(
} }
var newProp = prop.Where(tpl => var newProp = prop.Where(tpl =>
{ {
return !_itemFilterService.IsLootableItemBlacklisted(tpl.Key); return !_itemFilterService.IsLootableItemBlacklisted(tpl.Key);
}).ToDictionary(); })
.ToDictionary();
propInfo.SetValue(botInventory.Items, newProp); propInfo.SetValue(botInventory.Items, newProp);
} }
} }
@@ -457,7 +506,11 @@ public class BotGenerator(
/// <param name="bot">Bot to adjust</param> /// <param name="bot">Bot to adjust</param>
/// <param name="appearance">Appearance settings to choose from</param> /// <param name="appearance">Appearance settings to choose from</param>
/// <param name="botGenerationDetails">Generation details</param> /// <param name="botGenerationDetails">Generation details</param>
public void SetBotAppearance(BotBase bot, Appearance appearance, BotGenerationDetails botGenerationDetails) public void SetBotAppearance(
BotBase bot,
Appearance appearance,
BotGenerationDetails botGenerationDetails
)
{ {
// Choose random values by weight // Choose random values by weight
bot.Customization.Head = _weightedRandomHelper.GetWeightedValue<string>(appearance.Head); bot.Customization.Head = _weightedRandomHelper.GetWeightedValue<string>(appearance.Head);
@@ -468,10 +521,13 @@ public class BotGenerator(
var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body]; var chosenBodyTemplate = _databaseService.GetCustomization()[bot.Customization.Body];
// Some bodies have matching hands, look up body to see if this is the case // Some bodies have matching hands, look up body to see if this is the case
var chosenBody = bodyGlobalDictDb.FirstOrDefault(c => c.Key == chosenBodyTemplate?.Name.Trim()); var chosenBody = bodyGlobalDictDb.FirstOrDefault(c =>
bot.Customization.Hands = chosenBody.Value?.IsNotRandom ?? false c.Key == chosenBodyTemplate?.Name.Trim()
? chosenBody.Value.Hands // Has fixed hands for chosen body, update to match );
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict bot.Customization.Hands =
chosenBody.Value?.IsNotRandom ?? false
? chosenBody.Value.Hands // Has fixed hands for chosen body, update to match
: _weightedRandomHelper.GetWeightedValue<string>(appearance.Hands); // Hands can be random, choose any from weighted dict
} }
/// <summary> /// <summary>
@@ -491,93 +547,121 @@ public class BotGenerator(
Hydration = new CurrentMinMax Hydration = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(healthObj.Hydration.Min, healthObj.Hydration.Max), Current = _randomUtil.GetDouble(healthObj.Hydration.Min, healthObj.Hydration.Max),
Maximum = healthObj.Hydration.Max Maximum = healthObj.Hydration.Max,
}, },
Energy = new CurrentMinMax Energy = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(healthObj.Energy.Min, healthObj.Energy.Max), Current = _randomUtil.GetDouble(healthObj.Energy.Min, healthObj.Energy.Max),
Maximum = healthObj.Energy.Max Maximum = healthObj.Energy.Max,
}, },
Temperature = new CurrentMinMax Temperature = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(healthObj.Temperature.Min, healthObj.Temperature.Max), Current = _randomUtil.GetDouble(
Maximum = healthObj.Temperature.Max healthObj.Temperature.Min,
healthObj.Temperature.Max
),
Maximum = healthObj.Temperature.Max,
}, },
BodyParts = new Dictionary<string, BodyPartHealth> BodyParts = new Dictionary<string, BodyPartHealth>
{ {
{ {
BodyParts.Head, new BodyPartHealth BodyParts.Head,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.Head.Min, bodyParts.Head.Max), Current = _randomUtil.GetDouble(bodyParts.Head.Min, bodyParts.Head.Max),
Maximum = Math.Round(bodyParts.Head.Max) Maximum = Math.Round(bodyParts.Head.Max),
} },
} }
}, },
{ {
BodyParts.Chest, new BodyPartHealth BodyParts.Chest,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.Chest.Min, bodyParts.Chest.Max), Current = _randomUtil.GetDouble(
Maximum = Math.Round(bodyParts.Chest.Max) bodyParts.Chest.Min,
} bodyParts.Chest.Max
),
Maximum = Math.Round(bodyParts.Chest.Max),
},
} }
}, },
{ {
BodyParts.Stomach, new BodyPartHealth BodyParts.Stomach,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.Stomach.Min, bodyParts.Stomach.Max), Current = _randomUtil.GetDouble(
Maximum = Math.Round(bodyParts.Stomach.Max) bodyParts.Stomach.Min,
} bodyParts.Stomach.Max
),
Maximum = Math.Round(bodyParts.Stomach.Max),
},
} }
}, },
{ {
BodyParts.LeftArm, new BodyPartHealth BodyParts.LeftArm,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.LeftArm.Min, bodyParts.LeftArm.Max), Current = _randomUtil.GetDouble(
Maximum = Math.Round(bodyParts.LeftArm.Max) bodyParts.LeftArm.Min,
} bodyParts.LeftArm.Max
),
Maximum = Math.Round(bodyParts.LeftArm.Max),
},
} }
}, },
{ {
BodyParts.RightArm, new BodyPartHealth BodyParts.RightArm,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.RightArm.Min, bodyParts.RightArm.Max), Current = _randomUtil.GetDouble(
Maximum = Math.Round(bodyParts.RightArm.Max) bodyParts.RightArm.Min,
} bodyParts.RightArm.Max
),
Maximum = Math.Round(bodyParts.RightArm.Max),
},
} }
}, },
{ {
BodyParts.LeftLeg, new BodyPartHealth BodyParts.LeftLeg,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.LeftLeg.Min, bodyParts.LeftLeg.Max), Current = _randomUtil.GetDouble(
Maximum = Math.Round(bodyParts.LeftLeg.Max) bodyParts.LeftLeg.Min,
} bodyParts.LeftLeg.Max
),
Maximum = Math.Round(bodyParts.LeftLeg.Max),
},
} }
}, },
{ {
BodyParts.RightLeg, new BodyPartHealth BodyParts.RightLeg,
new BodyPartHealth
{ {
Health = new CurrentMinMax Health = new CurrentMinMax
{ {
Current = _randomUtil.GetDouble(bodyParts.RightLeg.Min, bodyParts.RightLeg.Max), Current = _randomUtil.GetDouble(
Maximum = Math.Round(bodyParts.RightLeg.Max) bodyParts.RightLeg.Min,
} bodyParts.RightLeg.Max
),
Maximum = Math.Round(bodyParts.RightLeg.Max),
},
} }
} },
}, },
UpdateTime = 0, // 0 for player-scav too UpdateTime = 0, // 0 for player-scav too
Immortal = false Immortal = false,
}; };
return health; return health;
@@ -602,9 +686,13 @@ public class BotGenerator(
{ {
double? hpTotal = 0; double? hpTotal = 0;
foreach (var prop in props.Where(property => !property.Name.Equals("extensiondata", StringComparison.OrdinalIgnoreCase))) foreach (
var prop in props.Where(property =>
!property.Name.Equals("extensiondata", StringComparison.OrdinalIgnoreCase)
)
)
{ {
var value = (MinMax<double>) prop.GetValue(bodyPart); var value = (MinMax<double>)prop.GetValue(bodyPart);
hpTotal += value.Max; hpTotal += value.Max;
} }
@@ -630,7 +718,7 @@ public class BotGenerator(
{ {
Common = GetCommonSkillsWithRandomisedProgressValue(botSkills.Common), Common = GetCommonSkillsWithRandomisedProgressValue(botSkills.Common),
Mastering = GetMasteringSkillsWithRandomisedProgressValue(botSkills.Mastering), Mastering = GetMasteringSkillsWithRandomisedProgressValue(botSkills.Mastering),
Points = 0 Points = 0,
}; };
return skillsToReturn; return skillsToReturn;
@@ -641,7 +729,9 @@ public class BotGenerator(
/// </summary> /// </summary>
/// <param name="skills">Skills to randomise</param> /// <param name="skills">Skills to randomise</param>
/// <returns>Skills with randomised progress values as a collection</returns> /// <returns>Skills with randomised progress values as a collection</returns>
public List<CommonSkill> GetCommonSkillsWithRandomisedProgressValue(Dictionary<string, MinMax<double>>? skills) public List<CommonSkill> GetCommonSkillsWithRandomisedProgressValue(
Dictionary<string, MinMax<double>>? skills
)
{ {
if (skills is null) if (skills is null)
{ {
@@ -650,23 +740,22 @@ public class BotGenerator(
return skills return skills
.Select(kvp => .Select(kvp =>
{
// Get skill from dict, skip if not found
var skill = kvp.Value;
if (skill == null)
{ {
// Get skill from dict, skip if not found return null;
var skill = kvp.Value;
if (skill == null)
{
return null;
}
return new CommonSkill
{
Id = Enum.Parse<SkillTypes>(kvp.Key),
Progress = _randomUtil.GetDouble(skill.Min, skill.Max),
PointsEarnedDuringSession = 0,
LastAccess = 0
};
} }
)
return new CommonSkill
{
Id = Enum.Parse<SkillTypes>(kvp.Key),
Progress = _randomUtil.GetDouble(skill.Min, skill.Max),
PointsEarnedDuringSession = 0,
LastAccess = 0,
};
})
.Where(baseSkill => baseSkill != null) .Where(baseSkill => baseSkill != null)
.ToList(); .ToList();
} }
@@ -676,7 +765,9 @@ public class BotGenerator(
/// </summary> /// </summary>
/// <param name="skills">Skills to randomise</param> /// <param name="skills">Skills to randomise</param>
/// <returns>Skills with randomised progress values as a collection</returns> /// <returns>Skills with randomised progress values as a collection</returns>
public List<MasterySkill> GetMasteringSkillsWithRandomisedProgressValue(Dictionary<string, MinMax<double>>? skills) public List<MasterySkill> GetMasteringSkillsWithRandomisedProgressValue(
Dictionary<string, MinMax<double>>? skills
)
{ {
if (skills is null) if (skills is null)
{ {
@@ -685,22 +776,21 @@ public class BotGenerator(
return skills return skills
.Select(kvp => .Select(kvp =>
{
// Get skill from dict, skip if not found
var skill = kvp.Value;
if (skill == null)
{ {
// Get skill from dict, skip if not found return null;
var skill = kvp.Value;
if (skill == null)
{
return null;
}
// All skills have id and progress props
return new MasterySkill
{
Id = kvp.Key,
Progress = _randomUtil.GetDouble(skill.Min, skill.Max)
};
} }
)
// All skills have id and progress props
return new MasterySkill
{
Id = kvp.Key,
Progress = _randomUtil.GetDouble(skill.Min, skill.Max),
};
})
.Where(baseSkill => baseSkill != null) .Where(baseSkill => baseSkill != null)
.ToList(); .ToList();
} }
@@ -716,7 +806,9 @@ public class BotGenerator(
var botId = _hashUtil.Generate(); var botId = _hashUtil.Generate();
bot.Id = botId; bot.Id = botId;
bot.Aid = botGenerationDetails.IsPmc.GetValueOrDefault(false) ? _hashUtil.GenerateAccountId() : 0; bot.Aid = botGenerationDetails.IsPmc.GetValueOrDefault(false)
? _hashUtil.GenerateAccountId()
: 0;
} }
/// <summary> /// <summary>
@@ -788,7 +880,9 @@ public class BotGenerator(
break; break;
default: default:
// Everyone else gets a weighted randomised category // Everyone else gets a weighted randomised category
botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue(_pmcConfig.AccountTypeWeight); botInfo.MemberCategory = _weightedRandomHelper.GetWeightedValue(
_pmcConfig.AccountTypeWeight
);
break; break;
} }
@@ -811,10 +905,7 @@ public class BotGenerator(
Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion), Template = GetDogtagTplByGameVersionAndSide(bot.Info.Side, bot.Info.GameVersion),
ParentId = bot.Inventory.Equipment, ParentId = bot.Inventory.Equipment,
SlotId = Slots.Dogtag, SlotId = Slots.Dogtag,
Upd = new Upd Upd = new Upd { SpawnedInSession = true },
{
SpawnedInSession = true
}
}; };
bot.Inventory.Items.Add(inventoryItem); bot.Inventory.Items.Add(inventoryItem);
@@ -47,12 +47,16 @@ public class BotInventoryGenerator(
EquipmentSlots.TacticalVest, EquipmentSlots.TacticalVest,
EquipmentSlots.FaceCover, EquipmentSlots.FaceCover,
EquipmentSlots.Headwear, EquipmentSlots.Headwear,
EquipmentSlots.Earpiece EquipmentSlots.Earpiece,
]; ];
private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>(); private readonly BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
private readonly HashSet<string> _slotsToCheck = [EquipmentSlots.Pockets.ToString(), EquipmentSlots.SecuredContainer.ToString()]; private readonly HashSet<string> _slotsToCheck =
[
EquipmentSlots.Pockets.ToString(),
EquipmentSlots.SecuredContainer.ToString(),
];
/// <summary> /// <summary>
/// Add equipment/weapons/loot to bot /// Add equipment/weapons/loot to bot
@@ -64,7 +68,14 @@ public class BotInventoryGenerator(
/// <param name="botLevel">Level of bot being generated</param> /// <param name="botLevel">Level of bot being generated</param>
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param> /// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
/// <returns>PmcInventory object with equipment/weapons/loot</returns> /// <returns>PmcInventory object with equipment/weapons/loot</returns>
public BotBaseInventory GenerateInventory(string sessionId, BotType botJsonTemplate, string botRole, bool isPmc, int botLevel, string chosenGameVersion) public BotBaseInventory GenerateInventory(
string sessionId,
BotType botJsonTemplate,
string botRole,
bool isPmc,
int botLevel,
string chosenGameVersion
)
{ {
var templateInventory = botJsonTemplate.BotInventory; var templateInventory = botJsonTemplate.BotInventory;
var wornItemChances = botJsonTemplate.BotChances; var wornItemChances = botJsonTemplate.BotChances;
@@ -74,7 +85,9 @@ public class BotInventoryGenerator(
var botInventory = GenerateInventoryBase(); var botInventory = GenerateInventoryBase();
// Get generated raid details bot will be spawned in // Get generated raid details bot will be spawned in
var raidConfig = _profileActivityService.GetProfileActivityRaidData(sessionId)?.RaidConfiguration; var raidConfig = _profileActivityService
.GetProfileActivityRaidData(sessionId)
?.RaidConfiguration;
GenerateAndAddEquipmentToBot( GenerateAndAddEquipmentToBot(
sessionId, sessionId,
@@ -101,7 +114,14 @@ public class BotInventoryGenerator(
); );
// Pick loot and add to bots containers (rig/backpack/pockets/secure) // Pick loot and add to bots containers (rig/backpack/pockets/secure)
_botLootGenerator.GenerateLoot(sessionId, botJsonTemplate, isPmc, botRole, botInventory, botLevel); _botLootGenerator.GenerateLoot(
sessionId,
botJsonTemplate,
isPmc,
botRole,
botInventory,
botLevel
);
return botInventory; return botInventory;
} }
@@ -123,36 +143,16 @@ public class BotInventoryGenerator(
{ {
Items = Items =
[ [
new Item new Item { Id = equipmentId, Template = ItemTpl.INVENTORY_DEFAULT },
{ new Item { Id = stashId, Template = ItemTpl.STASH_STANDARD_STASH_10X30 },
Id = equipmentId, new Item { Id = questRaidItemsId, Template = ItemTpl.STASH_QUESTRAID },
Template = ItemTpl.INVENTORY_DEFAULT new Item { Id = questStashItemsId, Template = ItemTpl.STASH_QUESTOFFLINE },
}, new Item { Id = sortingTableId, Template = ItemTpl.SORTINGTABLE_SORTING_TABLE },
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 new Item
{ {
Id = hideoutCustomizationStashId, Id = hideoutCustomizationStashId,
Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION Template = ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION,
} },
], ],
Equipment = equipmentId, Equipment = equipmentId,
Stash = stashId, Stash = stashId,
@@ -162,7 +162,7 @@ public class BotInventoryGenerator(
HideoutAreaStashes = new Dictionary<string, string>(), HideoutAreaStashes = new Dictionary<string, string>(),
FastPanel = new Dictionary<string, string>(), FastPanel = new Dictionary<string, string>(),
FavoriteItems = [], FavoriteItems = [],
HideoutCustomizationStashId = hideoutCustomizationStashId HideoutCustomizationStashId = hideoutCustomizationStashId,
}; };
} }
@@ -177,24 +177,42 @@ public class BotInventoryGenerator(
/// <param name="botLevel">Level of bot</param> /// <param name="botLevel">Level of bot</param>
/// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param> /// <param name="chosenGameVersion">Game version for bot, only really applies for PMCs</param>
/// <param name="raidConfig">RadiConfig</param> /// <param name="raidConfig">RadiConfig</param>
public void GenerateAndAddEquipmentToBot(string sessionId, BotTypeInventory templateInventory, Chances wornItemChances, string botRole, public void GenerateAndAddEquipmentToBot(
BotBaseInventory botInventory, int botLevel, string chosenGameVersion, bool isPmc, GetRaidConfigurationRequestData? raidConfig) string sessionId,
BotTypeInventory templateInventory,
Chances wornItemChances,
string botRole,
BotBaseInventory botInventory,
int botLevel,
string chosenGameVersion,
bool isPmc,
GetRaidConfigurationRequestData? raidConfig
)
{ {
_botConfig.Equipment.TryGetValue(_botGeneratorHelper.GetBotEquipmentRole(botRole), out var botEquipConfig); _botConfig.Equipment.TryGetValue(
_botGeneratorHelper.GetBotEquipmentRole(botRole),
out var botEquipConfig
);
var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig); var randomistionDetails = _botHelper.GetBotRandomizationDetails(botLevel, botEquipConfig);
// Apply nighttime changes if its nighttime + there's changes to make // Apply nighttime changes if its nighttime + there's changes to make
if ( if (
randomistionDetails?.NighttimeChanges is not null && randomistionDetails?.NighttimeChanges is not null
raidConfig is not null && && raidConfig is not null
_weatherHelper.IsNightTime(raidConfig.TimeVariant, raidConfig.Location) && _weatherHelper.IsNightTime(raidConfig.TimeVariant, raidConfig.Location)
) )
{ {
foreach (var equipmentSlotKvP in randomistionDetails.NighttimeChanges.EquipmentModsModifiers) foreach (
// Never let mod chance go outside 0 - 100 var equipmentSlotKvP in randomistionDetails.NighttimeChanges.EquipmentModsModifiers
)
// Never let mod chance go outside 0 - 100
{ {
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min( randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] = Math.Min(
Math.Max(randomistionDetails.EquipmentMods[equipmentSlotKvP.Key] + equipmentSlotKvP.Value, 0), Math.Max(
randomistionDetails.EquipmentMods[equipmentSlotKvP.Key]
+ equipmentSlotKvP.Value,
0
),
100 100
); );
} }
@@ -204,7 +222,6 @@ public class BotInventoryGenerator(
var pmcProfile = _profileHelper.GetPmcProfile(sessionId); var pmcProfile = _profileHelper.GetPmcProfile(sessionId);
var botEquipmentRole = _botGeneratorHelper.GetBotEquipmentRole(botRole); var botEquipmentRole = _botGeneratorHelper.GetBotEquipmentRole(botRole);
// Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts // Iterate over all equipment slots of bot, do it in specifc order to reduce conflicts
// e.g. ArmorVest should be generated after TactivalVest // e.g. ArmorVest should be generated after TactivalVest
// or FACE_COVER before HEADWEAR // or FACE_COVER before HEADWEAR
@@ -228,12 +245,12 @@ public class BotInventoryGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
} }
@@ -244,20 +261,24 @@ public class BotInventoryGenerator(
{ {
RootEquipmentSlot = EquipmentSlots.Pockets, RootEquipmentSlot = EquipmentSlots.Pockets,
// Unheard profiles have unique sized pockets // Unheard profiles have unique sized pockets
RootEquipmentPool = GetPocketPoolByGameEdition(chosenGameVersion, templateInventory, isPmc), RootEquipmentPool = GetPocketPoolByGameEdition(
chosenGameVersion,
templateInventory,
isPmc
),
ModPool = templateInventory.Mods, ModPool = templateInventory.Mods,
SpawnChances = wornItemChances, SpawnChances = wornItemChances,
BotData = new BotData BotData = new BotData
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE, ItemTpl.POCKETS_LARGE], GenerateModsBlacklist = [ItemTpl.POCKETS_1X4_TUE, ItemTpl.POCKETS_LARGE],
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
@@ -272,12 +293,12 @@ public class BotInventoryGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
@@ -292,12 +313,12 @@ public class BotInventoryGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
@@ -312,12 +333,12 @@ public class BotInventoryGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
@@ -332,25 +353,25 @@ public class BotInventoryGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
// Bot has no armor vest and flagged to be forced to wear armored rig in this event // Bot has no armor vest and flagged to be forced to wear armored rig in this event
if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest) if (botEquipConfig.ForceOnlyArmoredRigWhenNoArmor.GetValueOrDefault(false) && !hasArmorVest)
// Filter rigs down to only those with armor // Filter rigs down to only those with armor
{ {
FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole); FilterRigsToThoseWithProtection(templateInventory.Equipment, botRole);
} }
// Optimisation - Remove armored rigs from pool // Optimisation - Remove armored rigs from pool
if (hasArmorVest) if (hasArmorVest)
// Filter rigs down to only those with armor // Filter rigs down to only those with armor
{ {
FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole); FilterRigsToThoseWithoutProtection(templateInventory.Equipment, botRole);
} }
@@ -372,12 +393,12 @@ public class BotInventoryGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
Inventory = botInventory, Inventory = botInventory,
BotEquipmentConfig = botEquipConfig, BotEquipmentConfig = botEquipConfig,
RandomisationDetails = randomistionDetails, RandomisationDetails = randomistionDetails,
GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1 GeneratingPlayerLevel = pmcProfile?.Info?.Level ?? 1,
} }
); );
} }
@@ -389,13 +410,14 @@ public class BotInventoryGenerator(
/// <param name="templateInventory"></param> /// <param name="templateInventory"></param>
/// <param name="isPmc">is bot a PMC</param> /// <param name="isPmc">is bot a PMC</param>
/// <returns></returns> /// <returns></returns>
protected Dictionary<string, double> GetPocketPoolByGameEdition(string chosenGameVersion, BotTypeInventory templateInventory, bool isPmc) protected Dictionary<string, double> GetPocketPoolByGameEdition(
string chosenGameVersion,
BotTypeInventory templateInventory,
bool isPmc
)
{ {
return chosenGameVersion == GameEditions.UNHEARD && isPmc return chosenGameVersion == GameEditions.UNHEARD && isPmc
? new Dictionary<string, double> ? new Dictionary<string, double> { [ItemTpl.POCKETS_1X4_TUE] = 1 }
{
[ItemTpl.POCKETS_1X4_TUE] = 1
}
: templateInventory.Equipment.GetValueOrDefault(EquipmentSlots.Pockets); : templateInventory.Equipment.GetValueOrDefault(EquipmentSlots.Pockets);
} }
@@ -404,7 +426,10 @@ public class BotInventoryGenerator(
/// </summary> /// </summary>
/// <param name="templateEquipment">Equipment to filter TacticalVest of</param> /// <param name="templateEquipment">Equipment to filter TacticalVest of</param>
/// <param name="botRole">Role of bot vests are being filtered for</param> /// <param name="botRole">Role of bot vests are being filtered for</param>
public void FilterRigsToThoseWithProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole) public void FilterRigsToThoseWithProtection(
Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment,
string botRole
)
{ {
var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest] var tacVestsWithArmor = templateEquipment[EquipmentSlots.TacticalVest]
.Where(kvp => _itemHelper.ItemHasSlots(kvp.Key)) .Where(kvp => _itemHelper.ItemHasSlots(kvp.Key))
@@ -414,7 +439,9 @@ public class BotInventoryGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Unable to filter to only armored rigs as bot: {botRole} has none in pool"); _logger.Debug(
$"Unable to filter to only armored rigs as bot: {botRole} has none in pool"
);
} }
return; return;
@@ -429,8 +456,11 @@ public class BotInventoryGenerator(
/// <param name="templateEquipment">Equipment to filter TacticalVest by</param> /// <param name="templateEquipment">Equipment to filter TacticalVest by</param>
/// <param name="botRole">Role of bot vests are being filtered for</param> /// <param name="botRole">Role of bot vests are being filtered for</param>
/// <param name="allowEmptyResult">Should the function return all rigs when 0 unarmored are found</param> /// <param name="allowEmptyResult">Should the function return all rigs when 0 unarmored are found</param>
public void FilterRigsToThoseWithoutProtection(Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment, string botRole, public void FilterRigsToThoseWithoutProtection(
bool allowEmptyResult = true) Dictionary<EquipmentSlots, Dictionary<string, double>> templateEquipment,
string botRole,
bool allowEmptyResult = true
)
{ {
var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest] var tacVestsWithoutArmor = templateEquipment[EquipmentSlots.TacticalVest]
.Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key)) .Where(kvp => !_itemHelper.ItemHasSlots(kvp.Key))
@@ -440,7 +470,9 @@ public class BotInventoryGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool"); _logger.Debug(
$"Unable to filter to only unarmored rigs as bot: {botRole} has none in pool"
);
} }
return; return;
@@ -458,7 +490,9 @@ public class BotInventoryGenerator(
{ {
double? spawnChance = _slotsToCheck.Contains(settings.RootEquipmentSlot.ToString()) double? spawnChance = _slotsToCheck.Contains(settings.RootEquipmentSlot.ToString())
? 100 ? 100
: settings.SpawnChances.EquipmentChances.GetValueOrDefault(settings.RootEquipmentSlot.ToString()); : settings.SpawnChances.EquipmentChances.GetValueOrDefault(
settings.RootEquipmentSlot.ToString()
);
if (!spawnChance.HasValue) if (!spawnChance.HasValue)
{ {
@@ -489,12 +523,16 @@ public class BotInventoryGenerator(
return false; return false;
} }
var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(settings.RootEquipmentPool); var chosenItemTpl = _weightedRandomHelper.GetWeightedValue(
settings.RootEquipmentPool
);
var dbResult = _itemHelper.GetItem(chosenItemTpl); var dbResult = _itemHelper.GetItem(chosenItemTpl);
if (!dbResult.Key) if (!dbResult.Key)
{ {
_logger.Error(_localisationService.GetText("bot-missing_item_template", chosenItemTpl)); _logger.Error(
_localisationService.GetText("bot-missing_item_template", chosenItemTpl)
);
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}"); _logger.Debug($"EquipmentSlot-> {settings.RootEquipmentSlot}");
@@ -544,7 +582,10 @@ public class BotInventoryGenerator(
Template = pickedItemDb.Id, Template = pickedItemDb.Id,
ParentId = settings.Inventory.Equipment, ParentId = settings.Inventory.Equipment,
SlotId = settings.RootEquipmentSlot.ToString(), SlotId = settings.RootEquipmentSlot.ToString(),
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(pickedItemDb, settings.BotData.Role) Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
pickedItemDb,
settings.BotData.Role
),
}; };
var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist( var botEquipBlacklist = _botEquipmentFilterService.GetBotEquipmentBlacklist(
@@ -553,10 +594,14 @@ public class BotInventoryGenerator(
); );
// Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot // Edge case: Filter the armor items mod pool if bot exists in config dict + config has armor slot
if (_botConfig.Equipment.ContainsKey(settings.BotData.EquipmentRole) && if (
settings.RandomisationDetails?.RandomisedArmorSlots != null && _botConfig.Equipment.ContainsKey(settings.BotData.EquipmentRole)
settings.RandomisationDetails.RandomisedArmorSlots.Contains(settings.RootEquipmentSlot.ToString())) && settings.RandomisationDetails?.RandomisedArmorSlots != null
// Filter out mods from relevant blacklist && settings.RandomisationDetails.RandomisedArmorSlots.Contains(
settings.RootEquipmentSlot.ToString()
)
)
// Filter out mods from relevant blacklist
{ {
settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem( settings.ModPool[pickedItemDb.Id] = GetFilteredDynamicModsForItem(
pickedItemDb.Id, pickedItemDb.Id,
@@ -564,7 +609,9 @@ public class BotInventoryGenerator(
); );
} }
var itemIsOnGenerateModBlacklist = settings.GenerateModsBlacklist != null && settings.GenerateModsBlacklist.Contains(pickedItemDb.Id); var itemIsOnGenerateModBlacklist =
settings.GenerateModsBlacklist != null
&& settings.GenerateModsBlacklist.Contains(pickedItemDb.Id);
// Does item have slots for sub-mods to be inserted into // Does item have slots for sub-mods to be inserted into
if (pickedItemDb.Properties?.Slots?.Count > 0 && !itemIsOnGenerateModBlacklist) if (pickedItemDb.Properties?.Slots?.Count > 0 && !itemIsOnGenerateModBlacklist)
{ {
@@ -595,7 +642,10 @@ public class BotInventoryGenerator(
/// <param name="itemTpl">Item mod pool is being retrieved and filtered</param> /// <param name="itemTpl">Item mod pool is being retrieved and filtered</param>
/// <param name="equipmentBlacklist">Blacklist to filter mod pool with</param> /// <param name="equipmentBlacklist">Blacklist to filter mod pool with</param>
/// <returns>Filtered pool of mods</returns> /// <returns>Filtered pool of mods</returns>
public Dictionary<string, HashSet<string>> GetFilteredDynamicModsForItem(string itemTpl, Dictionary<string, HashSet<string>> equipmentBlacklist) public Dictionary<string, HashSet<string>> GetFilteredDynamicModsForItem(
string itemTpl,
Dictionary<string, HashSet<string>> equipmentBlacklist
)
{ {
var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl); var modPool = _botEquipmentModPoolService.GetModsForGearSlot(itemTpl);
foreach (var modSlot in modPool) foreach (var modSlot in modPool)
@@ -605,14 +655,16 @@ public class BotInventoryGenerator(
{ {
blacklistedMods = []; blacklistedMods = [];
} }
; ;
// Get mods not on blacklist // 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()) if (!filteredMods.Any())
{ {
_logger.Warning($"Filtering {modSlot.Key} pool resulting in 0 items, skipping filter"); _logger.Warning(
$"Filtering {modSlot.Key} pool resulting in 0 items, skipping filter"
);
continue; continue;
} }
@@ -633,14 +685,24 @@ public class BotInventoryGenerator(
/// <param name="isPmc">Is the bot being generated as a pmc</param> /// <param name="isPmc">Is the bot being generated as a pmc</param>
/// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param> /// <param name="itemGenerationLimitsMinMax">Limits for items the bot can have</param>
/// <param name="botLevel">level of bot having weapon generated</param> /// <param name="botLevel">level of bot having weapon generated</param>
public void GenerateAndAddWeaponsToBot(BotTypeInventory templateInventory, Chances equipmentChances, string sessionId, BotBaseInventory botInventory, public void GenerateAndAddWeaponsToBot(
string botRole, bool isPmc, Generation itemGenerationLimitsMinMax, int botLevel) BotTypeInventory templateInventory,
Chances equipmentChances,
string sessionId,
BotBaseInventory botInventory,
string botRole,
bool isPmc,
Generation itemGenerationLimitsMinMax,
int botLevel
)
{ {
var weaponSlotsToFill = GetDesiredWeaponsForBot(equipmentChances); var weaponSlotsToFill = GetDesiredWeaponsForBot(equipmentChances);
foreach (var desiredWeapons in weaponSlotsToFill) foreach (var desiredWeapons in weaponSlotsToFill)
// Add weapon to bot if true and bot json has something to put into the slot // Add weapon to bot if true and bot json has something to put into the slot
{ {
if (desiredWeapons.ShouldSpawn && templateInventory.Equipment[desiredWeapons.Slot].Any()) if (
desiredWeapons.ShouldSpawn && templateInventory.Equipment[desiredWeapons.Slot].Any()
)
{ {
AddWeaponAndMagazinesToInventory( AddWeaponAndMagazinesToInventory(
sessionId, sessionId,
@@ -664,24 +726,32 @@ public class BotInventoryGenerator(
/// <returns>What slots bot should have weapons generated for</returns> /// <returns>What slots bot should have weapons generated for</returns>
public List<DesiredWeapons> GetDesiredWeaponsForBot(Chances equipmentChances) public List<DesiredWeapons> GetDesiredWeaponsForBot(Chances equipmentChances)
{ {
var shouldSpawnPrimary = _randomUtil.GetChance100(equipmentChances.EquipmentChances["FirstPrimaryWeapon"]); var shouldSpawnPrimary = _randomUtil.GetChance100(
equipmentChances.EquipmentChances["FirstPrimaryWeapon"]
);
return return
[ [
new DesiredWeapons new DesiredWeapons
{ {
Slot = EquipmentSlots.FirstPrimaryWeapon, Slot = EquipmentSlots.FirstPrimaryWeapon,
ShouldSpawn = shouldSpawnPrimary ShouldSpawn = shouldSpawnPrimary,
}, },
new DesiredWeapons new DesiredWeapons
{ {
Slot = EquipmentSlots.SecondPrimaryWeapon, Slot = EquipmentSlots.SecondPrimaryWeapon,
ShouldSpawn = shouldSpawnPrimary && _randomUtil.GetChance100(equipmentChances.EquipmentChances["SecondPrimaryWeapon"]) ShouldSpawn =
shouldSpawnPrimary
&& _randomUtil.GetChance100(
equipmentChances.EquipmentChances["SecondPrimaryWeapon"]
),
}, },
new DesiredWeapons new DesiredWeapons
{ {
Slot = EquipmentSlots.Holster, Slot = EquipmentSlots.Holster,
ShouldSpawn = !shouldSpawnPrimary || _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]) // No primary = force pistol ShouldSpawn =
} !shouldSpawnPrimary
|| _randomUtil.GetChance100(equipmentChances.EquipmentChances["Holster"]), // No primary = force pistol
},
]; ];
} }
@@ -697,9 +767,17 @@ public class BotInventoryGenerator(
/// <param name="isPmc">Is the bot being generated as a pmc</param> /// <param name="isPmc">Is the bot being generated as a pmc</param>
/// <param name="itemGenerationWeights"></param> /// <param name="itemGenerationWeights"></param>
/// <param name="botLevel"></param> /// <param name="botLevel"></param>
public void AddWeaponAndMagazinesToInventory(string sessionId, DesiredWeapons weaponSlot, BotTypeInventory templateInventory, BotBaseInventory botInventory, public void AddWeaponAndMagazinesToInventory(
Chances equipmentChances, string botRole, string sessionId,
bool isPmc, Generation itemGenerationWeights, int botLevel) DesiredWeapons weaponSlot,
BotTypeInventory templateInventory,
BotBaseInventory botInventory,
Chances equipmentChances,
string botRole,
bool isPmc,
Generation itemGenerationWeights,
int botLevel
)
{ {
var generatedWeapon = _botWeaponGenerator.GenerateRandomWeapon( var generatedWeapon = _botWeaponGenerator.GenerateRandomWeapon(
sessionId, sessionId,
@@ -725,15 +803,7 @@ public class BotInventoryGenerator(
public class DesiredWeapons public class DesiredWeapons
{ {
public EquipmentSlots Slot public EquipmentSlots Slot { get; set; }
{
get;
set;
}
public bool ShouldSpawn public bool ShouldSpawn { get; set; }
{
get;
set;
}
} }
@@ -24,25 +24,28 @@ public class BotLevelGenerator(
/// <param name="botGenerationDetails">Details to help generate a bot</param> /// <param name="botGenerationDetails">Details to help generate a bot</param>
/// <param name="bot">Bot the level is being generated for</param> /// <param name="bot">Bot the level is being generated for</param>
/// <returns>IRandomisedBotLevelResult object</returns> /// <returns>IRandomisedBotLevelResult object</returns>
public RandomisedBotLevelResult GenerateBotLevel(MinMax<int> levelDetails, BotGenerationDetails botGenerationDetails, BotBase bot) public RandomisedBotLevelResult GenerateBotLevel(
MinMax<int> levelDetails,
BotGenerationDetails botGenerationDetails,
BotBase bot
)
{ {
if (!botGenerationDetails.IsPmc.GetValueOrDefault(false)) if (!botGenerationDetails.IsPmc.GetValueOrDefault(false))
{ {
return new RandomisedBotLevelResult return new RandomisedBotLevelResult { Exp = 0, Level = 1 };
{
Exp = 0,
Level = 1
};
} }
var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable; var expTable = _databaseService.GetGlobals().Configuration.Exp.Level.ExperienceTable;
var botLevelRange = GetRelativePmcBotLevelRange(botGenerationDetails, levelDetails, expTable.Length); var botLevelRange = GetRelativePmcBotLevelRange(
botGenerationDetails,
levelDetails,
expTable.Length
);
// Get random level based on the exp table. // Get random level based on the exp table.
var exp = 0; var exp = 0;
var level = int.Parse( var level = int.Parse(
ChooseBotLevel(botLevelRange.Min, botLevelRange.Max, 1, 1.15) ChooseBotLevel(botLevelRange.Min, botLevelRange.Max, 1, 1.15).ToString()
.ToString()
); // TODO - nasty double to string to int conversion ); // TODO - nasty double to string to int conversion
for (var i = 0; i < level; i++) for (var i = 0; i < level; i++)
{ {
@@ -55,11 +58,7 @@ public class BotLevelGenerator(
exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1); exp += _randomUtil.GetInt(0, expTable[level].Experience.Value - 1);
} }
return new RandomisedBotLevelResult return new RandomisedBotLevelResult { Level = level, Exp = exp };
{
Level = level,
Exp = exp
};
} }
public double ChooseBotLevel(double min, double max, int shift, double number) public double ChooseBotLevel(double min, double max, int shift, double number)
@@ -74,7 +73,11 @@ public class BotLevelGenerator(
/// <param name="levelDetails"></param> /// <param name="levelDetails"></param>
/// <param name="maxAvailableLevel">Max level allowed</param> /// <param name="maxAvailableLevel">Max level allowed</param>
/// <returns>A MinMax of the lowest and highest level to generate the bots</returns> /// <returns>A MinMax of the lowest and highest level to generate the bots</returns>
public MinMax<int> GetRelativePmcBotLevelRange(BotGenerationDetails botGenerationDetails, MinMax<int> levelDetails, int maxAvailableLevel) public MinMax<int> GetRelativePmcBotLevelRange(
BotGenerationDetails botGenerationDetails,
MinMax<int> levelDetails,
int maxAvailableLevel
)
{ {
var levelOverride = botGenerationDetails.LocationSpecificPmcLevelOverride; var levelOverride = botGenerationDetails.LocationSpecificPmcLevelOverride;
@@ -93,12 +96,14 @@ public class BotLevelGenerator(
// Get min level relative to player if value exists // Get min level relative to player if value exists
var minLevel = botGenerationDetails.PlayerLevel.HasValue var minLevel = botGenerationDetails.PlayerLevel.HasValue
? botGenerationDetails.PlayerLevel.Value - botGenerationDetails.BotRelativeLevelDeltaMin.Value ? botGenerationDetails.PlayerLevel.Value
- botGenerationDetails.BotRelativeLevelDeltaMin.Value
: 1 - botGenerationDetails.BotRelativeLevelDeltaMin.Value; : 1 - botGenerationDetails.BotRelativeLevelDeltaMin.Value;
// Get max level relative to player if value exists // Get max level relative to player if value exists
var maxLevel = botGenerationDetails.PlayerLevel.HasValue var maxLevel = botGenerationDetails.PlayerLevel.HasValue
? botGenerationDetails.PlayerLevel.Value + botGenerationDetails.BotRelativeLevelDeltaMax.Value ? botGenerationDetails.PlayerLevel.Value
+ botGenerationDetails.BotRelativeLevelDeltaMax.Value
: 1 + botGenerationDetails.BotRelativeLevelDeltaMin.Value; : 1 + botGenerationDetails.BotRelativeLevelDeltaMin.Value;
// Bound the level to the min/max possible // Bound the level to the min/max possible
@@ -54,7 +54,7 @@ public class BotLootGenerator(
return new ItemSpawnLimitSettings return new ItemSpawnLimitSettings
{ {
CurrentLimits = limitsForBotDict, CurrentLimits = limitsForBotDict,
GlobalLimits = GetItemSpawnLimitsForBotType(botRole) GlobalLimits = GetItemSpawnLimitsForBotType(botRole),
}; };
} }
@@ -67,33 +67,46 @@ public class BotLootGenerator(
/// <param name="botRole">Role of bot, e.g. asssult</param> /// <param name="botRole">Role of bot, e.g. asssult</param>
/// <param name="botInventory">Inventory to add loot to</param> /// <param name="botInventory">Inventory to add loot to</param>
/// <param name="botLevel">Level of bot</param> /// <param name="botLevel">Level of bot</param>
public void GenerateLoot(string sessionId, BotType botJsonTemplate, bool isPmc, string botRole, BotBaseInventory botInventory, int botLevel) public void GenerateLoot(
string sessionId,
BotType botJsonTemplate,
bool isPmc,
string botRole,
BotBaseInventory botInventory,
int botLevel
)
{ {
// Limits on item types to be added as loot // Limits on item types to be added as loot
var itemCounts = botJsonTemplate.BotGeneration?.Items; var itemCounts = botJsonTemplate.BotGeneration?.Items;
if ( if (
itemCounts?.BackpackLoot.Weights is null || itemCounts?.BackpackLoot.Weights is null
itemCounts.PocketLoot.Weights is null || || itemCounts.PocketLoot.Weights is null
itemCounts.VestLoot.Weights is null || || itemCounts.VestLoot.Weights is null
itemCounts.SpecialItems.Weights is null || || itemCounts.SpecialItems.Weights is null
itemCounts.Healing.Weights is null || || itemCounts.Healing.Weights is null
itemCounts.Drugs.Weights is null || || itemCounts.Drugs.Weights is null
itemCounts.Food.Weights is null || || itemCounts.Food.Weights is null
itemCounts.Drink.Weights is null || || itemCounts.Drink.Weights is null
itemCounts.Currency.Weights is null || || itemCounts.Currency.Weights is null
itemCounts.Stims.Weights is null || || itemCounts.Stims.Weights is null
itemCounts.Grenades.Weights is null || itemCounts.Grenades.Weights is null
) )
{ {
_logger.Warning(_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole)); _logger.Warning(
_localisationService.GetText("bot-unable_to_generate_bot_loot", botRole)
);
return; return;
} }
var backpackLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.BackpackLoot.Weights); var backpackLootCount = _weightedRandomHelper.GetWeightedValue(
itemCounts.BackpackLoot.Weights
);
var pocketLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.PocketLoot.Weights); var pocketLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.PocketLoot.Weights);
var vestLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.VestLoot.Weights); var vestLootCount = _weightedRandomHelper.GetWeightedValue(itemCounts.VestLoot.Weights);
var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.SpecialItems.Weights); var specialLootItemCount = _weightedRandomHelper.GetWeightedValue(
itemCounts.SpecialItems.Weights
);
var healingItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Healing.Weights); var healingItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Healing.Weights);
var drugItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Drugs.Weights); var drugItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Drugs.Weights);
var foodItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Food.Weights); var foodItemCount = _weightedRandomHelper.GetWeightedValue(itemCounts.Food.Weights);
@@ -127,7 +140,12 @@ public class BotLootGenerator(
// Special items // Special items
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Special, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.Special,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
specialLootItemCount, specialLootItemCount,
botInventory, botInventory,
@@ -138,7 +156,12 @@ public class BotLootGenerator(
// Healing items / Meds // Healing items / Meds
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.HealingItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.HealingItems,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
healingItemCount, healingItemCount,
botInventory, botInventory,
@@ -151,7 +174,12 @@ public class BotLootGenerator(
// Drugs // Drugs
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrugItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.DrugItems,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
drugItemCount, drugItemCount,
botInventory, botInventory,
@@ -164,7 +192,12 @@ public class BotLootGenerator(
// Food // Food
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.FoodItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.FoodItems,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
foodItemCount, foodItemCount,
botInventory, botInventory,
@@ -177,7 +210,12 @@ public class BotLootGenerator(
// Drink // Drink
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.DrinkItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.DrinkItems,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
drinkItemCount, drinkItemCount,
botInventory, botInventory,
@@ -190,7 +228,12 @@ public class BotLootGenerator(
// Currency // Currency
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.CurrencyItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.CurrencyItems,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
currencyItemCount, currencyItemCount,
botInventory, botInventory,
@@ -203,7 +246,12 @@ public class BotLootGenerator(
// Stims // Stims
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.StimItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.StimItems,
botJsonTemplate
),
containersBotHasAvailable, containersBotHasAvailable,
stimItemCount, stimItemCount,
botInventory, botInventory,
@@ -216,7 +264,12 @@ public class BotLootGenerator(
// Grenades // Grenades
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.GrenadeItems, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.GrenadeItems,
botJsonTemplate
),
[EquipmentSlots.Pockets, EquipmentSlots.TacticalVest], // Can't use containersBotHasEquipped as we don't want grenades added to backpack [EquipmentSlots.Pockets, EquipmentSlots.TacticalVest], // Can't use containersBotHasEquipped as we don't want grenades added to backpack
grenadeCount, grenadeCount,
botInventory, botInventory,
@@ -270,7 +323,7 @@ public class BotLootGenerator(
// TacticalVest - generate loot if they have one // TacticalVest - generate loot if they have one
if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest)) if (containersBotHasAvailable.Contains(EquipmentSlots.TacticalVest))
// Vest // Vest
{ {
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache( _botLootCacheService.GetLootFromCache(
@@ -316,7 +369,12 @@ public class BotLootGenerator(
if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig)) if (!isPmc || (isPmc && _pmcConfig.AddSecureContainerLootFromBotConfig))
{ {
AddLootFromPool( AddLootFromPool(
_botLootCacheService.GetLootFromCache(botRole, isPmc, LootCacheType.Secure, botJsonTemplate), _botLootCacheService.GetLootFromCache(
botRole,
isPmc,
LootCacheType.Secure,
botJsonTemplate
),
[EquipmentSlots.SecuredContainer], [EquipmentSlots.SecuredContainer],
50, 50,
botInventory, botInventory,
@@ -337,7 +395,8 @@ public class BotLootGenerator(
return null; return null;
} }
var matchingValue = _pmcConfig?.LootItemLimitsRub?.FirstOrDefault(minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max var matchingValue = _pmcConfig?.LootItemLimitsRub?.FirstOrDefault(minMaxValue =>
botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
); );
return matchingValue; return matchingValue;
@@ -357,7 +416,8 @@ public class BotLootGenerator(
return 0; return 0;
} }
var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault(minMaxValue => botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max var matchingValue = _pmcConfig.MaxBackpackLootTotalRub.FirstOrDefault(minMaxValue =>
botLevel >= minMaxValue.Min && botLevel <= minMaxValue.Max
); );
return matchingValue?.Value; return matchingValue?.Value;
} }
@@ -367,11 +427,17 @@ public class BotLootGenerator(
/// </summary> /// </summary>
/// <param name="botInventory">Bot to check</param> /// <param name="botInventory">Bot to check</param>
/// <returns>Array of available slots</returns> /// <returns>Array of available slots</returns>
protected HashSet<EquipmentSlots> GetAvailableContainersBotCanStoreItemsIn(BotBaseInventory botInventory) protected HashSet<EquipmentSlots> GetAvailableContainersBotCanStoreItemsIn(
BotBaseInventory botInventory
)
{ {
HashSet<EquipmentSlots> result = [EquipmentSlots.Pockets]; HashSet<EquipmentSlots> result = [EquipmentSlots.Pockets];
if ((botInventory.Items ?? []).Any(item => item.SlotId == nameof(EquipmentSlots.TacticalVest))) if (
(botInventory.Items ?? []).Any(item =>
item.SlotId == nameof(EquipmentSlots.TacticalVest)
)
)
{ {
result.Add(EquipmentSlots.TacticalVest); result.Add(EquipmentSlots.TacticalVest);
} }
@@ -393,10 +459,7 @@ public class BotLootGenerator(
{ {
// surv12 // surv12
AddLootFromPool( AddLootFromPool(
new Dictionary<string, double> new Dictionary<string, double> { { "5d02797c86f774203f38e30a", 1 } },
{
{ "5d02797c86f774203f38e30a", 1 }
},
[EquipmentSlots.SecuredContainer], [EquipmentSlots.SecuredContainer],
1, 1,
botInventory, botInventory,
@@ -408,10 +471,7 @@ public class BotLootGenerator(
// AFAK // AFAK
AddLootFromPool( AddLootFromPool(
new Dictionary<string, double> new Dictionary<string, double> { { "60098ad7c2240c0fe85c570a", 1 } },
{
{ "60098ad7c2240c0fe85c570a", 1 }
},
[EquipmentSlots.SecuredContainer], [EquipmentSlots.SecuredContainer],
10, 10,
botInventory, botInventory,
@@ -434,8 +494,7 @@ public class BotLootGenerator(
/// <param name="containersIdFull"></param> /// <param name="containersIdFull"></param>
/// <param name="totalValueLimitRub">Total value of loot allowed in roubles</param> /// <param name="totalValueLimitRub">Total value of loot allowed in roubles</param>
/// <param name="isPmc">Is bot being generated for a pmc</param> /// <param name="isPmc">Is bot being generated for a pmc</param>
protected void AddLootFromPool protected void AddLootFromPool(
(
Dictionary<string, double> pool, Dictionary<string, double> pool,
HashSet<EquipmentSlots> equipmentSlots, HashSet<EquipmentSlots> equipmentSlots,
double totalItemCount, double totalItemCount,
@@ -471,12 +530,17 @@ public class BotLootGenerator(
if (!key) if (!key)
{ {
_logger.Warning($"Unable to process item tpl: {weightedItemTpl} for slots: {equipmentSlots} on bot: {botRole}"); _logger.Warning(
$"Unable to process item tpl: {weightedItemTpl} for slots: {equipmentSlots} on bot: {botRole}"
);
continue; continue;
} }
if (itemSpawnLimits is not null && ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)) if (
itemSpawnLimits is not null
&& ItemHasReachedSpawnLimit(itemToAddTemplate, botRole, itemSpawnLimits)
)
{ {
// Remove item from pool to prevent it being picked again // Remove item from pool to prevent it being picked again
pool.Remove(weightedItemTpl); pool.Remove(weightedItemTpl);
@@ -492,14 +556,19 @@ public class BotLootGenerator(
{ {
Id = newRootItemId, Id = newRootItemId,
Template = itemToAddTemplate?.Id ?? string.Empty, Template = itemToAddTemplate?.Id ?? string.Empty,
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemToAddTemplate, botRole) Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
} itemToAddTemplate,
botRole
),
},
]; ];
// Is Simple-Wallet / WZ wallet // Is Simple-Wallet / WZ wallet
if (_botConfig.WalletLoot.WalletTplPool.Contains(weightedItemTpl)) if (_botConfig.WalletLoot.WalletTplPool.Contains(weightedItemTpl))
{ {
var addCurrencyToWallet = _randomUtil.GetChance100(_botConfig.WalletLoot.ChancePercent); var addCurrencyToWallet = _randomUtil.GetChance100(
_botConfig.WalletLoot.ChancePercent
);
if (addCurrencyToWallet) if (addCurrencyToWallet)
{ {
// Create the currency items we want to add to wallet // Create the currency items we want to add to wallet
@@ -552,7 +621,9 @@ public class BotLootGenerator(
// Bot has no container to put item in, exit // Bot has no container to put item in, exit
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Unable to add: {totalItemCount} items to bot as it lacks a container to include them"); _logger.Debug(
$"Unable to add: {totalItemCount} items to bot as it lacks a container to include them"
);
} }
break; break;
@@ -564,9 +635,9 @@ public class BotLootGenerator(
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug( _logger.Debug(
$"Failed placing item: {itemToAddTemplate.Id} - {itemToAddTemplate.Name}: {i} of: {totalItemCount} items into: {botRole} " + $"Failed placing item: {itemToAddTemplate.Id} - {itemToAddTemplate.Name}: {i} of: {totalItemCount} items into: {botRole} "
$"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} " + + $"containers: {string.Join(",", equipmentSlots)}. Tried: {fitItemIntoContainerAttempts} "
$"times, reason: {itemAddedResult}, skipping" + $"times, reason: {itemAddedResult}, skipping"
); );
} }
@@ -609,19 +680,20 @@ public class BotLootGenerator(
for (var index = 0; index < itemCount; index++) for (var index = 0; index < itemCount; index++)
{ {
// Choose the size of the currency stack - default is 5k, 10k, 15k, 20k, 25k // Choose the size of the currency stack - default is 5k, 10k, 15k, 20k, 25k
var chosenStackCount = _weightedRandomHelper.GetWeightedValue(_botConfig.WalletLoot.StackSizeWeight); var chosenStackCount = _weightedRandomHelper.GetWeightedValue(
_botConfig.WalletLoot.StackSizeWeight
);
List<Item> items = List<Item> items =
[ [
new() new()
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = _weightedRandomHelper.GetWeightedValue(_botConfig.WalletLoot.CurrencyWeight), Template = _weightedRandomHelper.GetWeightedValue(
_botConfig.WalletLoot.CurrencyWeight
),
ParentId = walletId, ParentId = walletId,
Upd = new Upd Upd = new Upd { StackObjectsCount = int.Parse(chosenStackCount) },
{ },
StackObjectsCount = int.Parse(chosenStackCount)
}
}
]; ];
result.Add(items); result.Add(items);
} }
@@ -636,7 +708,12 @@ public class BotLootGenerator(
/// <param name="itemToAddChildrenTo">Item to add children to</param> /// <param name="itemToAddChildrenTo">Item to add children to</param>
/// <param name="isPmc">Is the item being generated for a pmc (affects money/ammo stack sizes)</param> /// <param name="isPmc">Is the item being generated for a pmc (affects money/ammo stack sizes)</param>
/// <param name="botRole">role bot has that owns item</param> /// <param name="botRole">role bot has that owns item</param>
public void AddRequiredChildItemsToParent(TemplateItem? itemToAddTemplate, List<Item> itemToAddChildrenTo, bool isPmc, string botRole) public void AddRequiredChildItemsToParent(
TemplateItem? itemToAddTemplate,
List<Item> itemToAddChildrenTo,
bool isPmc,
string botRole
)
{ {
// Fill ammo box // Fill ammo box
if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO_BOX)) if (_itemHelper.IsOfBaseclass(itemToAddTemplate.Id, BaseClasses.AMMO_BOX))
@@ -672,7 +749,8 @@ public class BotLootGenerator(
/// <param name="isPmc">are we generating for a pmc</param> /// <param name="isPmc">are we generating for a pmc</param>
/// <param name="botLevel"></param> /// <param name="botLevel"></param>
/// <param name="containersIdFull"></param> /// <param name="containersIdFull"></param>
public void AddLooseWeaponsToInventorySlot(string sessionId, public void AddLooseWeaponsToInventorySlot(
string sessionId,
BotBaseInventory botInventory, BotBaseInventory botInventory,
EquipmentSlots equipmentSlot, EquipmentSlots equipmentSlot,
BotTypeInventory? templateInventory, BotTypeInventory? templateInventory,
@@ -680,14 +758,15 @@ public class BotLootGenerator(
string botRole, string botRole,
bool isPmc, bool isPmc,
int botLevel, int botLevel,
HashSet<string>? containersIdFull) HashSet<string>? containersIdFull
)
{ {
var chosenWeaponType = _randomUtil.GetArrayValue<string>( var chosenWeaponType = _randomUtil.GetArrayValue<string>(
[ [
EquipmentSlots.FirstPrimaryWeapon.ToString(), EquipmentSlots.FirstPrimaryWeapon.ToString(),
EquipmentSlots.FirstPrimaryWeapon.ToString(), EquipmentSlots.FirstPrimaryWeapon.ToString(),
EquipmentSlots.FirstPrimaryWeapon.ToString(), EquipmentSlots.FirstPrimaryWeapon.ToString(),
EquipmentSlots.Holster.ToString() EquipmentSlots.Holster.ToString(),
] ]
); );
var randomisedWeaponCount = _randomUtil.GetInt( var randomisedWeaponCount = _randomUtil.GetInt(
@@ -716,7 +795,9 @@ public class BotLootGenerator(
var weaponRootItem = generatedWeapon.Weapon?.FirstOrDefault(); var weaponRootItem = generatedWeapon.Weapon?.FirstOrDefault();
if (weaponRootItem is null) if (weaponRootItem is null)
{ {
_logger.Error($"Generated loose weapon: {chosenWeaponType} for: {botRole} level: {botLevel} was null, skipping"); _logger.Error(
$"Generated loose weapon: {chosenWeaponType} for: {botRole} level: {botLevel} was null, skipping"
);
continue; continue;
} }
@@ -733,7 +814,9 @@ public class BotLootGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Failed to add additional weapon: {weaponRootItem.Id} to bot backpack, reason: {result.ToString()}"); _logger.Debug(
$"Failed to add additional weapon: {weaponRootItem.Id} to bot backpack, reason: {result.ToString()}"
);
} }
} }
} }
@@ -746,11 +829,15 @@ public class BotLootGenerator(
/// <param name="botRole">Bot type</param> /// <param name="botRole">Bot type</param>
/// <param name="itemSpawnLimits"></param> /// <param name="itemSpawnLimits"></param>
/// <returns>true if item has reached spawn limit</returns> /// <returns>true if item has reached spawn limit</returns>
protected bool ItemHasReachedSpawnLimit(TemplateItem? itemTemplate, string botRole, ItemSpawnLimitSettings? itemSpawnLimits) protected bool ItemHasReachedSpawnLimit(
TemplateItem? itemTemplate,
string botRole,
ItemSpawnLimitSettings? itemSpawnLimits
)
{ {
// PMCs and scavs have different sections of bot config for spawn limits // PMCs and scavs have different sections of bot config for spawn limits
if (itemSpawnLimits is not null && itemSpawnLimits.GlobalLimits?.Count == 0) if (itemSpawnLimits is not null && itemSpawnLimits.GlobalLimits?.Count == 0)
// No items found in spawn limit, drop out // No items found in spawn limit, drop out
{ {
return false; return false;
} }
@@ -763,24 +850,24 @@ public class BotLootGenerator(
var idToCheckFor = GetMatchingIdFromSpawnLimits(itemTemplate, itemSpawnLimits.GlobalLimits); var idToCheckFor = GetMatchingIdFromSpawnLimits(itemTemplate, itemSpawnLimits.GlobalLimits);
if (idToCheckFor is null) if (idToCheckFor is null)
// ParentId or tplid not found in spawnLimits, not a spawn limited item, skip // ParentId or tplid not found in spawnLimits, not a spawn limited item, skip
{ {
return false; return false;
} }
// Use tryAdd to see if it exists, and automatically add 1 // Use tryAdd to see if it exists, and automatically add 1
if (!itemSpawnLimits.CurrentLimits.TryAdd(idToCheckFor, 1)) if (!itemSpawnLimits.CurrentLimits.TryAdd(idToCheckFor, 1))
// if it does exist, come in here and increment // if it does exist, come in here and increment
// Increment item count with this bot type // Increment item count with this bot type
{ {
itemSpawnLimits.CurrentLimits[idToCheckFor]++; itemSpawnLimits.CurrentLimits[idToCheckFor]++;
} }
// Check if over limit // Check if over limit
var currentLimitCount = itemSpawnLimits.CurrentLimits[idToCheckFor]; var currentLimitCount = itemSpawnLimits.CurrentLimits[idToCheckFor];
if (itemSpawnLimits.CurrentLimits[idToCheckFor] > itemSpawnLimits.GlobalLimits[idToCheckFor]) if (
itemSpawnLimits.CurrentLimits[idToCheckFor] > itemSpawnLimits.GlobalLimits[idToCheckFor]
)
{ {
// Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely // Prevent edge-case of small loot pools + code trying to add limited item over and over infinitely
if (currentLimitCount > currentLimitCount * 10) if (currentLimitCount > currentLimitCount * 10)
@@ -794,7 +881,7 @@ public class BotLootGenerator(
{ {
botRole, botRole,
itemName = itemTemplate.Name, itemName = itemTemplate.Name,
attempts = currentLimitCount attempts = currentLimitCount,
} }
) )
); );
@@ -827,7 +914,9 @@ public class BotLootGenerator(
_itemHelper.AddUpdObjectToItem(moneyItem); _itemHelper.AddUpdObjectToItem(moneyItem);
moneyItem.Upd.StackObjectsCount = int.Parse(_weightedRandomHelper.GetWeightedValue(currencyWeight)); moneyItem.Upd.StackObjectsCount = int.Parse(
_weightedRandomHelper.GetWeightedValue(currencyWeight)
);
} }
/// <summary> /// <summary>
@@ -862,7 +951,12 @@ public class BotLootGenerator(
return _botConfig.ItemSpawnLimits[botRole.ToLower()]; return _botConfig.ItemSpawnLimits[botRole.ToLower()];
} }
_logger.Warning(_localisationService.GetText("bot-unable_to_find_spawn_limits_fallback_to_defaults", botRole)); _logger.Warning(
_localisationService.GetText(
"bot-unable_to_find_spawn_limits_fallback_to_defaults",
botRole
)
);
return new Dictionary<string, double>(); return new Dictionary<string, double>();
} }
@@ -873,7 +967,10 @@ public class BotLootGenerator(
/// <param name="itemTemplate">item we want to look for in spawn limits</param> /// <param name="itemTemplate">item we want to look for in spawn limits</param>
/// <param name="spawnLimits">Limits to check for item</param> /// <param name="spawnLimits">Limits to check for item</param>
/// <returns>id as string, otherwise undefined</returns> /// <returns>id as string, otherwise undefined</returns>
public string? GetMatchingIdFromSpawnLimits(TemplateItem itemTemplate, Dictionary<string, double> spawnLimits) public string? GetMatchingIdFromSpawnLimits(
TemplateItem itemTemplate,
Dictionary<string, double> spawnLimits
)
{ {
if (spawnLimits.ContainsKey(itemTemplate.Id)) if (spawnLimits.ContainsKey(itemTemplate.Id))
{ {
@@ -36,7 +36,9 @@ public class BotWeaponGenerator(
{ {
protected const string _modMagazineSlotId = "mod_magazine"; protected const string _modMagazineSlotId = "mod_magazine";
protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>(); protected BotConfig _botConfig = _configServer.GetConfig<BotConfig>();
protected IEnumerable<IInventoryMagGen> _inventoryMagGenComponents = MagGenSetUp(inventoryMagGenComponents); protected IEnumerable<IInventoryMagGen> _inventoryMagGenComponents = MagGenSetUp(
inventoryMagGenComponents
);
protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>(); protected PmcConfig _pmcConfig = _configServer.GetConfig<PmcConfig>();
protected RepairConfig _repairConfig = _configServer.GetConfig<RepairConfig>(); protected RepairConfig _repairConfig = _configServer.GetConfig<RepairConfig>();
@@ -59,8 +61,16 @@ public class BotWeaponGenerator(
/// <param name="isPmc">Is weapon generated for a pmc</param> /// <param name="isPmc">Is weapon generated for a pmc</param>
/// <param name="botLevel"></param> /// <param name="botLevel"></param>
/// <returns>GenerateWeaponResult object</returns> /// <returns>GenerateWeaponResult object</returns>
public GenerateWeaponResult GenerateRandomWeapon(string sessionId, string equipmentSlot, BotTypeInventory botTemplateInventory, string weaponParentId, public GenerateWeaponResult GenerateRandomWeapon(
Dictionary<string, double> modChances, string botRole, bool isPmc, int botLevel) string sessionId,
string equipmentSlot,
BotTypeInventory botTemplateInventory,
string weaponParentId,
Dictionary<string, double> modChances,
string botRole,
bool isPmc,
int botLevel
)
{ {
var weaponTpl = PickWeightedWeaponTemplateFromPool(equipmentSlot, botTemplateInventory); var weaponTpl = PickWeightedWeaponTemplateFromPool(equipmentSlot, botTemplateInventory);
return GenerateWeaponByTpl( return GenerateWeaponByTpl(
@@ -82,7 +92,10 @@ public class BotWeaponGenerator(
/// <param name="equipmentSlot">Primary/secondary/holster</param> /// <param name="equipmentSlot">Primary/secondary/holster</param>
/// <param name="botTemplateInventory">e.g. assault.json</param> /// <param name="botTemplateInventory">e.g. assault.json</param>
/// <returns>Weapon template</returns> /// <returns>Weapon template</returns>
public string PickWeightedWeaponTemplateFromPool(string equipmentSlot, BotTypeInventory botTemplateInventory) public string PickWeightedWeaponTemplateFromPool(
string equipmentSlot,
BotTypeInventory botTemplateInventory
)
{ {
if (!Enum.TryParse(equipmentSlot, out EquipmentSlots key)) if (!Enum.TryParse(equipmentSlot, out EquipmentSlots key))
{ {
@@ -106,8 +119,17 @@ public class BotWeaponGenerator(
/// <param name="isPmc">Is weapon being generated for a PMC.</param> /// <param name="isPmc">Is weapon being generated for a PMC.</param>
/// <param name="botLevel">The level of the bot.</param> /// <param name="botLevel">The level of the bot.</param>
/// <returns>GenerateWeaponResult object.</returns> /// <returns>GenerateWeaponResult object.</returns>
public GenerateWeaponResult? GenerateWeaponByTpl(string sessionId, string weaponTpl, string slotName, BotTypeInventory botTemplateInventory, public GenerateWeaponResult? GenerateWeaponByTpl(
string weaponParentId, Dictionary<string, double> modChances, string botRole, bool isPmc, int botLevel) string sessionId,
string weaponTpl,
string slotName,
BotTypeInventory botTemplateInventory,
string weaponParentId,
Dictionary<string, double> modChances,
string botRole,
bool isPmc,
int botLevel
)
{ {
var modPool = botTemplateInventory.Mods; var modPool = botTemplateInventory.Mods;
var weaponItemTemplate = _itemHelper.GetItem(weaponTpl).Value; var weaponItemTemplate = _itemHelper.GetItem(weaponTpl).Value;
@@ -140,7 +162,7 @@ public class BotWeaponGenerator(
// Chance to add randomised weapon enhancement // Chance to add randomised weapon enhancement
if (isPmc && _randomUtil.GetChance100(_pmcConfig.WeaponHasEnhancementChancePercent)) if (isPmc && _randomUtil.GetChance100(_pmcConfig.WeaponHasEnhancementChancePercent))
// Add buff to weapon root // Add buff to weapon root
{ {
_repairService.AddBuff(_repairConfig.RepairKit.Weapon, weaponWithModsArray[0]); _repairService.AddBuff(_repairConfig.RepairKit.Weapon, weaponWithModsArray[0]);
} }
@@ -166,11 +188,11 @@ public class BotWeaponGenerator(
{ {
Role = botRole, Role = botRole,
Level = botLevel, Level = botLevel,
EquipmentRole = botEquipmentRole EquipmentRole = botEquipmentRole,
}, },
ModLimits = modLimits, ModLimits = modLimits,
WeaponStats = new WeaponStats(), WeaponStats = new WeaponStats(),
ConflictingItemTpls = new HashSet<string>() ConflictingItemTpls = new HashSet<string>(),
}; };
weaponWithModsArray = _botEquipmentModGenerator.GenerateModsForWeapon( weaponWithModsArray = _botEquipmentModGenerator.GenerateModsForWeapon(
sessionId, sessionId,
@@ -180,7 +202,7 @@ public class BotWeaponGenerator(
// Use weapon preset from globals.json if weapon isn't valid // Use weapon preset from globals.json if weapon isn't valid
if (!IsWeaponValid(weaponWithModsArray, botRole)) if (!IsWeaponValid(weaponWithModsArray, botRole))
// Weapon is bad, fall back to weapons preset // Weapon is bad, fall back to weapons preset
{ {
weaponWithModsArray = GetPresetWeaponMods( weaponWithModsArray = GetPresetWeaponMods(
weaponTpl, weaponTpl,
@@ -191,7 +213,9 @@ public class BotWeaponGenerator(
); );
} }
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 // Fill existing magazines to full and sync ammo type
foreach (var magazine in tempList) foreach (var magazine in tempList)
{ {
@@ -199,11 +223,18 @@ public class BotWeaponGenerator(
} }
// Add cartridge(s) to gun chamber(s) // Add cartridge(s) to gun chamber(s)
if (weaponItemTemplate.Properties?.Chambers?.Count > 0 && if (
weaponItemTemplate.Properties.Chambers.FirstOrDefault().Props.Filters.FirstOrDefault().Filter.Contains(ammoTpl)) weaponItemTemplate.Properties?.Chambers?.Count > 0
&& weaponItemTemplate
.Properties.Chambers.FirstOrDefault()
.Props.Filters.FirstOrDefault()
.Filter.Contains(ammoTpl)
)
{ {
// Guns have variety of possible Chamber ids, patron_in_weapon/patron_in_weapon_000/patron_in_weapon_001 // 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()); AddCartridgeToChamber(weaponWithModsArray, ammoTpl, chamberSlotNames.ToList());
} }
@@ -228,7 +259,7 @@ public class BotWeaponGenerator(
ChosenAmmoTemplate = ammoTpl, ChosenAmmoTemplate = ammoTpl,
ChosenUbglAmmoTemplate = ubglAmmoTpl, ChosenUbglAmmoTemplate = ubglAmmoTpl,
WeaponMods = modPool, WeaponMods = modPool,
WeaponTemplate = weaponItemTemplate WeaponTemplate = weaponItemTemplate,
}; };
} }
@@ -239,7 +270,11 @@ public class BotWeaponGenerator(
/// <param name="weaponWithModsList">Weapon and mods</param> /// <param name="weaponWithModsList">Weapon and mods</param>
/// <param name="ammoTemplate">Cartridge to add to weapon</param> /// <param name="ammoTemplate">Cartridge to add to weapon</param>
/// <param name="chamberSlotIds">Name of slots to create or add ammo to</param> /// <param name="chamberSlotIds">Name of slots to create or add ammo to</param>
protected void AddCartridgeToChamber(List<Item> weaponWithModsList, string ammoTemplate, List<string> chamberSlotIds) protected void AddCartridgeToChamber(
List<Item> weaponWithModsList,
string ammoTemplate,
List<string> chamberSlotIds
)
{ {
foreach (var slotId in chamberSlotIds) foreach (var slotId in chamberSlotIds)
{ {
@@ -254,10 +289,7 @@ public class BotWeaponGenerator(
Template = ammoTemplate, Template = ammoTemplate,
ParentId = weaponWithModsList[0].Id, ParentId = weaponWithModsList[0].Id,
SlotId = slotId, SlotId = slotId,
Upd = new Upd Upd = new Upd { StackObjectsCount = 1 },
{
StackObjectsCount = 1
}
} }
); );
} }
@@ -265,10 +297,7 @@ public class BotWeaponGenerator(
{ {
// Already exists, update values // Already exists, update values
existingItemWithSlot.Template = ammoTemplate; existingItemWithSlot.Template = ammoTemplate;
existingItemWithSlot.Upd = new Upd existingItemWithSlot.Upd = new Upd { StackObjectsCount = 1 };
{
StackObjectsCount = 1
};
} }
} }
} }
@@ -283,8 +312,13 @@ public class BotWeaponGenerator(
/// <param name="weaponItemTemplate">Database template for weapon</param> /// <param name="weaponItemTemplate">Database template for weapon</param>
/// <param name="botRole">For durability values</param> /// <param name="botRole">For durability values</param>
/// <returns>Base weapon item in a list</returns> /// <returns>Base weapon item in a list</returns>
protected List<Item> ConstructWeaponBaseList(string weaponTemplate, string weaponParentId, string equipmentSlot, TemplateItem weaponItemTemplate, protected List<Item> ConstructWeaponBaseList(
string botRole) string weaponTemplate,
string weaponParentId,
string equipmentSlot,
TemplateItem weaponItemTemplate,
string botRole
)
{ {
return return
[ [
@@ -294,8 +328,11 @@ public class BotWeaponGenerator(
Template = weaponTemplate, Template = weaponTemplate,
ParentId = weaponParentId, ParentId = weaponParentId,
SlotId = equipmentSlot, SlotId = equipmentSlot,
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(weaponItemTemplate, botRole) Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
} weaponItemTemplate,
botRole
),
},
]; ];
} }
@@ -308,10 +345,21 @@ public class BotWeaponGenerator(
/// <param name="itemTemplate">Item template</param> /// <param name="itemTemplate">Item template</param>
/// <param name="botRole">Bot role</param> /// <param name="botRole">Bot role</param>
/// <returns>List of weapon mods</returns> /// <returns>List of weapon mods</returns>
protected List<Item> GetPresetWeaponMods(string weaponTemplate, string equipmentSlot, string weaponParentId, TemplateItem itemTemplate, string botRole) protected List<Item> GetPresetWeaponMods(
string weaponTemplate,
string equipmentSlot,
string weaponParentId,
TemplateItem itemTemplate,
string botRole
)
{ {
// Invalid weapon generated, fallback to preset // Invalid weapon generated, fallback to preset
_logger.Warning(_localisationService.GetText("bot-weapon_generated_incorrect_using_default", $"{weaponTemplate} - {itemTemplate.Name}")); _logger.Warning(
_localisationService.GetText(
"bot-weapon_generated_incorrect_using_default",
$"{weaponTemplate} - {itemTemplate.Name}"
)
);
List<Item> weaponMods = []; List<Item> weaponMods = [];
// TODO: Preset weapons trigger a lot of warnings regarding missing ammo in magazines & such // TODO: Preset weapons trigger a lot of warnings regarding missing ammo in magazines & such
@@ -331,13 +379,18 @@ public class BotWeaponGenerator(
var parentItem = preset.Items[0]; var parentItem = preset.Items[0];
parentItem.ParentId = weaponParentId; parentItem.ParentId = weaponParentId;
parentItem.SlotId = equipmentSlot; parentItem.SlotId = equipmentSlot;
parentItem.Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate, botRole); parentItem.Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(
itemTemplate,
botRole
);
preset.Items[0] = parentItem; preset.Items[0] = parentItem;
weaponMods.AddRange(preset.Items); weaponMods.AddRange(preset.Items);
} }
else else
{ {
_logger.Error(_localisationService.GetText("bot-missing_weapon_preset", weaponTemplate)); _logger.Error(
_localisationService.GetText("bot-missing_weapon_preset", weaponTemplate)
);
} }
return weaponMods; return weaponMods;
@@ -360,10 +413,16 @@ public class BotWeaponGenerator(
} }
// Iterate over required slots in db item, check mod exists for that slot // 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 slotName = modSlotTemplate.Name;
var hasWeaponSlotItem = weaponItemList.Any(weaponItem => weaponItem.ParentId == mod.Id && weaponItem.SlotId == slotName); var hasWeaponSlotItem = weaponItemList.Any(weaponItem =>
weaponItem.ParentId == mod.Id && weaponItem.SlotId == slotName
);
if (!hasWeaponSlotItem) if (!hasWeaponSlotItem)
{ {
_logger.Warning( _logger.Warning(
@@ -374,7 +433,7 @@ public class BotWeaponGenerator(
modSlot = modSlotTemplate.Name, modSlot = modSlotTemplate.Name,
modName = modTemplate.Name, modName = modTemplate.Name,
slotId = mod.SlotId, slotId = mod.SlotId,
botRole botRole,
} }
) )
); );
@@ -395,16 +454,27 @@ public class BotWeaponGenerator(
/// <param name="magWeights">Magazine weights for count to add to inventory</param> /// <param name="magWeights">Magazine weights for count to add to inventory</param>
/// <param name="inventory">Inventory to add magazines to</param> /// <param name="inventory">Inventory to add magazines to</param>
/// <param name="botRole">The bot type we're generating extra mags for</param> /// <param name="botRole">The bot type we're generating extra mags for</param>
public void AddExtraMagazinesToInventory(GenerateWeaponResult generatedWeaponResult, GenerationData magWeights, BotBaseInventory inventory, string botRole) public void AddExtraMagazinesToInventory(
GenerateWeaponResult generatedWeaponResult,
GenerationData magWeights,
BotBaseInventory inventory,
string botRole
)
{ {
var weaponAndMods = generatedWeaponResult.Weapon; var weaponAndMods = generatedWeaponResult.Weapon;
var weaponTemplate = generatedWeaponResult.WeaponTemplate; var weaponTemplate = generatedWeaponResult.WeaponTemplate;
var magazineTpl = GetMagazineTemplateFromWeaponTemplate(weaponAndMods, weaponTemplate, botRole); var magazineTpl = GetMagazineTemplateFromWeaponTemplate(
weaponAndMods,
weaponTemplate,
botRole
);
var magTemplate = _itemHelper.GetItem(magazineTpl).Value; var magTemplate = _itemHelper.GetItem(magazineTpl).Value;
if (magTemplate is null) if (magTemplate is null)
{ {
_logger.Error(_localisationService.GetText("bot-unable_to_find_magazine_item", magazineTpl)); _logger.Error(
_localisationService.GetText("bot-unable_to_find_magazine_item", magazineTpl)
);
return; return;
} }
@@ -414,7 +484,10 @@ public class BotWeaponGenerator(
if (!ammoTemplate.Key) if (!ammoTemplate.Key)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText("bot-unable_to_find_ammo_item", generatedWeaponResult.ChosenAmmoTemplate) _localisationService.GetText(
"bot-unable_to_find_ammo_item",
generatedWeaponResult.ChosenAmmoTemplate
)
); );
return; return;
@@ -434,7 +507,8 @@ public class BotWeaponGenerator(
inventory inventory
); );
_inventoryMagGenComponents.FirstOrDefault(v => v.CanHandleInventoryMagGen(inventoryMagGenModel)) _inventoryMagGenComponents
.FirstOrDefault(v => v.CanHandleInventoryMagGen(inventoryMagGenModel))
.Process(inventoryMagGenModel); .Process(inventoryMagGenModel);
// Add x stacks of bullets to SecuredContainer (bots use a magic mag packing skill to reload instantly) // Add x stacks of bullets to SecuredContainer (bots use a magic mag packing skill to reload instantly)
@@ -452,7 +526,11 @@ public class BotWeaponGenerator(
/// <param name="weaponMods">Weapon list with mods</param> /// <param name="weaponMods">Weapon list with mods</param>
/// <param name="generatedWeaponResult">Result of weapon generation</param> /// <param name="generatedWeaponResult">Result of weapon generation</param>
/// <param name="inventory">Bot inventory to add grenades to</param> /// <param name="inventory">Bot inventory to add grenades to</param>
protected void AddUbglGrenadesToBotInventory(List<Item> weaponMods, GenerateWeaponResult generatedWeaponResult, BotBaseInventory inventory) protected void AddUbglGrenadesToBotInventory(
List<Item> weaponMods,
GenerateWeaponResult generatedWeaponResult,
BotBaseInventory inventory
)
{ {
// Find ubgl mod item + get details of it from db // 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");
@@ -461,16 +539,14 @@ public class BotWeaponGenerator(
// Define min/max of how many grenades bot will have // Define min/max of how many grenades bot will have
GenerationData ubglMinMax = new() GenerationData ubglMinMax = new()
{ {
Weights = new Dictionary<double, double> Weights = new Dictionary<double, double> { { 1, 1 }, { 2, 1 } },
{ Whitelist = new Dictionary<string, double>(),
{ 1, 1 },
{ 2, 1 }
},
Whitelist = new Dictionary<string, double>()
}; };
// get ammo template from db // get ammo template from db
var ubglAmmoDbTemplate = _itemHelper.GetItem(generatedWeaponResult.ChosenUbglAmmoTemplate).Value; var ubglAmmoDbTemplate = _itemHelper
.GetItem(generatedWeaponResult.ChosenUbglAmmoTemplate)
.Value;
// Add greandes to bot inventory // Add greandes to bot inventory
var ubglAmmoGenModel = new InventoryMagGen( var ubglAmmoGenModel = new InventoryMagGen(
@@ -495,16 +571,18 @@ public class BotWeaponGenerator(
/// <param name="ammoTpl">Ammo type to add.</param> /// <param name="ammoTpl">Ammo type to add.</param>
/// <param name="stackSize">Size of the ammo stack to add.</param> /// <param name="stackSize">Size of the ammo stack to add.</param>
/// <param name="inventory">Player inventory.</param> /// <param name="inventory">Player inventory.</param>
protected void AddAmmoToSecureContainer(int stackCount, string ammoTpl, int stackSize, BotBaseInventory inventory) protected void AddAmmoToSecureContainer(
int stackCount,
string ammoTpl,
int stackSize,
BotBaseInventory inventory
)
{ {
for (var i = 0; i < stackCount; i++) for (var i = 0; i < stackCount; i++)
{ {
var id = _hashUtil.Generate(); var id = _hashUtil.Generate();
_botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
new HashSet<EquipmentSlots> new HashSet<EquipmentSlots> { EquipmentSlots.SecuredContainer },
{
EquipmentSlots.SecuredContainer
},
id, id,
ammoTpl, ammoTpl,
new List<Item> new List<Item>
@@ -513,11 +591,8 @@ public class BotWeaponGenerator(
{ {
Id = id, Id = id,
Template = ammoTpl, Template = ammoTpl,
Upd = new Upd Upd = new Upd { StackObjectsCount = stackSize },
{ },
StackObjectsCount = stackSize
}
}
}, },
inventory inventory
); );
@@ -531,7 +606,11 @@ public class BotWeaponGenerator(
/// <param name="weaponTemplate">Weapon to get magazine template for.</param> /// <param name="weaponTemplate">Weapon to get magazine template for.</param>
/// <param name="botRole">The bot type we are getting the magazine for.</param> /// <param name="botRole">The bot type we are getting the magazine for.</param>
/// <returns>Magazine template string.</returns> /// <returns>Magazine template string.</returns>
protected string GetMagazineTemplateFromWeaponTemplate(List<Item> weaponMods, TemplateItem weaponTemplate, string botRole) 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) if (magazine is null)
@@ -545,21 +624,19 @@ public class BotWeaponGenerator(
// log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver) // log error if no magazine AND not a chamber loaded weapon (e.g. shotgun revolver)
if (!weaponTemplate.Properties.IsChamberLoad ?? false) if (!weaponTemplate.Properties.IsChamberLoad ?? false)
// Shouldn't happen // Shouldn't happen
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText( _localisationService.GetText(
"bot-weapon_missing_magazine_or_chamber", "bot-weapon_missing_magazine_or_chamber",
new new { weaponId = weaponTemplate.Id, botRole }
{
weaponId = weaponTemplate.Id,
botRole
}
) )
); );
} }
var defaultMagTplId = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(weaponTemplate); var defaultMagTplId = _botWeaponGeneratorHelper.GetWeaponsDefaultMagazineTpl(
weaponTemplate
);
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug( _logger.Debug(
@@ -579,10 +656,16 @@ public class BotWeaponGenerator(
/// <param name="cartridgePool">Dictionary of all cartridges keyed by type e.g. Caliber556x45NATO</param> /// <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> /// <param name="weaponTemplate">Weapon details from database we want to pick ammo for</param>
/// <returns>Ammo template that works with the desired gun</returns> /// <returns>Ammo template that works with the desired gun</returns>
protected string? GetWeightedCompatibleAmmo(Dictionary<string, Dictionary<string, double>> cartridgePool, TemplateItem weaponTemplate) protected string? GetWeightedCompatibleAmmo(
Dictionary<string, Dictionary<string, double>> cartridgePool,
TemplateItem weaponTemplate
)
{ {
var desiredCaliber = GetWeaponCaliber(weaponTemplate); var desiredCaliber = GetWeaponCaliber(weaponTemplate);
if (!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon) || cartridgePoolForWeapon?.Count == 0) if (
!cartridgePool.TryGetValue(desiredCaliber, out var cartridgePoolForWeapon)
|| cartridgePoolForWeapon?.Count == 0
)
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
@@ -593,7 +676,7 @@ public class BotWeaponGenerator(
{ {
weaponId = weaponTemplate.Id, weaponId = weaponTemplate.Id,
weaponName = weaponTemplate.Name, weaponName = weaponTemplate.Name,
defaultAmmo = weaponTemplate.Properties.DefAmmo defaultAmmo = weaponTemplate.Properties.DefAmmo,
} }
) )
); );
@@ -605,9 +688,11 @@ public class BotWeaponGenerator(
} }
// Get cartridges the weapons first chamber allow // Get cartridges the weapons first chamber allow
var compatibleCartridgesInTemplate = GetCompatibleCartridgesFromWeaponTemplate(weaponTemplate); var compatibleCartridgesInTemplate = GetCompatibleCartridgesFromWeaponTemplate(
weaponTemplate
);
if (compatibleCartridgesInTemplate.Count == 0) if (compatibleCartridgesInTemplate.Count == 0)
// No chamber data found in weapon, send default // No chamber data found in weapon, send default
{ {
return weaponTemplate.Properties.DefAmmo; return weaponTemplate.Properties.DefAmmo;
} }
@@ -626,7 +711,9 @@ public class BotWeaponGenerator(
if (!compatibleCartridges.Any()) if (!compatibleCartridges.Any())
{ {
// Get cartridges from the weapons first magazine in filters // Get cartridges from the weapons first magazine in filters
var compatibleCartridgesInMagazine = GetCompatibleCartridgesFromMagazineTemplate(weaponTemplate); var compatibleCartridgesInMagazine = GetCompatibleCartridgesFromMagazineTemplate(
weaponTemplate
);
if (compatibleCartridgesInMagazine.Count == 0) if (compatibleCartridgesInMagazine.Count == 0)
{ {
// No compatible cartridges found in magazine, use default // No compatible cartridges found in magazine, use default
@@ -634,7 +721,9 @@ public class BotWeaponGenerator(
} }
// Get the caliber data from the first compatible round in the magazine // Get the caliber data from the first compatible round in the magazine
var magazineCaliberData = _itemHelper.GetItem(compatibleCartridgesInMagazine.FirstOrDefault()).Value.Properties.Caliber; var magazineCaliberData = _itemHelper
.GetItem(compatibleCartridgesInMagazine.FirstOrDefault())
.Value.Properties.Caliber;
cartridgePoolForWeapon = cartridgePool[magazineCaliberData]; cartridgePoolForWeapon = cartridgePool[magazineCaliberData];
foreach (var cartridgeKvP in cartridgePoolForWeapon) foreach (var cartridgeKvP in cartridgePoolForWeapon)
@@ -664,7 +753,9 @@ public class BotWeaponGenerator(
{ {
ArgumentNullException.ThrowIfNull(weaponTemplate); ArgumentNullException.ThrowIfNull(weaponTemplate);
var cartridges = weaponTemplate.Properties?.Chambers?.FirstOrDefault()?.Props?.Filters?[0].Filter; var cartridges = weaponTemplate
.Properties?.Chambers?.FirstOrDefault()
?.Props?.Filters?[0].Filter;
if (cartridges is not null) if (cartridges is not null)
{ {
return cartridges; return cartridges;
@@ -680,26 +771,39 @@ public class BotWeaponGenerator(
/// <param name="weaponTemplate">Weapon db template to get magazine cartridges for</param> /// <param name="weaponTemplate">Weapon db template to get magazine cartridges for</param>
/// <returns>Hashset of cartridge tpls</returns> /// <returns>Hashset of cartridge tpls</returns>
/// <exception cref="ArgumentNullException">Thrown when weaponTemplate is null.</exception> /// <exception cref="ArgumentNullException">Thrown when weaponTemplate is null.</exception>
protected HashSet<string> GetCompatibleCartridgesFromMagazineTemplate(TemplateItem weaponTemplate) protected HashSet<string> GetCompatibleCartridgesFromMagazineTemplate(
TemplateItem weaponTemplate
)
{ {
ArgumentNullException.ThrowIfNull(weaponTemplate); ArgumentNullException.ThrowIfNull(weaponTemplate);
// Get the first magazine's template from the weapon // Get the first magazine's template from the weapon
var magazineSlot = weaponTemplate.Properties.Slots?.FirstOrDefault(slot => slot.Name == "mod_magazine"); var magazineSlot = weaponTemplate.Properties.Slots?.FirstOrDefault(slot =>
slot.Name == "mod_magazine"
);
if (magazineSlot is null) if (magazineSlot is null)
{ {
return []; return [];
} }
var magazineTemplate = _itemHelper.GetItem(magazineSlot.Props?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault()); var magazineTemplate = _itemHelper.GetItem(
magazineSlot.Props?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault()
);
if (!magazineTemplate.Key) if (!magazineTemplate.Key)
{ {
return []; return [];
} }
// Try to get cartridges from slots array first, if none found, try Cartridges array // Try to get cartridges from slots array first, if none found, try Cartridges array
var cartridges = magazineTemplate.Value.Properties.Slots.FirstOrDefault()?.Props?.Filters.FirstOrDefault()?.Filter var cartridges =
?? magazineTemplate.Value.Properties.Cartridges.FirstOrDefault()?.Props?.Filters.FirstOrDefault()?.Filter; magazineTemplate
.Value.Properties.Slots.FirstOrDefault()
?.Props?.Filters.FirstOrDefault()
?.Filter
?? magazineTemplate
.Value.Properties.Cartridges.FirstOrDefault()
?.Props?.Filters.FirstOrDefault()
?.Filter;
return cartridges ?? []; return cartridges ?? [];
} }
@@ -717,7 +821,7 @@ public class BotWeaponGenerator(
} }
if (!string.IsNullOrEmpty(weaponTemplate.Properties.AmmoCaliber)) if (!string.IsNullOrEmpty(weaponTemplate.Properties.AmmoCaliber))
// 9x18pmm has a typo, should be Caliber9x18PM // 9x18pmm has a typo, should be Caliber9x18PM
{ {
return weaponTemplate.Properties.AmmoCaliber == "Caliber9x18PMM" return weaponTemplate.Properties.AmmoCaliber == "Caliber9x18PMM"
? "Caliber9x18PM" ? "Caliber9x18PM"
@@ -729,9 +833,7 @@ public class BotWeaponGenerator(
var ammoInChamber = _itemHelper.GetItem( var ammoInChamber = _itemHelper.GetItem(
weaponTemplate.Properties.Chambers[0].Props.Filters[0].Filter.FirstOrDefault() weaponTemplate.Properties.Chambers[0].Props.Filters[0].Filter.FirstOrDefault()
); );
return !ammoInChamber.Key return !ammoInChamber.Key ? null : ammoInChamber.Value.Properties.Caliber;
? null
: ammoInChamber.Value.Properties.Caliber;
} }
return null; return null;
@@ -743,12 +845,18 @@ public class BotWeaponGenerator(
/// <param name="weaponMods">Weapon with children</param> /// <param name="weaponMods">Weapon with children</param>
/// <param name="magazine">Magazine item</param> /// <param name="magazine">Magazine item</param>
/// <param name="cartridgeTemplate">Cartridge to insert into magazine</param> /// <param name="cartridgeTemplate">Cartridge to insert into magazine</param>
protected void FillExistingMagazines(List<Item> weaponMods, Item magazine, string cartridgeTemplate) protected void FillExistingMagazines(
List<Item> weaponMods,
Item magazine,
string cartridgeTemplate
)
{ {
var magazineTemplate = _itemHelper.GetItem(magazine.Template).Value; var magazineTemplate = _itemHelper.GetItem(magazine.Template).Value;
if (magazineTemplate is null) if (magazineTemplate is null)
{ {
_logger.Error(_localisationService.GetText("bot-unable_to_find_magazine_item", magazine.Template)); _logger.Error(
_localisationService.GetText("bot-unable_to_find_magazine_item", magazine.Template)
);
return; return;
} }
@@ -765,7 +873,12 @@ public class BotWeaponGenerator(
} }
else else
{ {
AddOrUpdateMagazinesChildWithAmmo(weaponMods, magazine, cartridgeTemplate, magazineTemplate); AddOrUpdateMagazinesChildWithAmmo(
weaponMods,
magazine,
cartridgeTemplate,
magazineTemplate
);
} }
} }
@@ -784,10 +897,7 @@ public class BotWeaponGenerator(
Template = ubglAmmoTpl, Template = ubglAmmoTpl,
ParentId = ubglMod.Id, ParentId = ubglMod.Id,
SlotId = "patron_in_weapon", SlotId = "patron_in_weapon",
Upd = new Upd Upd = new Upd { StackObjectsCount = 1 },
{
StackObjectsCount = 1
}
} }
); );
} }
@@ -799,9 +909,15 @@ public class BotWeaponGenerator(
/// <param name="magazine">Magazine item details we're adding cartridges to</param> /// <param name="magazine">Magazine item details we're adding cartridges to</param>
/// <param name="chosenAmmoTpl">Cartridge to put into the magazine</param> /// <param name="chosenAmmoTpl">Cartridge to put into the magazine</param>
/// <param name="magazineTemplate">Magazines db template</param> /// <param name="magazineTemplate">Magazines db template</param>
protected void AddOrUpdateMagazinesChildWithAmmo(List<Item> weaponWithMods, Item magazine, string chosenAmmoTpl, TemplateItem magazineTemplate) protected void AddOrUpdateMagazinesChildWithAmmo(
List<Item> weaponWithMods,
Item magazine,
string chosenAmmoTpl,
TemplateItem magazineTemplate
)
{ {
var magazineCartridgeChildItem = weaponWithMods.FirstOrDefault(m => m.ParentId == magazine.Id && m.SlotId == "cartridges" var magazineCartridgeChildItem = weaponWithMods.FirstOrDefault(m =>
m.ParentId == magazine.Id && m.SlotId == "cartridges"
); );
if (magazineCartridgeChildItem is not null) if (magazineCartridgeChildItem is not null)
{ {
@@ -813,13 +929,20 @@ public class BotWeaponGenerator(
List<Item> magazineWithCartridges = [magazine]; List<Item> magazineWithCartridges = [magazine];
// Add cartridges as children to above mag array // Add cartridges as children to above mag array
_itemHelper.FillMagazineWithCartridge(magazineWithCartridges, magazineTemplate, chosenAmmoTpl, 1); _itemHelper.FillMagazineWithCartridge(
magazineWithCartridges,
magazineTemplate,
chosenAmmoTpl,
1
);
// Replace existing magazine with above array of mag + cartridge stacks // Replace existing magazine with above array of mag + cartridge stacks
var magazineIndex = weaponWithMods.FindIndex(i => i.Id == magazine.Id); // magazineWithCartridges var magazineIndex = weaponWithMods.FindIndex(i => i.Id == magazine.Id); // magazineWithCartridges
if (magazineIndex == -1) if (magazineIndex == -1)
{ {
_logger.Error($"Unable to add cartridges: {chosenAmmoTpl} to magazine: {magazine.Id} as none found"); _logger.Error(
$"Unable to add cartridges: {chosenAmmoTpl} to magazine: {magazine.Id} as none found"
);
return; return;
} }
@@ -842,7 +965,9 @@ public class BotWeaponGenerator(
// This might not be necessary since we already filled the camoras with a random whitelisted and compatible ammo type, // 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 // but I'm not sure whether this is also used elsewhere
var camoras = weaponMods var camoras = weaponMods
.Where(x => x.ParentId == magazineId && x.SlotId.StartsWith("camora", StringComparison.Ordinal)) .Where(x =>
x.ParentId == magazineId && x.SlotId.StartsWith("camora", StringComparison.Ordinal)
)
.ToList(); .ToList();
if (camoras.Count == 0) if (camoras.Count == 0)
@@ -859,10 +984,7 @@ public class BotWeaponGenerator(
} }
else else
{ {
camora.Upd = new Upd camora.Upd = new Upd { StackObjectsCount = 1 };
{
StackObjectsCount = 1
};
} }
} }
} }
@@ -60,24 +60,29 @@ public class FenceBaseAssortGenerator(
// Item base type blacklisted // Item base type blacklisted
if (traderConfig.Fence.Blacklist.Count > 0) if (traderConfig.Fence.Blacklist.Count > 0)
{ {
if (traderConfig.Fence.Blacklist.Contains(rootItemDb.Id) || if (
itemHelper.IsOfBaseclasses(rootItemDb.Id, traderConfig.Fence.Blacklist) traderConfig.Fence.Blacklist.Contains(rootItemDb.Id)
) || itemHelper.IsOfBaseclasses(rootItemDb.Id, traderConfig.Fence.Blacklist)
)
{ {
continue; continue;
} }
} }
// Only allow rigs with no slots (carrier rigs) // Only allow rigs with no slots (carrier rigs)
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.VEST) && if (
(rootItemDb.Properties?.Slots?.Count ?? 0) > 0 itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.VEST)
) && (rootItemDb.Properties?.Slots?.Count ?? 0) > 0
)
{ {
continue; continue;
} }
// Skip seasonal event items when not in seasonal event // Skip seasonal event items when not in seasonal event
if (traderConfig.Fence.BlacklistSeasonalItems && blockedSeasonalItems.Contains(rootItemDb.Id)) if (
traderConfig.Fence.BlacklistSeasonalItems
&& blockedSeasonalItems.Contains(rootItemDb.Id)
)
{ {
continue; continue;
} }
@@ -91,11 +96,8 @@ public class FenceBaseAssortGenerator(
Template = rootItemDb.Id, Template = rootItemDb.Id,
ParentId = "hideout", ParentId = "hideout",
SlotId = "hideout", SlotId = "hideout",
Upd = new Upd Upd = new Upd { StackObjectsCount = 9999999 },
{ },
StackObjectsCount = 9999999
}
}
}; };
// Ensure ammo is not above penetration limit value // Ensure ammo is not above penetration limit value
@@ -108,7 +110,7 @@ public class FenceBaseAssortGenerator(
} }
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX)) if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX))
// Only add cartridges to box if box has no children // Only add cartridges to box if box has no children
{ {
if (itemWithChildrenToAdd.Count == 1) if (itemWithChildrenToAdd.Count == 1)
{ {
@@ -127,12 +129,17 @@ public class FenceBaseAssortGenerator(
// Create barter scheme (price) // Create barter scheme (price)
var barterSchemeToAdd = new BarterScheme var barterSchemeToAdd = new BarterScheme
{ {
Count = Math.Round((double) fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd)), Count = Math.Round(
Template = Money.ROUBLES (double)fenceService.GetItemPrice(rootItemDb.Id, itemWithChildrenToAdd)
),
Template = Money.ROUBLES,
}; };
// Add barter data to base // Add barter data to base
baseFenceAssort.BarterScheme[itemWithChildrenToAdd[0].Id] = [[barterSchemeToAdd]]; baseFenceAssort.BarterScheme[itemWithChildrenToAdd[0].Id] =
[
[barterSchemeToAdd],
];
// Add item to base // Add item to base
baseFenceAssort.Items.AddRange(itemWithChildrenToAdd); baseFenceAssort.Items.AddRange(itemWithChildrenToAdd);
@@ -146,7 +153,11 @@ public class FenceBaseAssortGenerator(
foreach (var defaultPreset in defaultPresets) foreach (var defaultPreset in defaultPresets)
{ {
// Skip presets we've already added // Skip presets we've already added
if (baseFenceAssort.Items.Any(item => item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id)) if (
baseFenceAssort.Items.Any(item =>
item.Upd != null && item.Upd.SptPresetId == defaultPreset.Id
)
)
{ {
continue; continue;
} }
@@ -167,8 +178,7 @@ public class FenceBaseAssortGenerator(
mod.Upd = new Upd mod.Upd = new Upd
{ {
StackObjectsCount = 1, StackObjectsCount = 1,
SptPresetId = SptPresetId = defaultPreset.Id, // Store preset id here so we can check it later to prevent preset dupes
defaultPreset.Id // Store preset id here so we can check it later to prevent preset dupes
}; };
// Updated root item, exit loop // Updated root item, exit loop
@@ -191,9 +201,9 @@ public class FenceBaseAssortGenerator(
new BarterScheme new BarterScheme
{ {
Template = Money.ROUBLES, Template = Money.ROUBLES,
Count = Math.Round(price * itemQualityModifier) Count = Math.Round(price * itemQualityModifier),
} },
} },
}; };
baseFenceAssort.LoyalLevelItems[itemAndChildren[0].Id] = 1; baseFenceAssort.LoyalLevelItems[itemAndChildren[0].Id] = 1;
@@ -210,7 +220,12 @@ public class FenceBaseAssortGenerator(
var ammoPenetrationPower = GetAmmoPenetrationPower(rootItemDb); var ammoPenetrationPower = GetAmmoPenetrationPower(rootItemDb);
if (ammoPenetrationPower == null) if (ammoPenetrationPower == null)
{ {
logger.Warning(localisationService.GetText("fence-unable_to_get_ammo_penetration_value", rootItemDb.Id)); logger.Warning(
localisationService.GetText(
"fence-unable_to_get_ammo_penetration_value",
rootItemDb.Id
)
);
return false; return false;
} }
@@ -227,13 +242,18 @@ public class FenceBaseAssortGenerator(
if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX)) if (itemHelper.IsOfBaseclass(rootItemDb.Id, BaseClasses.AMMO_BOX))
{ {
// Get the cartridge tpl found inside ammo box // Get the cartridge tpl found inside ammo box
var cartridgeTplInBox = rootItemDb.Properties.StackSlots[0].Props.Filters[0].Filter.FirstOrDefault(); var cartridgeTplInBox = rootItemDb
.Properties.StackSlots[0]
.Props.Filters[0]
.Filter.FirstOrDefault();
// Look up cartridge tpl in db // Look up cartridge tpl in db
var ammoItemDb = itemHelper.GetItem(cartridgeTplInBox); var ammoItemDb = itemHelper.GetItem(cartridgeTplInBox);
if (!ammoItemDb.Key) if (!ammoItemDb.Key)
{ {
logger.Warning(localisationService.GetText("fence-ammo_not_found_in_db", cartridgeTplInBox)); logger.Warning(
localisationService.GetText("fence-ammo_not_found_in_db", cartridgeTplInBox)
);
return null; return null;
} }
@@ -265,17 +285,20 @@ public class FenceBaseAssortGenerator(
} }
// Check for and add required soft inserts to armors // 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; var hasRequiredSlots = requiredSlots.Count > 0;
if (hasRequiredSlots) if (hasRequiredSlots)
{ {
foreach (var requiredSlot in requiredSlots) foreach (var requiredSlot in requiredSlots)
{ {
var modItemDbDetails = itemHelper.GetItem(requiredSlot.Props.Filters[0].Plate).Value; var modItemDbDetails = itemHelper
var plateTpl = .GetItem(requiredSlot.Props.Filters[0].Plate)
requiredSlot.Props.Filters[0].Plate; // `Plate` property appears to be the 'default' item for slot .Value;
var plateTpl = requiredSlot.Props.Filters[0].Plate; // `Plate` property appears to be the 'default' item for slot
if (string.IsNullOrEmpty(plateTpl)) if (string.IsNullOrEmpty(plateTpl))
// Some bsg plate properties are empty, skip mod // Some bsg plate properties are empty, skip mod
{ {
continue; continue;
} }
@@ -291,9 +314,9 @@ public class FenceBaseAssortGenerator(
Repairable = new UpdRepairable Repairable = new UpdRepairable
{ {
Durability = modItemDbDetails.Properties.MaxDurability, Durability = modItemDbDetails.Properties.MaxDurability,
MaxDurability = modItemDbDetails.Properties.MaxDurability MaxDurability = modItemDbDetails.Properties.MaxDurability,
} },
} },
}; };
armor.Add(mod); armor.Add(mod);
@@ -301,7 +324,8 @@ public class FenceBaseAssortGenerator(
} }
// Check for and add plate items // 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(); .ToList();
if (plateSlots.Count > 0) if (plateSlots.Count > 0)
{ {
@@ -309,7 +333,7 @@ public class FenceBaseAssortGenerator(
{ {
var plateTpl = plateSlot.Props.Filters[0].Plate; var plateTpl = plateSlot.Props.Filters[0].Plate;
if (string.IsNullOrEmpty(plateTpl)) if (string.IsNullOrEmpty(plateTpl))
// Bsg data lacks a default plate, skip adding mod // Bsg data lacks a default plate, skip adding mod
{ {
continue; continue;
} }
@@ -327,9 +351,9 @@ public class FenceBaseAssortGenerator(
Repairable = new UpdRepairable Repairable = new UpdRepairable
{ {
Durability = modItemDbDetails.Properties.MaxDurability, Durability = modItemDbDetails.Properties.MaxDurability,
MaxDurability = modItemDbDetails.Properties.MaxDurability MaxDurability = modItemDbDetails.Properties.MaxDurability,
} },
} },
} }
); );
} }
@@ -33,14 +33,17 @@ public class LocationLootGenerator(
) )
{ {
protected readonly LocationConfig _locationConfig = _configServer.GetConfig<LocationConfig>(); protected readonly LocationConfig _locationConfig = _configServer.GetConfig<LocationConfig>();
protected readonly SeasonalEventConfig _seasonalEventConfig = _configServer.GetConfig<SeasonalEventConfig>(); protected readonly SeasonalEventConfig _seasonalEventConfig =
_configServer.GetConfig<SeasonalEventConfig>();
/// Create a list of container objects with randomised loot /// Create a list of container objects with randomised loot
/// <param name="locationBase">Map base to generate containers for</param> /// <param name="locationBase">Map base to generate containers for</param>
/// <param name="staticAmmoDist">Static ammo distribution</param> /// <param name="staticAmmoDist">Static ammo distribution</param>
/// <returns>List of container objects</returns> /// <returns>List of container objects</returns>
public List<SpawnpointTemplate> GenerateStaticContainers(LocationBase locationBase, public List<SpawnpointTemplate> GenerateStaticContainers(
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist) LocationBase locationBase,
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist
)
{ {
var staticLootItemCount = 0; var staticLootItemCount = 0;
var result = new List<SpawnpointTemplate>(); var result = new List<SpawnpointTemplate>();
@@ -52,18 +55,26 @@ public class LocationLootGenerator(
if (staticWeaponsOnMapClone is null) if (staticWeaponsOnMapClone is null)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText("location-unable_to_find_static_weapon_for_map", locationBase.Name) _localisationService.GetText(
"location-unable_to_find_static_weapon_for_map",
locationBase.Name
)
); );
} }
// Add mounted weapons to output loot // Add mounted weapons to output loot
result.AddRange(staticWeaponsOnMapClone); result.AddRange(staticWeaponsOnMapClone);
var allStaticContainersOnMapClone = _cloner.Clone(mapData.StaticContainers.Value.StaticContainers); var allStaticContainersOnMapClone = _cloner.Clone(
mapData.StaticContainers.Value.StaticContainers
);
if (allStaticContainersOnMapClone is null) if (allStaticContainersOnMapClone is null)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText("location-unable_to_find_static_container_for_map", locationBase.Name) _localisationService.GetText(
"location-unable_to_find_static_container_for_map",
locationBase.Name
)
); );
} }
@@ -82,12 +93,16 @@ public class LocationLootGenerator(
// Remove christmas items from loot data // Remove christmas items from loot data
if (!_seasonalEventService.ChristmasEventEnabled()) if (!_seasonalEventService.ChristmasEventEnabled())
{ {
allStaticContainersOnMapClone = allStaticContainersOnMapClone.Where(item => !_seasonalEventConfig.ChristmasContainerIds.Contains(item.Template.Id) allStaticContainersOnMapClone = allStaticContainersOnMapClone
.Where(item =>
!_seasonalEventConfig.ChristmasContainerIds.Contains(item.Template.Id)
) )
.ToList(); .ToList();
} }
var staticRandomisableContainersOnMap = GetRandomisableContainersOnMap(allStaticContainersOnMapClone); var staticRandomisableContainersOnMap = GetRandomisableContainersOnMap(
allStaticContainersOnMapClone
);
// Keep track of static loot count // Keep track of static loot count
var staticContainerCount = 0; var staticContainerCount = 0;
@@ -98,13 +113,17 @@ public class LocationLootGenerator(
staticContainerCount += guaranteedContainers.Count; staticContainerCount += guaranteedContainers.Count;
// Add loot to guaranteed containers and add to result // Add loot to guaranteed containers and add to result
foreach (var containerWithLoot in guaranteedContainers.Select(container => AddLootToContainer( foreach (
container, var containerWithLoot in guaranteedContainers.Select(container =>
staticForcedOnMapClone, AddLootToContainer(
staticLootDist.Value, container,
staticAmmoDist, staticForcedOnMapClone,
locationId staticLootDist.Value,
))) staticAmmoDist,
locationId
)
)
)
{ {
result.Add(containerWithLoot.Template); result.Add(containerWithLoot.Template);
@@ -117,8 +136,10 @@ public class LocationLootGenerator(
} }
// Randomisation is turned off globally or just turned off for this map // Randomisation is turned off globally or just turned off for this map
if (!_locationConfig.ContainerRandomisationSettings.Enabled || !_locationConfig.ContainerRandomisationSettings.Maps.ContainsKey(locationId) if (
) !_locationConfig.ContainerRandomisationSettings.Enabled
|| !_locationConfig.ContainerRandomisationSettings.Maps.ContainsKey(locationId)
)
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
@@ -149,15 +170,18 @@ public class LocationLootGenerator(
// Group containers by their groupId // Group containers by their groupId
if (mapData.Statics is null) if (mapData.Statics is null)
{ {
_logger.Warning(_localisationService.GetText("location-unable_to_generate_static_loot", locationId)); _logger.Warning(
_localisationService.GetText("location-unable_to_generate_static_loot", locationId)
);
return result; return result;
} }
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array // For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
var mapping = GetGroupIdToContainerMappings(mapData.Statics, staticRandomisableContainersOnMap); var mapping = GetGroupIdToContainerMappings(
mapData.Statics,
staticRandomisableContainersOnMap
);
foreach (var (key, data) in mapping) foreach (var (key, data) in mapping)
{ {
// Count chosen was 0, skip // Count chosen was 0, skip
@@ -170,7 +194,9 @@ public class LocationLootGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping"); _logger.Debug(
$"Group: {key} has no containers with < 100 % spawn chance to choose from, skipping"
);
} }
continue; continue;
@@ -186,7 +212,9 @@ public class LocationLootGenerator(
{ {
if (_randomUtil.GetChance100(containerIdsCopy[containerId.Key] * 100)) if (_randomUtil.GetChance100(containerIdsCopy[containerId.Key] * 100))
{ {
data.ContainerIdsWithProbability[containerId.Key] = containerIdsCopy[containerId.Key]; data.ContainerIdsWithProbability[containerId.Key] = containerIdsCopy[
containerId.Key
];
} }
} }
@@ -205,7 +233,8 @@ public class LocationLootGenerator(
foreach (var chosenContainerId in chosenContainerIds) foreach (var chosenContainerId in chosenContainerIds)
{ {
// Look up container object from full list of containers on map // Look up container object from full list of containers on map
var containerObject = staticRandomisableContainersOnMap.FirstOrDefault(staticContainer => staticContainer.Template.Id == chosenContainerId var containerObject = staticRandomisableContainersOnMap.FirstOrDefault(
staticContainer => staticContainer.Template.Id == chosenContainerId
); );
if (containerObject is null) if (containerObject is null)
{ {
@@ -236,7 +265,10 @@ public class LocationLootGenerator(
_logger.Success($"A total of: {staticLootItemCount} static items spawned"); _logger.Success($"A total of: {staticLootItemCount} static items spawned");
_logger.Success( _logger.Success(
_localisationService.GetText("location-containers_generated_success", staticContainerCount) _localisationService.GetText(
"location-containers_generated_success",
staticContainerCount
)
); );
return result; return result;
@@ -247,12 +279,15 @@ public class LocationLootGenerator(
/// </summary> /// </summary>
/// <param name="staticContainers"></param> /// <param name="staticContainers"></param>
/// <returns>StaticContainerData array</returns> /// <returns>StaticContainerData array</returns>
protected List<StaticContainerData> GetRandomisableContainersOnMap(List<StaticContainerData> staticContainers) protected List<StaticContainerData> GetRandomisableContainersOnMap(
List<StaticContainerData> staticContainers
)
{ {
return staticContainers.Where(staticContainer => return staticContainers
staticContainer.Probability != 1 && .Where(staticContainer =>
!staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false) && staticContainer.Probability != 1
!_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains( && !staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false)
&& !_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
staticContainer.Template.Items.FirstOrDefault().Template staticContainer.Template.Items.FirstOrDefault().Template
) )
) )
@@ -264,12 +299,15 @@ public class LocationLootGenerator(
/// </summary> /// </summary>
/// <param name="staticContainersOnMap"></param> /// <param name="staticContainersOnMap"></param>
/// <returns>IStaticContainerData array</returns> /// <returns>IStaticContainerData array</returns>
protected List<StaticContainerData> GetGuaranteedContainers(List<StaticContainerData> staticContainersOnMap) protected List<StaticContainerData> GetGuaranteedContainers(
List<StaticContainerData> staticContainersOnMap
)
{ {
return staticContainersOnMap.Where(staticContainer => return staticContainersOnMap
staticContainer.Probability == 1 || .Where(staticContainer =>
staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false) || staticContainer.Probability == 1
_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains( || staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false)
|| _locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
staticContainer.Template.Items.FirstOrDefault().Template staticContainer.Template.Items.FirstOrDefault().Template
) )
) )
@@ -283,7 +321,10 @@ public class LocationLootGenerator(
/// <param name="groupId">Name of the group the containers are being collected for</param> /// <param name="groupId">Name of the group the containers are being collected for</param>
/// <param name="containerData">Containers and probability values for a groupId</param> /// <param name="containerData">Containers and probability values for a groupId</param>
/// <returns>List of chosen container Ids</returns> /// <returns>List of chosen container Ids</returns>
protected List<string> GetContainersByProbability(string groupId, ContainerGroupCount containerData) protected List<string> GetContainersByProbability(
string groupId,
ContainerGroupCount containerData
)
{ {
var chosenContainerIds = new List<string>(); var chosenContainerIds = new List<string>();
@@ -301,15 +342,14 @@ public class LocationLootGenerator(
} }
// Create probability array with all possible container ids in this group and their relative probability of spawning // Create probability array with all possible container ids in this group and their relative probability of spawning
var containerDistribution = var containerDistribution = new ProbabilityObjectArray<string, double>(_mathUtil, _cloner);
new ProbabilityObjectArray<string, double>(_mathUtil, _cloner);
foreach (var x in containerIds) foreach (var x in containerIds)
{ {
var value = containerData.ContainerIdsWithProbability[x]; var value = containerData.ContainerIdsWithProbability[x];
containerDistribution.Add(new ProbabilityObject<string, double>(x, value, value)); 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; return chosenContainerIds;
} }
@@ -322,7 +362,8 @@ public class LocationLootGenerator(
/// <returns>dictionary keyed by groupId</returns> /// <returns>dictionary keyed by groupId</returns>
protected Dictionary<string, ContainerGroupCount> GetGroupIdToContainerMappings( protected Dictionary<string, ContainerGroupCount> GetGroupIdToContainerMappings(
StaticContainer staticContainerGroupData, StaticContainer staticContainerGroupData,
List<StaticContainerData> staticContainersOnMap) List<StaticContainerData> staticContainersOnMap
)
{ {
// Create dictionary of all group ids and choose a count of containers the map will spawn of that group // 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>(); var mapping = new Dictionary<string, ContainerGroupCount>();
@@ -332,31 +373,45 @@ public class LocationLootGenerator(
{ {
ContainerIdsWithProbability = new Dictionary<string, double>(), ContainerIdsWithProbability = new Dictionary<string, double>(),
ChosenCount = _randomUtil.GetInt( ChosenCount = _randomUtil.GetInt(
(int) Math.Round( (int)
groupKvP.Value.MinContainers.Value * Math.Round(
_locationConfig.ContainerRandomisationSettings.ContainerGroupMinSizeMultiplier groupKvP.Value.MinContainers.Value
), * _locationConfig
(int) Math.Round( .ContainerRandomisationSettings
groupKvP.Value.MaxContainers.Value * .ContainerGroupMinSizeMultiplier
_locationConfig.ContainerRandomisationSettings.ContainerGroupMaxSizeMultiplier ),
) (int)
) Math.Round(
groupKvP.Value.MaxContainers.Value
* _locationConfig
.ContainerRandomisationSettings
.ContainerGroupMaxSizeMultiplier
)
),
}; };
} }
// Add an empty group for containers without a group id but still have a < 100% chance to spawn // 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` // 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.Add(string.Empty, new ContainerGroupCount mapping.Add(
{ string.Empty,
ContainerIdsWithProbability = new Dictionary<string, double>(), new ContainerGroupCount
ChosenCount = -1 {
}); ContainerIdsWithProbability = new Dictionary<string, double>(),
ChosenCount = -1,
}
);
// Iterate over all containers and add to group keyed by groupId // Iterate over all containers and add to group keyed by groupId
// Containers without a group go into a group with empty key "" // Containers without a group go into a group with empty key ""
foreach (var container in staticContainersOnMap) foreach (var container in staticContainersOnMap)
{ {
if (!staticContainerGroupData.Containers.TryGetValue(container.Template.Id, out var groupData)) if (
!staticContainerGroupData.Containers.TryGetValue(
container.Template.Id,
out var groupData
)
)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
@@ -385,10 +440,14 @@ public class LocationLootGenerator(
new ContainerGroupCount new ContainerGroupCount
{ {
ChosenCount = 0d, ChosenCount = 0d,
ContainerIdsWithProbability = new Dictionary<string, double>() ContainerIdsWithProbability = new Dictionary<string, double>(),
} }
); );
mapping[groupData.GroupId].ContainerIdsWithProbability.TryAdd(container.Template.Id, container.Probability.Value); mapping[groupData.GroupId]
.ContainerIdsWithProbability.TryAdd(
container.Template.Id,
container.Probability.Value
);
} }
return mapping; return mapping;
@@ -404,7 +463,8 @@ public class LocationLootGenerator(
/// <param name="staticAmmoDist">staticAmmo.json</param> /// <param name="staticAmmoDist">staticAmmo.json</param>
/// <param name="locationName">Name of the map to generate static loot for</param> /// <param name="locationName">Name of the map to generate static loot for</param>
/// <returns>StaticContainerData</returns> /// <returns>StaticContainerData</returns>
protected StaticContainerData AddLootToContainer(StaticContainerData staticContainer, protected StaticContainerData AddLootToContainer(
StaticContainerData staticContainer,
List<StaticForced>? staticForced, List<StaticForced>? staticForced,
Dictionary<string, StaticLootDetails> staticLootDist, Dictionary<string, StaticLootDetails> staticLootDist,
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
@@ -422,7 +482,11 @@ public class LocationLootGenerator(
var containerMap = _itemHelper.GetContainerMapping(containerTpl); var containerMap = _itemHelper.GetContainerMapping(containerTpl);
// Choose count of items to add to container // Choose count of items to add to container
var itemCountToAdd = GetWeightedCountOfContainerItems(containerTpl, staticLootDist, locationName); var itemCountToAdd = GetWeightedCountOfContainerItems(
containerTpl,
staticLootDist,
locationName
);
if (itemCountToAdd == 0) if (itemCountToAdd == 0)
{ {
return containerClone; return containerClone;
@@ -468,11 +532,15 @@ public class LocationLootGenerator(
: chosenItemWithChildren.Items; : chosenItemWithChildren.Items;
// look for open slot to put chosen item into // look for open slot to put chosen item into
var result = _containerHelper.FindSlotForItem(containerMap, chosenItemWithChildren.Width, chosenItemWithChildren.Height); var result = _containerHelper.FindSlotForItem(
containerMap,
chosenItemWithChildren.Width,
chosenItemWithChildren.Height
);
if (!result.Success.GetValueOrDefault(false)) if (!result.Success.GetValueOrDefault(false))
{ {
if (failedToFitAttemptCount > _locationConfig.FitLootIntoContainerAttempts) if (failedToFitAttemptCount > _locationConfig.FitLootIntoContainerAttempts)
// x attempts to fit an item, container is probably full, stop trying to add more // x attempts to fit an item, container is probably full, stop trying to add more
{ {
break; break;
} }
@@ -499,7 +567,9 @@ public class LocationLootGenerator(
{ {
X = result.X, X = result.X,
Y = result.Y, Y = result.Y,
R = result.Rotation.GetValueOrDefault(false) ? ItemRotation.Vertical : ItemRotation.Horizontal R = result.Rotation.GetValueOrDefault(false)
? ItemRotation.Vertical
: ItemRotation.Horizontal,
}; };
// Add loot to container before returning // Add loot to container before returning
@@ -516,23 +586,21 @@ public class LocationLootGenerator(
/// <param name="staticLootDist">staticLoot.json</param> /// <param name="staticLootDist">staticLoot.json</param>
/// <param name="locationName">Map name (to get per-map multiplier for from config)</param> /// <param name="locationName">Map name (to get per-map multiplier for from config)</param>
/// <returns>item count</returns> /// <returns>item count</returns>
protected int GetWeightedCountOfContainerItems(string containerTypeId, protected int GetWeightedCountOfContainerItems(
Dictionary<string, StaticLootDetails> staticLootDist, string locationName) string containerTypeId,
Dictionary<string, StaticLootDetails> staticLootDist,
string locationName
)
{ {
// Create probability array to calculate the total count of lootable items inside container // Create probability array to calculate the total count of lootable items inside container
var itemCountArray = var itemCountArray = new ProbabilityObjectArray<int, float?>(_mathUtil, _cloner);
new ProbabilityObjectArray<int, float?>(_mathUtil, _cloner);
var countDistribution = staticLootDist[containerTypeId]?.ItemCountDistribution; var countDistribution = staticLootDist[containerTypeId]?.ItemCountDistribution;
if (countDistribution is null) if (countDistribution is null)
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText( _localisationService.GetText(
"location-unable_to_find_count_distribution_for_container", "location-unable_to_find_count_distribution_for_container",
new new { containerId = containerTypeId, locationName }
{
containerId = containerTypeId,
locationName
}
) )
); );
@@ -551,7 +619,8 @@ public class LocationLootGenerator(
); );
} }
return (int) Math.Round(GetStaticLootMultiplierForLocation(locationName) * itemCountArray.Draw()[0]); return (int)
Math.Round(GetStaticLootMultiplierForLocation(locationName) * itemCountArray.Draw()[0]);
} }
/// <summary> /// <summary>
@@ -563,18 +632,23 @@ public class LocationLootGenerator(
/// <returns>ProbabilityObjectArray of item tpls + probability</returns> /// <returns>ProbabilityObjectArray of item tpls + probability</returns>
protected ProbabilityObjectArray<string, float?> GetPossibleLootItemsForContainer( protected ProbabilityObjectArray<string, float?> GetPossibleLootItemsForContainer(
string containerTypeId, string containerTypeId,
Dictionary<string, StaticLootDetails> staticLootDist) Dictionary<string, StaticLootDetails> staticLootDist
)
{ {
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled(); var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems(); var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
var itemDistribution = var itemDistribution = new ProbabilityObjectArray<string, float?>(_mathUtil, _cloner);
new ProbabilityObjectArray<string, float?>(_mathUtil, _cloner);
var itemContainerDistribution = staticLootDist[containerTypeId]?.ItemDistribution; var itemContainerDistribution = staticLootDist[containerTypeId]?.ItemDistribution;
if (itemContainerDistribution is null) if (itemContainerDistribution is null)
{ {
_logger.Warning(_localisationService.GetText("location-missing_item_distribution_data", containerTypeId)); _logger.Warning(
_localisationService.GetText(
"location-missing_item_distribution_data",
containerTypeId
)
);
return itemDistribution; return itemDistribution;
} }
@@ -586,14 +660,16 @@ public class LocationLootGenerator(
// Skip seasonal event items if they're not enabled // Skip seasonal event items if they're not enabled
continue; continue;
} }
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl)) if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl))
{ {
// Ensure no blacklisted lootable items are in pool // Ensure no blacklisted lootable items are in pool
continue; continue;
} }
itemDistribution.Add(new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null)); itemDistribution.Add(
new ProbabilityObject<string, float?>(icd.Tpl, icd.RelativeProbability.Value, null)
);
} }
return itemDistribution; return itemDistribution;
@@ -620,9 +696,11 @@ public class LocationLootGenerator(
/// <param name="staticAmmoDist"></param> /// <param name="staticAmmoDist"></param>
/// <param name="locationName">Location to generate loot for</param> /// <param name="locationName">Location to generate loot for</param>
/// <returns>Array of spawn points with loot in them</returns> /// <returns>Array of spawn points with loot in them</returns>
public List<SpawnpointTemplate> GenerateDynamicLoot(LooseLoot dynamicLootDist, public List<SpawnpointTemplate> GenerateDynamicLoot(
LooseLoot dynamicLootDist,
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
string locationName) string locationName
)
{ {
List<SpawnpointTemplate> loot = []; List<SpawnpointTemplate> loot = [];
List<Spawnpoint> dynamicForcedSpawnPoints = []; List<Spawnpoint> dynamicForcedSpawnPoints = [];
@@ -630,11 +708,13 @@ public class LocationLootGenerator(
// Remove christmas items from loot data // Remove christmas items from loot data
if (!_seasonalEventService.ChristmasEventEnabled()) if (!_seasonalEventService.ChristmasEventEnabled())
{ {
dynamicLootDist.Spawnpoints = dynamicLootDist.Spawnpoints.Where(point => dynamicLootDist.Spawnpoints = dynamicLootDist
.Spawnpoints.Where(point =>
!point.Template.Id.StartsWith("christmas", StringComparison.OrdinalIgnoreCase) !point.Template.Id.StartsWith("christmas", StringComparison.OrdinalIgnoreCase)
) )
.ToList(); .ToList();
dynamicLootDist.SpawnpointsForced = dynamicLootDist.SpawnpointsForced.Where(point => dynamicLootDist.SpawnpointsForced = dynamicLootDist
.SpawnpointsForced.Where(point =>
!point.Template.Id.StartsWith("christmas", StringComparison.OrdinalIgnoreCase) !point.Template.Id.StartsWith("christmas", StringComparison.OrdinalIgnoreCase)
) )
.ToList(); .ToList();
@@ -642,7 +722,9 @@ public class LocationLootGenerator(
// Build the list of forced loot from both `spawnpointsForced` and any point marked `IsAlwaysSpawn` // Build the list of forced loot from both `spawnpointsForced` and any point marked `IsAlwaysSpawn`
dynamicForcedSpawnPoints.AddRange(dynamicLootDist.SpawnpointsForced); 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 // Add forced loot
AddForcedLoot(loot, dynamicForcedSpawnPoints, locationName, staticAmmoDist); AddForcedLoot(loot, dynamicForcedSpawnPoints, locationName, staticAmmoDist);
@@ -651,16 +733,19 @@ public class LocationLootGenerator(
// Draw from random distribution // Draw from random distribution
var desiredSpawnPointCount = Math.Round( var desiredSpawnPointCount = Math.Round(
GetLooseLootMultiplierForLocation(locationName) * _randomUtil.GetNormallyDistributedRandomNumber( GetLooseLootMultiplierForLocation(locationName)
(double) dynamicLootDist.SpawnpointCount.Mean, * _randomUtil.GetNormallyDistributedRandomNumber(
(double) dynamicLootDist.SpawnpointCount.Std (double)dynamicLootDist.SpawnpointCount.Mean,
) (double)dynamicLootDist.SpawnpointCount.Std
)
); );
// Positions not in forced but have 100% chance to spawn // Positions not in forced but have 100% chance to spawn
List<Spawnpoint> guaranteedLoosePoints = []; List<Spawnpoint> guaranteedLoosePoints = [];
var blacklistedSpawnPoints = _locationConfig.LooseLootBlacklist.GetValueOrDefault(locationName); var blacklistedSpawnPoints = _locationConfig.LooseLootBlacklist.GetValueOrDefault(
locationName
);
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner); var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
foreach (var spawnPoint in allDynamicSpawnPoints) foreach (var spawnPoint in allDynamicSpawnPoints)
@@ -689,7 +774,13 @@ public class LocationLootGenerator(
continue; continue;
} }
spawnPointArray.Add(new ProbabilityObject<string, Spawnpoint>(spawnPoint.Template.Id, spawnPoint.Probability ?? 0, spawnPoint)); spawnPointArray.Add(
new ProbabilityObject<string, Spawnpoint>(
spawnPoint.Template.Id,
spawnPoint.Probability ?? 0,
spawnPoint
)
);
} }
// Select a number of spawn points to add loot to // Select a number of spawn points to add loot to
@@ -700,16 +791,19 @@ public class LocationLootGenerator(
var randomSpawnPointCount = desiredSpawnPointCount - chosenSpawnPoints.Count; var randomSpawnPointCount = desiredSpawnPointCount - chosenSpawnPoints.Count;
// Only draw random spawn points if needed // Only draw random spawn points if needed
if (randomSpawnPointCount > 0 && spawnPointArray.Count > 0) if (randomSpawnPointCount > 0 && spawnPointArray.Count > 0)
// Add randomly chosen spawn points // 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)); chosenSpawnPoints.Add(spawnPointArray.Data(si));
} }
} }
// Filter out duplicate locationIds // prob can be done better // Filter out duplicate locationIds // prob can be done better
chosenSpawnPoints = chosenSpawnPoints.GroupBy(spawnPoint => spawnPoint.LocationId).Select(group => group.First()).ToList(); chosenSpawnPoints = chosenSpawnPoints
.GroupBy(spawnPoint => spawnPoint.LocationId)
.Select(group => group.First())
.ToList();
// Do we have enough items in pool to fulfill requirement // Do we have enough items in pool to fulfill requirement
var tooManySpawnPointsRequested = desiredSpawnPointCount - chosenSpawnPoints.Count > 0; var tooManySpawnPointsRequested = desiredSpawnPointCount - chosenSpawnPoints.Count > 0;
@@ -724,7 +818,7 @@ public class LocationLootGenerator(
{ {
requested = desiredSpawnPointCount + guaranteedLoosePoints.Count, requested = desiredSpawnPointCount + guaranteedLoosePoints.Count,
found = chosenSpawnPoints.Count, found = chosenSpawnPoints.Count,
mapName = locationName mapName = locationName,
} }
) )
); );
@@ -740,22 +834,27 @@ public class LocationLootGenerator(
if (spawnPoint.Template is null) if (spawnPoint.Template is null)
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText("location-missing_dynamic_template", spawnPoint.LocationId) _localisationService.GetText(
"location-missing_dynamic_template",
spawnPoint.LocationId
)
); );
continue; continue;
} }
// Ensure no blacklisted lootable items are in pool // Ensure no blacklisted lootable items are in pool
spawnPoint.Template.Items = spawnPoint.Template.Items.Where(item => !_itemFilterService.IsLootableItemBlacklisted(item.Template) spawnPoint.Template.Items = spawnPoint
.Template.Items.Where(item =>
!_itemFilterService.IsLootableItemBlacklisted(item.Template)
) )
.ToList(); .ToList();
// Ensure no seasonal items are in pool if not in-season // Ensure no seasonal items are in pool if not in-season
if (!seasonalEventActive) if (!seasonalEventActive)
{ {
spawnPoint.Template.Items = spawnPoint.Template.Items.Where(item => !seasonalItemTplBlacklist.Contains(item.Template) spawnPoint.Template.Items = spawnPoint
) .Template.Items.Where(item => !seasonalItemTplBlacklist.Contains(item.Template))
.ToList(); .ToList();
} }
@@ -765,7 +864,10 @@ public class LocationLootGenerator(
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug( _logger.Debug(
_localisationService.GetText("location-spawnpoint_missing_items", spawnPoint.Template.Id) _localisationService.GetText(
"location-spawnpoint_missing_items",
spawnPoint.Template.Id
)
); );
} }
@@ -784,13 +886,22 @@ public class LocationLootGenerator(
continue; continue;
} }
itemArray.Add(new ProbabilityObject<string, double?>(itemDist.ComposedKey.Key, itemDist.RelativeProbability ?? 0, null)); itemArray.Add(
new ProbabilityObject<string, double?>(
itemDist.ComposedKey.Key,
itemDist.RelativeProbability ?? 0,
null
)
);
} }
if (itemArray.Count == 0) if (itemArray.Count == 0)
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText("location-loot_pool_is_empty_skipping", spawnPoint.Template.Id) _localisationService.GetText(
"location-loot_pool_is_empty_skipping",
spawnPoint.Template.Id
)
); );
continue; continue;
@@ -822,44 +933,65 @@ public class LocationLootGenerator(
/// <param name="lootLocationTemplates">List to add forced loot spawn locations to</param> /// <param name="lootLocationTemplates">List to add forced loot spawn locations to</param>
/// <param name="forcedSpawnPoints">Forced loot locations that must be added</param> /// <param name="forcedSpawnPoints">Forced loot locations that must be added</param>
/// <param name="locationName">Name of map currently having force loot created for</param> /// <param name="locationName">Name of map currently having force loot created for</param>
protected void AddForcedLoot(List<SpawnpointTemplate> lootLocationTemplates, protected void AddForcedLoot(
List<Spawnpoint> forcedSpawnPoints, string locationName, List<SpawnpointTemplate> lootLocationTemplates,
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist) List<Spawnpoint> forcedSpawnPoints,
string locationName,
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist
)
{ {
var lootToForceSingleAmountOnMap = _locationConfig.ForcedLootSingleSpawnById.GetValueOrDefault(locationName); var lootToForceSingleAmountOnMap =
_locationConfig.ForcedLootSingleSpawnById.GetValueOrDefault(locationName);
if (lootToForceSingleAmountOnMap is not null) if (lootToForceSingleAmountOnMap is not null)
// Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map // Process loot items defined as requiring only 1 spawn position as they appear in multiple positions on the map
{ {
foreach (var itemTpl in lootToForceSingleAmountOnMap) foreach (var itemTpl in lootToForceSingleAmountOnMap)
{ {
// Get all spawn positions for item tpl in forced loot array // Get all spawn positions for item tpl in forced loot array
var items = forcedSpawnPoints.Where(forcedSpawnPoint => forcedSpawnPoint.Template.Items.FirstOrDefault().Template == itemTpl); var items = forcedSpawnPoints.Where(forcedSpawnPoint =>
forcedSpawnPoint.Template.Items.FirstOrDefault().Template == itemTpl
);
if (!items.Any()) if (!items.Any())
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Unable to adjust loot item {itemTpl} as it does not exist inside {locationName} forced loot."); _logger.Debug(
$"Unable to adjust loot item {itemTpl} as it does not exist inside {locationName} forced loot."
);
} }
continue; continue;
} }
// Create probability array of all spawn positions for this spawn id // Create probability array of all spawn positions for this spawn id
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner); var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(
_mathUtil,
_cloner
);
foreach (var si in items) foreach (var si in items)
// use locationId as template.Id is the same across all items // use locationId as template.Id is the same across all items
{ {
spawnPointArray.Add(new ProbabilityObject<string, Spawnpoint>(si.LocationId, si.Probability ?? 0, si)); 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 // Choose 1 out of all found spawn positions for spawn id and add to loot array
foreach (var spawnPointLocationId in spawnPointArray.Draw(1, false)) 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; var lootItem = itemToAdd?.Template;
if (lootItem is null) if (lootItem is null)
{ {
_logger.Warning($"Item with spawn point id {spawnPointLocationId} could not be found, skipping"); _logger.Warning(
$"Item with spawn point id {spawnPointLocationId} could not be found, skipping"
);
continue; continue;
} }
@@ -909,7 +1041,8 @@ public class LocationLootGenerator(
forcedLootLocation.Template.Items = createItemResult.Items; forcedLootLocation.Template.Items = createItemResult.Items;
// Push forced location into array as long as it doesnt exist already // Push forced location into array as long as it doesnt exist already
var existingLocation = lootLocationTemplates.Any(spawnPoint => spawnPoint.Id == locationTemplateToAdd.Id var existingLocation = lootLocationTemplates.Any(spawnPoint =>
spawnPoint.Id == locationTemplateToAdd.Id
); );
if (!existingLocation) if (!existingLocation)
{ {
@@ -934,13 +1067,19 @@ public class LocationLootGenerator(
/// <param name="items"> Location loot Template </param> /// <param name="items"> Location loot Template </param>
/// <param name="staticAmmoDist"> Ammo distributions </param> /// <param name="staticAmmoDist"> Ammo distributions </param>
/// <returns> ContainerItem object </returns> /// <returns> ContainerItem object </returns>
protected ContainerItem CreateDynamicLootItem(string? chosenComposedKey, List<Item> items, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist) protected 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; var chosenTpl = chosenItem?.Template;
if (chosenTpl is null) if (chosenTpl is null)
{ {
throw new Exception($"Item for tpl {chosenComposedKey} was not found in the spawn point"); throw new Exception(
$"Item for tpl {chosenComposedKey} was not found in the spawn point"
);
} }
var itemTemplate = _itemHelper.GetItem(chosenTpl).Value; var itemTemplate = _itemHelper.GetItem(chosenTpl).Value;
@@ -958,48 +1097,34 @@ public class LocationLootGenerator(
var stackCount = var stackCount =
itemTemplate.Properties.StackMaxSize == 1 itemTemplate.Properties.StackMaxSize == 1
? 1 ? 1
: _randomUtil.GetInt(itemTemplate.Properties.StackMinRandom.Value, itemTemplate.Properties.StackMaxRandom.Value); : _randomUtil.GetInt(
itemTemplate.Properties.StackMinRandom.Value,
itemTemplate.Properties.StackMaxRandom.Value
);
itemWithMods.Add( itemWithMods.Add(
new Item new Item
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = chosenTpl, Template = chosenTpl,
Upd = new Upd Upd = new Upd { StackObjectsCount = stackCount },
{
StackObjectsCount = stackCount
}
} }
); );
} }
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX)) else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
{ {
// Fill with cartridges // Fill with cartridges
List<Item> ammoBoxItem = List<Item> ammoBoxItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
[
new()
{
Id = _hashUtil.Generate(),
Template = chosenTpl
}
];
_itemHelper.AddCartridgesToAmmoBox(ammoBoxItem, itemTemplate); _itemHelper.AddCartridgesToAmmoBox(ammoBoxItem, itemTemplate);
itemWithMods.AddRange(ammoBoxItem); itemWithMods.AddRange(ammoBoxItem);
} }
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MAGAZINE)) else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MAGAZINE))
{ {
// Create array with just magazine // Create array with just magazine
List<Item> magazineItem = List<Item> magazineItem = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
[
new()
{
Id = _hashUtil.Generate(),
Template = chosenTpl
}
];
if (_randomUtil.GetChance100(_locationConfig.StaticMagazineLootHasAmmoChancePercent)) if (_randomUtil.GetChance100(_locationConfig.StaticMagazineLootHasAmmoChancePercent))
// Add randomised amount of cartridges // Add randomised amount of cartridges
{ {
_itemHelper.FillMagazineWithRandomCartridge( _itemHelper.FillMagazineWithRandomCartridge(
magazineItem, magazineItem,
@@ -1022,7 +1147,7 @@ public class LocationLootGenerator(
itemWithChildren = _itemHelper.ReplaceIDs(_cloner.Clone(itemWithChildren)); itemWithChildren = _itemHelper.ReplaceIDs(_cloner.Clone(itemWithChildren));
if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template)) if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template))
// Strip children from parent before adding // Strip children from parent before adding
{ {
itemWithChildren = [itemWithChildren.FirstOrDefault()]; itemWithChildren = [itemWithChildren.FirstOrDefault()];
} }
@@ -1037,16 +1162,16 @@ public class LocationLootGenerator(
{ {
Items = itemWithMods, Items = itemWithMods,
Width = size.Width, Width = size.Width,
Height = size.Height Height = size.Height,
}; };
} }
// TODO: rewrite, BIG yikes // TODO: rewrite, BIG yikes
protected ContainerItem? CreateStaticLootItem( protected ContainerItem? CreateStaticLootItem(
string chosenTpl, string chosenTpl,
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
string? parentId = null) string? parentId = null
)
{ {
var itemTemplate = _itemHelper.GetItem(chosenTpl).Value; var itemTemplate = _itemHelper.GetItem(chosenTpl).Value;
if (itemTemplate.Properties is null) if (itemTemplate.Properties is null)
@@ -1058,14 +1183,7 @@ public class LocationLootGenerator(
var width = itemTemplate.Properties.Width; var width = itemTemplate.Properties.Width;
var height = itemTemplate.Properties.Height; var height = itemTemplate.Properties.Height;
List<Item> items = List<Item> items = [new() { Id = _hashUtil.Generate(), Template = chosenTpl }];
[
new()
{
Id = _hashUtil.Generate(),
Template = chosenTpl
}
];
var rootItem = items.FirstOrDefault(); var rootItem = items.FirstOrDefault();
// Use passed in parentId as override for new item // Use passed in parentId as override for new item
@@ -1075,19 +1193,20 @@ public class LocationLootGenerator(
} }
if ( if (
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MONEY) || _itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.MONEY)
_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO) || _itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO)
) )
{ {
// Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked // Edge case - some ammos e.g. flares or M406 grenades shouldn't be stacked
var stackCount = itemTemplate.Properties.StackMaxSize == 1 var stackCount =
? 1 itemTemplate.Properties.StackMaxSize == 1
: _randomUtil.GetInt(itemTemplate.Properties.StackMinRandom.Value, itemTemplate.Properties.StackMaxRandom.Value); ? 1
: _randomUtil.GetInt(
itemTemplate.Properties.StackMinRandom.Value,
itemTemplate.Properties.StackMaxRandom.Value
);
rootItem.Upd = new Upd rootItem.Upd = new Upd { StackObjectsCount = stackCount };
{
StackObjectsCount = stackCount
};
} }
// No spawn point, use default template // No spawn point, use default template
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.WEAPON)) else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.WEAPON))
@@ -1120,11 +1239,16 @@ public class LocationLootGenerator(
{ {
Items = items, Items = items,
Width = width, Width = width,
Height = height Height = height,
}; };
} }
protected List<Item> GetArmorItems(string chosenTpl, Item? rootItem, List<Item> items, TemplateItem armorDbTemplate) protected List<Item> GetArmorItems(
string chosenTpl,
Item? rootItem,
List<Item> items,
TemplateItem armorDbTemplate
)
{ {
var defaultPreset = _presetHelper.GetDefaultPreset(chosenTpl); var defaultPreset = _presetHelper.GetDefaultPreset(chosenTpl);
if (defaultPreset is not null) if (defaultPreset is not null)
@@ -1163,9 +1287,10 @@ public class LocationLootGenerator(
/// <returns>Root Item</returns> /// <returns>Root Item</returns>
protected Item? CreateWeaponRootAndChildren( protected Item? CreateWeaponRootAndChildren(
string chosenTpl, string chosenTpl,
Dictionary<string,List<StaticAmmoDetails>> cartridgePool, Dictionary<string, List<StaticAmmoDetails>> cartridgePool,
string? parentId, string? parentId,
ref List<Item> items) ref List<Item> items
)
{ {
List<Item> children = []; List<Item> children = [];
@@ -1175,7 +1300,10 @@ public class LocationLootGenerator(
{ {
try try
{ {
children = _itemHelper.ReparentItemAndChildren(defaultPreset.Items.FirstOrDefault(), defaultPreset.Items); children = _itemHelper.ReparentItemAndChildren(
defaultPreset.Items.FirstOrDefault(),
defaultPreset.Items
);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -1190,7 +1318,7 @@ public class LocationLootGenerator(
tpl = chosenTpl, tpl = chosenTpl,
defaultId = defaultPreset.Id, defaultId = defaultPreset.Id,
defaultName = defaultPreset.Name, defaultName = defaultPreset.Name,
parentId parentId,
} }
) )
); );
@@ -1214,11 +1342,7 @@ public class LocationLootGenerator(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"location-missing_root_item", "location-missing_root_item",
new new { tpl = chosenTpl, parentId }
{
tpl = chosenTpl,
parentId
}
) )
); );
@@ -1237,11 +1361,7 @@ public class LocationLootGenerator(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"location-unable_to_reparent_item", "location-unable_to_reparent_item",
new new { tpl = chosenTpl, parentId }
{
tpl = chosenTpl,
parentId
}
) )
); );
_logger.Error(e.StackTrace); _logger.Error(e.StackTrace);
@@ -1285,7 +1405,8 @@ public class LocationLootGenerator(
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
Item? rootItem, Item? rootItem,
TemplateItem itemTemplate, TemplateItem itemTemplate,
List<Item> items) List<Item> items
)
{ {
List<Item> magazineWithCartridges = [rootItem]; List<Item> magazineWithCartridges = [rootItem];
_itemHelper.FillMagazineWithRandomCartridge( _itemHelper.FillMagazineWithRandomCartridge(
@@ -1305,40 +1426,20 @@ public class LocationLootGenerator(
public record ContainerGroupCount public record ContainerGroupCount
{ {
[JsonPropertyName("containerIdsWithProbability")] [JsonPropertyName("containerIdsWithProbability")]
public Dictionary<string, double>? ContainerIdsWithProbability public Dictionary<string, double>? ContainerIdsWithProbability { get; set; }
{
get;
set;
}
[JsonPropertyName("chosenCount")] [JsonPropertyName("chosenCount")]
public double? ChosenCount public double? ChosenCount { get; set; }
{
get;
set;
}
} }
public class ContainerItem public class ContainerItem
{ {
[JsonPropertyName("items")] [JsonPropertyName("items")]
public List<Item>? Items public List<Item>? Items { get; set; }
{
get;
set;
}
[JsonPropertyName("width")] [JsonPropertyName("width")]
public int? Width public int? Width { get; set; }
{
get;
set;
}
[JsonPropertyName("height")] [JsonPropertyName("height")]
public int? Height public int? Height { get; set; }
{
get;
set;
}
} }
@@ -58,18 +58,16 @@ public class LootGenerator(
{ {
// Choose one at random + add to results array // Choose one at random + add to results array
var chosenSealedContainer = _randomUtil.GetArrayValue(sealedWeaponContainerPool); var chosenSealedContainer = _randomUtil.GetArrayValue(sealedWeaponContainerPool);
result.Add([ result.Add(
new Item [
{ new Item
Id = _hashUtil.Generate(),
Template = chosenSealedContainer.Id,
Upd = new Upd
{ {
StackObjectsCount = 1, Id = _hashUtil.Generate(),
SpawnedInSession = true Template = chosenSealedContainer.Id,
} Upd = new Upd { StackObjectsCount = 1, SpawnedInSession = true },
} },
]); ]
);
} }
} }
@@ -85,11 +83,21 @@ public class LootGenerator(
// Pool has items we could add as loot, proceed // Pool has items we could add as loot, proceed
if (rewardPoolResults.ItemPool.Count > 0) if (rewardPoolResults.ItemPool.Count > 0)
{ {
var randomisedItemCount = _randomUtil.GetInt(options.ItemCount.Min, options.ItemCount.Max); var randomisedItemCount = _randomUtil.GetInt(
options.ItemCount.Min,
options.ItemCount.Max
);
for (var index = 0; index < randomisedItemCount; index++) for (var index = 0; index < randomisedItemCount; index++)
{ {
if (!FindAndAddRandomItemToLoot(rewardPoolResults.ItemPool, itemTypeCounts, options, result)) if (
// Failed to add, reduce index so we get another attempt !FindAndAddRandomItemToLoot(
rewardPoolResults.ItemPool,
itemTypeCounts,
options,
result
)
)
// Failed to add, reduce index so we get another attempt
{ {
index--; index--;
} }
@@ -105,9 +113,8 @@ public class LootGenerator(
); );
if (randomisedWeaponPresetCount > 0) if (randomisedWeaponPresetCount > 0)
{ {
var weaponDefaultPresets = globalDefaultPresets.Where(preset => var weaponDefaultPresets = globalDefaultPresets
_itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON) .Where(preset => _itemHelper.IsOfBaseclass(preset.Encyclopedia, BaseClasses.WEAPON))
)
.ToList(); .ToList();
if (weaponDefaultPresets.Any()) if (weaponDefaultPresets.Any())
@@ -115,14 +122,14 @@ public class LootGenerator(
for (var index = 0; index < randomisedWeaponPresetCount; index++) for (var index = 0; index < randomisedWeaponPresetCount; index++)
{ {
if ( if (
!FindAndAddRandomPresetToLoot( !FindAndAddRandomPresetToLoot(
weaponDefaultPresets, weaponDefaultPresets,
itemTypeCounts, itemTypeCounts,
rewardPoolResults.Blacklist, rewardPoolResults.Blacklist,
result result
)
) )
// Failed to add, reduce index so we get another attempt )
// Failed to add, reduce index so we get another attempt
{ {
index--; index--;
} }
@@ -140,9 +147,8 @@ public class LootGenerator(
var armorDefaultPresets = globalDefaultPresets.Where(preset => var armorDefaultPresets = globalDefaultPresets.Where(preset =>
_itemHelper.ArmorItemCanHoldMods(preset.Encyclopedia) _itemHelper.ArmorItemCanHoldMods(preset.Encyclopedia)
); );
var levelFilteredArmorPresets = armorDefaultPresets.Where(armor => var levelFilteredArmorPresets = armorDefaultPresets
IsArmorOfDesiredProtectionLevel(armor, options) .Where(armor => IsArmorOfDesiredProtectionLevel(armor, options))
)
.ToList(); .ToList();
// Add some armors to rewards // Add some armors to rewards
@@ -151,14 +157,14 @@ public class LootGenerator(
for (var index = 0; index < randomisedArmorPresetCount; index++) for (var index = 0; index < randomisedArmorPresetCount; index++)
{ {
if ( if (
!FindAndAddRandomPresetToLoot( !FindAndAddRandomPresetToLoot(
levelFilteredArmorPresets, levelFilteredArmorPresets,
itemTypeCounts, itemTypeCounts,
rewardPoolResults.Blacklist, rewardPoolResults.Blacklist,
result result
)
) )
// Failed to add, reduce index so we get another attempt )
// Failed to add, reduce index so we get another attempt
{ {
index--; index--;
} }
@@ -195,7 +201,9 @@ public class LootGenerator(
for (var i = 0; i < randomisedItemCount; i++) for (var i = 0; i < randomisedItemCount; i++)
{ {
// Clone preset and alter Ids to be unique // Clone preset and alter Ids to be unique
var presetWithUniqueIds = _itemHelper.ReplaceIDs(_cloner.Clone(preset.Items)); var presetWithUniqueIds = _itemHelper.ReplaceIDs(
_cloner.Clone(preset.Items)
);
// Add to results // Add to results
result.Add(presetWithUniqueIds); result.Add(presetWithUniqueIds);
@@ -203,7 +211,6 @@ public class LootGenerator(
} }
continue; continue;
} }
// Non-preset item to be added // Non-preset item to be added
@@ -211,11 +218,7 @@ public class LootGenerator(
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = itemTpl, Template = itemTpl,
Upd = new Upd Upd = new Upd { StackObjectsCount = randomisedItemCount, SpawnedInSession = true },
{
StackObjectsCount = randomisedItemCount,
SpawnedInSession = true
}
}; };
var splitResults = _itemHelper.SplitStack(newLootItem); var splitResults = _itemHelper.SplitStack(newLootItem);
foreach (var splitItem in splitResults) foreach (var splitItem in splitResults)
@@ -242,11 +245,12 @@ public class LootGenerator(
List<string> itemTypeWhitelist, List<string> itemTypeWhitelist,
bool useRewardItemBlacklist, bool useRewardItemBlacklist,
bool allowBossItems, bool allowBossItems,
bool blockSeasonalItemsOutOfSeason) bool blockSeasonalItemsOutOfSeason
)
{ {
var itemsDb = _databaseService.GetItems().Values; var itemsDb = _databaseService.GetItems().Values;
var itemBlacklist = new HashSet<string>(); var itemBlacklist = new HashSet<string>();
itemBlacklist.UnionWith([.._itemFilterService.GetBlacklistedItems(), ..itemTplBlacklist]); itemBlacklist.UnionWith([.. _itemFilterService.GetBlacklistedItems(), .. itemTplBlacklist]);
if (useRewardItemBlacklist) if (useRewardItemBlacklist)
{ {
@@ -256,10 +260,12 @@ public class LootGenerator(
var itemTypeBlacklist = _itemFilterService.GetItemRewardBaseTypeBlacklist(); var itemTypeBlacklist = _itemFilterService.GetItemRewardBaseTypeBlacklist();
var itemsMatchingTypeBlacklist = itemsDb var itemsMatchingTypeBlacklist = itemsDb
.Where(templateItem => !string.IsNullOrEmpty(templateItem.Parent)) // Ignore items without parents .Where(templateItem => !string.IsNullOrEmpty(templateItem.Parent)) // Ignore items without parents
.Where(templateItem => _itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist)) .Where(templateItem =>
_itemHelper.IsOfBaseclasses(templateItem.Parent, itemTypeBlacklist)
)
.Select(templateItem => templateItem.Id); .Select(templateItem => templateItem.Id);
itemBlacklist.UnionWith([..rewardItemBlacklist, ..itemsMatchingTypeBlacklist]); itemBlacklist.UnionWith([.. rewardItemBlacklist, .. itemsMatchingTypeBlacklist]);
} }
if (!allowBossItems) if (!allowBossItems)
@@ -272,19 +278,16 @@ public class LootGenerator(
itemBlacklist.UnionWith(_seasonalEventService.GetInactiveSeasonalEventItems()); itemBlacklist.UnionWith(_seasonalEventService.GetInactiveSeasonalEventItems());
} }
var items = itemsDb.Where(item => var items = itemsDb
!itemBlacklist.Contains(item.Id) && .Where(item =>
string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase) && !itemBlacklist.Contains(item.Id)
!item.Properties.QuestItem.GetValueOrDefault(false) && && string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase)
itemTypeWhitelist.Contains(item.Parent) && !item.Properties.QuestItem.GetValueOrDefault(false)
&& itemTypeWhitelist.Contains(item.Parent)
) )
.ToList(); .ToList();
return new ItemRewardPoolResults return new ItemRewardPoolResults { ItemPool = items, Blacklist = itemBlacklist };
{
ItemPool = items,
Blacklist = itemBlacklist
};
} }
/// <summary> /// <summary>
@@ -326,7 +329,7 @@ public class LootGenerator(
itemTypeCounts[itemTypeId.Key] = new ItemLimit itemTypeCounts[itemTypeId.Key] = new ItemLimit
{ {
Current = 0, Current = 0,
Max = limits[itemTypeId.Key] Max = limits[itemTypeId.Key],
}; };
} }
@@ -341,13 +344,19 @@ public class LootGenerator(
/// <param name="options">item filters</param> /// <param name="options">item filters</param>
/// <param name="result">array to add found item to</param> /// <param name="result">array to add found item to</param>
/// <returns>true if item was valid and added to pool</returns> /// <returns>true if item was valid and added to pool</returns>
protected bool FindAndAddRandomItemToLoot(List<TemplateItem> items, Dictionary<string, ItemLimit> itemTypeCounts, protected bool FindAndAddRandomItemToLoot(
List<TemplateItem> items,
Dictionary<string, ItemLimit> itemTypeCounts,
LootRequest options, LootRequest options,
List<List<Item>> result) List<List<Item>> result
)
{ {
var randomItem = _randomUtil.GetArrayValue(items); var randomItem = _randomUtil.GetArrayValue(items);
var itemLimitCount = itemTypeCounts.TryGetValue(randomItem.Parent, out var randomItemLimitCount); var itemLimitCount = itemTypeCounts.TryGetValue(
randomItem.Parent,
out var randomItemLimitCount
);
if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max) if (!itemLimitCount && randomItemLimitCount?.Current > randomItemLimitCount?.Max)
{ {
return false; return false;
@@ -363,11 +372,7 @@ public class LootGenerator(
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = randomItem.Id, Template = randomItem.Id,
Upd = new Upd Upd = new Upd { StackObjectsCount = 1, SpawnedInSession = true },
{
StackObjectsCount = 1,
SpawnedInSession = true
}
}; };
// Special case - handle items that need a stackcount > 1 // Special case - handle items that need a stackcount > 1
@@ -380,7 +385,7 @@ public class LootGenerator(
result.Add([newLootItem]); result.Add([newLootItem]);
if (randomItemLimitCount is not null) if (randomItemLimitCount is not null)
// Increment item count as it's in limit array // Increment item count as it's in limit array
{ {
randomItemLimitCount.Current++; randomItemLimitCount.Current++;
} }
@@ -417,10 +422,12 @@ public class LootGenerator(
/// <param name="itemBlacklist">Items to skip</param> /// <param name="itemBlacklist">Items to skip</param>
/// <param name="result">List to add chosen preset to</param> /// <param name="result">List to add chosen preset to</param>
/// <returns>true if preset was valid and added to pool</returns> /// <returns>true if preset was valid and added to pool</returns>
protected bool FindAndAddRandomPresetToLoot(List<Preset> presetPool, protected bool FindAndAddRandomPresetToLoot(
List<Preset> presetPool,
Dictionary<string, ItemLimit> itemTypeCounts, Dictionary<string, ItemLimit> itemTypeCounts,
HashSet<string> itemBlacklist, HashSet<string> itemBlacklist,
List<List<Item>> result) List<List<Item>> result
)
{ {
if (presetPool.Count == 0) if (presetPool.Count == 0)
{ {
@@ -437,7 +444,12 @@ public class LootGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Warning(_localisationService.GetText("loot-chosen_preset_missing_encyclopedia_value", chosenPreset?.Id)); _logger.Warning(
_localisationService.GetText(
"loot-chosen_preset_missing_encyclopedia_value",
chosenPreset?.Id
)
);
} }
return false; return false;
@@ -449,7 +461,9 @@ public class LootGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping"); _logger.Debug(
$"$Unable to find preset with tpl: {chosenPreset.Encyclopedia}, skipping"
);
} }
return false; return false;
@@ -464,13 +478,21 @@ public class LootGenerator(
// Some custom mod items lack a parent property // Some custom mod items lack a parent property
if (itemDbDetails.Value?.Parent is null) if (itemDbDetails.Value?.Parent is null)
{ {
_logger.Error(_localisationService.GetText("loot-item_missing_parentid", itemDbDetails.Value?.Name)); _logger.Error(
_localisationService.GetText(
"loot-item_missing_parentid",
itemDbDetails.Value?.Name
)
);
return false; return false;
} }
// Check chosen preset hasn't exceeded spawn limit // Check chosen preset hasn't exceeded spawn limit
var hasItemLimitCount = itemTypeCounts.TryGetValue(itemDbDetails.Value.Parent, out var itemLimitCount); var hasItemLimitCount = itemTypeCounts.TryGetValue(
itemDbDetails.Value.Parent,
out var itemLimitCount
);
if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max) if (!hasItemLimitCount && itemLimitCount?.Current > itemLimitCount?.Max)
{ {
return false; return false;
@@ -485,7 +507,7 @@ public class LootGenerator(
result.Add(presetAndMods); result.Add(presetAndMods);
if (itemLimitCount is not null) if (itemLimitCount is not null)
// Increment item count as item has been chosen and its inside itemLimitCount dictionary // Increment item count as item has been chosen and its inside itemLimitCount dictionary
{ {
itemLimitCount.Current++; itemLimitCount.Current++;
} }
@@ -499,7 +521,9 @@ public class LootGenerator(
/// </summary> /// </summary>
/// <param name="containerSettings">sealed weapon container settings</param> /// <param name="containerSettings">sealed weapon container settings</param>
/// <returns>List of items with children lists</returns> /// <returns>List of items with children lists</returns>
public List<List<Item>> GetSealedWeaponCaseLoot(SealedAirdropContainerSettings containerSettings) public List<List<Item>> GetSealedWeaponCaseLoot(
SealedAirdropContainerSettings containerSettings
)
{ {
List<List<Item>> itemsToReturn = []; List<List<Item>> itemsToReturn = [];
@@ -513,7 +537,10 @@ public class LootGenerator(
if (!weaponDetailsDb.Key) if (!weaponDetailsDb.Key)
{ {
_logger.Error( _logger.Error(
_localisationService.GetText("loot-non_item_picked_as_sealed_weapon_crate_reward", chosenWeaponTpl) _localisationService.GetText(
"loot-non_item_picked_as_sealed_weapon_crate_reward",
chosenWeaponTpl
)
); );
return itemsToReturn; return itemsToReturn;
@@ -528,9 +555,14 @@ public class LootGenerator(
if (chosenWeaponPreset is null) if (chosenWeaponPreset is null)
{ {
_logger.Warning( _logger.Warning(
_localisationService.GetText("loot-default_preset_not_found_using_random", chosenWeaponTpl) _localisationService.GetText(
"loot-default_preset_not_found_using_random",
chosenWeaponTpl
)
);
chosenWeaponPreset = _randomUtil.GetArrayValue(
_presetHelper.GetPresets(chosenWeaponTpl)
); );
chosenWeaponPreset = _randomUtil.GetArrayValue(_presetHelper.GetPresets(chosenWeaponTpl));
} }
// Clean up Ids to ensure they're all unique and prevent collisions // Clean up Ids to ensure they're all unique and prevent collisions
@@ -543,11 +575,17 @@ public class LootGenerator(
// Get a random collection of weapon mods related to chosen weawpon and add them to result array // Get a random collection of weapon mods related to chosen weawpon and add them to result array
var linkedItemsToWeapon = _ragfairLinkedItemService.GetLinkedDbItems(chosenWeaponTpl); var linkedItemsToWeapon = _ragfairLinkedItemService.GetLinkedDbItems(chosenWeaponTpl);
itemsToReturn.AddRange( itemsToReturn.AddRange(
GetSealedContainerWeaponModRewards(containerSettings, linkedItemsToWeapon, chosenWeaponPreset) GetSealedContainerWeaponModRewards(
containerSettings,
linkedItemsToWeapon,
chosenWeaponPreset
)
); );
// Handle non-weapon mod reward types // Handle non-weapon mod reward types
itemsToReturn.AddRange(GetSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb.Value)); itemsToReturn.AddRange(
GetSealedContainerNonWeaponModRewards(containerSettings, weaponDetailsDb.Value)
);
return itemsToReturn; return itemsToReturn;
} }
@@ -558,8 +596,10 @@ public class LootGenerator(
/// <param name="containerSettings">Sealed weapon container settings</param> /// <param name="containerSettings">Sealed weapon container settings</param>
/// <param name="weaponDetailsDb">Details for the weapon to reward player</param> /// <param name="weaponDetailsDb">Details for the weapon to reward player</param>
/// <returns>List of item with children lists</returns> /// <returns>List of item with children lists</returns>
protected List<List<Item>> GetSealedContainerNonWeaponModRewards(SealedAirdropContainerSettings containerSettings, protected List<List<Item>> GetSealedContainerNonWeaponModRewards(
TemplateItem weaponDetailsDb) SealedAirdropContainerSettings containerSettings,
TemplateItem weaponDetailsDb
)
{ {
List<List<Item>> rewards = []; List<List<Item>> rewards = [];
@@ -576,11 +616,10 @@ public class LootGenerator(
{ {
// Get ammo boxes from db // Get ammo boxes from db
var ammoBoxesDetails = containerSettings.AmmoBoxWhitelist.Select(tpl => var ammoBoxesDetails = containerSettings.AmmoBoxWhitelist.Select(tpl =>
{ {
var itemDetails = _itemHelper.GetItem(tpl); var itemDetails = _itemHelper.GetItem(tpl);
return itemDetails.Value; return itemDetails.Value;
} });
);
// Need to find boxes that matches weapons caliber // Need to find boxes that matches weapons caliber
var weaponCaliber = weaponDetailsDb.Properties.AmmoCaliber; var weaponCaliber = weaponDetailsDb.Properties.AmmoCaliber;
@@ -602,11 +641,7 @@ public class LootGenerator(
var chosenAmmoBox = _randomUtil.GetArrayValue(ammoBoxesMatchingCaliber); var chosenAmmoBox = _randomUtil.GetArrayValue(ammoBoxesMatchingCaliber);
var ammoBoxReward = new List<Item> var ammoBoxReward = new List<Item>
{ {
new() new() { Id = _hashUtil.Generate(), Template = chosenAmmoBox.Id },
{
Id = _hashUtil.Generate(),
Template = chosenAmmoBox.Id
}
}; };
_itemHelper.AddCartridgesToAmmoBox(ammoBoxReward, chosenAmmoBox); _itemHelper.AddCartridgesToAmmoBox(ammoBoxReward, chosenAmmoBox);
rewards.Add(ammoBoxReward); rewards.Add(ammoBoxReward);
@@ -616,13 +651,14 @@ public class LootGenerator(
} }
// Get all items of the desired type + not quest items + not globally blacklisted // Get all items of the desired type + not quest items + not globally blacklisted
var rewardItemPool = _databaseService.GetItems() var rewardItemPool = _databaseService
.GetItems()
.Values.Where(item => .Values.Where(item =>
item.Parent == rewardKey && item.Parent == rewardKey
string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase) && && string.Equals(item.Type, "item", StringComparison.OrdinalIgnoreCase)
_itemFilterService.IsItemBlacklisted(item.Id) && && _itemFilterService.IsItemBlacklisted(item.Id)
!(containerSettings.AllowBossItems || _itemFilterService.IsBossItem(item.Id)) && && !(containerSettings.AllowBossItems || _itemFilterService.IsBossItem(item.Id))
item.Properties.QuestItem is null && item.Properties.QuestItem is null
); );
if (!rewardItemPool.Any()) if (!rewardItemPool.Any())
@@ -641,11 +677,7 @@ public class LootGenerator(
var chosenRewardItem = _randomUtil.GetArrayValue(rewardItemPool); var chosenRewardItem = _randomUtil.GetArrayValue(rewardItemPool);
var rewardItem = new List<Item> var rewardItem = new List<Item>
{ {
new() new() { Id = _hashUtil.Generate(), Template = chosenRewardItem.Id },
{
Id = _hashUtil.Generate(),
Template = chosenRewardItem.Id
}
}; };
rewards.Add(rewardItem); rewards.Add(rewardItem);
@@ -662,8 +694,11 @@ public class LootGenerator(
/// <param name="linkedItemsToWeapon">All items that can be attached/inserted into weapon</param> /// <param name="linkedItemsToWeapon">All items that can be attached/inserted into weapon</param>
/// <param name="chosenWeaponPreset">The weapon preset given to player as reward</param> /// <param name="chosenWeaponPreset">The weapon preset given to player as reward</param>
/// <returns>List of item with children lists</returns> /// <returns>List of item with children lists</returns>
protected List<List<Item>> GetSealedContainerWeaponModRewards(SealedAirdropContainerSettings containerSettings, List<TemplateItem> linkedItemsToWeapon, protected List<List<Item>> GetSealedContainerWeaponModRewards(
Preset chosenWeaponPreset) SealedAirdropContainerSettings containerSettings,
List<TemplateItem> linkedItemsToWeapon,
Preset chosenWeaponPreset
)
{ {
List<List<Item>> modRewards = []; List<List<Item>> modRewards = [];
@@ -678,7 +713,8 @@ public class LootGenerator(
} }
// Get items that fulfil reward type criteria from items that fit on gun // 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) var relatedItems = linkedItemsToWeapon?.Where(item =>
item?.Parent == rewardKey && !_itemFilterService.IsItemBlacklisted(item.Id)
); );
if (relatedItems is null || !relatedItems.Any()) if (relatedItems is null || !relatedItems.Any())
{ {
@@ -698,11 +734,7 @@ public class LootGenerator(
var chosenItem = _randomUtil.DrawRandomFromList(relatedItems.ToList()); var chosenItem = _randomUtil.DrawRandomFromList(relatedItems.ToList());
var reward = new List<Item> var reward = new List<Item>
{ {
new() new() { Id = _hashUtil.Generate(), Template = chosenItem[0].Id },
{
Id = _hashUtil.Generate(),
Template = chosenItem[0].Id
}
}; };
modRewards.Add(reward); modRewards.Add(reward);
@@ -742,11 +774,7 @@ public class LootGenerator(
List<Item> rewardItem = List<Item> rewardItem =
[ [
new() new() { Id = _hashUtil.Generate(), Template = chosenRewardItemTpl },
{
Id = _hashUtil.Generate(),
Template = chosenRewardItemTpl
}
]; ];
itemsToReturn.Add(rewardItem); itemsToReturn.Add(rewardItem);
} }
@@ -761,47 +789,33 @@ public class LootGenerator(
/// <returns>Single tpl</returns> /// <returns>Single tpl</returns>
protected string PickRewardItem(RewardDetails rewardContainerDetails) protected string PickRewardItem(RewardDetails rewardContainerDetails)
{ {
if (rewardContainerDetails.RewardTplPool is not null && rewardContainerDetails.RewardTplPool.Count > 0) if (
rewardContainerDetails.RewardTplPool is not null
&& rewardContainerDetails.RewardTplPool.Count > 0
)
{ {
return _weightedRandomHelper.GetWeightedValue(rewardContainerDetails.RewardTplPool); return _weightedRandomHelper.GetWeightedValue(rewardContainerDetails.RewardTplPool);
} }
return _randomUtil.GetArrayValue( return _randomUtil.GetArrayValue(
GetItemRewardPool([], rewardContainerDetails.RewardTypePool, true, true, false) GetItemRewardPool([], rewardContainerDetails.RewardTypePool, true, true, false)
.ItemPool.Select(item => item.Id .ItemPool.Select(item => item.Id)
)
); );
} }
public record ItemRewardPoolResults public record ItemRewardPoolResults
{ {
public List<TemplateItem> ItemPool public List<TemplateItem> ItemPool { get; set; }
{
get;
set;
}
public HashSet<string> Blacklist public HashSet<string> Blacklist { get; set; }
{
get;
set;
}
} }
} }
public class ItemLimit public class ItemLimit
{ {
[JsonPropertyName("current")] [JsonPropertyName("current")]
public int Current public int Current { get; set; }
{
get;
set;
}
[JsonPropertyName("max")] [JsonPropertyName("max")]
public int Max public int Max { get; set; }
{
get;
set;
}
} }
@@ -18,7 +18,8 @@ public class PMCLootGenerator(
RagfairPriceService ragfairPriceService, RagfairPriceService ragfairPriceService,
SeasonalEventService seasonalEventService, SeasonalEventService seasonalEventService,
WeightedRandomHelper weightedRandomHelper, WeightedRandomHelper weightedRandomHelper,
ConfigServer configServer) ConfigServer configServer
)
{ {
private readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>(); private readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>();
@@ -53,7 +54,12 @@ public class PMCLootGenerator(
var blacklist = GetContainerLootBlacklist(); var blacklist = GetContainerLootBlacklist();
// Generate loot and cache - Also pass check to ensure only 1x2 items are allowed (Unheard bots have big pockets, hence the need for 1x2) // Generate loot and cache - Also pass check to ensure only 1x2 items are allowed (Unheard bots have big pockets, hence the need for 1x2)
var pool = GenerateLootPool(pmcRole, allowedItemTypeWhitelist, blacklist, ItemFitsInto1By2Slot); var pool = GenerateLootPool(
pmcRole,
allowedItemTypeWhitelist,
blacklist,
ItemFitsInto1By2Slot
);
_pocketLootPool.TryAdd(pmcRole, pool); _pocketLootPool.TryAdd(pmcRole, pool);
return pool; return pool;
@@ -83,7 +89,12 @@ public class PMCLootGenerator(
blacklist.UnionWith(_pmcConfig.VestLoot.Blacklist); // Include vest-specific blacklist blacklist.UnionWith(_pmcConfig.VestLoot.Blacklist); // Include vest-specific blacklist
// Generate loot and cache - Also pass check to ensure items up to 2x2 are allowed, some vests have big slots // Generate loot and cache - Also pass check to ensure items up to 2x2 are allowed, some vests have big slots
var pool = GenerateLootPool(pmcRole, allowedItemTypeWhitelist, blacklist, ItemFitsInto2By2Slot); var pool = GenerateLootPool(
pmcRole,
allowedItemTypeWhitelist,
blacklist,
ItemFitsInto2By2Slot
);
_vestLootPool.TryAdd(pmcRole, pool); _vestLootPool.TryAdd(pmcRole, pool);
return pool; return pool;
@@ -99,7 +110,6 @@ public class PMCLootGenerator(
{ {
lock (BackpackLock) lock (BackpackLock)
{ {
// Already exists, return values // Already exists, return values
if (_backpackLootPool.TryGetValue(pmcRole, out var existingLootPool)) if (_backpackLootPool.TryGetValue(pmcRole, out var existingLootPool))
{ {
@@ -126,7 +136,12 @@ public class PMCLootGenerator(
/// <param name="itemTplAndParentBlacklist">Item and parent blacklist</param> /// <param name="itemTplAndParentBlacklist">Item and parent blacklist</param>
/// <param name="genericItemCheck">An optional delegate to validate the TemplateItem object being processed</param> /// <param name="genericItemCheck">An optional delegate to validate the TemplateItem object being processed</param>
/// <returns>Dictionary of items and weights inversely tied to the items price</returns> /// <returns>Dictionary of items and weights inversely tied to the items price</returns>
protected Dictionary<string, double> GenerateLootPool(string pmcRole, HashSet<string> allowedItemTypeWhitelist, HashSet<string> itemTplAndParentBlacklist, Func<TemplateItem, bool>? genericItemCheck) protected Dictionary<string, double> GenerateLootPool(
string pmcRole,
HashSet<string> allowedItemTypeWhitelist,
HashSet<string> itemTplAndParentBlacklist,
Func<TemplateItem, bool>? genericItemCheck
)
{ {
var lootPool = new Dictionary<string, double>(); var lootPool = new Dictionary<string, double>();
var items = databaseService.GetItems(); var items = databaseService.GetItems();
@@ -136,13 +151,15 @@ public class PMCLootGenerator(
// Filter all items in DB to ones we want with passed in whitelist + blacklist + generic 'IsValidItem' check // Filter all items in DB to ones we want with passed in whitelist + blacklist + generic 'IsValidItem' check
// Also run Delegate if it's not null // Also run Delegate if it's not null
var itemTplsToAdd = items.Where(item => var itemTplsToAdd = items
allowedItemTypeWhitelist.Contains(item.Value.Parent) && .Where(item =>
itemHelper.IsValidItem(item.Value.Id) && allowedItemTypeWhitelist.Contains(item.Value.Parent)
!itemTplAndParentBlacklist.Contains(item.Value.Id) && && itemHelper.IsValidItem(item.Value.Id)
!itemTplAndParentBlacklist.Contains(item.Value.Parent) && && !itemTplAndParentBlacklist.Contains(item.Value.Id)
(genericItemCheck?.Invoke(item.Value) ?? true) // if delegate is null, force check to be true && !itemTplAndParentBlacklist.Contains(item.Value.Parent)
).Select(x => x.Key); && (genericItemCheck?.Invoke(item.Value) ?? true) // if delegate is null, force check to be true
)
.Select(x => x.Key);
// Store all items + price in above lootPool dictionary // Store all items + price in above lootPool dictionary
foreach (var tpl in itemTplsToAdd) foreach (var tpl in itemTplsToAdd)
@@ -202,7 +219,6 @@ public class PMCLootGenerator(
logger.Error($"Unable to find price overrides for PMC: {pmcRole}"); logger.Error($"Unable to find price overrides for PMC: {pmcRole}");
return null; return null;
} }
/// <summary> /// <summary>
@@ -213,7 +229,10 @@ public class PMCLootGenerator(
/// <returns>Rouble price</returns> /// <returns>Rouble price</returns>
protected double GetItemPrice(string tpl, Dictionary<string, double>? pmcPriceOverrides = null) protected double GetItemPrice(string tpl, Dictionary<string, double>? pmcPriceOverrides = null)
{ {
if (pmcPriceOverrides is not null && pmcPriceOverrides.TryGetValue(tpl, out var overridePrice)) if (
pmcPriceOverrides is not null
&& pmcPriceOverrides.TryGetValue(tpl, out var overridePrice)
)
{ {
// There's a price override for this item, use override instead of default price // There's a price override for this item, use override instead of default price
return overridePrice; return overridePrice;
@@ -245,7 +264,7 @@ public class PMCLootGenerator(
return $"{item.Properties.Width}x{item.Properties.Height}" switch return $"{item.Properties.Width}x{item.Properties.Height}" switch
{ {
"1x1" or "1x2" or "2x1" => true, "1x1" or "1x2" or "2x1" => true,
_ => false _ => false,
}; };
} }
} }
@@ -13,7 +13,6 @@ using SPTarkov.Server.Core.Utils.Cloners;
using SPTarkov.Server.Core.Utils.Json; using SPTarkov.Server.Core.Utils.Json;
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Core.Generators; namespace SPTarkov.Server.Core.Generators;
[Injectable] [Injectable]
@@ -54,9 +53,16 @@ public class PlayerScavGenerator(
var scavKarmaLevel = GetScavKarmaLevel(pmcDataClone); var scavKarmaLevel = GetScavKarmaLevel(pmcDataClone);
// use karma level to get correct karmaSettings // use karma level to get correct karmaSettings
if (!_playerScavConfig.KarmaLevel.TryGetValue(scavKarmaLevel.ToString(), out var playerScavKarmaSettings)) if (
!_playerScavConfig.KarmaLevel.TryGetValue(
scavKarmaLevel.ToString(),
out var playerScavKarmaSettings
)
)
{ {
_logger.Error(_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel)); _logger.Error(
_localisationService.GetText("scav-missing_karma_settings", scavKarmaLevel)
);
} }
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
@@ -101,24 +107,20 @@ public class PlayerScavGenerator(
scavData.Info.Level = GetScavLevel(existingScavDataClone); scavData.Info.Level = GetScavLevel(existingScavDataClone);
scavData.Info.Experience = GetScavExperience(existingScavDataClone); scavData.Info.Experience = GetScavExperience(existingScavDataClone);
scavData.Quests = existingScavDataClone.Quests ?? []; scavData.Quests = existingScavDataClone.Quests ?? [];
scavData.TaskConditionCounters = existingScavDataClone.TaskConditionCounters ?? new Dictionary<string, TaskConditionCounter>(); scavData.TaskConditionCounters =
scavData.Notes = existingScavDataClone.Notes ?? existingScavDataClone.TaskConditionCounters
new Notes ?? new Dictionary<string, TaskConditionCounter>();
{ scavData.Notes = existingScavDataClone.Notes ?? new Notes { DataNotes = new List<Note>() };
DataNotes = new List<Note>() scavData.WishList =
}; existingScavDataClone.WishList
scavData.WishList = existingScavDataClone.WishList ?? new DictionaryOrList<string, int>(new Dictionary<string, int>(), new List<int>()); ?? new DictionaryOrList<string, int>(new Dictionary<string, int>(), new List<int>());
scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new Dictionary<string, bool>(); scavData.Encyclopedia = pmcDataClone.Encyclopedia ?? new Dictionary<string, bool>();
// Add additional items to player scav as loot // Add additional items to player scav as loot
AddAdditionalLootToPlayerScavContainers( AddAdditionalLootToPlayerScavContainers(
playerScavKarmaSettings.LootItemsToAddChancePercent, playerScavKarmaSettings.LootItemsToAddChancePercent,
scavData, scavData,
[ [EquipmentSlots.TacticalVest, EquipmentSlots.Pockets, EquipmentSlots.Backpack]
EquipmentSlots.TacticalVest,
EquipmentSlots.Pockets,
EquipmentSlots.Backpack
]
); );
// Remove secure container // Remove secure container
@@ -139,8 +141,11 @@ public class PlayerScavGenerator(
/// <param name="possibleItemsToAdd">dict of tpl + % chance to be added</param> /// <param name="possibleItemsToAdd">dict of tpl + % chance to be added</param>
/// <param name="scavData"></param> /// <param name="scavData"></param>
/// <param name="containersToAddTo">Possible slotIds to add loot to</param> /// <param name="containersToAddTo">Possible slotIds to add loot to</param>
protected void AddAdditionalLootToPlayerScavContainers(Dictionary<string, double> possibleItemsToAdd, BotBase scavData, protected void AddAdditionalLootToPlayerScavContainers(
HashSet<EquipmentSlots> containersToAddTo) Dictionary<string, double> possibleItemsToAdd,
BotBase scavData,
HashSet<EquipmentSlots> containersToAddTo
)
{ {
foreach (var tpl in possibleItemsToAdd) foreach (var tpl in possibleItemsToAdd)
{ {
@@ -153,7 +158,9 @@ public class PlayerScavGenerator(
var itemResult = _itemHelper.GetItem(tpl.Key); var itemResult = _itemHelper.GetItem(tpl.Key);
if (!itemResult.Key) if (!itemResult.Key)
{ {
_logger.Warning(_localisationService.GetText("scav-unable_to_add_item_to_player_scav", tpl)); _logger.Warning(
_localisationService.GetText("scav-unable_to_add_item_to_player_scav", tpl)
);
continue; continue;
} }
@@ -164,8 +171,8 @@ public class PlayerScavGenerator(
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = itemTemplate.Id, Template = itemTemplate.Id,
Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate) Upd = _botGeneratorHelper.GenerateExtraPropertiesForItem(itemTemplate),
} },
}; };
var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot( var result = _botGeneratorHelper.AddItemWithChildrenToEquipmentSlot(
@@ -197,7 +204,9 @@ public class PlayerScavGenerator(
// can be empty during profile creation // can be empty during profile creation
if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo)) if (!pmcData.TradersInfo.TryGetValue(Traders.FENCE, out var fenceInfo))
{ {
_logger.Warning(_localisationService.GetText("scav-missing_karma_level_getting_default")); _logger.Warning(
_localisationService.GetText("scav-missing_karma_level_getting_default")
);
return 0; return 0;
} }
@@ -239,7 +248,10 @@ public class PlayerScavGenerator(
/// </summary> /// </summary>
/// <param name="karmaSettings">Values to modify the bot template with</param> /// <param name="karmaSettings">Values to modify the bot template with</param>
/// <param name="baseBotNode">bot template to modify according to karama level settings</param> /// <param name="baseBotNode">bot template to modify according to karama level settings</param>
protected void AdjustBotTemplateWithKarmaSpecificSettings(KarmaLevel karmaSettings, BotType baseBotNode) protected void AdjustBotTemplateWithKarmaSpecificSettings(
KarmaLevel karmaSettings,
BotType baseBotNode
)
{ {
// Adjust equipment chance values // Adjust equipment chance values
foreach (var equipmentKvP in karmaSettings.Modifiers.Equipment) foreach (var equipmentKvP in karmaSettings.Modifiers.Equipment)
@@ -251,8 +263,13 @@ public class PlayerScavGenerator(
} }
// Try add new key with value // Try add new key with value
if (!baseBotNode.BotChances.EquipmentChances.TryAdd(equipmentKvP.Key, equipmentKvP.Value)) if (
// Unable to add new, update existing !baseBotNode.BotChances.EquipmentChances.TryAdd(
equipmentKvP.Key,
equipmentKvP.Value
)
)
// Unable to add new, update existing
{ {
baseBotNode.BotChances.EquipmentChances[equipmentKvP.Key] += equipmentKvP.Value; baseBotNode.BotChances.EquipmentChances[equipmentKvP.Key] += equipmentKvP.Value;
} }
@@ -272,7 +289,6 @@ public class PlayerScavGenerator(
baseBotNode.BotChances.WeaponModsChances.TryAdd(modKvP.Key, 0); baseBotNode.BotChances.WeaponModsChances.TryAdd(modKvP.Key, 0);
baseBotNode.BotChances.WeaponModsChances[modKvP.Key] += value; baseBotNode.BotChances.WeaponModsChances[modKvP.Key] += value;
} }
; ;
} }
@@ -280,14 +296,19 @@ public class PlayerScavGenerator(
var props = baseBotNode.BotGeneration.Items.GetType().GetProperties(); var props = baseBotNode.BotGeneration.Items.GetType().GetProperties();
foreach (var itemLimitKvP in karmaSettings.ItemLimits) foreach (var itemLimitKvP in karmaSettings.ItemLimits)
{ {
var prop = props.FirstOrDefault(x => string.Equals(x.Name, itemLimitKvP.Key, StringComparison.OrdinalIgnoreCase)); var prop = props.FirstOrDefault(x =>
string.Equals(x.Name, itemLimitKvP.Key, StringComparison.OrdinalIgnoreCase)
);
prop.SetValue(baseBotNode.BotGeneration.Items, itemLimitKvP.Value); prop.SetValue(baseBotNode.BotGeneration.Items, itemLimitKvP.Value);
} }
// Blacklist equipment, keyed by equipment slot // Blacklist equipment, keyed by equipment slot
foreach (var equipmentBlacklistKvP in karmaSettings.EquipmentBlacklist) foreach (var equipmentBlacklistKvP in karmaSettings.EquipmentBlacklist)
{ {
baseBotNode.BotInventory.Equipment.TryGetValue(equipmentBlacklistKvP.Key, out var equipmentDict); baseBotNode.BotInventory.Equipment.TryGetValue(
equipmentBlacklistKvP.Key,
out var equipmentDict
);
foreach (var itemToRemove in equipmentBlacklistKvP.Value) foreach (var itemToRemove in equipmentBlacklistKvP.Value)
{ {
equipmentDict.Remove(itemToRemove); equipmentDict.Remove(itemToRemove);
@@ -311,7 +332,7 @@ public class PlayerScavGenerator(
{ {
Common = [], Common = [],
Mastering = [], Mastering = [],
Points = 0 Points = 0,
}; };
} }
@@ -357,18 +378,26 @@ public class PlayerScavGenerator(
protected PmcData SetScavCooldownTimer(PmcData scavData, PmcData pmcData) protected PmcData SetScavCooldownTimer(PmcData scavData, PmcData pmcData)
{ {
// Get sum of all scav cooldown reduction timer bonuses // Get sum of all scav cooldown reduction timer bonuses
var modifier = 1d + pmcData.Bonuses var modifier =
.Where(x => x.Type == BonusType.ScavCooldownTimer) 1d
.Sum(bonus => (bonus?.Value ?? 1) / 100); + pmcData
.Bonuses.Where(x => x.Type == BonusType.ScavCooldownTimer)
.Sum(bonus => (bonus?.Value ?? 1) / 100);
var fenceInfo = _fenceService.GetFenceInfo(pmcData); var fenceInfo = _fenceService.GetFenceInfo(pmcData);
modifier *= fenceInfo.SavageCooldownModifier ?? 1d; modifier *= fenceInfo.SavageCooldownModifier ?? 1d;
// Make sure to apply ScavCooldownTimer bonus from Hideout if the player has it. // Make sure to apply ScavCooldownTimer bonus from Hideout if the player has it.
var scavLockDuration = _databaseService.GetGlobals().Configuration.SavagePlayCooldown * modifier; var scavLockDuration =
_databaseService.GetGlobals().Configuration.SavagePlayCooldown * modifier;
var fullProfile = _profileHelper.GetFullProfile(pmcData?.SessionId); var fullProfile = _profileHelper.GetFullProfile(pmcData?.SessionId);
if (fullProfile?.ProfileInfo?.Edition?.StartsWith(AccountTypes.SPT_DEVELOPER, StringComparison.OrdinalIgnoreCase) ?? false) if (
fullProfile?.ProfileInfo?.Edition?.StartsWith(
AccountTypes.SPT_DEVELOPER,
StringComparison.OrdinalIgnoreCase
) ?? false
)
{ {
// Force lock duration to 10seconds for dev profiles // Force lock duration to 10seconds for dev profiles
scavLockDuration = 10; scavLockDuration = 10;
@@ -376,7 +405,9 @@ public class PlayerScavGenerator(
if (scavData?.Info != null) if (scavData?.Info != null)
{ {
scavData.Info.SavageLockTime = Math.Round(_timeUtil.GetTimeStamp() + (scavLockDuration ?? 0)); scavData.Info.SavageLockTime = Math.Round(
_timeUtil.GetTimeStamp() + (scavLockDuration ?? 0)
);
} }
return scavData; return scavData;
@@ -11,7 +11,8 @@ namespace SPTarkov.Server.Core.Generators;
public class PmcWaveGenerator( public class PmcWaveGenerator(
ISptLogger<PmcWaveGenerator> logger, ISptLogger<PmcWaveGenerator> logger,
DatabaseService databaseService, DatabaseService databaseService,
ConfigServer configServer) ConfigServer configServer
)
{ {
protected readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>(); protected readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>();
@@ -31,7 +31,7 @@ public class RagfairAssortGenerator(
BaseClasses.INVENTORY, BaseClasses.INVENTORY,
BaseClasses.STATIONARY_CONTAINER, BaseClasses.STATIONARY_CONTAINER,
BaseClasses.POCKETS, BaseClasses.POCKETS,
BaseClasses.BUILT_IN_INSERTS BaseClasses.BUILT_IN_INSERTS,
]; ];
/// <summary> /// <summary>
@@ -53,7 +53,9 @@ public class RagfairAssortGenerator(
List<List<Item>> results = []; List<List<Item>> results = [];
// Get cloned items from db // Get cloned items from db
var dbItemsClone = itemHelper.GetItems().Where(item => !string.Equals(item.Type, "Node", StringComparison.OrdinalIgnoreCase)); var dbItemsClone = itemHelper
.GetItems()
.Where(item => !string.Equals(item.Type, "Node", StringComparison.OrdinalIgnoreCase));
// Store processed preset tpls so we don't add them when processing non-preset items // Store processed preset tpls so we don't add them when processing non-preset items
HashSet<string> processedArmorItems = []; HashSet<string> processedArmorItems = [];
@@ -76,7 +78,7 @@ public class RagfairAssortGenerator(
{ {
StackObjectsCount = 99999999, StackObjectsCount = 99999999,
UnlimitedCount = true, UnlimitedCount = true,
SptPresetId = preset.Id SptPresetId = preset.Id,
}; };
results.Add(presetAndMods); results.Add(presetAndMods);
@@ -91,24 +93,21 @@ public class RagfairAssortGenerator(
// Skip seasonal items when not in-season // Skip seasonal items when not in-season
if ( if (
RagfairConfig.Dynamic.RemoveSeasonalItemsWhenNotInEvent && RagfairConfig.Dynamic.RemoveSeasonalItemsWhenNotInEvent
!seasonalEventActive && && !seasonalEventActive
seasonalItemTplBlacklist.Contains(item.Id) && seasonalItemTplBlacklist.Contains(item.Id)
) )
{ {
continue; continue;
} }
if (processedArmorItems.Contains(item.Id)) if (processedArmorItems.Contains(item.Id))
// Already processed // Already processed
{ {
continue; continue;
} }
var ragfairAssort = CreateRagfairAssortRootItem( var ragfairAssort = CreateRagfairAssortRootItem(item.Id, item.Id); // tpl and id must be the same so hideout recipe rewards work
item.Id,
item.Id
); // tpl and id must be the same so hideout recipe rewards work
results.Add([ragfairAssort]); results.Add([ragfairAssort]);
} }
@@ -147,11 +146,7 @@ public class RagfairAssortGenerator(
Template = tplId, Template = tplId,
ParentId = "hideout", ParentId = "hideout",
SlotId = "hideout", SlotId = "hideout",
Upd = new Upd Upd = new Upd { StackObjectsCount = 99999999, UnlimitedCount = true },
{
StackObjectsCount = 99999999,
UnlimitedCount = true
}
}; };
} }
} }
@@ -68,7 +68,15 @@ public class RagfairOfferGenerator(
bool sellInOnePiece = false bool sellInOnePiece = false
) )
{ {
var offer = CreateOffer(userId, time, items, barterScheme, loyalLevel, quantity, sellInOnePiece); var offer = CreateOffer(
userId,
time,
items,
barterScheme,
loyalLevel,
quantity,
sellInOnePiece
);
ragfairOfferService.AddOffer(offer); ragfairOfferService.AddOffer(offer);
return offer; return offer;
@@ -95,25 +103,25 @@ public class RagfairOfferGenerator(
bool isPackOffer = false bool isPackOffer = false
) )
{ {
var offerRequirements = barterScheme.Select(barter => var offerRequirements = barterScheme
.Select(barter =>
{
var offerRequirement = new OfferRequirement
{ {
var offerRequirement = new OfferRequirement Template = barter.Template,
{ Count = Math.Round(barter.Count.Value, 2),
Template = barter.Template, OnlyFunctional = barter.OnlyFunctional ?? false,
Count = Math.Round(barter.Count.Value, 2), };
OnlyFunctional = barter.OnlyFunctional ?? false
};
// Dogtags define level and side // Dogtags define level and side
if (barter.Level != null) if (barter.Level != null)
{ {
offerRequirement.Level = barter.Level; offerRequirement.Level = barter.Level;
offerRequirement.Side = barter.Side; offerRequirement.Side = barter.Side;
}
return offerRequirement;
} }
)
return offerRequirement;
})
.ToList(); .ToList();
// Clone to avoid modifying original array // Clone to avoid modifying original array
@@ -122,13 +130,21 @@ public class RagfairOfferGenerator(
// Hydrate ammo boxes with cartridges + ensure only 1 item is present (ammo box) // 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 // 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) if (
itemHelper.IsOfBaseclass(itemsClone[0].Template, BaseClasses.AMMO_BOX)
&& itemsClone.Count == 1
)
{ {
itemHelper.AddCartridgesToAmmoBox(itemsClone, itemHelper.GetItem(rootItem.Template).Value); itemHelper.AddCartridgesToAmmoBox(
itemsClone,
itemHelper.GetItem(rootItem.Template).Value
);
} }
var roubleListingPrice = Math.Round(ConvertOfferRequirementsIntoRoubles(offerRequirements)); var roubleListingPrice = Math.Round(ConvertOfferRequirementsIntoRoubles(offerRequirements));
var singleItemListingPrice = isPackOffer ? roubleListingPrice / quantity : roubleListingPrice; var singleItemListingPrice = isPackOffer
? roubleListingPrice / quantity
: roubleListingPrice;
var offer = new RagfairOffer var offer = new RagfairOffer
{ {
@@ -146,7 +162,7 @@ public class RagfairOfferGenerator(
LoyaltyLevel = loyalLevel, LoyaltyLevel = loyalLevel,
SellInOnePiece = isPackOffer, SellInOnePiece = isPackOffer,
Locked = false, Locked = false,
Quantity = quantity Quantity = quantity,
}; };
offerCounter++; offerCounter++;
@@ -165,11 +181,7 @@ public class RagfairOfferGenerator(
// Trader offer // Trader offer
if (isTrader) if (isTrader)
{ {
return new RagfairOfferUser return new RagfairOfferUser { Id = userId, MemberType = MemberCategory.Trader };
{
Id = userId,
MemberType = MemberCategory.Trader
};
} }
var isPlayerOffer = profileHelper.IsPlayer(userId); var isPlayerOffer = profileHelper.IsPlayer(userId);
@@ -185,7 +197,7 @@ public class RagfairOfferGenerator(
Rating = playerProfile.RagfairInfo.Rating ?? 0, Rating = playerProfile.RagfairInfo.Rating ?? 0,
IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing, IsRatingGrowing = playerProfile.RagfairInfo.IsRatingGrowing,
Avatar = null, Avatar = null,
Aid = playerProfile.Aid Aid = playerProfile.Aid,
}; };
} }
@@ -201,7 +213,7 @@ public class RagfairOfferGenerator(
), ),
IsRatingGrowing = randomUtil.GetBool(), IsRatingGrowing = randomUtil.GetBool(),
Avatar = null, Avatar = null,
Aid = hashUtil.GenerateAccountId() Aid = hashUtil.GenerateAccountId(),
}; };
} }
@@ -210,14 +222,17 @@ public class RagfairOfferGenerator(
/// </summary> /// </summary>
/// <param name="offerRequirements"> barter requirements for offer </param> /// <param name="offerRequirements"> barter requirements for offer </param>
/// <returns> rouble cost of offer </returns> /// <returns> rouble cost of offer </returns>
protected double ConvertOfferRequirementsIntoRoubles(IEnumerable<OfferRequirement> offerRequirements) protected double ConvertOfferRequirementsIntoRoubles(
IEnumerable<OfferRequirement> offerRequirements
)
{ {
var roublePrice = 0d; var roublePrice = 0d;
foreach (var requirement in offerRequirements) foreach (var requirement in offerRequirements)
{ {
roublePrice += paymentHelper.IsMoneyTpl(requirement.Template) roublePrice += paymentHelper.IsMoneyTpl(requirement.Template)
? Math.Round(CalculateRoublePrice(requirement.Count.Value, requirement.Template)) ? Math.Round(CalculateRoublePrice(requirement.Count.Value, requirement.Template))
: ragfairPriceService.GetFleaPriceForItem(requirement.Template) * requirement.Count.Value; // Get flea price for barter offer items : ragfairPriceService.GetFleaPriceForItem(requirement.Template)
* requirement.Count.Value; // Get flea price for barter offer items
} }
return roublePrice; return roublePrice;
@@ -290,7 +305,10 @@ public class RagfairOfferGenerator(
} }
// Generated pmc offer // Generated pmc offer
return randomUtil.GetDouble(ragfairConfig.Dynamic.Rating.Min, ragfairConfig.Dynamic.Rating.Max); return randomUtil.GetDouble(
ragfairConfig.Dynamic.Rating.Min,
ragfairConfig.Dynamic.Rating.Max
);
} }
/// <summary> /// <summary>
@@ -301,13 +319,15 @@ public class RagfairOfferGenerator(
protected bool GetRatingGrowing(string userID) protected bool GetRatingGrowing(string userID)
{ {
if (profileHelper.IsPlayer(userID)) if (profileHelper.IsPlayer(userID))
// player offer // player offer
{ {
return saveServer.GetProfile(userID).CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false; return saveServer
.GetProfile(userID)
.CharacterData?.PmcData?.RagfairInfo?.IsRatingGrowing ?? false;
} }
if (ragfairServerHelper.IsTrader(userID)) if (ragfairServerHelper.IsTrader(userID))
// trader offer // trader offer
{ {
return true; return true;
} }
@@ -328,20 +348,30 @@ public class RagfairOfferGenerator(
if (profileHelper.IsPlayer(userID)) if (profileHelper.IsPlayer(userID))
{ {
// Player offer = current time + offerDurationTimeInHour; // Player offer = current time + offerDurationTimeInHour;
var offerDurationTimeHours = databaseService.GetGlobals().Configuration.RagFair.OfferDurationTimeInHour; var offerDurationTimeHours = databaseService
return (long) (timeUtil.GetTimeStamp() + Math.Round((double) offerDurationTimeHours * TimeUtil.OneHourAsSeconds)); .GetGlobals()
.Configuration.RagFair.OfferDurationTimeInHour;
return (long)(
timeUtil.GetTimeStamp()
+ Math.Round((double)offerDurationTimeHours * TimeUtil.OneHourAsSeconds)
);
} }
if (ragfairServerHelper.IsTrader(userID)) if (ragfairServerHelper.IsTrader(userID))
// Trader offer // Trader offer
{ {
return (long) databaseService.GetTrader(userID).Base.NextResupply; return (long)databaseService.GetTrader(userID).Base.NextResupply;
} }
// Generated fake-player offer // Generated fake-player offer
return (long) Math.Round( return (long)
time + randomUtil.GetDouble(ragfairConfig.Dynamic.EndTimeSeconds.Min, ragfairConfig.Dynamic.EndTimeSeconds.Max) Math.Round(
); time
+ randomUtil.GetDouble(
ragfairConfig.Dynamic.EndTimeSeconds.Min,
ragfairConfig.Dynamic.EndTimeSeconds.Max
)
);
} }
/// <summary> /// <summary>
@@ -360,7 +390,9 @@ public class RagfairOfferGenerator(
stopwatch.Stop(); stopwatch.Stop();
if (logger.IsLogEnabled(LogLevel.Debug) && stopwatch.ElapsedMilliseconds > 0) if (logger.IsLogEnabled(LogLevel.Debug) && stopwatch.ElapsedMilliseconds > 0)
{ {
logger.Debug($"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts - {assortItemsToProcess.Count} items"); logger.Debug(
$"Took {stopwatch.ElapsedMilliseconds}ms to GetRagfairAssorts - {assortItemsToProcess.Count} items"
);
} }
stopwatch.Restart(); stopwatch.Restart();
@@ -369,10 +401,13 @@ public class RagfairOfferGenerator(
{ {
tasks.Add( tasks.Add(
Task.Factory.StartNew(() => Task.Factory.StartNew(() =>
{ {
CreateOffersFromAssort(assortItem, replacingExpiredOffers, ragfairConfig.Dynamic); CreateOffersFromAssort(
} assortItem,
) replacingExpiredOffers,
ragfairConfig.Dynamic
);
})
); );
} }
@@ -408,7 +443,10 @@ public class RagfairOfferGenerator(
// Armor presets can hold plates above the allowed flea level, remove if necessary // Armor presets can hold plates above the allowed flea level, remove if necessary
if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList) if (isPreset && ragfairConfig.Dynamic.Blacklist.EnableBsgList)
{ {
RemoveBannedPlatesFromPreset(assortItemWithChildren, ragfairConfig.Dynamic.Blacklist.ArmorPlate); RemoveBannedPlatesFromPreset(
assortItemWithChildren,
ragfairConfig.Dynamic.Blacklist.ArmorPlate
);
} }
// Get number of offers to create // Get number of offers to create
@@ -427,7 +465,12 @@ public class RagfairOfferGenerator(
clonedAssort[0].ParentId = null; clonedAssort[0].ParentId = null;
clonedAssort[0].SlotId = null; clonedAssort[0].SlotId = null;
CreateSingleOfferForItem(hashUtil.Generate(), clonedAssort, isPreset, itemToSellDetails.Value); CreateSingleOfferForItem(
hashUtil.Generate(),
clonedAssort,
isPreset,
itemToSellDetails.Value
);
} }
} }
@@ -443,14 +486,16 @@ public class RagfairOfferGenerator(
) )
{ {
if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template)) if (!itemHelper.ArmorItemCanHoldMods(presetWithChildren[0].Template))
// Cant hold armor inserts, skip // Cant hold armor inserts, skip
{ {
return false; 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) if (plateSlots.Count == 0)
// Has no plate slots e.g. "front_plate", exit // Has no plate slots e.g. "front_plate", exit
{ {
return false; return false;
} }
@@ -500,10 +545,10 @@ public class RagfairOfferGenerator(
var isBarterOffer = randomUtil.GetChance100(ragfairConfig.Dynamic.Barter.ChancePercent); var isBarterOffer = randomUtil.GetChance100(ragfairConfig.Dynamic.Barter.ChancePercent);
var isPackOffer = var isPackOffer =
randomUtil.GetChance100(ragfairConfig.Dynamic.Pack.ChancePercent) && randomUtil.GetChance100(ragfairConfig.Dynamic.Pack.ChancePercent)
!isBarterOffer && && !isBarterOffer
itemWithChildren.Count == 1 && && itemWithChildren.Count == 1
itemHelper.IsOfBaseclasses( && itemHelper.IsOfBaseclasses(
itemWithChildren[0].Template, itemWithChildren[0].Template,
ragfairConfig.Dynamic.Pack.ItemTypeWhitelist ragfairConfig.Dynamic.Pack.ItemTypeWhitelist
); );
@@ -513,15 +558,21 @@ public class RagfairOfferGenerator(
{ {
var armorConfig = ragfairConfig.Dynamic.Armor; var armorConfig = ragfairConfig.Dynamic.Armor;
var shouldRemovePlates = randomUtil.GetChance100(armorConfig.RemoveRemovablePlateChance); var shouldRemovePlates = randomUtil.GetChance100(
if (shouldRemovePlates && itemHelper.ArmorItemHasRemovablePlateSlots(itemWithChildren[0].Template)) armorConfig.RemoveRemovablePlateChance
);
if (
shouldRemovePlates
&& itemHelper.ArmorItemHasRemovablePlateSlots(itemWithChildren[0].Template)
)
{ {
var offerItemPlatesToRemove = itemWithChildren.Where(item => var offerItemPlatesToRemove = itemWithChildren.Where(item =>
armorConfig.PlateSlotIdToRemovePool.Contains(item.SlotId?.ToLower()) 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 // 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)) var indexesToRemove = offerItemPlatesToRemove
.Select(plateItem => itemWithChildren.IndexOf(plateItem))
.ToHashSet(); .ToHashSet();
foreach (var index in indexesToRemove.OrderByDescending(x => x)) foreach (var index in indexesToRemove.OrderByDescending(x => x))
{ {
@@ -540,7 +591,11 @@ public class RagfairOfferGenerator(
); );
// Don't randomise pack items // Don't randomise pack items
barterScheme = CreateCurrencyBarterScheme(itemWithChildren, isPackOffer, desiredStackSize); barterScheme = CreateCurrencyBarterScheme(
itemWithChildren,
isPackOffer,
desiredStackSize
);
} }
else if (isBarterOffer) else if (isBarterOffer)
{ {
@@ -600,13 +655,14 @@ public class RagfairOfferGenerator(
} }
var blacklist = ragfairConfig.Dynamic.Blacklist; var blacklist = ragfairConfig.Dynamic.Blacklist;
var childAssortItems = assortsClone.Items var childAssortItems = assortsClone
.Where(x => !string.Equals(x.ParentId, "hideout", StringComparison.Ordinal)).ToList(); .Items.Where(x => !string.Equals(x.ParentId, "hideout", StringComparison.Ordinal))
.ToList();
foreach (var item in assortsClone.Items) foreach (var item in assortsClone.Items)
{ {
// We only want to process 'base/root' items, no children // We only want to process 'base/root' items, no children
if (item.SlotId != "hideout") if (item.SlotId != "hideout")
// skip mod items // skip mod items
{ {
continue; continue;
} }
@@ -617,12 +673,17 @@ public class RagfairOfferGenerator(
var itemDetails = itemHelper.GetItem(item.Template); var itemDetails = itemHelper.GetItem(item.Template);
if (!itemDetails.Key) if (!itemDetails.Key)
{ {
logger.Warning(localisationService.GetText("ragfair-tpl_not_a_valid_item", item.Template)); logger.Warning(
localisationService.GetText("ragfair-tpl_not_a_valid_item", item.Template)
);
continue; continue;
} }
// Don't include items that BSG has blacklisted from flea // Don't include items that BSG has blacklisted from flea
if (blacklist.EnableBsgList && !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false)) if (
blacklist.EnableBsgList
&& !(itemDetails.Value?.Properties?.CanSellOnRagfair ?? false)
)
{ {
continue; continue;
} }
@@ -631,7 +692,7 @@ public class RagfairOfferGenerator(
var isPreset = presetHelper.IsPreset(item.Id); var isPreset = presetHelper.IsPreset(item.Id);
var items = isPreset var items = isPreset
? ragfairServerHelper.GetPresetItems(item) ? ragfairServerHelper.GetPresetItems(item)
: [item, ..itemHelper.FindAndReturnChildrenByAssort(item.Id, childAssortItems)]; : [item, .. itemHelper.FindAndReturnChildrenByAssort(item.Id, childAssortItems)];
if (!assortsClone.BarterScheme.TryGetValue(item.Id, out var barterScheme)) if (!assortsClone.BarterScheme.TryGetValue(item.Id, out var barterScheme))
{ {
@@ -642,7 +703,7 @@ public class RagfairOfferGenerator(
{ {
itemId = item.Id, itemId = item.Id,
tpl = item.Template, tpl = item.Template,
name = trader.Base.Nickname name = trader.Base.Nickname,
} }
) )
); );
@@ -652,7 +713,14 @@ public class RagfairOfferGenerator(
var barterSchemeItems = barterScheme[0]; var barterSchemeItems = barterScheme[0];
var loyalLevel = assortsClone.LoyalLevelItems[item.Id]; var loyalLevel = assortsClone.LoyalLevelItems[item.Id];
CreateAndAddFleaOffer(traderID, time, items, barterSchemeItems, loyalLevel, (int?) item.Upd.StackObjectsCount ?? 1); CreateAndAddFleaOffer(
traderID,
time,
items,
barterSchemeItems,
loyalLevel,
(int?)item.Upd.StackObjectsCount ?? 1
);
// Refresh complete, reset flag to false // Refresh complete, reset flag to false
trader.Base.RefreshTraderRagfairOffers = false; trader.Base.RefreshTraderRagfairOffers = false;
@@ -666,7 +734,11 @@ public class RagfairOfferGenerator(
/// <param name="userID"> ID of owner of item </param> /// <param name="userID"> ID of owner of item </param>
/// <param name="itemWithMods"> Item and mods, get condition of first item (only first array item is modified) </param> /// <param name="itemWithMods"> Item and mods, get condition of first item (only first array item is modified) </param>
/// <param name="itemDetails"> DB details of first item</param> /// <param name="itemDetails"> DB details of first item</param>
protected void RandomiseOfferItemUpdProperties(string userID, List<Item> itemWithMods, TemplateItem itemDetails) protected void RandomiseOfferItemUpdProperties(
string userID,
List<Item> itemWithMods,
TemplateItem itemDetails
)
{ {
// Add any missing properties to first item in array // Add any missing properties to first item in array
AddMissingConditions(itemWithMods[0]); AddMissingConditions(itemWithMods[0]);
@@ -675,13 +747,17 @@ public class RagfairOfferGenerator(
{ {
var parentId = GetDynamicConditionIdForTpl(itemDetails.Id); var parentId = GetDynamicConditionIdForTpl(itemDetails.Id);
if (string.IsNullOrEmpty(parentId)) if (string.IsNullOrEmpty(parentId))
// No condition details found, don't proceed with modifying item conditions // No condition details found, don't proceed with modifying item conditions
{ {
return; return;
} }
// Roll random chance to randomise item condition // Roll random chance to randomise item condition
if (randomUtil.GetChance100(ragfairConfig.Dynamic.Condition[parentId].ConditionChance * 100)) if (
randomUtil.GetChance100(
ragfairConfig.Dynamic.Condition[parentId].ConditionChance * 100
)
)
{ {
RandomiseItemCondition(parentId, itemWithMods, itemDetails); RandomiseItemCondition(parentId, itemWithMods, itemDetails);
} }
@@ -723,29 +799,35 @@ public class RagfairOfferGenerator(
var rootItem = itemWithMods[0]; var rootItem = itemWithMods[0];
var itemConditionValues = ragfairConfig.Dynamic.Condition[conditionSettingsId]; var itemConditionValues = ragfairConfig.Dynamic.Condition[conditionSettingsId];
var maxMultiplier = randomUtil.GetDouble(itemConditionValues.Max.Min, itemConditionValues.Max.Min); var maxMultiplier = randomUtil.GetDouble(
itemConditionValues.Max.Min,
itemConditionValues.Max.Min
);
var currentMultiplier = randomUtil.GetDouble( var currentMultiplier = randomUtil.GetDouble(
itemConditionValues.Current.Min, itemConditionValues.Current.Min,
itemConditionValues.Current.Max itemConditionValues.Current.Max
); );
// Randomise armor + plates + armor related things // Randomise armor + plates + armor related things
if (itemHelper.ArmorItemCanHoldMods(rootItem.Template) || if (
itemHelper.IsOfBaseclasses(rootItem.Template, [BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT]) itemHelper.ArmorItemCanHoldMods(rootItem.Template)
) || itemHelper.IsOfBaseclasses(
rootItem.Template,
[BaseClasses.ARMOR_PLATE, BaseClasses.ARMORED_EQUIPMENT]
)
)
{ {
RandomiseArmorDurabilityValues(itemWithMods, currentMultiplier, maxMultiplier); RandomiseArmorDurabilityValues(itemWithMods, currentMultiplier, maxMultiplier);
// Add hits to visor // 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) if (randomUtil.GetChance100(25) && visorMod != null)
{ {
itemHelper.AddUpdObjectToItem(visorMod); itemHelper.AddUpdObjectToItem(visorMod);
visorMod.Upd.FaceShield = new UpdFaceShield visorMod.Upd.FaceShield = new UpdFaceShield { Hits = randomUtil.GetInt(1, 3) };
{
Hits = randomUtil.GetInt(1, 3)
};
} }
return; return;
@@ -754,7 +836,12 @@ public class RagfairOfferGenerator(
// Randomise Weapons // Randomise Weapons
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.WEAPON)) if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.WEAPON))
{ {
RandomiseWeaponDurability(itemWithMods[0], itemDetails, maxMultiplier, currentMultiplier); RandomiseWeaponDurability(
itemWithMods[0],
itemDetails,
maxMultiplier,
currentMultiplier
);
return; return;
} }
@@ -762,7 +849,7 @@ public class RagfairOfferGenerator(
if (rootItem.Upd?.MedKit != null) if (rootItem.Upd?.MedKit != null)
{ {
// Randomize health // 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; rootItem.Upd.MedKit.HpResource = hpResource == 0D ? 1D : hpResource;
return; return;
} }
@@ -770,14 +857,15 @@ public class RagfairOfferGenerator(
if (rootItem.Upd?.Key != null && itemDetails.Properties.MaximumNumberOfUsage > 1) if (rootItem.Upd?.Key != null && itemDetails.Properties.MaximumNumberOfUsage > 1)
{ {
// Randomize key uses // 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; return;
} }
if (rootItem.Upd?.FoodDrink != null) if (rootItem.Upd?.FoodDrink != null)
{ {
// randomize food/drink value // 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; rootItem.Upd.FoodDrink.HpPercent = hpPercent == 0D ? 1D : hpPercent;
return; return;
@@ -786,7 +874,9 @@ public class RagfairOfferGenerator(
if (rootItem.Upd?.RepairKit != null) if (rootItem.Upd?.RepairKit != null)
{ {
// randomize repair kit (armor/weapon) uses // 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; rootItem.Upd.RepairKit.Resource = resource == 0D ? 1D : resource;
return; return;
@@ -795,11 +885,11 @@ public class RagfairOfferGenerator(
if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL)) if (itemHelper.IsOfBaseclass(itemDetails.Id, BaseClasses.FUEL))
{ {
var totalCapacity = itemDetails.Properties.MaxResource; var totalCapacity = itemDetails.Properties.MaxResource;
var remainingFuel = Math.Round((double) totalCapacity * maxMultiplier); var remainingFuel = Math.Round((double)totalCapacity * maxMultiplier);
rootItem.Upd.Resource = new UpdResource rootItem.Upd.Resource = new UpdResource
{ {
UnitsConsumed = totalCapacity - remainingFuel, UnitsConsumed = totalCapacity - remainingFuel,
Value = remainingFuel Value = remainingFuel,
}; };
} }
} }
@@ -821,13 +911,19 @@ public class RagfairOfferGenerator(
// Max // Max
var baseMaxDurability = itemDbDetails.Properties.MaxDurability; var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability; 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 // Current
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability; var lowestCurrentDurability =
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability)); randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability;
var chosenCurrentDurability = Math.Round(
randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability)
);
item.Upd.Repairable.Durability = chosenCurrentDurability == 0 ? 1D : chosenCurrentDurability; // Never var value become 0 item.Upd.Repairable.Durability =
chosenCurrentDurability == 0 ? 1D : chosenCurrentDurability; // Never var value become 0
item.Upd.Repairable.MaxDurability = chosenMaxDurability; item.Upd.Repairable.MaxDurability = chosenMaxDurability;
} }
@@ -851,16 +947,22 @@ public class RagfairOfferGenerator(
itemHelper.AddUpdObjectToItem(armorItem); itemHelper.AddUpdObjectToItem(armorItem);
var baseMaxDurability = itemDbDetails.Properties.MaxDurability; var baseMaxDurability = itemDbDetails.Properties.MaxDurability;
var lowestMaxDurability = randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability; var lowestMaxDurability =
var chosenMaxDurability = Math.Round(randomUtil.GetDouble((double) lowestMaxDurability, (double) baseMaxDurability)); randomUtil.GetDouble(maxMultiplier, 1) * baseMaxDurability;
var chosenMaxDurability = Math.Round(
randomUtil.GetDouble((double)lowestMaxDurability, (double)baseMaxDurability)
);
var lowestCurrentDurability = randomUtil.GetDouble(currentMultiplier, 1) * chosenMaxDurability; var lowestCurrentDurability =
var chosenCurrentDurability = Math.Round(randomUtil.GetDouble(lowestCurrentDurability, chosenMaxDurability)); 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 Durability = chosenCurrentDurability == 0D ? 1D : chosenCurrentDurability, // Never var value become 0
MaxDurability = chosenMaxDurability MaxDurability = chosenMaxDurability,
}; };
} }
} }
@@ -886,7 +988,7 @@ public class RagfairOfferGenerator(
item.Upd.Repairable = new UpdRepairable item.Upd.Repairable = new UpdRepairable
{ {
Durability = props.Durability, Durability = props.Durability,
MaxDurability = props.Durability MaxDurability = props.Durability,
}; };
return; return;
@@ -894,20 +996,14 @@ public class RagfairOfferGenerator(
if (isMedkit && props.MaxHpResource > 0) if (isMedkit && props.MaxHpResource > 0)
{ {
item.Upd.MedKit = new UpdMedKit item.Upd.MedKit = new UpdMedKit { HpResource = props.MaxHpResource };
{
HpResource = props.MaxHpResource
};
return; return;
} }
if (isKey) if (isKey)
{ {
item.Upd.Key = new UpdKey item.Upd.Key = new UpdKey { NumberOfUsages = 0 };
{
NumberOfUsages = 0
};
return; return;
} }
@@ -915,20 +1011,14 @@ public class RagfairOfferGenerator(
// Food/drink // Food/drink
if (isConsumable) if (isConsumable)
{ {
item.Upd.FoodDrink = new UpdFoodDrink item.Upd.FoodDrink = new UpdFoodDrink { HpPercent = props.MaxResource };
{
HpPercent = props.MaxResource
};
return; return;
} }
if (isRepairKit) if (isRepairKit)
{ {
item.Upd.RepairKit = new UpdRepairKit item.Upd.RepairKit = new UpdRepairKit { Resource = props.MaxRepairResource };
{
Resource = props.MaxRepairResource
};
} }
} }
@@ -938,7 +1028,10 @@ public class RagfairOfferGenerator(
/// <param name="offerItems"> Items for sale in offer </param> /// <param name="offerItems"> Items for sale in offer </param>
/// <param name="barterConfig"> Barter config from ragfairConfig.Dynamic.barter </param> /// <param name="barterConfig"> Barter config from ragfairConfig.Dynamic.barter </param>
/// <returns> Barter scheme </returns> /// <returns> Barter scheme </returns>
protected List<BarterScheme> CreateBarterBarterScheme(List<Item> offerItems, BarterDetails barterConfig) protected List<BarterScheme> CreateBarterBarterScheme(
List<Item> offerItems,
BarterDetails barterConfig
)
{ {
// Get flea price of item being sold // Get flea price of item being sold
var priceOfOfferItem = ragfairPriceService.GetDynamicOfferPriceForOffer( var priceOfOfferItem = ragfairPriceService.GetDynamicOfferPriceForOffer(
@@ -954,13 +1047,17 @@ public class RagfairOfferGenerator(
} }
// Get a randomised number of barter items to list offer for // Get a randomised number of barter items to list offer for
var barterItemCount = randomUtil.GetInt(barterConfig.ItemCountMin, barterConfig.ItemCountMax); var barterItemCount = randomUtil.GetInt(
barterConfig.ItemCountMin,
barterConfig.ItemCountMax
);
// Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k // Get desired cost of individual item offer will be listed for e.g. offer = 15k, item count = 3, desired item cost = 5k
var desiredItemCostRouble = Math.Round(priceOfOfferItem / barterItemCount); var desiredItemCostRouble = Math.Round(priceOfOfferItem / barterItemCount);
// Rouble amount to go above/below when looking for an item (Wiggle cost of item a little) // Rouble amount to go above/below when looking for an item (Wiggle cost of item a little)
var offerCostVarianceRoubles = desiredItemCostRouble * barterConfig.PriceRangeVariancePercent / 100; var offerCostVarianceRoubles =
desiredItemCostRouble * barterConfig.PriceRangeVariancePercent / 100;
// Dict of items and their flea price (cached on first use) // Dict of items and their flea price (cached on first use)
var itemFleaPrices = GetFleaPricesAsArray(); var itemFleaPrices = GetFleaPricesAsArray();
@@ -969,13 +1066,15 @@ public class RagfairOfferGenerator(
var min = desiredItemCostRouble - offerCostVarianceRoubles; var min = desiredItemCostRouble - offerCostVarianceRoubles;
var max = desiredItemCostRouble + offerCostVarianceRoubles; var max = desiredItemCostRouble + offerCostVarianceRoubles;
var itemsInsidePriceBounds = itemFleaPrices.Where(itemAndPrice => var itemsInsidePriceBounds = itemFleaPrices.Where(itemAndPrice =>
itemAndPrice.Price >= min && itemAndPrice.Price >= min
itemAndPrice.Price <= max && && itemAndPrice.Price <= max
!string.Equals(itemAndPrice.Tpl, offerItems[0].Template, && !string.Equals(
StringComparison.OrdinalIgnoreCase) // Don't allow the item being sold to be chosen itemAndPrice.Tpl,
offerItems[0].Template,
StringComparison.OrdinalIgnoreCase
) // Don't allow the item being sold to be chosen
); );
// No items on flea have a matching price, fall back to currency // No items on flea have a matching price, fall back to currency
if (!itemsInsidePriceBounds.Any()) if (!itemsInsidePriceBounds.Any())
{ {
@@ -985,14 +1084,7 @@ public class RagfairOfferGenerator(
// Choose random item from price-filtered flea items // Choose random item from price-filtered flea items
var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds.ToList()); var randomItem = randomUtil.GetArrayValue(itemsInsidePriceBounds.ToList());
return return [new BarterScheme { Count = barterItemCount, Template = randomItem.Tpl }];
[
new BarterScheme
{
Count = barterItemCount,
Template = randomItem.Tpl
}
];
} }
/// <summary> /// <summary>
@@ -1008,16 +1100,13 @@ public class RagfairOfferGenerator(
// Only get prices for items that also exist in items.json // Only get prices for items that also exist in items.json
var filteredFleaItems = fleaPrices var filteredFleaItems = fleaPrices
.Select(kvTpl => new TplWithFleaPrice .Select(kvTpl => new TplWithFleaPrice { Tpl = kvTpl.Key, Price = kvTpl.Value })
{
Tpl = kvTpl.Key,
Price = kvTpl.Value
}
)
.Where(item => itemHelper.GetItem(item.Tpl).Key); .Where(item => itemHelper.GetItem(item.Tpl).Key);
var itemTypeBlacklist = ragfairConfig.Dynamic.Barter.ItemTypeBlacklist; var itemTypeBlacklist = ragfairConfig.Dynamic.Barter.ItemTypeBlacklist;
allowedFleaPriceItemsForBarter = filteredFleaItems.Where(item => !itemHelper.IsOfBaseclasses(item.Tpl, itemTypeBlacklist)).ToList(); allowedFleaPriceItemsForBarter = filteredFleaItems
.Where(item => !itemHelper.IsOfBaseclasses(item.Tpl, itemTypeBlacklist))
.ToList();
} }
return allowedFleaPriceItemsForBarter; return allowedFleaPriceItemsForBarter;
@@ -1037,15 +1126,13 @@ public class RagfairOfferGenerator(
) )
{ {
var currency = ragfairServerHelper.GetDynamicOfferCurrency(); var currency = ragfairServerHelper.GetDynamicOfferCurrency();
var price = ragfairPriceService.GetDynamicOfferPriceForOffer(offerWithChildren, currency, isPackOffer) * multiplier; var price =
ragfairPriceService.GetDynamicOfferPriceForOffer(
offerWithChildren,
currency,
isPackOffer
) * multiplier;
return return [new BarterScheme { Count = price, Template = currency }];
[
new BarterScheme
{
Count = price,
Template = currency
}
];
} }
} }
@@ -37,29 +37,10 @@ public class RepeatableQuestGenerator(
/// </summary> /// </summary>
private static readonly Dictionary<string, List<string>> _bodyPartsToClient = new() private static readonly Dictionary<string, List<string>> _bodyPartsToClient = new()
{ {
{ { BodyParts.Arms, [BodyParts.LeftArm, BodyParts.RightArm] },
BodyParts.Arms, [ { BodyParts.Legs, [BodyParts.LeftLeg, BodyParts.RightLeg] },
BodyParts.LeftArm, { BodyParts.Head, [BodyParts.Head] },
BodyParts.RightArm { BodyParts.Chest, [BodyParts.Chest, BodyParts.Stomach] },
]
},
{
BodyParts.Legs, [
BodyParts.LeftLeg,
BodyParts.RightLeg
]
},
{
BodyParts.Head, [
BodyParts.Head
]
},
{
BodyParts.Chest, [
BodyParts.Chest,
BodyParts.Stomach
]
},
}; };
protected int _maxRandomNumberAttempts = 6; protected int _maxRandomNumberAttempts = 6;
@@ -88,8 +69,8 @@ public class RepeatableQuestGenerator(
var questType = _randomUtil.DrawRandomFromList(questTypePool.Types).First(); var questType = _randomUtil.DrawRandomFromList(questTypePool.Types).First();
// Get traders from whitelist and filter by quest type availability // Get traders from whitelist and filter by quest type availability
var traders = repeatableConfig.TraderWhitelist var traders = repeatableConfig
.Where(x => x.QuestTypes.Contains(questType)) .TraderWhitelist.Where(x => x.QuestTypes.Contains(questType))
.Select(x => x.TraderId) .Select(x => x.TraderId)
.ToList(); .ToList();
// filter out locked traders // filter out locked traders
@@ -98,11 +79,34 @@ public class RepeatableQuestGenerator(
return questType switch return questType switch
{ {
"Elimination" => GenerateEliminationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig), "Elimination" => GenerateEliminationQuest(
"Completion" => GenerateCompletionQuest(sessionId, pmcLevel, traderId, repeatableConfig), sessionId,
"Exploration" => GenerateExplorationQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig), pmcLevel,
"Pickup" => GeneratePickupQuest(sessionId, pmcLevel, traderId, questTypePool, repeatableConfig), traderId,
_ => null questTypePool,
repeatableConfig
),
"Completion" => GenerateCompletionQuest(
sessionId,
pmcLevel,
traderId,
repeatableConfig
),
"Exploration" => GenerateExplorationQuest(
sessionId,
pmcLevel,
traderId,
questTypePool,
repeatableConfig
),
"Pickup" => GeneratePickupQuest(
sessionId,
pmcLevel,
traderId,
questTypePool,
repeatableConfig
),
_ => null,
}; };
} }
@@ -128,13 +132,31 @@ public class RepeatableQuestGenerator(
{ {
var rand = new Random(); var rand = new Random();
var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(pmcLevel, repeatableConfig); var eliminationConfig = _repeatableQuestHelper.GetEliminationConfigByPmcLevel(
pmcLevel,
repeatableConfig
);
var locationsConfig = repeatableConfig.Locations; var locationsConfig = repeatableConfig.Locations;
var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(_mathUtil, _cloner, eliminationConfig.Targets); var targetsConfig = new ProbabilityObjectArray<string, BossInfo>(
var bodyPartsConfig = new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.BodyParts); _mathUtil,
var weaponCategoryRequirementConfig = _cloner,
new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.WeaponCategoryRequirements); eliminationConfig.Targets
var weaponRequirementConfig = new ProbabilityObjectArray<string, List<string>>(_mathUtil, _cloner, eliminationConfig.WeaponRequirements); );
var bodyPartsConfig = new ProbabilityObjectArray<string, List<string>>(
_mathUtil,
_cloner,
eliminationConfig.BodyParts
);
var weaponCategoryRequirementConfig = new ProbabilityObjectArray<string, List<string>>(
_mathUtil,
_cloner,
eliminationConfig.WeaponCategoryRequirements
);
var weaponRequirementConfig = new ProbabilityObjectArray<string, List<string>>(
_mathUtil,
_cloner,
eliminationConfig.WeaponRequirements
);
// the difficulty of the quest varies in difficulty depending on the condition // the difficulty of the quest varies in difficulty depending on the condition
// possible conditions are // possible conditions are
@@ -157,8 +179,7 @@ public class RepeatableQuestGenerator(
// times the number of kills we have to perform): // times the number of kills we have to perform):
// The minimum difficulty is the difficulty for the most probable (= easiest target) with no additional conditions // The minimum difficulty is the difficulty for the most probable (= easiest target) with no additional conditions
var minDifficulty = var minDifficulty = 1 / targetsConfig.MaxProbability(); // min difficulty is the lowest amount of scavs without any constraints
1 / targetsConfig.MaxProbability(); // min difficulty is the lowest amount of scavs without any constraints
// Target on bodyPart max. difficulty is that of the least probable element // Target on bodyPart max. difficulty is that of the least probable element
var maxTargetDifficulty = 1 / targetsConfig.MinProbability(); var maxTargetDifficulty = 1 / targetsConfig.MinProbability();
@@ -172,7 +193,10 @@ public class RepeatableQuestGenerator(
var targetPool = questTypePool.Pool.Elimination; var targetPool = questTypePool.Pool.Elimination;
targetsConfig = targetsConfig.Filter(x => targetPool.Targets.ContainsKey(x.Key)); targetsConfig = targetsConfig.Filter(x => targetPool.Targets.ContainsKey(x.Key));
if (targetsConfig.Count == 0 || targetsConfig.All(x => x.Data.IsBoss.GetValueOrDefault(false))) if (
targetsConfig.Count == 0
|| targetsConfig.All(x => x.Data.IsBoss.GetValueOrDefault(false))
)
{ {
// There are no more targets left for elimination; delete it as a possible quest type // There are no more targets left for elimination; delete it as a possible quest type
// also if only bosses are left we need to leave otherwise it's a guaranteed boss elimination // also if only bosses are left we need to leave otherwise it's a guaranteed boss elimination
@@ -190,9 +214,13 @@ public class RepeatableQuestGenerator(
// we use any as location if "any" is in the pool, and we don't hit the specific location random // we use any as location if "any" is in the pool, and we don't hit the specific location random
// we use any also if the random condition is not met in case only "any" was in the pool // we use any also if the random condition is not met in case only "any" was in the pool
var locationKey = "any"; var locationKey = "any";
if (locations.Contains("any") && if (
(eliminationConfig.SpecificLocationProbability < rand.NextDouble() || locations.Count <= 1) locations.Contains("any")
) && (
eliminationConfig.SpecificLocationProbability < rand.NextDouble()
|| locations.Count <= 1
)
)
{ {
locationKey = "any"; locationKey = "any";
targetPool.Targets.Remove(botTypeToEliminate); targetPool.Targets.Remove(botTypeToEliminate);
@@ -207,17 +235,21 @@ public class RepeatableQuestGenerator(
locationKey = _randomUtil.DrawRandomFromList(locations).FirstOrDefault(); locationKey = _randomUtil.DrawRandomFromList(locations).FirstOrDefault();
// Get a pool of locations the chosen bot type can be eliminated on // Get a pool of locations the chosen bot type can be eliminated on
if (!targetPool.Targets.TryGetValue( if (
!targetPool.Targets.TryGetValue(
botTypeToEliminate, botTypeToEliminate,
out var possibleLocationPool out var possibleLocationPool
)) )
)
{ {
_logger.Warning($"Bot to kill: {botTypeToEliminate} not found in elimination dict"); _logger.Warning(
$"Bot to kill: {botTypeToEliminate} not found in elimination dict"
);
} }
// Filter locations bot can be killed on to just those not chosen by key // Filter locations bot can be killed on to just those not chosen by key
possibleLocationPool.Locations = possibleLocationPool.Locations possibleLocationPool.Locations = possibleLocationPool
.Where(location => location != locationKey) .Locations.Where(location => location != locationKey)
.ToList(); .ToList();
// None left after filtering // None left after filtering
@@ -231,7 +263,11 @@ public class RepeatableQuestGenerator(
else else
{ {
// Never should reach this if everything works out // Never should reach this if everything works out
_logger.Error(_localisationService.GetText("quest-repeatable_elimination_generation_failed_please_report")); _logger.Error(
_localisationService.GetText(
"quest-repeatable_elimination_generation_failed_please_report"
)
);
} }
} }
@@ -267,49 +303,54 @@ public class RepeatableQuestGenerator(
// Draw a distance condition // Draw a distance condition
int? distance = null; int? distance = null;
var distanceDifficulty = 0; var distanceDifficulty = 0;
var isDistanceRequirementAllowed = !eliminationConfig.DistLocationBlacklist.Contains(locationKey); var isDistanceRequirementAllowed = !eliminationConfig.DistLocationBlacklist.Contains(
locationKey
);
if (targetsConfig.Data(botTypeToEliminate).IsBoss.GetValueOrDefault(false)) if (targetsConfig.Data(botTypeToEliminate).IsBoss.GetValueOrDefault(false))
{ {
// Get all boss spawn information // Get all boss spawn information
var bossSpawns = _databaseService.GetLocations() var bossSpawns = _databaseService
.GetLocations()
.GetDictionary() .GetDictionary()
.Select(x => x.Value) .Select(x => x.Value)
.Where(x => x.Base?.Id != null) .Where(x => x.Base?.Id != null)
.Select(x => new .Select(x => new { x.Base.Id, BossSpawn = x.Base.BossLocationSpawn });
{
x.Base.Id,
BossSpawn = x.Base.BossLocationSpawn
}
);
// filter for the current boss to spawn on map // filter for the current boss to spawn on map
var thisBossSpawns = bossSpawns var thisBossSpawns = bossSpawns
.Select(x => new .Select(x => new
{ {
x.Id, x.Id,
BossSpawn = x.BossSpawn BossSpawn = x.BossSpawn.Where(e => e.BossName == botTypeToEliminate),
.Where(e => e.BossName == botTypeToEliminate) })
}
)
.Where(x => x.BossSpawn.Count() > 0); .Where(x => x.BossSpawn.Count() > 0);
// remove blacklisted locations // remove blacklisted locations
var allowedSpawns = thisBossSpawns.Where(x => !eliminationConfig.DistLocationBlacklist.Contains(x.Id)); var allowedSpawns = thisBossSpawns.Where(x =>
!eliminationConfig.DistLocationBlacklist.Contains(x.Id)
);
// if the boss spawns on nom-blacklisted locations and the current location is allowed we can generate a distance kill requirement // if the boss spawns on nom-blacklisted locations and the current location is allowed we can generate a distance kill requirement
isDistanceRequirementAllowed = isDistanceRequirementAllowed && allowedSpawns.Count() > 0; isDistanceRequirementAllowed =
isDistanceRequirementAllowed && allowedSpawns.Count() > 0;
} }
if (eliminationConfig.DistanceProbability > rand.NextDouble() && isDistanceRequirementAllowed) if (
eliminationConfig.DistanceProbability > rand.NextDouble()
&& isDistanceRequirementAllowed
)
{ {
// Random distance with lower values more likely; simple distribution for starters... // Random distance with lower values more likely; simple distribution for starters...
distance = (int) Math.Floor( distance = (int)
Math.Abs(rand.NextDouble() - rand.NextDouble()) * Math.Floor(
(1 + eliminationConfig.MaxDistance - eliminationConfig.MinDistance) + Math.Abs(rand.NextDouble() - rand.NextDouble())
eliminationConfig.MinDistance ?? * (1 + eliminationConfig.MaxDistance - eliminationConfig.MinDistance)
0 + eliminationConfig.MinDistance
); ?? 0
);
distance = (int) Math.Ceiling((decimal) (distance / 5)) * 5; distance = (int)Math.Ceiling((decimal)(distance / 5)) * 5;
distanceDifficulty = (int) (maxDistDifficulty * distance / eliminationConfig.MaxDistance); distanceDifficulty = (int)(
maxDistDifficulty * distance / eliminationConfig.MaxDistance
);
} }
string? allowedWeaponsCategory = null; string? allowedWeaponsCategory = null;
@@ -321,18 +362,18 @@ public class RepeatableQuestGenerator(
List<string> weaponTypeBlacklist = ["Shotgun", "Pistol"]; List<string> weaponTypeBlacklist = ["Shotgun", "Pistol"];
// Filter out close range weapons from long distance requirement // Filter out close range weapons from long distance requirement
weaponCategoryRequirementConfig weaponCategoryRequirementConfig.RemoveAll(category =>
.RemoveAll(category => weaponTypeBlacklist weaponTypeBlacklist.Contains(category.Key)
.Contains(category.Key)); );
} }
else if (distance < 20) else if (distance < 20)
{ {
List<string> weaponTypeBlacklist = ["MarksmanRifle", "DMR"]; List<string> weaponTypeBlacklist = ["MarksmanRifle", "DMR"];
// Filter out far range weapons from close distance requirement // Filter out far range weapons from close distance requirement
weaponCategoryRequirementConfig weaponCategoryRequirementConfig.RemoveAll(category =>
.RemoveAll(category => weaponTypeBlacklist weaponTypeBlacklist.Contains(category.Key)
.Contains(category.Key)); );
} }
// Pick a weighted weapon category // Pick a weighted weapon category
@@ -344,16 +385,25 @@ public class RepeatableQuestGenerator(
// Only allow a specific weapon requirement if a weapon category was not chosen // Only allow a specific weapon requirement if a weapon category was not chosen
string? allowedWeapon = null; string? allowedWeapon = null;
if (allowedWeaponsCategory is not null && eliminationConfig.WeaponRequirementProbability > rand.NextDouble()) if (
allowedWeaponsCategory is not null
&& eliminationConfig.WeaponRequirementProbability > rand.NextDouble()
)
{ {
var weaponRequirement = weaponRequirementConfig.Draw(1, false); var weaponRequirement = weaponRequirementConfig.Draw(1, false);
var specificAllowedWeaponCategory = weaponRequirementConfig.Data(weaponRequirement[0]); var specificAllowedWeaponCategory = weaponRequirementConfig.Data(weaponRequirement[0]);
var allowedWeapons = _itemHelper.GetItemTplsOfBaseType(specificAllowedWeaponCategory[0]); var allowedWeapons = _itemHelper.GetItemTplsOfBaseType(
specificAllowedWeaponCategory[0]
);
allowedWeapon = _randomUtil.GetArrayValue(allowedWeapons); allowedWeapon = _randomUtil.GetArrayValue(allowedWeapons);
} }
// Draw how many npm kills are required // Draw how many npm kills are required
var desiredKillCount = GetEliminationKillCount(botTypeToEliminate, targetsConfig, eliminationConfig); var desiredKillCount = GetEliminationKillCount(
botTypeToEliminate,
targetsConfig,
eliminationConfig
);
var killDifficulty = desiredKillCount; var killDifficulty = desiredKillCount;
// not perfectly happy here; we give difficulty = 1 to the quest reward generation when we have the most difficult mission // not perfectly happy here; we give difficulty = 1 to the quest reward generation when we have the most difficult mission
@@ -372,7 +422,12 @@ public class RepeatableQuestGenerator(
// crazy maximum difficulty will lead to a higher difficulty reward gain factor than 1 // crazy maximum difficulty will lead to a higher difficulty reward gain factor than 1
var difficulty = _mathUtil.MapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2); var difficulty = _mathUtil.MapToRange(curDifficulty, minDifficulty, maxDifficulty, 0.5, 2);
var quest = GenerateRepeatableTemplate("Elimination", traderId, repeatableConfig.Side, sessionId); var quest = GenerateRepeatableTemplate(
"Elimination",
traderId,
repeatableConfig.Side,
sessionId
);
// ASSUMPTION: All fence quests are for scavs // ASSUMPTION: All fence quests are for scavs
if (traderId == Traders.FENCE) if (traderId == Traders.FENCE)
@@ -427,19 +482,29 @@ public class RepeatableQuestGenerator(
protected int GetEliminationKillCount( protected int GetEliminationKillCount(
string targetKey, string targetKey,
ProbabilityObjectArray<string, BossInfo> targetsConfig, ProbabilityObjectArray<string, BossInfo> targetsConfig,
EliminationConfig eliminationConfig) EliminationConfig eliminationConfig
)
{ {
if (targetsConfig.Data(targetKey).IsBoss.GetValueOrDefault(false)) if (targetsConfig.Data(targetKey).IsBoss.GetValueOrDefault(false))
{ {
return _randomUtil.RandInt(eliminationConfig.MinBossKills.Value, eliminationConfig.MaxBossKills + 1); return _randomUtil.RandInt(
eliminationConfig.MinBossKills.Value,
eliminationConfig.MaxBossKills + 1
);
} }
if (targetsConfig.Data(targetKey).IsPmc.GetValueOrDefault(false)) if (targetsConfig.Data(targetKey).IsPmc.GetValueOrDefault(false))
{ {
return _randomUtil.RandInt(eliminationConfig.MinPmcKills.Value, eliminationConfig.MaxPmcKills + 1); return _randomUtil.RandInt(
eliminationConfig.MinPmcKills.Value,
eliminationConfig.MaxPmcKills + 1
);
} }
return _randomUtil.RandInt(eliminationConfig.MinKills.Value, eliminationConfig.MaxKills + 1); return _randomUtil.RandInt(
eliminationConfig.MinKills.Value,
eliminationConfig.MaxKills + 1
);
} }
protected double DifficultyWeighing( protected double DifficultyWeighing(
@@ -447,7 +512,8 @@ public class RepeatableQuestGenerator(
double bodyPart, double bodyPart,
int dist, int dist,
int kill, int kill,
int weaponRequirement) int weaponRequirement
)
{ {
return Math.Sqrt(Math.Sqrt(target) + bodyPart + dist + weaponRequirement) * kill; return Math.Sqrt(Math.Sqrt(target) + bodyPart + dist + weaponRequirement) * kill;
} }
@@ -466,7 +532,7 @@ public class RepeatableQuestGenerator(
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
DynamicLocale = true, DynamicLocale = true,
Target = new ListOrT<string>(location, null), Target = new ListOrT<string>(location, null),
ConditionType = "Location" ConditionType = "Location",
}; };
} }
@@ -495,12 +561,8 @@ public class RepeatableQuestGenerator(
Value = 1, Value = 1,
ResetOnSessionEnd = false, ResetOnSessionEnd = false,
EnemyHealthEffects = [], EnemyHealthEffects = [],
Daytime = new DaytimeCounter Daytime = new DaytimeCounter { From = 0, To = 0 },
{ ConditionType = "Kills",
From = 0,
To = 0
},
ConditionType = "Kills"
}; };
if (target.StartsWith("boss")) if (target.StartsWith("boss"))
@@ -521,7 +583,7 @@ public class RepeatableQuestGenerator(
killConditionProps.Distance = new CounterConditionDistance killConditionProps.Distance = new CounterConditionDistance
{ {
CompareMethod = ">=", CompareMethod = ">=",
Value = distance.Value Value = distance.Value,
}; };
} }
@@ -567,39 +629,49 @@ public class RepeatableQuestGenerator(
var levelsConfig = repeatableConfig.RewardScaling.Levels; var levelsConfig = repeatableConfig.RewardScaling.Levels;
var roublesConfig = repeatableConfig.RewardScaling.Roubles; var roublesConfig = repeatableConfig.RewardScaling.Roubles;
var quest = GenerateRepeatableTemplate("Completion", traderId, repeatableConfig.Side, sessionId); var quest = GenerateRepeatableTemplate(
"Completion",
traderId,
repeatableConfig.Side,
sessionId
);
// Filter the items.json items to items the player must retrieve to complete quest: shouldn't be a quest item or "non-existent" // Filter the items.json items to items the player must retrieve to complete quest: shouldn't be a quest item or "non-existent"
var itemsToRetrievePool = GetItemsToRetrievePool(completionConfig, repeatableConfig.RewardBlacklist); var itemsToRetrievePool = GetItemsToRetrievePool(
completionConfig,
repeatableConfig.RewardBlacklist
);
// Be fair, don't value the items be more expensive than the reward // Be fair, don't value the items be more expensive than the reward
var multiplier = _randomUtil.GetDouble(0.5, 1); var multiplier = _randomUtil.GetDouble(0.5, 1);
var roublesBudget = Math.Floor( var roublesBudget = Math.Floor(
(double) (_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multiplier) (double)(_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * multiplier)
); );
roublesBudget = Math.Max(roublesBudget, 5000d); roublesBudget = Math.Max(roublesBudget, 5000d);
var itemSelection = itemsToRetrievePool.Where(itemTpl => _itemHelper.GetItemPrice(itemTpl) < roublesBudget var itemSelection = itemsToRetrievePool
) .Where(itemTpl => _itemHelper.GetItemPrice(itemTpl) < roublesBudget)
.ToList(); .ToList();
// We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as // We also have the option to use whitelist and/or blacklist which is defined in repeatableQuests.json as
// [{"minPlayerLevel": 1, "itemIds": ["id1",...]}, {"minPlayerLevel": 15, "itemIds": ["id3",...]}] // [{"minPlayerLevel": 1, "itemIds": ["id1",...]}, {"minPlayerLevel": 15, "itemIds": ["id3",...]}]
if (repeatableConfig.QuestConfig.Completion.UseWhitelist.GetValueOrDefault(false)) if (repeatableConfig.QuestConfig.Completion.UseWhitelist.GetValueOrDefault(false))
{ {
var itemWhitelist = _databaseService.GetTemplates().RepeatableQuests.Data.Completion.ItemsWhitelist; var itemWhitelist = _databaseService
.GetTemplates()
.RepeatableQuests.Data.Completion.ItemsWhitelist;
// Filter and concatenate items according to current player level // Filter and concatenate items according to current player level
var itemIdsWhitelisted = itemWhitelist var itemIdsWhitelisted = itemWhitelist
.Where(p => p.MinPlayerLevel <= pmcLevel) .Where(p => p.MinPlayerLevel <= pmcLevel)
.SelectMany(x => x.ItemIds) .SelectMany(x => x.ItemIds)
.ToHashSet(); //.Aggregate((a, p) => a.Concat(p.ItemIds), []); .ToHashSet(); //.Aggregate((a, p) => a.Concat(p.ItemIds), []);
itemSelection = itemSelection.Where(x => itemSelection = itemSelection
{ .Where(x =>
// Whitelist can contain item tpls and item base type ids {
return itemIdsWhitelisted.Any(v => _itemHelper.IsOfBaseclass(x, v)) || // Whitelist can contain item tpls and item base type ids
itemIdsWhitelisted.Contains(x); return itemIdsWhitelisted.Any(v => _itemHelper.IsOfBaseclass(x, v))
} || itemIdsWhitelisted.Contains(x);
) })
.ToList(); .ToList();
// check if items are missing // check if items are missing
// var flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []); // var flatList = itemSelection.reduce((a, il) => a.concat(il[0]), []);
@@ -608,7 +680,9 @@ public class RepeatableQuestGenerator(
if (repeatableConfig.QuestConfig.Completion.UseBlacklist.GetValueOrDefault(false)) if (repeatableConfig.QuestConfig.Completion.UseBlacklist.GetValueOrDefault(false))
{ {
var itemBlacklist = _databaseService.GetTemplates().RepeatableQuests.Data.Completion.ItemsBlacklist; var itemBlacklist = _databaseService
.GetTemplates()
.RepeatableQuests.Data.Completion.ItemsBlacklist;
// Filter and concatenate the arrays according to current player level // Filter and concatenate the arrays according to current player level
var itemIdsBlacklisted = itemBlacklist var itemIdsBlacklisted = itemBlacklist
@@ -616,12 +690,12 @@ public class RepeatableQuestGenerator(
.SelectMany(x => x.ItemIds) .SelectMany(x => x.ItemIds)
.ToHashSet(); //.Aggregate(List<ItemsBlacklist> , (a, p) => a.Concat(p.ItemIds) ); .ToHashSet(); //.Aggregate(List<ItemsBlacklist> , (a, p) => a.Concat(p.ItemIds) );
itemSelection = itemSelection.Where(x => itemSelection = itemSelection
{ .Where(x =>
return itemIdsBlacklisted.All(v => !_itemHelper.IsOfBaseclass(x, v)) || {
!itemIdsBlacklisted.Contains(x); return itemIdsBlacklisted.All(v => !_itemHelper.IsOfBaseclass(x, v))
} || !itemIdsBlacklisted.Contains(x);
) })
.ToList(); .ToList();
} }
@@ -638,7 +712,10 @@ public class RepeatableQuestGenerator(
} }
// Store the indexes of items we are asking player to supply // Store the indexes of items we are asking player to supply
var distinctItemsToRetrieveCount = _randomUtil.GetInt(1, completionConfig.UniqueItemCount.Value); var distinctItemsToRetrieveCount = _randomUtil.GetInt(
1,
completionConfig.UniqueItemCount.Value
);
var chosenRequirementItemsTpls = new List<string>(); var chosenRequirementItemsTpls = new List<string>();
var usedItemIndexes = new HashSet<int>(); var usedItemIndexes = new HashSet<int>();
for (var i = 0; i < distinctItemsToRetrieveCount; i++) for (var i = 0; i < distinctItemsToRetrieveCount; i++)
@@ -664,11 +741,7 @@ public class RepeatableQuestGenerator(
_logger.Error( _logger.Error(
_localisationService.GetText( _localisationService.GetText(
"repeatable-no_reward_item_found_in_price_range", "repeatable-no_reward_item_found_in_price_range",
new new { minPrice = 0, roublesBudget }
{
minPrice = 0,
roublesBudget
}
) )
); );
@@ -686,11 +759,11 @@ public class RepeatableQuestGenerator(
var value = minValue; var value = minValue;
// Get the value range within budget // Get the value range within budget
var x = (int) Math.Floor(roublesBudget / itemPrice); var x = (int)Math.Floor(roublesBudget / itemPrice);
maxValue = Math.Min(maxValue, x); maxValue = Math.Min(maxValue, x);
if (maxValue > minValue) if (maxValue > minValue)
// If it doesn't blow the budget we have for the request, draw a random amount of the selected // If it doesn't blow the budget we have for the request, draw a random amount of the selected
// Item type to be requested // Item type to be requested
{ {
value = _randomUtil.RandInt(minValue, maxValue + 1); value = _randomUtil.RandInt(minValue, maxValue + 1);
} }
@@ -699,13 +772,20 @@ public class RepeatableQuestGenerator(
// Push a CompletionCondition with the item and the amount of the item into quest // Push a CompletionCondition with the item and the amount of the item into quest
chosenRequirementItemsTpls.Add(tplChosen); chosenRequirementItemsTpls.Add(tplChosen);
quest.Conditions.AvailableForFinish.Add(GenerateCompletionAvailableForFinish(tplChosen, value, repeatableConfig.QuestConfig.Completion)); quest.Conditions.AvailableForFinish.Add(
GenerateCompletionAvailableForFinish(
tplChosen,
value,
repeatableConfig.QuestConfig.Completion
)
);
// Is there budget left for more items // Is there budget left for more items
if (roublesBudget > 0) if (roublesBudget > 0)
{ {
// Reduce item pool to fit budget // Reduce item pool to fit budget
itemSelection = itemSelection.Where(tpl => _itemHelper.GetItemPrice(tpl) < roublesBudget) itemSelection = itemSelection
.Where(tpl => _itemHelper.GetItemPrice(tpl) < roublesBudget)
.ToList(); .ToList();
if (!itemSelection.Any()) if (!itemSelection.Any())
{ {
@@ -737,35 +817,39 @@ public class RepeatableQuestGenerator(
/// <param name="completionConfig">Completion quest type config</param> /// <param name="completionConfig">Completion quest type config</param>
/// <param name="itemTplBlacklist">Item tpls to not add to pool</param> /// <param name="itemTplBlacklist">Item tpls to not add to pool</param>
/// <returns>Set of item tpls</returns> /// <returns>Set of item tpls</returns>
protected HashSet<string> GetItemsToRetrievePool(Completion completionConfig, HashSet<string> itemTplBlacklist) protected HashSet<string> GetItemsToRetrievePool(
Completion completionConfig,
HashSet<string> itemTplBlacklist
)
{ {
// Get seasonal items that should not be added to pool as seasonal event is not active // Get seasonal items that should not be added to pool as seasonal event is not active
var seasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems(); var seasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
// Check for specific base classes which don't make sense as reward item // Check for specific base classes which don't make sense as reward item
// also check if the price is greater than 0; there are some items whose price can not be found // also check if the price is greater than 0; there are some items whose price can not be found
return _databaseService.GetItems() return _databaseService
.GetItems()
.Values.Where(itemTemplate => .Values.Where(itemTemplate =>
{
// Base "Item" item has no parent, ignore it
if (itemTemplate.Parent == string.Empty)
{ {
// Base "Item" item has no parent, ignore it return false;
if (itemTemplate.Parent == string.Empty)
{
return false;
}
if (seasonalItems.Contains(itemTemplate.Id))
{
return false;
}
// Valid reward items share same logic as items to retrieve
return _repeatableQuestRewardGenerator.IsValidRewardItem(
itemTemplate.Id,
itemTplBlacklist,
completionConfig.RequiredItemTypeBlacklist
);
} }
).Select(item => item.Id)
if (seasonalItems.Contains(itemTemplate.Id))
{
return false;
}
// Valid reward items share same logic as items to retrieve
return _repeatableQuestRewardGenerator.IsValidRewardItem(
itemTemplate.Id,
itemTplBlacklist,
completionConfig.RequiredItemTypeBlacklist
);
})
.Select(item => item.Id)
.ToHashSet(); .ToHashSet();
} }
@@ -779,13 +863,23 @@ public class RepeatableQuestGenerator(
/// <param name="value">Amount of items of this specific type to request</param> /// <param name="value">Amount of items of this specific type to request</param>
/// <param name="completionConfig">Completion config from quest.json</param> /// <param name="completionConfig">Completion config from quest.json</param>
/// <returns>object of "Completion"-condition</returns> /// <returns>object of "Completion"-condition</returns>
protected QuestCondition GenerateCompletionAvailableForFinish(string itemTpl, protected QuestCondition GenerateCompletionAvailableForFinish(
string itemTpl,
double value, double value,
Completion completionConfig) Completion completionConfig
)
{ {
var onlyFoundInRaid = completionConfig.RequiredItemsAreFiR; var onlyFoundInRaid = completionConfig.RequiredItemsAreFiR;
var minDurability = _itemHelper.IsOfBaseclasses(itemTpl, [BaseClasses.WEAPON, BaseClasses.ARMOR]) var minDurability = _itemHelper.IsOfBaseclasses(
? _randomUtil.GetArrayValue([completionConfig.RequiredItemMinDurabilityMinMax.Min, completionConfig.RequiredItemMinDurabilityMinMax.Max]) itemTpl,
[BaseClasses.WEAPON, BaseClasses.ARMOR]
)
? _randomUtil.GetArrayValue(
[
completionConfig.RequiredItemMinDurabilityMinMax.Min,
completionConfig.RequiredItemMinDurabilityMinMax.Max,
]
)
: 0; : 0;
// Dog tags MUST NOT be FiR for them to work // Dog tags MUST NOT be FiR for them to work
@@ -809,7 +903,7 @@ public class RepeatableQuestGenerator(
DogtagLevel = 0, DogtagLevel = 0,
OnlyFoundInRaid = onlyFoundInRaid, OnlyFoundInRaid = onlyFoundInRaid,
IsEncoded = false, IsEncoded = false,
ConditionType = "HandoverItem" ConditionType = "HandoverItem",
}; };
} }
@@ -830,11 +924,13 @@ public class RepeatableQuestGenerator(
int pmcLevel, int pmcLevel,
string traderId, string traderId,
QuestTypePool questTypePool, QuestTypePool questTypePool,
RepeatableQuestConfig repeatableConfig) RepeatableQuestConfig repeatableConfig
)
{ {
var explorationConfig = repeatableConfig.QuestConfig.Exploration; var explorationConfig = repeatableConfig.QuestConfig.Exploration;
var requiresSpecificExtract = var requiresSpecificExtract =
_randomUtil.Random.Next() < repeatableConfig.QuestConfig.Exploration.SpecificExits.Probability; _randomUtil.Random.Next()
< repeatableConfig.QuestConfig.Exploration.SpecificExits.Probability;
if (questTypePool.Pool.Exploration.Locations.Count == 0) if (questTypePool.Pool.Exploration.Locations.Count == 0)
{ {
@@ -845,7 +941,9 @@ public class RepeatableQuestGenerator(
// If location drawn is factory, it's possible to either get factory4_day and factory4_night or only one // If location drawn is factory, it's possible to either get factory4_day and factory4_night or only one
// of the both // of the both
var locationKey = _randomUtil.DrawRandomFromDict(questTypePool.Pool.Exploration.Locations)[0]; var locationKey = _randomUtil.DrawRandomFromDict(questTypePool.Pool.Exploration.Locations)[
0
];
var locationTarget = questTypePool.Pool.Exploration.Locations[locationKey]; var locationTarget = questTypePool.Pool.Exploration.Locations[locationKey];
// Remove the location from the available pool // Remove the location from the available pool
@@ -857,25 +955,34 @@ public class RepeatableQuestGenerator(
: explorationConfig.MaximumExtracts + 1; : explorationConfig.MaximumExtracts + 1;
var numExtracts = _randomUtil.RandInt(1, exitTimesMax); var numExtracts = _randomUtil.RandInt(1, exitTimesMax);
var quest = GenerateRepeatableTemplate("Exploration", traderId, repeatableConfig.Side, sessionId); var quest = GenerateRepeatableTemplate(
"Exploration",
traderId,
repeatableConfig.Side,
sessionId
);
var exitStatusCondition = new QuestConditionCounterCondition var exitStatusCondition = new QuestConditionCounterCondition
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
DynamicLocale = true, DynamicLocale = true,
Status = ["Survived"], Status = ["Survived"],
ConditionType = "ExitStatus" ConditionType = "ExitStatus",
}; };
var locationCondition = new QuestConditionCounterCondition var locationCondition = new QuestConditionCounterCondition
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
DynamicLocale = true, DynamicLocale = true,
Target = new ListOrT<string>(locationTarget, null), Target = new ListOrT<string>(locationTarget, null),
ConditionType = "Location" ConditionType = "Location",
}; };
quest.Conditions.AvailableForFinish[0].Counter.Id = _hashUtil.Generate(); quest.Conditions.AvailableForFinish[0].Counter.Id = _hashUtil.Generate();
quest.Conditions.AvailableForFinish[0].Counter.Conditions = [exitStatusCondition, locationCondition]; quest.Conditions.AvailableForFinish[0].Counter.Conditions =
[
exitStatusCondition,
locationCondition,
];
quest.Conditions.AvailableForFinish[0].Value = numExtracts; quest.Conditions.AvailableForFinish[0].Value = numExtracts;
quest.Conditions.AvailableForFinish[0].Id = _hashUtil.Generate(); quest.Conditions.AvailableForFinish[0].Id = _hashUtil.Generate();
quest.Location = GetQuestLocationByMapId(locationKey.ToString()); quest.Location = GetQuestLocationByMapId(locationKey.ToString());
@@ -889,9 +996,10 @@ public class RepeatableQuestGenerator(
var exitPool = mapExits.Where(exit => exit.Chance > 0).ToList(); var exitPool = mapExits.Where(exit => exit.Chance > 0).ToList();
// Exclude exits with a requirement to leave (e.g. car extracts) // Exclude exits with a requirement to leave (e.g. car extracts)
var possibleExits = exitPool.Where(exit => var possibleExits = exitPool
exit.PassageRequirement is not null || .Where(exit =>
repeatableConfig.QuestConfig.Exploration.SpecificExits.PassageRequirementWhitelist.Contains( exit.PassageRequirement is not null
|| repeatableConfig.QuestConfig.Exploration.SpecificExits.PassageRequirementWhitelist.Contains(
"PassageRequirement" "PassageRequirement"
) )
) )
@@ -899,7 +1007,9 @@ public class RepeatableQuestGenerator(
if (possibleExits.Count == 0) if (possibleExits.Count == 0)
{ {
_logger.Error($"Unable to choose specific exit on map: {locationKey}, Possible exit pool was empty"); _logger.Error(
$"Unable to choose specific exit on map: {locationKey}, Possible exit pool was empty"
);
} }
else else
{ {
@@ -914,7 +1024,13 @@ public class RepeatableQuestGenerator(
// Difficulty for exploration goes from 1 extract to maxExtracts // Difficulty for exploration goes from 1 extract to maxExtracts
// Difficulty for reward goes from 0.2...1 -> map // Difficulty for reward goes from 0.2...1 -> map
var difficulty = _mathUtil.MapToRange(numExtracts, 1, explorationConfig.MaximumExtracts.Value, 0.2, 1); var difficulty = _mathUtil.MapToRange(
numExtracts,
1,
explorationConfig.MaximumExtracts.Value,
0.2,
1
);
quest.Rewards = _repeatableQuestRewardGenerator.GenerateReward( quest.Rewards = _repeatableQuestRewardGenerator.GenerateReward(
pmcLevel, pmcLevel,
difficulty, difficulty,
@@ -944,13 +1060,21 @@ public class RepeatableQuestGenerator(
int pmcLevel, int pmcLevel,
string traderId, string traderId,
QuestTypePool questTypePool, QuestTypePool questTypePool,
RepeatableQuestConfig repeatableConfig) RepeatableQuestConfig repeatableConfig
)
{ {
var pickupConfig = repeatableConfig.QuestConfig.Pickup; var pickupConfig = repeatableConfig.QuestConfig.Pickup;
var quest = GenerateRepeatableTemplate("Pickup", traderId, repeatableConfig.Side, sessionId); var quest = GenerateRepeatableTemplate(
"Pickup",
traderId,
repeatableConfig.Side,
sessionId
);
var itemTypeToFetchWithCount = _randomUtil.GetArrayValue(pickupConfig.ItemTypeToFetchWithMaxCount); var itemTypeToFetchWithCount = _randomUtil.GetArrayValue(
pickupConfig.ItemTypeToFetchWithMaxCount
);
var itemCountToFetch = _randomUtil.RandInt( var itemCountToFetch = _randomUtil.RandInt(
itemTypeToFetchWithCount.MinimumPickupCount.Value, itemTypeToFetchWithCount.MinimumPickupCount.Value,
itemTypeToFetchWithCount.MaximumPickupCount + 1 itemTypeToFetchWithCount.MaximumPickupCount + 1
@@ -959,18 +1083,25 @@ public class RepeatableQuestGenerator(
// var locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0]; // var locationKey: string = this.randomUtil.drawRandomFromDict(questTypePool.pool.Pickup.locations)[0];
// var locationTarget = questTypePool.pool.Pickup.locations[locationKey]; // var locationTarget = questTypePool.pool.Pickup.locations[locationKey];
var findCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x => x.ConditionType == "FindItem"); var findCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x =>
x.ConditionType == "FindItem"
);
findCondition.Target = new ListOrT<string>([itemTypeToFetchWithCount.ItemType], null); findCondition.Target = new ListOrT<string>([itemTypeToFetchWithCount.ItemType], null);
findCondition.Value = itemCountToFetch; findCondition.Value = itemCountToFetch;
var counterCreatorCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x => x.ConditionType == "CounterCreator" var counterCreatorCondition = quest.Conditions.AvailableForFinish.FirstOrDefault(x =>
x.ConditionType == "CounterCreator"
); );
// var locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location"); // var locationCondition = counterCreatorCondition._props.counter.conditions.find(x => x._parent === "Location");
// (locationCondition._props as ILocationConditionProps).target = [...locationTarget]; // (locationCondition._props as ILocationConditionProps).target = [...locationTarget];
var equipmentCondition = counterCreatorCondition.Counter.Conditions.FirstOrDefault(x => x.ConditionType == "Equipment" var equipmentCondition = counterCreatorCondition.Counter.Conditions.FirstOrDefault(x =>
x.ConditionType == "Equipment"
); );
equipmentCondition.EquipmentInclusive = [[itemTypeToFetchWithCount.ItemType]]; equipmentCondition.EquipmentInclusive =
[
[itemTypeToFetchWithCount.ItemType],
];
// Add rewards // Add rewards
quest.Rewards = _repeatableQuestRewardGenerator.GenerateReward( quest.Rewards = _repeatableQuestRewardGenerator.GenerateReward(
@@ -1007,7 +1138,7 @@ public class RepeatableQuestGenerator(
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
DynamicLocale = true, DynamicLocale = true,
ExitName = exit.Name, ExitName = exit.Name,
ConditionType = "ExitName" ConditionType = "ExitName",
}; };
} }
@@ -1027,7 +1158,8 @@ public class RepeatableQuestGenerator(
string type, string type,
string traderId, string traderId,
string side, string side,
string sessionId) string sessionId
)
{ {
RepeatableQuest questData = null; RepeatableQuest questData = null;
switch (type) switch (type)
@@ -1082,35 +1214,35 @@ public class RepeatableQuestGenerator(
// Force REF templates to use prapors ID - solves missing text issue // Force REF templates to use prapors ID - solves missing text issue
var desiredTraderId = traderId == Traders.REF ? Traders.PRAPOR : traderId; var desiredTraderId = traderId == Traders.REF ? Traders.PRAPOR : traderId;
questClone.Name = questClone.Name questClone.Name = questClone
.Replace("{traderId}", traderId) .Name.Replace("{traderId}", traderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.Note = questClone.Note questClone.Note = questClone
.Replace("{traderId}", desiredTraderId) .Note.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.Description = questClone.Description questClone.Description = questClone
.Replace("{traderId}", desiredTraderId) .Description.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.SuccessMessageText = questClone.SuccessMessageText questClone.SuccessMessageText = questClone
.Replace("{traderId}", desiredTraderId) .SuccessMessageText.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.FailMessageText = questClone.FailMessageText questClone.FailMessageText = questClone
.Replace("{traderId}", desiredTraderId) .FailMessageText.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.StartedMessageText = questClone.StartedMessageText questClone.StartedMessageText = questClone
.Replace("{traderId}", desiredTraderId) .StartedMessageText.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.ChangeQuestMessageText = questClone.ChangeQuestMessageText questClone.ChangeQuestMessageText = questClone
.Replace("{traderId}", desiredTraderId) .ChangeQuestMessageText.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.AcceptPlayerMessage = questClone.AcceptPlayerMessage questClone.AcceptPlayerMessage = questClone
.Replace("{traderId}", desiredTraderId) .AcceptPlayerMessage.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.DeclinePlayerMessage = questClone.DeclinePlayerMessage questClone.DeclinePlayerMessage = questClone
.Replace("{traderId}", desiredTraderId) .DeclinePlayerMessage.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.CompletePlayerMessage = questClone.CompletePlayerMessage questClone.CompletePlayerMessage = questClone
.Replace("{traderId}", desiredTraderId) .CompletePlayerMessage.Replace("{traderId}", desiredTraderId)
.Replace("{templateId}", questClone.TemplateId); .Replace("{templateId}", questClone.TemplateId);
questClone.QuestStatus.Id = _hashUtil.Generate(); questClone.QuestStatus.Id = _hashUtil.Generate();
@@ -64,10 +64,15 @@ public class RepeatableQuestRewardGenerator(
string traderId, string traderId,
RepeatableQuestConfig repeatableConfig, RepeatableQuestConfig repeatableConfig,
BaseQuestConfig eliminationConfig, BaseQuestConfig eliminationConfig,
List<string>? rewardTplBlacklist = null) List<string>? rewardTplBlacklist = null
)
{ {
// Get vars to configure rewards with // Get vars to configure rewards with
var rewardParams = GetQuestRewardValues(repeatableConfig.RewardScaling, difficulty, pmcLevel); var rewardParams = GetQuestRewardValues(
repeatableConfig.RewardScaling,
difficulty,
pmcLevel
);
// Get budget to spend on item rewards (copy of raw roubles given) // Get budget to spend on item rewards (copy of raw roubles given)
var itemRewardBudget = rewardParams.RewardRoubles; var itemRewardBudget = rewardParams.RewardRoubles;
@@ -77,7 +82,7 @@ public class RepeatableQuestRewardGenerator(
{ {
Started = [], Started = [],
Success = [], Success = [],
Fail = [] Fail = [],
}; };
// Start reward index to keep track // Start reward index to keep track
@@ -95,28 +100,40 @@ public class RepeatableQuestRewardGenerator(
AvailableInGameEditions = [], AvailableInGameEditions = [],
Index = rewardIndex, Index = rewardIndex,
Value = rewardParams.RewardXP, Value = rewardParams.RewardXP,
Type = RewardType.Experience Type = RewardType.Experience,
} }
); );
rewardIndex++; rewardIndex++;
} }
// Add money reward // Add money reward
rewards.Success.Add(GetMoneyReward(traderId, rewardParams.RewardRoubles.Value, rewardIndex)); rewards.Success.Add(
GetMoneyReward(traderId, rewardParams.RewardRoubles.Value, rewardIndex)
);
rewardIndex++; rewardIndex++;
// Add GP coin reward // Add GP coin reward
rewards.Success.Add(GenerateItemReward(Money.GP, rewardParams.GpCoinRewardCount.Value, rewardIndex)); rewards.Success.Add(
GenerateItemReward(Money.GP, rewardParams.GpCoinRewardCount.Value, rewardIndex)
);
rewardIndex++; rewardIndex++;
// Add preset weapon to reward if checks pass // Add preset weapon to reward if checks pass
var traderWhitelistDetails = repeatableConfig.TraderWhitelist.FirstOrDefault(traderWhitelist => traderWhitelist.TraderId == traderId var traderWhitelistDetails = repeatableConfig.TraderWhitelist.FirstOrDefault(
traderWhitelist => traderWhitelist.TraderId == traderId
); );
if (traderWhitelistDetails?.RewardCanBeWeapon ?? if (
(false && _randomUtil.GetChance100(traderWhitelistDetails.WeaponRewardChancePercent ?? 0)) traderWhitelistDetails?.RewardCanBeWeapon
) ?? (
false
&& _randomUtil.GetChance100(traderWhitelistDetails.WeaponRewardChancePercent ?? 0)
)
)
{ {
var chosenWeapon = GetRandomWeaponPresetWithinBudget(itemRewardBudget.Value, rewardIndex); var chosenWeapon = GetRandomWeaponPresetWithinBudget(
itemRewardBudget.Value,
rewardIndex
);
if (chosenWeapon is not null) if (chosenWeapon is not null)
{ {
rewards.Success.Add(chosenWeapon.Value.Key); rewards.Success.Add(chosenWeapon.Value.Key);
@@ -127,11 +144,16 @@ public class RepeatableQuestRewardGenerator(
} }
} }
var inBudgetRewardItemPool = ChooseRewardItemsWithinBudget(repeatableConfig, itemRewardBudget, traderId); var inBudgetRewardItemPool = ChooseRewardItemsWithinBudget(
repeatableConfig,
itemRewardBudget,
traderId
);
if (rewardTplBlacklist is not null) if (rewardTplBlacklist is not null)
{ {
// Filter reward pool of items from blacklist, only use if there's at least 1 item remaining // Filter reward pool of items from blacklist, only use if there's at least 1 item remaining
var filteredRewardItemPool = inBudgetRewardItemPool.Where(item => !rewardTplBlacklist.Contains(item.Id) var filteredRewardItemPool = inBudgetRewardItemPool.Where(item =>
!rewardTplBlacklist.Contains(item.Id)
); );
if (filteredRewardItemPool.Count() > 0) if (filteredRewardItemPool.Count() > 0)
{ {
@@ -139,7 +161,6 @@ public class RepeatableQuestRewardGenerator(
} }
} }
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug( _logger.Debug(
@@ -159,7 +180,9 @@ public class RepeatableQuestRewardGenerator(
// Add item rewards // Add item rewards
foreach (var itemReward in itemsToReward) foreach (var itemReward in itemsToReward)
{ {
rewards.Success.Add(GenerateItemReward(itemReward.Key.Id, itemReward.Value, rewardIndex)); rewards.Success.Add(
GenerateItemReward(itemReward.Key.Id, itemReward.Value, rewardIndex)
);
rewardIndex++; rewardIndex++;
} }
} }
@@ -176,19 +199,21 @@ public class RepeatableQuestRewardGenerator(
Target = traderId, Target = traderId,
Value = rewardParams.RewardReputation, Value = rewardParams.RewardReputation,
Type = RewardType.TraderStanding, Type = RewardType.TraderStanding,
Index = rewardIndex Index = rewardIndex,
}; };
rewards.Success.Add(reward); rewards.Success.Add(reward);
rewardIndex++; rewardIndex++;
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward"); _logger.Debug(
$"Adding: {rewardParams.RewardReputation} {traderId} trader reputation reward"
);
} }
} }
// Chance of adding skill 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); var targetSkill = _randomUtil.GetArrayValue(eliminationConfig.PossibleSkillRewards);
Reward reward = new() Reward reward = new()
@@ -200,20 +225,26 @@ public class RepeatableQuestRewardGenerator(
Target = targetSkill, Target = targetSkill,
Value = rewardParams.SkillPointReward, Value = rewardParams.SkillPointReward,
Type = RewardType.Skill, Type = RewardType.Skill,
Index = rewardIndex Index = rewardIndex,
}; };
rewards.Success.Add(reward); rewards.Success.Add(reward);
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}"); _logger.Debug(
$"Adding {rewardParams.SkillPointReward} skill points to {targetSkill}"
);
} }
} }
return rewards; return rewards;
} }
protected QuestRewardValues GetQuestRewardValues(RewardScaling? rewardScaling, double? difficulty, int pmcLevel) protected QuestRewardValues GetQuestRewardValues(
RewardScaling? rewardScaling,
double? difficulty,
int pmcLevel
)
{ {
// difficulty could go from 0.2 ... -> for lowest difficulty receive 0.2*nominal reward // difficulty could go from 0.2 ... -> for lowest difficulty receive 0.2*nominal reward
var levelsConfig = rewardScaling.Levels; var levelsConfig = rewardScaling.Levels;
@@ -258,61 +289,102 @@ public class RepeatableQuestRewardGenerator(
gpCoinConfig, gpCoinConfig,
rewardSpreadConfig rewardSpreadConfig
), ),
RewardXP = GetRewardXp(effectiveDifficulty, pmcLevel, levelsConfig, xpConfig, rewardSpreadConfig) RewardXP = GetRewardXp(
effectiveDifficulty,
pmcLevel,
levelsConfig,
xpConfig,
rewardSpreadConfig
),
}; };
} }
protected double GetRewardXp(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig, protected double GetRewardXp(
List<double>? xpConfig, double? rewardSpreadConfig) double? effectiveDifficulty,
int pmcLevel,
List<double>? levelsConfig,
List<double>? xpConfig,
double? rewardSpreadConfig
)
{ {
return Math.Floor( return Math.Floor(
effectiveDifficulty * effectiveDifficulty
_mathUtil.Interp1(pmcLevel, levelsConfig, xpConfig) * * _mathUtil.Interp1(pmcLevel, levelsConfig, xpConfig)
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ?? * _randomUtil.GetDouble(
0 (double)(1 - rewardSpreadConfig),
(double)(1 + rewardSpreadConfig)
)
?? 0
); );
} }
protected double GetGpCoinRewardCount(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig, protected double GetGpCoinRewardCount(
double? effectiveDifficulty,
int pmcLevel,
List<double>? levelsConfig,
List<double>? gpCoinConfig, List<double>? gpCoinConfig,
double? rewardSpreadConfig) double? rewardSpreadConfig
)
{ {
return Math.Ceiling( return Math.Ceiling(
effectiveDifficulty * effectiveDifficulty
_mathUtil.Interp1(pmcLevel, levelsConfig, gpCoinConfig) * * _mathUtil.Interp1(pmcLevel, levelsConfig, gpCoinConfig)
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ?? * _randomUtil.GetDouble(
0 (double)(1 - rewardSpreadConfig),
(double)(1 + rewardSpreadConfig)
)
?? 0
); );
} }
protected double GetRewardRep(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig, protected double GetRewardRep(
double? effectiveDifficulty,
int pmcLevel,
List<double>? levelsConfig,
List<double>? reputationConfig, List<double>? reputationConfig,
double? rewardSpreadConfig) double? rewardSpreadConfig
)
{ {
return Math.Round( return Math.Round(
100 * 100
effectiveDifficulty * * effectiveDifficulty
_mathUtil.Interp1(pmcLevel, levelsConfig, reputationConfig) * * _mathUtil.Interp1(pmcLevel, levelsConfig, reputationConfig)
_randomUtil.GetDouble((double) (1 - rewardSpreadConfig), (double) (1 + rewardSpreadConfig)) ?? * _randomUtil.GetDouble(
0 (double)(1 - rewardSpreadConfig),
) / (double)(1 + rewardSpreadConfig)
100; )
?? 0
) / 100;
} }
protected int GetRewardNumItems(int pmcLevel, List<double>? levelsConfig, List<double>? itemsConfig) protected 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
);
} }
protected double GetRewardRoubles(double? effectiveDifficulty, int pmcLevel, List<double>? levelsConfig, protected double GetRewardRoubles(
double? effectiveDifficulty,
int pmcLevel,
List<double>? levelsConfig,
List<double>? roublesConfig, List<double>? roublesConfig,
double? rewardSpreadConfig) double? rewardSpreadConfig
)
{ {
return Math.Floor( return Math.Floor(
effectiveDifficulty * effectiveDifficulty
_mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig) * * _mathUtil.Interp1(pmcLevel, levelsConfig, roublesConfig)
_randomUtil.GetDouble(1d - rewardSpreadConfig.Value, 1d + rewardSpreadConfig.Value) ?? * _randomUtil.GetDouble(
0 1d - rewardSpreadConfig.Value,
1d + rewardSpreadConfig.Value
)
?? 0
); );
} }
@@ -324,8 +396,12 @@ public class RepeatableQuestRewardGenerator(
/// <param name="itemRewardBudget"> Rouble budget all item rewards must fit in </param> /// <param name="itemRewardBudget"> Rouble budget all item rewards must fit in </param>
/// <param name="repeatableConfig"> Config for quest type </param> /// <param name="repeatableConfig"> Config for quest type </param>
/// <returns> Dictionary of items and stack size</returns> /// <returns> Dictionary of items and stack size</returns>
protected Dictionary<TemplateItem, int> GetRewardableItemsFromPoolWithinBudget(List<TemplateItem> itemPool, protected Dictionary<TemplateItem, int> GetRewardableItemsFromPoolWithinBudget(
int maxItemCount, double itemRewardBudget, RepeatableQuestConfig repeatableConfig) List<TemplateItem> itemPool,
int maxItemCount,
double itemRewardBudget,
RepeatableQuestConfig repeatableConfig
)
{ {
var itemsToReturn = new Dictionary<TemplateItem, int>(); var itemsToReturn = new Dictionary<TemplateItem, int>();
var exhausableItemPool = new ExhaustableArray<TemplateItem>(itemPool, _randomUtil, _cloner); var exhausableItemPool = new ExhaustableArray<TemplateItem>(itemPool, _randomUtil, _cloner);
@@ -346,7 +422,10 @@ public class RepeatableQuestRewardGenerator(
if (_itemHelper.IsOfBaseclass(chosenItemFromPool.Id, BaseClasses.AMMO)) if (_itemHelper.IsOfBaseclass(chosenItemFromPool.Id, BaseClasses.AMMO))
{ {
// Don't reward ammo that stacks to less than what's allowed in config // Don't reward ammo that stacks to less than what's allowed in config
if (chosenItemFromPool.Properties.StackMaxSize < repeatableConfig.RewardAmmoStackMinSize) if (
chosenItemFromPool.Properties.StackMaxSize
< repeatableConfig.RewardAmmoStackMinSize
)
{ {
i--; i--;
continue; continue;
@@ -373,7 +452,9 @@ public class RepeatableQuestRewardGenerator(
var calculatedItemRewardBudget = itemRewardBudget - rewardItemStackCount * itemCost; var calculatedItemRewardBudget = itemRewardBudget - rewardItemStackCount * itemCost;
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}"); _logger.Debug(
$"Added item: {chosenItemFromPool.Id} with price: {rewardItemStackCount * itemCost}"
);
} }
// If we still have budget narrow down possible items // If we still have budget narrow down possible items
@@ -390,7 +471,9 @@ public class RepeatableQuestRewardGenerator(
{ {
if (_logger.IsLogEnabled(LogLevel.Debug)) if (_logger.IsLogEnabled(LogLevel.Debug))
{ {
_logger.Debug($"Reward pool empty with: {calculatedItemRewardBudget} roubles of budget remaining"); _logger.Debug(
$"Reward pool empty with: {calculatedItemRewardBudget} roubles of budget remaining"
);
} }
} }
} }
@@ -410,8 +493,11 @@ public class RepeatableQuestRewardGenerator(
/// <param name="roublesBudget"> Rouble budget </param> /// <param name="roublesBudget"> Rouble budget </param>
/// <param name="rewardNumItems"> Count of rewarded items </param> /// <param name="rewardNumItems"> Count of rewarded items </param>
/// <returns> Count that fits budget (min 1) </returns> /// <returns> Count that fits budget (min 1) </returns>
protected int CalculateAmmoStackSizeThatFitsBudget(TemplateItem itemSelected, double roublesBudget, protected int CalculateAmmoStackSizeThatFitsBudget(
int rewardNumItems) TemplateItem itemSelected,
double roublesBudget,
int rewardNumItems
)
{ {
// Calculate budget per reward item // Calculate budget per reward item
var stackRoubleBudget = roublesBudget / rewardNumItems; var stackRoubleBudget = roublesBudget / rewardNumItems;
@@ -425,23 +511,22 @@ public class RepeatableQuestRewardGenerator(
var stackMaxCount = Math.Min(itemSelected.Properties.StackMaxSize.Value, 100); 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 // 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));
} }
protected bool CanIncreaseRewardItemStackSize(TemplateItem item, int maxRoublePriceToStack, protected bool CanIncreaseRewardItemStackSize(
int randomChanceToPass = 100) TemplateItem item,
int maxRoublePriceToStack,
int randomChanceToPass = 100
)
{ {
var isEligibleForStackSizeIncrease = var isEligibleForStackSizeIncrease =
_presetHelper.GetDefaultPresetOrItemPrice(item.Id) < maxRoublePriceToStack && _presetHelper.GetDefaultPresetOrItemPrice(item.Id) < maxRoublePriceToStack
!_itemHelper.IsOfBaseclasses( && !_itemHelper.IsOfBaseclasses(
item.Id, item.Id,
[ [BaseClasses.WEAPON, BaseClasses.ARMORED_EQUIPMENT, BaseClasses.AMMO]
BaseClasses.WEAPON, )
BaseClasses.ARMORED_EQUIPMENT, && !_itemHelper.ItemRequiresSoftInserts(item.Id);
BaseClasses.AMMO
]
) &&
!_itemHelper.ItemRequiresSoftInserts(item.Id);
return isEligibleForStackSizeIncrease && _randomUtil.GetChance100(randomChanceToPass); return isEligibleForStackSizeIncrease && _randomUtil.GetChance100(randomChanceToPass);
} }
@@ -460,7 +545,7 @@ public class RepeatableQuestRewardGenerator(
{ {
new(3000, [2, 3, 4]), new(3000, [2, 3, 4]),
new(10000, [2, 3]), new(10000, [2, 3]),
new(int.MaxValue, [2, 3, 4]) // Default for prices 10001+ RUB new(int.MaxValue, [2, 3, 4]), // Default for prices 10001+ RUB
}; };
// Find the appropriate price tier and return a random stack size from its options // Find the appropriate price tier and return a random stack size from its options
@@ -480,8 +565,11 @@ public class RepeatableQuestRewardGenerator(
/// <param name="roublesBudget"> Total value of items to return </param> /// <param name="roublesBudget"> Total value of items to return </param>
/// <param name="traderId"> ID of the trader who will give player reward </param> /// <param name="traderId"> ID of the trader who will give player reward </param>
/// <returns> List of reward items that fit budget </returns> /// <returns> List of reward items that fit budget </returns>
protected List<TemplateItem> ChooseRewardItemsWithinBudget(RepeatableQuestConfig repeatableConfig, protected List<TemplateItem> ChooseRewardItemsWithinBudget(
double? roublesBudget, string traderId) RepeatableQuestConfig repeatableConfig,
double? roublesBudget,
string traderId
)
{ {
// First filter for type and baseclass to avoid lookup in handbook for non-available items // First filter for type and baseclass to avoid lookup in handbook for non-available items
var rewardableItemPool = GetRewardableItems(repeatableConfig, traderId); var rewardableItemPool = GetRewardableItems(repeatableConfig, traderId);
@@ -498,11 +586,7 @@ public class RepeatableQuestRewardGenerator(
_logger.Warning( _logger.Warning(
_localisationService.GetText( _localisationService.GetText(
"repeatable-no_reward_item_found_in_price_range", "repeatable-no_reward_item_found_in_price_range",
new new { minPrice, roublesBudget }
{
minPrice,
roublesBudget
}
) )
); );
@@ -522,15 +606,18 @@ public class RepeatableQuestRewardGenerator(
/// <param name="roublesBudget"> The budget remaining for rewards </param> /// <param name="roublesBudget"> The budget remaining for rewards </param>
/// <param name="minPrice"> The minimum priced item to include </param> /// <param name="minPrice"> The minimum priced item to include </param>
/// <returns> List of Items </returns> /// <returns> List of Items </returns>
protected List<TemplateItem> FilterRewardPoolWithinBudget(List<TemplateItem> rewardItems, double roublesBudget, protected List<TemplateItem> FilterRewardPoolWithinBudget(
double minPrice) List<TemplateItem> rewardItems,
double roublesBudget,
double minPrice
)
{ {
return rewardItems.Where(item => return rewardItems
{ .Where(item =>
var itemPrice = _presetHelper.GetDefaultPresetOrItemPrice(item.Id); {
return itemPrice < roublesBudget && itemPrice > minPrice; var itemPrice = _presetHelper.GetDefaultPresetOrItemPrice(item.Id);
} return itemPrice < roublesBudget && itemPrice > minPrice;
) })
.ToList(); .ToList();
} }
@@ -540,7 +627,10 @@ public class RepeatableQuestRewardGenerator(
/// <param name="roublesBudget"> Budget in roubles </param> /// <param name="roublesBudget"> Budget in roubles </param>
/// <param name="rewardIndex"> Index of the reward </param> /// <param name="rewardIndex"> Index of the reward </param>
/// <returns> Dictionary of the reward and it's price, can return null. </returns> /// <returns> Dictionary of the reward and it's price, can return null. </returns>
protected KeyValuePair<Reward, double>? GetRandomWeaponPresetWithinBudget(double roublesBudget, int rewardIndex) protected KeyValuePair<Reward, double>? GetRandomWeaponPresetWithinBudget(
double roublesBudget,
int rewardIndex
)
{ {
// Add a random default preset weapon as reward // Add a random default preset weapon as reward
var defaultPresetPool = new ExhaustableArray<Preset>( var defaultPresetPool = new ExhaustableArray<Preset>(
@@ -568,7 +658,12 @@ public class RepeatableQuestRewardGenerator(
var chosenPreset = _cloner.Clone(randomPreset); var chosenPreset = _cloner.Clone(randomPreset);
return new KeyValuePair<Reward, double>( return new KeyValuePair<Reward, double>(
GeneratePresetReward(chosenPreset.Encyclopedia, 1, rewardIndex, chosenPreset.Items), GeneratePresetReward(
chosenPreset.Encyclopedia,
1,
rewardIndex,
chosenPreset.Items
),
presetPrice presetPrice
); );
} }
@@ -586,7 +681,13 @@ public class RepeatableQuestRewardGenerator(
/// <param name="preset"> Optional list of preset items </param> /// <param name="preset"> Optional list of preset items </param>
/// <param name="foundInRaid"> If generated Item is found in raid, default True </param> /// <param name="foundInRaid"> If generated Item is found in raid, default True </param>
/// <returns> Object of "Reward"-item-type </returns> /// <returns> Object of "Reward"-item-type </returns>
protected Reward GeneratePresetReward(string tpl, int count, int index, List<Item>? preset, bool foundInRaid = true) protected Reward GeneratePresetReward(
string tpl,
int count,
int index,
List<Item>? preset,
bool foundInRaid = true
)
{ {
var id = _hashUtil.Generate(); var id = _hashUtil.Generate();
var questRewardItem = new Reward var questRewardItem = new Reward
@@ -601,7 +702,7 @@ public class RepeatableQuestRewardGenerator(
IsEncoded = false, IsEncoded = false,
FindInRaid = foundInRaid, FindInRaid = foundInRaid,
Type = RewardType.Item, Type = RewardType.Item,
Items = [] Items = [],
}; };
// Get presets root item // Get presets root item
@@ -630,7 +731,12 @@ public class RepeatableQuestRewardGenerator(
/// <param name="index"> All rewards will be appended to a list, for unknown reasons the client wants the index</param> /// <param name="index"> All rewards will be appended to a list, for unknown reasons the client wants the index</param>
/// <param name="foundInRaid"> If generated Item is found in raid, default True </param> /// <param name="foundInRaid"> If generated Item is found in raid, default True </param>
/// <returns> Object of "Reward"-item-type </returns> /// <returns> Object of "Reward"-item-type </returns>
protected Reward GenerateItemReward(string tpl, double count, int index, bool foundInRaid = true) protected Reward GenerateItemReward(
string tpl,
double count,
int index,
bool foundInRaid = true
)
{ {
var id = _hashUtil.Generate(); var id = _hashUtil.Generate();
var questRewardItem = new Reward var questRewardItem = new Reward
@@ -645,18 +751,14 @@ public class RepeatableQuestRewardGenerator(
IsEncoded = false, IsEncoded = false,
FindInRaid = foundInRaid, FindInRaid = foundInRaid,
Type = RewardType.Item, Type = RewardType.Item,
Items = [] Items = [],
}; };
var rootItem = new Item var rootItem = new Item
{ {
Id = id, Id = id,
Template = tpl, Template = tpl,
Upd = new Upd Upd = new Upd { StackObjectsCount = count, SpawnedInSession = foundInRaid },
{
StackObjectsCount = count,
SpawnedInSession = foundInRaid
}
}; };
questRewardItem.Items = [rootItem]; questRewardItem.Items = [rootItem];
@@ -667,17 +769,20 @@ public class RepeatableQuestRewardGenerator(
{ {
// Determine currency based on trader // Determine currency based on trader
// PK and Fence use Euros, everyone else is Roubles // PK and Fence use Euros, everyone else is Roubles
var currency = traderId is Traders.PEACEKEEPER or Traders.FENCE ? Money.EUROS : Money.ROUBLES; var currency = traderId is Traders.PEACEKEEPER or Traders.FENCE
? Money.EUROS
: Money.ROUBLES;
// Convert reward amount to Euros if necessary // Convert reward amount to Euros if necessary
var rewardAmountToGivePlayer = var rewardAmountToGivePlayer =
currency == Money.EUROS ? _handbookHelper.FromRUB(rewardRoubles, Money.EUROS) : rewardRoubles; currency == Money.EUROS
? _handbookHelper.FromRUB(rewardRoubles, Money.EUROS)
: rewardRoubles;
// Get chosen currency + amount and return // Get chosen currency + amount and return
return GenerateItemReward(currency, rewardAmountToGivePlayer, rewardIndex, false); return GenerateItemReward(currency, rewardAmountToGivePlayer, rewardIndex, false);
} }
/// <summary> /// <summary>
/// Picks rewardable items from items.json <br /> /// Picks rewardable items from items.json <br />
/// This means they must: <br /> /// This means they must: <br />
@@ -688,7 +793,10 @@ public class RepeatableQuestRewardGenerator(
/// <param name="repeatableQuestConfig"> Config </param> /// <param name="repeatableQuestConfig"> Config </param>
/// <param name="tradderId"> ID of trader who will give reward to player </param> /// <param name="tradderId"> ID of trader who will give reward to player </param>
/// <returns> List of rewardable items [[_tpl, itemTemplate],...] </returns> /// <returns> List of rewardable items [[_tpl, itemTemplate],...] </returns>
public List<TemplateItem> GetRewardableItems(RepeatableQuestConfig repeatableQuestConfig, string traderId) public List<TemplateItem> GetRewardableItems(
RepeatableQuestConfig repeatableQuestConfig,
string traderId
)
{ {
// Get an array of seasonal items that should not be shown right now as seasonal event is not active // Get an array of seasonal items that should not be shown right now as seasonal event is not active
var seasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems(); var seasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
@@ -696,31 +804,32 @@ public class RepeatableQuestRewardGenerator(
// Check for specific base classes which don't make sense as reward item // Check for specific base classes which don't make sense as reward item
// also check if the price is greater than 0; there are some items whose price can not be found // also check if the price is greater than 0; there are some items whose price can not be found
// those are not in the game yet (e.g. AGS grenade launcher) // those are not in the game yet (e.g. AGS grenade launcher)
return _databaseService.GetItems() return _databaseService
.GetItems()
.Values.Where(itemTemplate => .Values.Where(itemTemplate =>
{
// Base "Item" item has no parent, ignore it
if (itemTemplate.Parent == "")
{ {
// Base "Item" item has no parent, ignore it return false;
if (itemTemplate.Parent == "")
{
return false;
}
if (seasonalItems.Contains(itemTemplate.Id))
{
return false;
}
var traderWhitelist = repeatableQuestConfig.TraderWhitelist.FirstOrDefault(trader => trader.TraderId == traderId
);
return IsValidRewardItem(
itemTemplate.Id,
repeatableQuestConfig.RewardBlacklist,
repeatableQuestConfig.RewardBaseTypeBlacklist,
traderWhitelist?.RewardBaseWhitelist
);
} }
)
if (seasonalItems.Contains(itemTemplate.Id))
{
return false;
}
var traderWhitelist = repeatableQuestConfig.TraderWhitelist.FirstOrDefault(trader =>
trader.TraderId == traderId
);
return IsValidRewardItem(
itemTemplate.Id,
repeatableQuestConfig.RewardBlacklist,
repeatableQuestConfig.RewardBaseTypeBlacklist,
traderWhitelist?.RewardBaseWhitelist
);
})
.ToList(); .ToList();
} }
@@ -733,10 +842,12 @@ public class RepeatableQuestRewardGenerator(
/// <param name="itemTypeBlacklist"> Specific item base types to ignore </param> /// <param name="itemTypeBlacklist"> Specific item base types to ignore </param>
/// <param name="itemBaseWhitelist"> Default null, specific trader item base classes</param> /// <param name="itemBaseWhitelist"> Default null, specific trader item base classes</param>
/// <returns> True if item is valid reward </returns> /// <returns> True if item is valid reward </returns>
public bool IsValidRewardItem(string tpl, public bool IsValidRewardItem(
string tpl,
HashSet<string> itemTplBlacklist, HashSet<string> itemTplBlacklist,
HashSet<string> itemTypeBlacklist, HashSet<string> itemTypeBlacklist,
List<string>? itemBaseWhitelist = null) List<string>? itemBaseWhitelist = null
)
{ {
// Return early if not valid item to give as reward // Return early if not valid item to give as reward
if (!_itemHelper.IsValidItem(tpl)) if (!_itemHelper.IsValidItem(tpl))
@@ -746,10 +857,10 @@ public class RepeatableQuestRewardGenerator(
// Check item is not blacklisted // Check item is not blacklisted
if ( if (
_itemFilterService.IsItemBlacklisted(tpl) ||
_itemFilterService.IsItemRewardBlacklisted(tpl) ||
itemTplBlacklist.Contains(tpl) ||
_itemFilterService.IsItemBlacklisted(tpl) _itemFilterService.IsItemBlacklisted(tpl)
|| _itemFilterService.IsItemRewardBlacklisted(tpl)
|| itemTplBlacklist.Contains(tpl)
|| _itemFilterService.IsItemBlacklisted(tpl)
) )
{ {
return false; return false;
@@ -53,7 +53,10 @@ public class ScavCaseRewardGenerator(
// Get items that fit the price criteria as set by the scavCase config // Get items that fit the price criteria as set by the scavCase config
var commonPricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Common); var commonPricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Common);
var rarePricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Rare); var rarePricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Rare);
var superRarePricedItems = GetFilteredItemsByPrice(_dbItemsCache, rewardItemCounts.Superrare); var superRarePricedItems = GetFilteredItemsByPrice(
_dbItemsCache,
rewardItemCounts.Superrare
);
// Get randomly picked items from each item collection, the count range of which is defined in hideout/scavcase.json // Get randomly picked items from each item collection, the count range of which is defined in hideout/scavcase.json
var randomlyPickedCommonRewards = PickRandomRewards( var randomlyPickedCommonRewards = PickRandomRewards(
@@ -65,7 +68,8 @@ public class ScavCaseRewardGenerator(
var randomlyPickedRareRewards = PickRandomRewards( var randomlyPickedRareRewards = PickRandomRewards(
rarePricedItems, rarePricedItems,
rewardItemCounts.Rare, rewardItemCounts.Rare,
RewardRarity.Rare); RewardRarity.Rare
);
var randomlyPickedSuperRareRewards = PickRandomRewards( var randomlyPickedSuperRareRewards = PickRandomRewards(
superRarePricedItems, superRarePricedItems,
@@ -74,9 +78,18 @@ public class ScavCaseRewardGenerator(
); );
// Add randomised stack sizes to ammo and money rewards // Add randomised stack sizes to ammo and money rewards
var commonRewards = RandomiseContainerItemRewards(randomlyPickedCommonRewards, RewardRarity.Common); var commonRewards = RandomiseContainerItemRewards(
var rareRewards = RandomiseContainerItemRewards(randomlyPickedRareRewards, RewardRarity.Rare); randomlyPickedCommonRewards,
var superRareRewards = RandomiseContainerItemRewards(randomlyPickedSuperRareRewards, RewardRarity.SuperRare); RewardRarity.Common
);
var rareRewards = RandomiseContainerItemRewards(
randomlyPickedRareRewards,
RewardRarity.Rare
);
var superRareRewards = RandomiseContainerItemRewards(
randomlyPickedSuperRareRewards,
RewardRarity.SuperRare
);
var result = new List<List<Item>>(); var result = new List<List<Item>>();
result = result.Concat(commonRewards).ToList(); result = result.Concat(commonRewards).ToList();
@@ -97,120 +110,131 @@ public class ScavCaseRewardGenerator(
var inactiveSeasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems(); var inactiveSeasonalItems = _seasonalEventService.GetInactiveSeasonalEventItems();
if (!_dbItemsCache.Any()) if (!_dbItemsCache.Any())
{ {
_dbItemsCache = _databaseService.GetItems() _dbItemsCache = _databaseService
.GetItems()
.Values.Where(item => .Values.Where(item =>
{
// Base "Item" item has no parent, ignore it
if (item.Parent == "")
{ {
// Base "Item" item has no parent, ignore it return false;
if (item.Parent == "")
{
return false;
}
if (item.Type == "Node")
{
return false;
}
if (item.Properties.QuestItem ?? false)
{
return false;
}
// Skip item if item id is on blacklist
if (
item.Type != "Item" ||
_scavCaseConfig.RewardItemBlacklist.Contains(item.Id) ||
_itemFilterService.IsItemBlacklisted(item.Id)
)
{
return false;
}
// Globally reward-blacklisted
if (_itemFilterService.IsItemRewardBlacklisted(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 (inactiveSeasonalItems.Contains(item.Id))
{
return false;
}
return true;
} }
)
if (item.Type == "Node")
{
return false;
}
if (item.Properties.QuestItem ?? false)
{
return false;
}
// Skip item if item id is on blacklist
if (
item.Type != "Item"
|| _scavCaseConfig.RewardItemBlacklist.Contains(item.Id)
|| _itemFilterService.IsItemBlacklisted(item.Id)
)
{
return false;
}
// Globally reward-blacklisted
if (_itemFilterService.IsItemRewardBlacklisted(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 (inactiveSeasonalItems.Contains(item.Id))
{
return false;
}
return true;
})
.ToList(); .ToList();
} }
if (!_dbAmmoItemsCache.Any()) if (!_dbAmmoItemsCache.Any())
{ {
_dbAmmoItemsCache = _databaseService.GetItems() _dbAmmoItemsCache = _databaseService
.GetItems()
.Values.Where(item => .Values.Where(item =>
{
// Base "Item" item has no parent, ignore it
if (item.Parent == "")
{ {
// Base "Item" item has no parent, ignore it return false;
if (item.Parent == "")
{
return false;
}
if (item.Type != "Item")
{
return false;
}
// Not ammo, skip
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 (!_scavCaseConfig.AllowBossItemsAsRewards && _itemFilterService.IsBossItem(item.Id))
{
return false;
}
// Skip seasonal items
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;
}
return true;
} }
)
if (item.Type != "Item")
{
return false;
}
// Not ammo, skip
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 (
!_scavCaseConfig.AllowBossItemsAsRewards
&& _itemFilterService.IsBossItem(item.Id)
)
{
return false;
}
// Skip seasonal items
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;
}
return true;
})
.ToList(); .ToList();
} }
} }
@@ -224,13 +248,14 @@ public class ScavCaseRewardGenerator(
protected List<TemplateItem> PickRandomRewards( protected List<TemplateItem> PickRandomRewards(
List<TemplateItem> items, List<TemplateItem> items,
RewardCountAndPriceDetails itemFilters, RewardCountAndPriceDetails itemFilters,
string rarity) string rarity
)
{ {
List<TemplateItem> result = []; List<TemplateItem> result = [];
var rewardWasMoney = false; var rewardWasMoney = false;
var rewardWasAmmo = 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++) for (var i = 0; i < randomCount; i++)
{ {
if (RewardShouldBeMoney() && !rewardWasMoney) if (RewardShouldBeMoney() && !rewardWasMoney)
@@ -301,26 +326,30 @@ public class ScavCaseRewardGenerator(
protected TemplateItem GetRandomAmmo(string rarity) protected TemplateItem GetRandomAmmo(string rarity)
{ {
var possibleAmmoPool = _dbAmmoItemsCache.Where(ammo => var possibleAmmoPool = _dbAmmoItemsCache.Where(ammo =>
{ {
// Is ammo handbook price between desired range // Is ammo handbook price between desired range
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(ammo.Id); var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(ammo.Id);
if (_scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub.TryGetValue(rarity, if (
out var matchingAmmoRewardForRarity) && _scavCaseConfig.AmmoRewards.AmmoRewardValueRangeRub.TryGetValue(
handbookPrice >= matchingAmmoRewardForRarity.Min && rarity,
handbookPrice <= matchingAmmoRewardForRarity.Max out var matchingAmmoRewardForRarity
) )
{ && handbookPrice >= matchingAmmoRewardForRarity.Min
return true; && handbookPrice <= matchingAmmoRewardForRarity.Max
} )
{
return false; return true;
} }
);
return false;
});
if (!possibleAmmoPool.Any()) if (!possibleAmmoPool.Any())
{ {
// Filtered pool is empty // Filtered pool is empty
_logger.Warning(localisationService.GetText("scavcase-no_cartridges_found_matching_price")); _logger.Warning(
localisationService.GetText("scavcase-no_cartridges_found_matching_price")
);
} }
// Get a random ammo and return it // Get a random ammo and return it
@@ -334,7 +363,10 @@ public class ScavCaseRewardGenerator(
/// <param name="rewardItems">items to convert</param> /// <param name="rewardItems">items to convert</param>
/// <param name="rarity">The rarity desired ammo reward is for</param> /// <param name="rarity">The rarity desired ammo reward is for</param>
/// <returns>Product array</returns> /// <returns>Product array</returns>
protected List<List<Item>> RandomiseContainerItemRewards(List<TemplateItem> rewardItems, string rarity) protected List<List<Item>> RandomiseContainerItemRewards(
List<TemplateItem> rewardItems,
string rarity
)
{ {
// Each array is an item + children // Each array is an item + children
List<List<Item>> result = []; List<List<Item>> result = [];
@@ -346,8 +378,8 @@ public class ScavCaseRewardGenerator(
{ {
Id = _hashUtil.Generate(), Id = _hashUtil.Generate(),
Template = rewardItemDb.Id, Template = rewardItemDb.Id,
Upd = null Upd = null,
} },
]; ];
var rootItem = resultItem.FirstOrDefault(); var rootItem = resultItem.FirstOrDefault();
@@ -357,14 +389,16 @@ public class ScavCaseRewardGenerator(
} }
// Armor or weapon = use default preset from globals.json // Armor or weapon = use default preset from globals.json
else if ( else if (
_itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(rewardItemDb.Id) || _itemHelper.ArmorItemHasRemovableOrSoftInsertSlots(rewardItemDb.Id)
_itemHelper.IsOfBaseclass(rewardItemDb.Id, BaseClasses.WEAPON) || _itemHelper.IsOfBaseclass(rewardItemDb.Id, BaseClasses.WEAPON)
) )
{ {
var preset = _presetHelper.GetDefaultPreset(rewardItemDb.Id); var preset = _presetHelper.GetDefaultPreset(rewardItemDb.Id);
if (preset is null) if (preset is null)
{ {
_logger.Warning($"No preset for item: {rewardItemDb.Id} {rewardItemDb.Name}, skipping"); _logger.Warning(
$"No preset for item: {rewardItemDb.Id} {rewardItemDb.Name}, skipping"
);
continue; continue;
} }
@@ -375,11 +409,13 @@ public class ScavCaseRewardGenerator(
resultItem = presetAndMods; resultItem = presetAndMods;
} }
else if (_itemHelper.IsOfBaseclasses(rewardItemDb.Id, [BaseClasses.AMMO, BaseClasses.MONEY])) else if (
_itemHelper.IsOfBaseclasses(rewardItemDb.Id, [BaseClasses.AMMO, BaseClasses.MONEY])
)
{ {
rootItem.Upd = new Upd rootItem.Upd = new Upd
{ {
StackObjectsCount = GetRandomAmountRewardForScavCase(rewardItemDb, rarity) StackObjectsCount = GetRandomAmountRewardForScavCase(rewardItemDb, rarity),
}; };
} }
@@ -396,19 +432,23 @@ public class ScavCaseRewardGenerator(
/// <returns>filtered dbItems array</returns> /// <returns>filtered dbItems array</returns>
protected List<TemplateItem> GetFilteredItemsByPrice( protected List<TemplateItem> GetFilteredItemsByPrice(
List<TemplateItem> dbItems, List<TemplateItem> dbItems,
RewardCountAndPriceDetails itemFilters) RewardCountAndPriceDetails itemFilters
)
{ {
return dbItems.Where(item => return dbItems
.Where(item =>
{
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(item.Id);
if (
handbookPrice >= itemFilters.MinPriceRub
&& handbookPrice <= itemFilters.MaxPriceRub
)
{ {
var handbookPrice = _ragfairPriceService.GetStaticPriceForItem(item.Id); return true;
if (handbookPrice >= itemFilters.MinPriceRub && handbookPrice <= itemFilters.MaxPriceRub)
{
return true;
}
return false;
} }
)
return false;
})
.ToList(); .ToList();
} }
@@ -417,7 +457,9 @@ public class ScavCaseRewardGenerator(
/// </summary> /// </summary>
/// <param name="scavCaseDetails">production.json/scavRecipes object</param> /// <param name="scavCaseDetails">production.json/scavRecipes object</param>
/// <returns>ScavCaseRewardCountsAndPrices object</returns> /// <returns>ScavCaseRewardCountsAndPrices object</returns>
protected ScavCaseRewardCountsAndPrices GetScavCaseRewardCountsAndPrices(ScavRecipe scavCaseDetails) protected ScavCaseRewardCountsAndPrices GetScavCaseRewardCountsAndPrices(
ScavRecipe scavCaseDetails
)
{ {
return new ScavCaseRewardCountsAndPrices return new ScavCaseRewardCountsAndPrices
{ {
@@ -427,22 +469,22 @@ public class ScavCaseRewardGenerator(
MinCount = scavCaseDetails.EndProducts.Common.Min, MinCount = scavCaseDetails.EndProducts.Common.Min,
MaxCount = scavCaseDetails.EndProducts.Common.Max, MaxCount = scavCaseDetails.EndProducts.Common.Max,
MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Min, MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Min,
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Max MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Common].Max,
}, },
Rare = new RewardCountAndPriceDetails Rare = new RewardCountAndPriceDetails
{ {
MinCount = scavCaseDetails.EndProducts.Rare.Min, MinCount = scavCaseDetails.EndProducts.Rare.Min,
MaxCount = scavCaseDetails.EndProducts.Rare.Max, MaxCount = scavCaseDetails.EndProducts.Rare.Max,
MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Min, MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Min,
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Max MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.Rare].Max,
}, },
Superrare = new RewardCountAndPriceDetails Superrare = new RewardCountAndPriceDetails
{ {
MinCount = scavCaseDetails.EndProducts.Superrare.Min, MinCount = scavCaseDetails.EndProducts.Superrare.Min,
MaxCount = scavCaseDetails.EndProducts.Superrare.Max, MaxCount = scavCaseDetails.EndProducts.Superrare.Max,
MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Min, MinPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Min,
MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Max MaxPriceRub = _scavCaseConfig.RewardItemValueRangeRub[RewardRarity.SuperRare].Max,
} },
}; };
} }
@@ -458,7 +500,7 @@ public class ScavCaseRewardGenerator(
{ {
BaseClasses.AMMO => GetRandomisedAmmoRewardStackSize(itemToCalculate), BaseClasses.AMMO => GetRandomisedAmmoRewardStackSize(itemToCalculate),
BaseClasses.MONEY => GetRandomisedMoneyRewardStackSize(itemToCalculate, rarity), BaseClasses.MONEY => GetRandomisedMoneyRewardStackSize(itemToCalculate, rarity),
_ => 1 _ => 1,
}; };
} }
@@ -501,7 +543,7 @@ public class ScavCaseRewardGenerator(
_scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax<int>>(rarity).Min, _scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax<int>>(rarity).Min,
_scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax<int>>(rarity).Max _scavCaseConfig.MoneyRewards.GpCount.GetByJsonProp<MinMax<int>>(rarity).Max
), ),
_ => 1 _ => 1,
}; };
} }
} }
@@ -26,7 +26,7 @@ public class BarrelInvetoryMagGen(
// Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot" // Can't be done by _props.ammoType as grenade launcher shoots grenades with ammoType of "buckshot"
double? randomisedAmmoStackSize; double? randomisedAmmoStackSize;
if (inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom == 1) if (inventoryMagGen.GetAmmoTemplate().Properties.StackMaxRandom == 1)
// Doesn't stack // Doesn't stack
{ {
randomisedAmmoStackSize = _randomUtil.GetInt(3, 6); randomisedAmmoStackSize = _randomUtil.GetInt(3, 6);
} }
@@ -40,7 +40,7 @@ public class BarrelInvetoryMagGen(
_botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots( _botWeaponGeneratorHelper.AddAmmoIntoEquipmentSlots(
inventoryMagGen.GetAmmoTemplate().Id, inventoryMagGen.GetAmmoTemplate().Id,
(int) randomisedAmmoStackSize, (int)randomisedAmmoStackSize,
inventoryMagGen.GetPmcInventory(), inventoryMagGen.GetPmcInventory(),
null null
); );

Some files were not shown because too many files have changed in this diff Show More