initial push of Loot Processing Tool

This commit is contained in:
CWX 2023-08-12 19:08:38 +01:00
commit 8e3af6a893
137 changed files with 5160 additions and 0 deletions

400
.gitignore vendored Normal file
View File

@ -0,0 +1,400 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# rider
.idea/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

48
Config/config.json Normal file
View File

@ -0,0 +1,48 @@
{
"serverLocation": "D:\\Spt Stuff\\server",
"threads": 20,
"threadPoolingTimeoutMs": 1000,
"jsonSerializer": "DotNet",
"manualGarbageCollectionCalls": false,
"dataStorageConfig": {
"dataStorageType": "Memory",
"fileDataStorageTempLocation": "D:\\Spt Stuff\\Lootgenerator\\Dumps\\cache"
},
"loggerConfig": {
"logLevel": "Info",
"queueLoggerPoolingTimeoutMs": 500
},
"readerConfig": {
"preProcessorConfig": {
"preProcessors": [
"SevenZip"
],
"preProcessorTempFolder": "D:\\Spt Stuff\\Lootgenerator\\TEMP",
"cleanupTempFolderAfterProcess": true
},
"intakeReaderConfig": {
"readerType": "Json",
"ignoredDumpLocations": [
"Hideout"
]
},
"dumpFilesLocation": [
"D:\\Spt Stuff\\Lootgenerator\\dumps\\input"
],
"thresholdDate": "2023-01-08",
"acceptedFileExtensions": [
"json",
"7z"
],
"processSubFolders": true,
"fileFilters": [
"JsonDump"
]
},
"processorConfig": {
"spawnPointToleranceForForced": 99.5
},
"writerConfig": {
"outputLocation": "D:\\Spt Stuff\\Lootgenerator\\dumps\\output"
}
}

97
Config/forced_loose.yaml Normal file
View File

@ -0,0 +1,97 @@
---
Customs:
- 5938188786f77474f723e87f # Case 0031
- 5c12301c86f77419522ba7e4 # Flash drive with fake info
- 593965cf86f774087a77e1b6 # Case 0048
- 591092ef86f7747bb8703422 # Secure folder 0022 in big red offices
- 590c62a386f77412b0130255 # Sliderkey Secure Flash drive in Dorms 2-way room 220
- 5939e9b286f77462a709572c # Sealed letter (Terragroup)
- 5ac620eb86f7743a8e6e0da0 # Package of graphics cards in big red offices
- 590dde5786f77405e71908b2 # Bank case
- 5910922b86f7747d96753483 # Carbon case
- 5937fd0086f7742bf33fc198 # Bronze pocket watch on a chain
- 5939a00786f7742fe8132936 # Golden Zibbo lighter
- 5939e5a786f77461f11c0098 # Secure Folder 0013
#- 64bd1abff3a668f08805ce4f # Secure Flash drive V4 REMOVED BY BSG
Woods:
- 5938878586f7741b797c562f # Case 0052
- 5d3ec50586f774183a607442 # Jaeger's message Underneath the wooden lookout post.
- 5af04e0a86f7743a532b79e2 # Single-axis Fiber Optic Gyroscope: item_barter_electr_gyroscope
- 5a687e7886f7740c4a5133fb # Blood sample
- 5af04c0b86f774138708f78e # Motor Controller: item_barter_electr_controller
#- 64bde2248f3a947a990aa4a5 # Sliderkey Secure Flash drive #1 REMOVED BY BSG
#- 64bde265807321a9b905f076 # Sliderkey Secure Flash drive #2 REMOVED BY BSG
Shoreline:
- 5a294d7c86f7740651337cf9 # Drone 1 SAS disk
- 5a294d8486f774068638cd93 # Drone 2 SAS disk: ambiguous with itemTpl 5a294f1686f774340c7b7e4a
- 5efdafc1e70b5e33f86de058 # Sanitar's Surgery kit marked with a blue symbol
- 5939e5a786f77461f11c0098 # Secure folder 0013
- 5a6860d886f77411cd3a9e47 # Secure folder 0060
- 5a29357286f77409c705e025 # Sliderkey Flash drive
- 5efdaf6de6a30218ed211a48 # Sanitar's Ophthalmoscope In potted plant on dining room table.
- 5d357d6b86f7745b606e3508 # Photo album in west wing room 303
- 5b4c72b386f7745b453af9c0 # Motor Controller: item_barter_electr_controller2
- 5a0448bc86f774736f14efa8 # Key to the closed premises of the Health Resort
- 5a29276886f77435ed1b117c # Working hard drive
- 5b4c72fb86f7745cef1cffc5 # Single-axis Fiber Optic Gyroscope: item_barter_electr_gyroscope2
- 5b4c72c686f77462ac37e907 # Motor Controller: item_barter_electr_controller3
- 5b43237186f7742f3a4ab252 # Chemical container: item_quest_chem_container
- 5a29284f86f77463ef3db363 # Toughbook reinforced laptop
Interchange:
- 5ae9a18586f7746e381e16a3 # OLI cargo manifests
- 5ae9a0dd86f7742e5f454a05 # Goshan cargo manifests
- 5ae9a1b886f77404c8537c62 # Idea cargo manifests
- 5ae9a25386f7746dd946e6d9 # OLI cargo route documents (locked)
- 5ae9a3f586f7740aab00e4e6 # Clothes design handbook - Part 1
- 5ae9a4fc86f7746e381e1753 # Clothes design handbook - Part 2
- 5b4c81a086f77417d26be63f # Chemical container item_quest_chem_container2
- 5b4c81bd86f77418a75ae159 # Chemical container item_quest_chem_container3
Factory:
- 591093bb86f7747caa7bb2ee # On the neck of the dead scav in the bunker (Postman Pat Part 2)
- 593a87af86f774122f54a951 # Syringe with a chemical
Lighthouse:
- 61904c9df62c89219a56e034 # The message is tucked under the bottom of the door to the cabin.
- 619268ad78f4fa33f173dbe5 # Water pump operation data On the desk between other documents in the upper office.
- 619268de2be33f2604340159 # Pumping Station Operation Data In the upper floor office on the shelf.
- 61a00bcb177fb945751bbe6a # Stolen military documents On the back corner of the dining room table on the third floor at Chalet.
- 619252352be33f26043400a7 # Laptop with information
- 628393620d8524273e7eb028 # Working hard drive
- 6331bb0d1aa9f42b804997a6 # V3 Flash Drive
- 6398a0861c712b1e1d4dadf1 # Forged Lightkeeper intelligence (Snatch)
- 6399f54b0a36db13c823ad21 # Radio transmitter body (Key to the Tower)
# - 64b91627dd13d43b9d01d6d1 # Toughbook reinforced laptop (Event quest) REMOVED BY BSG
ReserveBase:
- 60915994c49cf53e4772cc38 # Military documents 1 on the table inside bunker control room (Documents)
- 60a3b6359c427533db36cf84 # Military documents 2 On the bottom shelf of the cupboard near the corner.
- 60a3b65c27adf161da7b6e14 # Military documents 3 Inside the cupboard next to the 4x4 Weapon Box.
- 608c22a003292f4ba43f8a1a # Medical record 1 (locked by RB-KSM key) (Disease history)
- 60a3b5b05f84d429b732e934 # Medical record 2 (locked by RB-SMP key) Disease history)
- 609267a2bb3f46069c3e6c7d # T-90M Commander Control Panel
- 60c080eb991ac167ad1c3ad4 # MBT Integrated Navigation System
- 6398a072e301557ae24cec92 # Original Lightkeeper Intelligence (Snatch)
Laboratory:
- 5eff135be0d3331e9d282b7b # Flash drive marked with blue tape (TerraGroup employee)
- 6398a4cfb5992f573c6562b3 # Secured tape
#- 64bdcfed8f3a947a990aa49a # Hermetic container for storing various chemicals #1 REMOVED BY BSG
#- 64bdd008b0bf3baa6702f35f # Hermetic container for storing various chemicals #2 REMOVED BY BSG
#- 64bdd014f3a668f08805ce64 # Hermetic container for storing various chemicals #3 REMOVED BY BSG
Streets of Tarkov:
- 63a943cead5cc12f22161ff7 # Accountant's notes (Audit)
- 638cbc68a63f1b49be6a3010 # Registered letter (Youve Got Mail)
- 638df4cc7b560b03794a18d2 # AG guitar pick (Audiophile)
- 638e0057ab150a5f56238960 # Housing office journal (Population Census)
- 63927b29c115f907b14700b9 # Chemical container with samples (Urban Medicine)
- 638dfc803083a019d447768e # Working hard drive (Surveillance)
- 638e9d5536b3b72c944e2fc7 # Flash drive with recordings (Watching You)
- 6393262086e646067c176aa2 # Medical observation record (?)
- 63989ced706b793c7d60cfef # Informant's journal (Missing Informant)
- 638cbb3ba63f1b49be6a300e # Secure Flash drive (Your Car Needs a Service)
- 63a39e1d234195315d4020bd # Primorsky 46-48 skybridge key

16
Config/forced_static.yaml Normal file
View File

@ -0,0 +1,16 @@
---
static_weapon_ids:
# ids for static weapons
- 5d52cc5ba4b9367408500062
- 5cdeb229d7f00c000e7ce174
forced_items:
Customs:
# unknown key
- containerId: custom_multiScene_00058
itemTpl: 593962ca86f774068014d9af
Streets of Tarkov:
# Backup hideout key
- containerId: container_City_SE_02_Primorskiy_51_indoor_00001
itemTpl: 6398fd8ad3de3849057f5128

View File

@ -0,0 +1,29 @@
---
Customs:
name:
- bigmap
Factory:
name:
- factory4_day
- factory4_night
Interchange:
name:
- interchange
Laboratory:
name:
- laboratory
Lighthouse:
name:
- lighthouse
ReserveBase:
name:
- rezervbase
Shoreline:
name:
- shoreline
Woods:
name:
- woods
Streets of Tarkov:
name:
- tarkovstreets

12
GCHandler.cs Normal file
View File

@ -0,0 +1,12 @@
namespace LootDumpProcessor;
public class GCHandler
{
public static void Collect()
{
if (LootDumpProcessorContext.GetConfig().ManualGarbageCollectionCalls)
{
GC.Collect();
}
}
}

8
Logger/ILogger.cs Normal file
View File

@ -0,0 +1,8 @@
namespace LootDumpProcessor.Logger;
public interface ILogger
{
void Setup();
void Log(string message, LogLevel level);
void Stop();
}

9
Logger/LogLevel.cs Normal file
View File

@ -0,0 +1,9 @@
namespace LootDumpProcessor.Logger;
public enum LogLevel
{
Error,
Warning,
Info,
Debug
}

14
Logger/LoggerFactory.cs Normal file
View File

@ -0,0 +1,14 @@
namespace LootDumpProcessor.Logger;
public static class LoggerFactory
{
private static ILogger? _logger;
public static ILogger GetInstance()
{
if (_logger == null)
_logger = new QueueLogger();
// TODO: implement factory
return _logger;
}
}

109
Logger/QueueLogger.cs Normal file
View File

@ -0,0 +1,109 @@
using System.Collections.Concurrent;
namespace LootDumpProcessor.Logger;
public class QueueLogger : ILogger
{
private BlockingCollection<LoggedMessage> queuedMessages = new BlockingCollection<LoggedMessage>();
private Task? loggerThread;
private bool isRunning;
private int logLevel;
private static readonly int _logTerminationTimeoutMs = 1000;
private static readonly int _logTerminationRetryCount = 3;
public void Setup()
{
SetLogLevel();
isRunning = true;
loggerThread = Task.Factory.StartNew(() =>
{
while (isRunning)
{
while (queuedMessages.TryTake(out var value))
{
Console.ResetColor();
switch (value.LogLevel)
{
case LogLevel.Error:
Console.BackgroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.Black;
break;
case LogLevel.Warning:
Console.BackgroundColor = ConsoleColor.Yellow;
Console.ForegroundColor = ConsoleColor.Black;
break;
case LogLevel.Debug:
Console.ForegroundColor = ConsoleColor.DarkCyan;
break;
case LogLevel.Info:
default:
break;
}
Console.WriteLine(value.Message);
}
Thread.Sleep(
TimeSpan.FromMilliseconds(LootDumpProcessorContext.GetConfig().LoggerConfig.QueueLoggerPoolingTimeoutMs));
}
}, TaskCreationOptions.LongRunning);
}
private void SetLogLevel()
{
logLevel = GetLogLevel(LootDumpProcessorContext.GetConfig().LoggerConfig.LogLevel);
}
private int GetLogLevel(LogLevel level)
{
switch (level)
{
case LogLevel.Error:
return 1;
case LogLevel.Warning:
return 2;
case LogLevel.Info:
return 3;
case LogLevel.Debug:
return 4;
default:
throw new ArgumentOutOfRangeException();
}
}
public void Log(string message, LogLevel level)
{
if (GetLogLevel(level) <= logLevel)
queuedMessages.Add(new LoggedMessage { Message = message, LogLevel = level });
}
// Wait for graceful termination of the logging thread
public void Stop()
{
isRunning = false;
if (loggerThread != null)
{
Console.ResetColor();
int retryCount = 0;
while (!loggerThread.IsCompleted)
{
if (retryCount == _logTerminationRetryCount)
{
Console.WriteLine(
$"Logger thread did not terminate by itself after {retryCount} retries. Some log messages may be lost.");
break;
}
Console.WriteLine($"Waiting {_logTerminationTimeoutMs}ms for logger termination");
Thread.Sleep(_logTerminationTimeoutMs);
retryCount++;
}
}
}
class LoggedMessage
{
public string Message { get; init; }
public LogLevel LogLevel { get; init; }
}
}

36
LootDumpProcessor.csproj Normal file
View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="7z.Libs" Version="21.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="NumSharp" Version="0.30.0" />
<PackageReference Include="SevenZipSharp.Interop" Version="19.0.2" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
<PackageReference Include="YamlDotNet" Version="13.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="Config\forced_loose.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\forced_static.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\map_directory_mapping.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Config\config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

25
LootDumpProcessor.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LootDumpProcessor", "LootDumpProcessor.csproj", "{887819E1-72BF-4F10-9246-77D8088AC7D2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{887819E1-72BF-4F10-9246-77D8088AC7D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{887819E1-72BF-4F10-9246-77D8088AC7D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{887819E1-72BF-4F10-9246-77D8088AC7D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{887819E1-72BF-4F10-9246-77D8088AC7D2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {39C0A9FF-B0F5-4C3F-AAA7-F9E9225AE70F}
EndGlobalSection
EndGlobal

127
LootDumpProcessorContext.cs Normal file
View File

@ -0,0 +1,127 @@
using LootDumpProcessor.Model.Config;
using LootDumpProcessor.Model.Output.StaticContainer;
using LootDumpProcessor.Process;
using LootDumpProcessor.Serializers.Json;
using LootDumpProcessor.Serializers.Yaml;
namespace LootDumpProcessor;
public class LootDumpProcessorContext
{
private static Config? _config;
private static readonly object _configLock = new object();
private static ForcedStatic? _forcedStatic;
private static readonly object _forcedStaticLock = new object();
private static Dictionary<string, MapDirectoryMapping>? _mapDirectoryMappings;
private static readonly object _mapDirectoryMappingsLock = new object();
private static HashSet<string>? _staticWeaponIds;
private static readonly object _staticWeaponIdsLock = new object();
private static Dictionary<string, List<StaticForced>>? _forcedItems;
private static readonly object _forcedItemsLock = new object();
private static Dictionary<string, HashSet<string>>? _forcedLoose;
private static readonly object _forcedLooseLock = new object();
private static TarkovItems? _tarkovItems;
private static readonly object _tarkovItemsLock = new object();
public static Config GetConfig()
{
lock (_configLock)
{
if (_config == null)
{
// This is the only instance where manual selection of the serializer is required
// after this, GetInstance() for the JsonSerializerFactory should used without
// parameters
_config = JsonSerializerFactory.GetInstance(JsonSerializerTypes.DotNet)
.Deserialize<Config>(File.ReadAllText("./Config/config.json"));
}
}
return _config;
}
public static ForcedStatic GetForcedStatic()
{
lock (_forcedStaticLock)
{
if (_forcedStatic == null)
{
_forcedStatic = YamlSerializerFactory.GetInstance()
.Deserialize<ForcedStatic>(File.ReadAllText("./Config/forced_static.yaml"));
}
}
return _forcedStatic;
}
public static Dictionary<string, MapDirectoryMapping> GetDirectoryMappings()
{
lock (_mapDirectoryMappingsLock)
{
if (_mapDirectoryMappings == null)
{
_mapDirectoryMappings = YamlSerializerFactory.GetInstance()
.Deserialize<Dictionary<string, MapDirectoryMapping>>(
File.ReadAllText("./Config/map_directory_mapping.yaml"));
}
}
return _mapDirectoryMappings;
}
public static HashSet<string> GetStaticWeaponIds()
{
lock (_staticWeaponIdsLock)
{
if (_staticWeaponIds == null)
{
_staticWeaponIds = GetForcedStatic().StaticWeaponIds.ToHashSet();
}
}
return _staticWeaponIds;
}
public static Dictionary<string, List<StaticForced>> GetForcedItems()
{
lock (_forcedItemsLock)
{
if (_forcedItems == null)
{
_forcedItems = GetForcedStatic().ForcedItems;
}
}
return _forcedItems;
}
public static Dictionary<string, HashSet<string>> GetForcedLooseItems()
{
lock (_forcedLooseLock)
{
if (_forcedLoose == null)
{
_forcedLoose = YamlSerializerFactory.GetInstance().Deserialize<Dictionary<string, HashSet<string>>>(
File.ReadAllText("./Config/forced_loose.yaml"));
}
}
return _forcedLoose;
}
public static TarkovItems GetTarkovItems()
{
lock (_tarkovItemsLock)
{
if (_tarkovItems == null)
{
_tarkovItems = new TarkovItems(
$"{GetConfig().ServerLocation}/project/assets/database/templates/items.json",
$"{GetConfig().ServerLocation}/project/assets/database/templates/handbook.json"
);
}
}
return _tarkovItems;
}
}

41
Model/ComposedKey.cs Normal file
View File

@ -0,0 +1,41 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Model.Processing;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model;
public class ComposedKey
{
[JsonProperty("key")]
[JsonPropertyName("key")]
public string Key { get; init; }
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public Item? FirstItem { get; }
public ComposedKey(Template template) : this(template.Items)
{
}
public ComposedKey(List<Item>? items)
{
Key = items?.Select(i => i.Tpl)
.Where(i => !string.IsNullOrEmpty(i) &&
!LootDumpProcessorContext.GetTarkovItems().IsBaseClass(i, BaseClasses.Ammo))
.Cast<string>()
.Select(i => (double)i.GetHashCode())
.Sum()
.ToString() ?? Guid.NewGuid().ToString();
FirstItem = items?[0];
}
public override bool Equals(object? obj)
{
if (obj is not ComposedKey key)
return false;
return this.Key == key.Key;
}
public override int GetHashCode() => Key.GetHashCode();
}

48
Model/Config/Config.cs Normal file
View File

@ -0,0 +1,48 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Serializers.Json;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class Config
{
[JsonProperty("serverLocation")]
[JsonPropertyName("serverLocation")]
public string? ServerLocation { get; set; }
[JsonProperty("threads")]
[JsonPropertyName("threads")]
public int Threads { get; set; } = 6;
[JsonProperty("threadPoolingTimeoutMs")]
[JsonPropertyName("threadPoolingTimeoutMs")]
public int ThreadPoolingTimeoutMs { get; set; } = 1000;
[JsonProperty("jsonSerializer")]
[JsonPropertyName("jsonSerializer")]
public JsonSerializerTypes JsonSerializer { get; set; } = JsonSerializerTypes.DotNet;
[JsonProperty("manualGarbageCollectionCalls")]
[JsonPropertyName("manualGarbageCollectionCalls")]
public bool ManualGarbageCollectionCalls { get; set; }
[JsonProperty("dataStorageConfig")]
[JsonPropertyName("dataStorageConfig")]
public DataStorageConfig DataStorageConfig { get; set; }
[JsonProperty("loggerConfig")]
[JsonPropertyName("loggerConfig")]
public LoggerConfig LoggerConfig { get; set; }
[JsonProperty("readerConfig")]
[JsonPropertyName("readerConfig")]
public ReaderConfig ReaderConfig { get; set; }
[JsonProperty("processorConfig")]
[JsonPropertyName("processorConfig")]
public ProcessorConfig ProcessorConfig { get; set; }
[JsonProperty("writerConfig")]
[JsonPropertyName("writerConfig")]
public WriterConfig WriterConfig { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Storage;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class DataStorageConfig
{
[JsonProperty("dataStorageType")]
[JsonPropertyName("dataStorageType")]
public DataStorageTypes DataStorageType { get; set; } = DataStorageTypes.File;
[JsonProperty("fileDataStorageTempLocation")]
[JsonPropertyName("fileDataStorageTempLocation")]
public string? FileDataStorageTempLocation { get; set; }
}

View File

@ -0,0 +1,12 @@
using LootDumpProcessor.Model.Output.StaticContainer;
using YamlDotNet.Serialization;
namespace LootDumpProcessor.Model.Config;
public class ForcedStatic
{
[YamlMember(Alias = "static_weapon_ids")]
public List<string> StaticWeaponIds { get; set; }
[YamlMember(Alias = "forced_items")] public Dictionary<string, List<StaticForced>> ForcedItems { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Reader;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class IntakeReaderConfig
{
[JsonProperty("readerType")]
[JsonPropertyName("readerType")]
public IntakeReaderTypes IntakeReaderType { get; set; } = IntakeReaderTypes.Json;
[JsonProperty("ignoredDumpLocations")]
[JsonPropertyName("ignoredDumpLocations")]
public List<string> IgnoredDumpLocations { get; set; } = new List<string>();
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Logger;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class LoggerConfig
{
[JsonProperty("logLevel")]
[JsonPropertyName("logLevel")]
public LogLevel LogLevel { get; set; } = LogLevel.Info;
[JsonProperty("queueLoggerPoolingTimeoutMs")]
[JsonPropertyName("queueLoggerPoolingTimeoutMs")]
public int QueueLoggerPoolingTimeoutMs { get; set; } = 1000;
}

View File

@ -0,0 +1,11 @@
using YamlDotNet.Serialization;
namespace LootDumpProcessor.Model.Config;
public class MapDirectoryMapping
{
[YamlMember(Alias = "name")]
public List<string> Name { get; set; }
[YamlMember(Alias = "threshold_date")]
public string ThresholdDate { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Reader.PreProcess;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class PreProcessorConfig
{
[JsonProperty("preProcessors")]
[JsonPropertyName("preProcessors")]
public List<PreProcessReaderTypes>? PreProcessors { get; set; }
[JsonProperty("preProcessorTempFolder")]
[JsonPropertyName("preProcessorTempFolder")]
public string? PreProcessorTempFolder { get; set; }
[JsonProperty("cleanupTempFolderAfterProcess")]
[JsonPropertyName("cleanupTempFolderAfterProcess")]
public bool CleanupTempFolderAfterProcess { get; set; } = true;
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class ProcessorConfig
{
[JsonProperty("spawnPointToleranceForForced")]
[JsonPropertyName("spawnPointToleranceForForced")]
public double SpawnPointToleranceForForced { get; set; } = 99D;
}

View File

@ -0,0 +1,36 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Reader.Filters;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class ReaderConfig
{
[JsonProperty("intakeReaderConfig")]
[JsonPropertyName("intakeReaderConfig")]
public IntakeReaderConfig? IntakeReaderConfig { get; set; }
[JsonProperty("preProcessorConfig")]
[JsonPropertyName("preProcessorConfig")]
public PreProcessorConfig? PreProcessorConfig { get; set; }
[JsonProperty("dumpFilesLocation")]
[JsonPropertyName("dumpFilesLocation")]
public List<string>? DumpFilesLocation { get; set; }
[JsonProperty("thresholdDate")]
[JsonPropertyName("thresholdDate")]
public string? ThresholdDate { get; set; }
[JsonProperty("acceptedFileExtensions")]
[JsonPropertyName("acceptedFileExtensions")]
public List<string> AcceptedFileExtensions { get; set; } = new List<string>();
[JsonProperty("processSubFolders")]
[JsonPropertyName("processSubFolders")]
public bool ProcessSubFolders { get; set; }
[JsonProperty("fileFilters")]
[JsonPropertyName("fileFilters")]
public List<FileFilterTypes>? FileFilters { get; set; }
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Config;
public class WriterConfig
{
[JsonProperty("outputLocation")]
[JsonPropertyName("outputLocation")]
public string? OutputLocation { get; set; }
}

20
Model/FireMode.cs Normal file
View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class FireMode : ICloneable
{
[JsonProperty("FireMode", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("FireMode")]
public string? FireModeType { get; set; }
public object Clone()
{
return new FireMode
{
FireModeType = this.FireModeType
};
}
}
}

20
Model/Foldable.cs Normal file
View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class Foldable : ICloneable
{
[JsonProperty("Folded", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Folded")]
public bool? Folded { get; set; }
public object Clone()
{
return new Foldable
{
Folded = this.Folded
};
}
}
}

36
Model/GroupPosition.cs Normal file
View File

@ -0,0 +1,36 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Processor;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class GroupPosition : ICloneable
{
[JsonProperty("Name", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Name")]
public string? Name { get; set; }
[JsonProperty("Weight", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Weight")]
public int? Weight { get; set; }
[JsonProperty("Position", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Position")]
public Vector3? Position { get; set; }
[JsonProperty("Rotation", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Rotation")]
public Vector3? Rotation { get; set; }
public object Clone()
{
return new GroupPosition
{
Name = this.Name,
Weight = this.Weight,
Position = ProcessorUtil.Copy(this.Position),
Rotation = ProcessorUtil.Copy(this.Rotation)
};
}
}
}

View File

@ -0,0 +1,48 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class AirdropParameter
{
[JsonProperty("PlaneAirdropStartMin", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropStartMin")]
public int? PlaneAirdropStartMin { get; set; }
[JsonProperty("PlaneAirdropStartMax", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropStartMax")]
public int? PlaneAirdropStartMax { get; set; }
[JsonProperty("PlaneAirdropEnd", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropEnd")]
public int? PlaneAirdropEnd { get; set; }
[JsonProperty("PlaneAirdropChance", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropChance")]
public double? PlaneAirdropChance { get; set; }
[JsonProperty("PlaneAirdropMax", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropMax")]
public int? PlaneAirdropMax { get; set; }
[JsonProperty("PlaneAirdropCooldownMin", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropCooldownMin")]
public int? PlaneAirdropCooldownMin { get; set; }
[JsonProperty("PlaneAirdropCooldownMax", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlaneAirdropCooldownMax")]
public int? PlaneAirdropCooldownMax { get; set; }
[JsonProperty("AirdropPointDeactivateDistance", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("AirdropPointDeactivateDistance")]
public int? AirdropPointDeactivateDistance { get; set; }
[JsonProperty("MinPlayersCountToSpawnAirdrop", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MinPlayersCountToSpawnAirdrop")]
public int? MinPlayersCountToSpawnAirdrop { get; set; }
[JsonProperty("UnsuccessfulTryPenalty", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("UnsuccessfulTryPenalty")]
public int? UnsuccessfulTryPenalty { get; set; }
}
}

14
Model/Input/Banner.cs Normal file
View File

@ -0,0 +1,14 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class Banner
{
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] [JsonPropertyName("id")]
public string? Id { get; set; }
[JsonProperty("pic")] [JsonPropertyName("pic")]
public Preview? Pic { get; set; }
}
}

View File

@ -0,0 +1,28 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class BotLocationModifier
{
[JsonProperty("AccuracySpeed", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("AccuracySpeed")]
public double? AccuracySpeed { get; set; }
[JsonProperty("Scattering", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Scattering")]
public double? Scattering { get; set; }
[JsonProperty("GainSight", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("GainSight")]
public double? GainSight { get; set; }
[JsonProperty("MarksmanAccuratyCoef", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MarksmanAccuratyCoef")]
public double? MarksmanAccuratyCoef { get; set; }
[JsonProperty("VisibleDistance", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("VisibleDistance")]
public double? VisibleDistance { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class ColliderParams
{
[JsonProperty("_parent", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("_parent")]
public string? Parent { get; set; }
[JsonProperty("_props", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("_props")]
public Props? Props { get; set; }
}
}

320
Model/Input/Data.cs Normal file
View File

@ -0,0 +1,320 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class Data
{
[JsonProperty("Enabled", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Enabled")]
public bool? Enabled { get; set; }
[JsonProperty("EnableCoop", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("EnableCoop")]
public bool? EnableCoop { get; set; }
[JsonProperty("Locked", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Locked")]
public bool? Locked { get; set; }
[JsonProperty("Insurance", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Insurance")]
public bool? Insurance { get; set; }
[JsonProperty("SafeLocation", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("SafeLocation")]
public bool? SafeLocation { get; set; }
[JsonProperty("Name", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Name")]
public string? Name { get; set; }
[JsonProperty("Description", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Description")]
public string? Description { get; set; }
[JsonProperty("Scene", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Scene")]
public Preview? Scene { get; set; }
[JsonProperty("Area", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Area")]
public double? Area { get; set; }
[JsonProperty("RequiredPlayerLevel", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("RequiredPlayerLevel")]
public int? RequiredPlayerLevel { get; set; }
[JsonProperty("PmcMaxPlayersInGroup", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PmcMaxPlayersInGroup")]
public int? PmcMaxPlayersInGroup { get; set; }
[JsonProperty("ScavMaxPlayersInGroup", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("ScavMaxPlayersInGroup")]
public int? ScavMaxPlayersInGroup { get; set; }
[JsonProperty("MinPlayers", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MinPlayers")]
public int? MinPlayers { get; set; }
[JsonProperty("MaxPlayers", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MaxPlayers")]
public int? MaxPlayers { get; set; }
[JsonProperty("MaxCoopGroup", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MaxCoopGroup")]
public int? MaxCoopGroup { get; set; }
[JsonProperty("exit_count", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("exit_count")]
public int? ExitCount { get; set; }
[JsonProperty("exit_access_time", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("exit_access_time")]
public int? ExitAccessTime { get; set; }
[JsonProperty("exit_time", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("exit_time")]
public int? ExitTime { get; set; }
[JsonProperty("Preview", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Preview")]
public Preview? Preview { get; set; }
[JsonProperty("IconX", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("IconX")]
public int? IconX { get; set; }
[JsonProperty("IconY", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("IconY")]
public int? IconY { get; set; }
[JsonProperty("filter_ex", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("filter_ex")]
public List<object>? FilterEx { get; set; }
[JsonProperty("waves", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("waves")]
public List<Wave>? Waves { get; set; }
[JsonProperty("limits", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("limits")]
public List<object>? Limits { get; set; }
[JsonProperty("AveragePlayTime", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("AveragePlayTime")]
public int? AveragePlayTime { get; set; }
[JsonProperty("AveragePlayerLevel", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("AveragePlayerLevel")]
public int? AveragePlayerLevel { get; set; }
[JsonProperty("EscapeTimeLimit", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("EscapeTimeLimit")]
public int? EscapeTimeLimit { get; set; }
[JsonProperty("EscapeTimeLimitCoop", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("EscapeTimeLimitCoop")]
public int? EscapeTimeLimitCoop { get; set; }
[JsonProperty("Rules", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Rules")]
public string? Rules { get; set; }
[JsonProperty("IsSecret", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("IsSecret")]
public bool? IsSecret { get; set; }
[JsonProperty("doors", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("doors")]
public List<object>? Doors { get; set; }
[JsonProperty("tmp_location_field_remove_me", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("tmp_location_field_remove_me")]
public int? TmpLocationFieldRemoveMe { get; set; }
[JsonProperty("MinDistToExitPoint", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MinDistToExitPoint")]
public int? MinDistToExitPoint { get; set; }
[JsonProperty("MaxDistToFreePoint", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MaxDistToFreePoint")]
public int? MaxDistToFreePoint { get; set; }
[JsonProperty("MinDistToFreePoint", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MinDistToFreePoint")]
public int? MinDistToFreePoint { get; set; }
[JsonProperty("MaxBotPerZone", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MaxBotPerZone")]
public int? MaxBotPerZone { get; set; }
[JsonProperty("OpenZones", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("OpenZones")]
public string? OpenZones { get; set; }
[JsonProperty("OcculsionCullingEnabled", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("OcculsionCullingEnabled")]
public bool? OcculsionCullingEnabled { get; set; }
[JsonProperty("GlobalLootChanceModifier", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("GlobalLootChanceModifier")]
public double? GlobalLootChanceModifier { get; set; }
[JsonProperty("OldSpawn", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("OldSpawn")]
public bool? OldSpawn { get; set; }
[JsonProperty("NewSpawn", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("NewSpawn")]
public bool? NewSpawn { get; set; }
[JsonProperty("BotMax", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotMax")]
public int? BotMax { get; set; }
[JsonProperty("BotStart", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotStart")]
public int? BotStart { get; set; }
[JsonProperty("BotStop", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotStop")]
public int? BotStop { get; set; }
[JsonProperty("BotMaxTimePlayer", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotMaxTimePlayer")]
public int? BotMaxTimePlayer { get; set; }
[JsonProperty("BotSpawnTimeOnMin", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotSpawnTimeOnMin")]
public int? BotSpawnTimeOnMin { get; set; }
[JsonProperty("BotSpawnTimeOnMax", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotSpawnTimeOnMax")]
public int? BotSpawnTimeOnMax { get; set; }
[JsonProperty("BotSpawnTimeOffMin", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotSpawnTimeOffMin")]
public int? BotSpawnTimeOffMin { get; set; }
[JsonProperty("BotSpawnTimeOffMax", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotSpawnTimeOffMax")]
public int? BotSpawnTimeOffMax { get; set; }
[JsonProperty("BotMaxPlayer", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotMaxPlayer")]
public int? BotMaxPlayer { get; set; }
[JsonProperty("BotEasy", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotEasy")]
public int? BotEasy { get; set; }
[JsonProperty("BotNormal", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotNormal")]
public int? BotNormal { get; set; }
[JsonProperty("BotHard", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotHard")]
public int? BotHard { get; set; }
[JsonProperty("BotImpossible", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotImpossible")]
public int? BotImpossible { get; set; }
[JsonProperty("BotAssault", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotAssault")]
public int? BotAssault { get; set; }
[JsonProperty("BotMarksman", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotMarksman")]
public int? BotMarksman { get; set; }
[JsonProperty("DisabledScavExits", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("DisabledScavExits")]
public string? DisabledScavExits { get; set; }
[JsonProperty("AccessKeys", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("AccessKeys")]
public List<object>? AccessKeys { get; set; }
[JsonProperty("UnixDateTime", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("UnixDateTime")]
public int? UnixDateTime { get; set; }
[JsonProperty("users_gather_seconds", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("users_gather_seconds")]
public int? UsersGatherSeconds { get; set; }
[JsonProperty("users_spawn_seconds_n", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("users_spawn_seconds_n")]
public int? UsersSpawnSecondsN { get; set; }
[JsonProperty("users_spawn_seconds_n2", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("users_spawn_seconds_n2")]
public int? UsersSpawnSecondsN2 { get; set; }
[JsonProperty("users_summon_seconds", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("users_summon_seconds")]
public int? UsersSummonSeconds { get; set; }
[JsonProperty("sav_summon_seconds", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("sav_summon_seconds")]
public int? SavSummonSeconds { get; set; }
[JsonProperty("matching_min_seconds", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("matching_min_seconds")]
public int? MatchingMinSeconds { get; set; }
[JsonProperty("GenerateLocalLootCache", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("GenerateLocalLootCache")]
public bool? GenerateLocalLootCache { get; set; }
[JsonProperty("MinMaxBots", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MinMaxBots")]
public List<object>? MinMaxBots { get; set; }
[JsonProperty("BotLocationModifier", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotLocationModifier")]
public BotLocationModifier? BotLocationModifier { get; set; }
[JsonProperty("exits", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("exits")]
public List<Exit>? Exits { get; set; }
[JsonProperty("DisabledForScav", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("DisabledForScav")]
public bool? DisabledForScav { get; set; }
[JsonProperty("ExitZones", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("ExitZones")]
public ExitZones? ExitZones { get; set; }
[JsonProperty("SpawnPointParams", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("SpawnPointParams")]
public List<SpawnPointParam>? SpawnPointParams { get; set; }
[JsonProperty("AirdropParameters", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("AirdropParameters")]
public List<AirdropParameter>? AirdropParameters { get; set; }
[JsonProperty("Id", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Id")]
public string? Id { get; set; }
[JsonProperty("_Id", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("_Id")]
public string? _Id { get; set; }
[JsonProperty("Loot", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Loot")]
public List<Template>? Loot { get; set; }
[JsonProperty("Banners", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Banners")]
public List<Banner>? Banners { get; set; }
[JsonProperty("BossLocationSpawn", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BossLocationSpawn")]
public List<object>? BossLocationSpawn { get; set; }
}
}

60
Model/Input/Exit.cs Normal file
View File

@ -0,0 +1,60 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class Exit
{
[JsonProperty("Name", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Name")]
public string? Name { get; set; }
[JsonProperty("EntryPoints", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("EntryPoints")]
public string? EntryPoints { get; set; }
[JsonProperty("Chance", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Chance")]
public int? Chance { get; set; }
[JsonProperty("MinTime", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MinTime")]
public int? MinTime { get; set; }
[JsonProperty("MaxTime", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MaxTime")]
public int? MaxTime { get; set; }
[JsonProperty("PlayersCount", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PlayersCount")]
public int? PlayersCount { get; set; }
[JsonProperty("ExfiltrationTime", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("ExfiltrationTime")]
public int? ExfiltrationTime { get; set; }
[JsonProperty("PassageRequirement", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("PassageRequirement")]
public string? PassageRequirement { get; set; }
[JsonProperty("ExfiltrationType", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("ExfiltrationType")]
public string? ExfiltrationType { get; set; }
[JsonProperty("RequiredSlot", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("RequiredSlot")]
public string? RequiredSlot { get; set; }
[JsonProperty("Id", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Id")]
public string? Id { get; set; }
[JsonProperty("RequirementTip", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("RequirementTip")]
public string? RequirementTip { get; set; }
[JsonProperty("Count", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Count")]
public int? Count { get; set; }
}
}

12
Model/Input/ExitZones.cs Normal file
View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class ExitZones
{
[JsonProperty("$oid", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("$oid")]
public string? Oid { get; set; }
}
}

16
Model/Input/Preview.cs Normal file
View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class Preview
{
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("path")]
public string? Path { get; set; }
[JsonProperty("rcid", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("rcid")]
public string? Rcid { get; set; }
}
}

16
Model/Input/Props.cs Normal file
View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class Props
{
[JsonProperty("Center", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Center")]
public Vector3? Center { get; set; }
[JsonProperty("Radius", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Radius")]
public double? Radius { get; set; }
}
}

20
Model/Input/RootData.cs Normal file
View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class RootData
{
[JsonProperty("err", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("err")]
public int? Err { get; set; }
[JsonProperty("errmsg")]
[JsonPropertyName("errmsg")]
public object? errmsg { get; set; }
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("data")]
public Data? Data { get; set; }
}
}

View File

@ -0,0 +1,40 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class SpawnPointParam
{
[JsonProperty("Id", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Id")]
public string? Id { get; set; }
[JsonProperty("Position", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Position")]
public Vector3? Position { get; set; }
[JsonProperty("Rotation", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Rotation")]
public double? Rotation { get; set; }
[JsonProperty("Sides", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Sides")]
public List<string>? Sides { get; set; }
[JsonProperty("Categories", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Categories")]
public List<string>? Categories { get; set; }
[JsonProperty("Infiltration", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Infiltration")]
public string? Infiltration { get; set; }
[JsonProperty("DelayToCanSpawnSec", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("DelayToCanSpawnSec")]
public int? DelayToCanSpawnSec { get; set; }
[JsonProperty("ColliderParams", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("ColliderParams")]
public ColliderParams? ColliderParams { get; set; }
}
}

48
Model/Input/Wave.cs Normal file
View File

@ -0,0 +1,48 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Input
{
public class Wave
{
[JsonProperty("number", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("number")]
public int? Number { get; set; }
[JsonProperty("time_min", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("time_min")]
public int? TimeMin { get; set; }
[JsonProperty("time_max", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("time_max")]
public int? TimeMax { get; set; }
[JsonProperty("slots_min", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("slots_min")]
public int? SlotsMin { get; set; }
[JsonProperty("slots_max", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("slots_max")]
public int? SlotsMax { get; set; }
[JsonProperty("SpawnPoints", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("SpawnPoints")]
public string? SpawnPoints { get; set; }
[JsonProperty("BotSide", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotSide")]
public string? BotSide { get; set; }
[JsonProperty("BotPreset", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("BotPreset")]
public string? BotPreset { get; set; }
[JsonProperty("isPlayers", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("isPlayers")]
public bool? IsPlayers { get; set; }
[JsonProperty("WildSpawnType", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("WildSpawnType")]
public string? WildSpawnType { get; set; }
}
}

58
Model/Item.cs Normal file
View File

@ -0,0 +1,58 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Processor;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class Item : ICloneable
{
[JsonProperty("_id", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("_id")]
public string? Id { get; set; }
[JsonProperty("_tpl", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("_tpl")]
public string? Tpl { get; set; }
[JsonProperty("parentId", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("parentId")]
public string? ParentId { get; set; }
[JsonProperty("slotId", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("slotId")]
public string? SlotId { get; set; }
[JsonProperty("location", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("location")]
public object? Location { get; set; }
[JsonProperty("upd", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("upd")]
public Upd? Upd { get; set; }
public override bool Equals(object? obj)
{
if (obj is not Item parsed)
return false;
return parsed.Tpl == this.Tpl && parsed.ParentId == this.ParentId;
}
public override int GetHashCode()
{
return (this.Tpl?.GetHashCode() + this.ParentId?.GetHashCode()) ?? base.GetHashCode();
}
public object Clone()
{
return new Item
{
Id = this.Id,
Tpl = this.Tpl,
ParentId = this.ParentId,
SlotId = this.SlotId,
Location = this.Location,
Upd = ProcessorUtil.Copy(this.Upd)
};
}
}
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output;
public abstract class AbstractDistribution
{
[JsonProperty("relativeProbability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("relativeProbability")]
public int? RelativeProbability { get; set; }
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output;
public class AmmoDistribution : AbstractDistribution
{
[JsonProperty("tpl", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("tpl")]
public string? Tpl { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output
{
public class ItemCountDistribution
{
[JsonProperty("count", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("count")]
public int? Count { get; set; }
[JsonProperty("relativeProbability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("relativeProbability")]
public int? RelativeProbability { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output
{
public class ItemDistribution : AbstractDistribution
{
[JsonProperty("composedKey", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("composedKey")]
public ComposedKey? ComposedKey { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.LooseLoot
{
public class LooseLootRoot
{
[JsonProperty("spawnpointCount", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("spawnpointCount")]
public SpawnPointCount? SpawnPointCount { get; set; }
[JsonProperty("spawnpointsForced", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("spawnpointsForced")]
public List<SpawnPointsForced>? SpawnPointsForced { get; set; }
[JsonProperty("spawnpoints", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("spawnpoints")]
public List<SpawnPoint>? SpawnPoints { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.LooseLoot
{
public class SpawnPoint
{
[JsonProperty("locationId", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("locationId")]
public string? LocationId { get; set; }
[JsonProperty("probability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("probability")]
public double? Probability { get; set; }
[JsonProperty("template", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("template")]
public Template? Template { get; set; }
[JsonProperty("itemDistribution", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("itemDistribution")]
public List<ItemDistribution>? ItemDistribution { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.LooseLoot
{
public class SpawnPointCount
{
[JsonProperty("mean", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("mean")]
public double? Mean { get; set; }
[JsonProperty("std", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("std")]
public double? Std { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.LooseLoot
{
public class SpawnPointsForced
{
[JsonProperty("locationId", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("locationId")]
public string? LocationId { get; set; }
[JsonProperty("probability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("probability")]
public double? Probability { get; set; }
[JsonProperty("template", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("template")]
public Template? Template { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.StaticContainer
{
public class MapStaticLoot
{
[JsonProperty("staticWeapons", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("staticWeapons")]
public List<Template>? StaticWeapons { get; set; }
[JsonProperty("staticContainers", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("staticContainers")]
public List<StaticDataPoint>? StaticContainers { get; set; }
[JsonProperty("staticForced", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("staticForced")]
public List<StaticForced>? StaticForced { get; set; }
}
}

View File

@ -0,0 +1,44 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.StaticContainer
{
public class StaticContainerRoot
{
[JsonProperty("Laboratory", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Laboratory")]
public MapStaticLoot? Laboratory { get; set; }
[JsonProperty("Shoreline", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Shoreline")]
public MapStaticLoot? Shoreline { get; set; }
[JsonProperty("Streets of Tarkov", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Streets of Tarkov")]
public MapStaticLoot? StreetsofTarkov { get; set; }
[JsonProperty("Interchange", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Interchange")]
public MapStaticLoot? Interchange { get; set; }
[JsonProperty("Customs", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Customs")]
public MapStaticLoot? Customs { get; set; }
[JsonProperty("Woods", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Woods")]
public MapStaticLoot? Woods { get; set; }
[JsonProperty("Factory", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Factory")]
public MapStaticLoot? Factory { get; set; }
[JsonProperty("ReserveBase", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("ReserveBase")]
public MapStaticLoot? ReserveBase { get; set; }
[JsonProperty("Lighthouse", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Lighthouse")]
public MapStaticLoot? Lighthouse { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output.StaticContainer;
public class StaticDataPoint
{
[JsonProperty("probability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("probability")]
public double? Probability { get; set; }
[JsonProperty("template", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("template")]
public Template? Template { get; set; }
}

View File

@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using YamlDotNet.Serialization;
namespace LootDumpProcessor.Model.Output.StaticContainer
{
public class StaticForced
{
[JsonProperty("containerId", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("containerId")]
[YamlMember(Alias = "containerId")]
public string? ContainerId { get; set; }
[JsonProperty("itemTpl", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("itemTpl")]
[YamlMember(Alias = "itemTpl")]
public string? ItemTpl { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output;
public class StaticDistribution : AbstractDistribution
{
[JsonProperty("tpl", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("tpl")]
public string? Tpl { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Output
{
public class StaticItemDistribution
{
[JsonProperty("itemcountDistribution", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("itemcountDistribution")]
public List<ItemCountDistribution>? ItemCountDistribution { get; set; }
[JsonProperty("itemDistribution", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("itemDistribution")]
public List<StaticDistribution>? ItemDistribution { get; set; }
}
}

View File

@ -0,0 +1,81 @@
namespace LootDumpProcessor.Model.Processing;
public static class BaseClasses
{
public static string DefaultInventory = "55d7217a4bdc2d86028b456d";
public static string Inventory = "55d720f24bdc2d88028b456d";
public static string Pockets = "557596e64bdc2dc2118b4571";
public static string Weapon = "5422acb9af1c889c16000029";
public static string Headwear = "5a341c4086f77401f2541505";
public static string Armor = "5448e54d4bdc2dcc718b4568";
public static string Vest = "5448e5284bdc2dcb718b4567";
public static string Backpack = "5448e53e4bdc2d60728b4567";
public static string Visors = "5448e5724bdc2ddf718b4568";
public static string Food = "5448e8d04bdc2ddf718b4569";
public static string Drink = "5448e8d64bdc2dce718b4568";
public static string BarterItem = "5448eb774bdc2d0a728b4567";
public static string Info = "5448ecbe4bdc2d60728b4568";
public static string MedKit = "5448f39d4bdc2d0a728b4568";
public static string Drugs = "5448f3a14bdc2d27728b4569";
public static string Stimulator = "5448f3a64bdc2d60728b456a";
public static string Medical = "5448f3ac4bdc2dce718b4569";
public static string MedicalSupplies = "57864c8c245977548867e7f1";
public static string Mod = "5448fe124bdc2da5018b4567";
public static string Stock = "55818a594bdc2db9688b456a";
public static string Foregrip = "55818af64bdc2d5b648b4570";
public static string Mount = "55818b224bdc2dde698b456f";
public static string Muzzle = "5448fe394bdc2d0d028b456c";
public static string Sights = "5448fe7a4bdc2d6f028b456b";
public static string Meds = "543be5664bdc2dd4348b4569";
public static string Money = "543be5dd4bdc2deb348b4569";
public static string Key = "543be5e94bdc2df1348b4568";
public static string KeyMechanical = "5c99f98d86f7745c314214b3";
public static string Keycard = "5c164d2286f774194c5e69fa";
public static string Equipment = "543be5f84bdc2dd4348b456a";
public static string ThrowWeap = "543be6564bdc2df4348b4568";
public static string FoodDrink = "543be6674bdc2df1348b4569";
public static string Pistol = "5447b5cf4bdc2d65278b4567";
public static string Smg = "5447b5e04bdc2d62278b4567";
public static string AssaultRifle = "5447b5f14bdc2d61278b4567";
public static string AssaultCarbine = "5447b5fc4bdc2d87278b4567";
public static string Shotgun = "5447b6094bdc2dc3278b4567";
public static string MarksmanRifle = "5447b6194bdc2d67278b4567";
public static string SniperRifle = "5447b6254bdc2dc3278b4568";
public static string MachineGun = "5447bed64bdc2d97278b4568";
public static string GrenadeLauncher = "5447bedf4bdc2d87278b4568";
public static string SpecialWeapon = "5447bee84bdc2dc3278b4569";
public static string SpecItem = "5447e0e74bdc2d3c308b4567";
public static string Knife = "5447e1d04bdc2dff2f8b4567";
public static string Ammo = "5485a8684bdc2da71d8b4567";
public static string AmmoBox = "543be5cb4bdc2deb348b4568";
public static string LootContainer = "566965d44bdc2d814c8b4571";
public static string MobContainer = "5448bf274bdc2dfc2f8b456a";
public static string SearchableItem = "566168634bdc2d144c8b456c";
public static string Stash = "566abbb64bdc2d144c8b457d";
public static string SortingTable = "6050cac987d3f925bf016837";
public static string LockableContainer = "5671435f4bdc2d96058b4569";
public static string SimpleContainer = "5795f317245977243854e041";
public static string StationaryContainer = "567583764bdc2d98058b456e";
public static string Armband = "5b3f15d486f77432d0509248";
public static string DogTagUsec = "59f32c3b86f77472a31742f0";
public static string DogTagBear = "59f32bb586f774757e1e8442";
public static string Jewelry = "57864a3d24597754843f8721";
public static string Electronics = "57864a66245977548f04a81f";
public static string BuildingMaterial = "57864ada245977548638de91";
public static string Tool = "57864bb7245977548b3b66c2";
public static string HouseholdGoods = "57864c322459775490116fbf";
public static string Lubricant = "57864e4c24597754843f8723";
public static string Battery = "57864ee62459775490116fc1";
public static string FunctionalMod = "550aa4154bdc2dd8348b456b";
public static string GearMod = "55802f3e4bdc2de7118b4584";
public static string MasterMod = "55802f4a4bdc2ddb688b4569";
public static string Other = "590c745b86f7743cc433c5f2";
public static string AssaultScope = "55818add4bdc2d5b648b456f";
public static string ReflexSight = "55818ad54bdc2ddc698b4569";
public static string TacticalCombo = "55818b164bdc2ddc698b456c";
public static string Magazine = "5448bc234bdc2d3c308b4569";
public static string LightLaser = "55818b0e4bdc2dde698b456e";
public static string Silencer = "550aa4cd4bdc2dd8348b456c";
public static string PortableRangeFinder = "61605ddea09d851a0a0c1bbc";
public static string Item = "54009119af1c881c07000029";
}

View File

@ -0,0 +1,26 @@
using LootDumpProcessor.Model.Input;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Processing;
public class BasicInfo
{
public string Map { get; set; }
public string FileHash { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public RootData Data { get; set; }
public DateTime Date { get; set; }
public string FileName { get; set; }
public override bool Equals(object? obj)
{
if (obj is BasicInfo info)
return FileHash == info.FileHash;
return false;
}
public override int GetHashCode()
{
return FileHash.GetHashCode();
}
}

View File

@ -0,0 +1,8 @@
namespace LootDumpProcessor.Model.Processing;
public class CaliberTemplateCount
{
public string Caliber { get; set; }
public string Template { get; set; }
public int Count { get; set; }
}

View File

@ -0,0 +1,10 @@
using LootDumpProcessor.Storage;
namespace LootDumpProcessor.Model.Processing;
public class DumpProcessData
{
public Dictionary<string, IKey> LooseLootCounts { get; set; } = new();
public List<PreProcessedStaticLoot> ContainerCounts { get; set; } = new();
public Dictionary<string, int> MapCounts { get; set; } = new();
}

View File

@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Storage;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Processing;
public class LooseLootCounts : IKeyable
{
[JsonProperty("__id__")]
[JsonPropertyName("__id__")]
public string __ID { get; set; } = Guid.NewGuid().ToString();
public IKey Counts { get; set; }
// public IKey Items { get; set; }
public IKey ItemProperties { get; set; }
public List<int> MapSpawnpointCount { get; set; } = new();
public IKey GetKey()
{
return new FlatUniqueKey(new[] { __ID });
}
}

View File

@ -0,0 +1,31 @@
using System.Text.RegularExpressions;
using LootDumpProcessor.Storage;
namespace LootDumpProcessor.Model.Processing;
public class ParsedDump : IKeyable
{
private static readonly Regex _hashRegex = new Regex("([^a-zA-Z0-9])");
public BasicInfo BasicInfo { get; set; }
public PreProcessedLooseLoot LooseLoot { get; set; }
public List<PreProcessedStaticLoot> Containers { get; set; }
public override bool Equals(object? obj)
{
if (obj is ParsedDump dump)
return dump.BasicInfo.Equals(this.BasicInfo);
return false;
}
public override int GetHashCode()
{
return this.BasicInfo.GetHashCode();
}
public IKey GetKey()
{
var sanitizedHash = _hashRegex.Replace(BasicInfo.FileHash, "");
return new SubdivisionedUniqueKey(new[]
{ "parsedDumps", BasicInfo.Map, $"{BasicInfo.FileName.Split("\\").Last().Replace(".", "")}-{sanitizedHash}" });
}
}

View File

@ -0,0 +1,21 @@
using LootDumpProcessor.Storage;
namespace LootDumpProcessor.Model.Processing;
public class PartialData
{
public BasicInfo BasicInfo { get; set; }
public IKey ParsedDumpKey { get; set; }
public override bool Equals(object? obj)
{
if (obj is ParsedDump dump)
return dump.BasicInfo.Equals(this.BasicInfo);
return false;
}
public override int GetHashCode()
{
return this.BasicInfo.GetHashCode();
}
}

View File

@ -0,0 +1,19 @@
using LootDumpProcessor.Storage;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Processing;
public class PreProcessedLooseLoot : IKeyable
{
public Dictionary<string, int> Counts { get; set; }
[JsonConverter(typeof(NewtonsoftJsonKeyConverter))]
public IKey ItemProperties { get; set; }
public int MapSpawnpointCount { get; set; }
public IKey GetKey()
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,8 @@
namespace LootDumpProcessor.Model.Processing;
public class PreProcessedStaticLoot
{
public string Type { get; set; }
public string ContainerId { get; set; }
public List<Item> Items { get; set; }
}

25
Model/Repairable.cs Normal file
View File

@ -0,0 +1,25 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class Repairable : ICloneable
{
[JsonProperty("Durability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Durability")]
public int? Durability { get; set; }
[JsonProperty("MaxDurability", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("MaxDurability")]
public int? MaxDurability { get; set; }
public object Clone()
{
return new Repairable
{
Durability = this.Durability,
MaxDurability = this.MaxDurability
};
}
}
}

23
Model/Tarkov/Handbook.cs Normal file
View File

@ -0,0 +1,23 @@
namespace LootDumpProcessor.Model.Tarkov;
public class Category
{
public string Id { get; set; }
public string ParentId { get; set; }
public string Icon { get; set; }
public string Color { get; set; }
public string Order { get; set; }
}
public class HandbookItem
{
public string Id { get; set; }
public string ParentId { get; set; }
public int Price { get; set; }
}
public class HandbookRoot
{
public List<Category> Categories { get; set; }
public List<HandbookItem> Items { get; set; }
}

View File

@ -0,0 +1,152 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model.Tarkov;
public class TemplateFileItem
{
[JsonProperty("_id")]
[JsonPropertyName("_id")]
public string Id { get; set; }
[JsonProperty("_name")]
[JsonPropertyName("_name")]
public string Name { get; set; }
[JsonProperty("_parent")]
[JsonPropertyName("_parent")]
public string? Parent { get; set; }
[JsonProperty("_type")]
[JsonPropertyName("_type")]
public string Type { get; set; }
[JsonProperty("_props")]
[JsonPropertyName("_props")]
public Props Props { get; set; }
}
public class Props
{
public string Name { get; set; }
public string ShortName { get; set; }
public string Description { get; set; }
public double? MaxDurability { get; set; }
public string? Caliber { get; set; }
[JsonProperty("ammoCaliber")]
[JsonPropertyName("ammoCaliber")]
public string? AmmoCaliber { get; set; }
public bool QuestItem { get; set; }
public int SpawnChance { get; set; }
public List<string> SpawnFilter { get; set; }
public List<Grid> Grids { get; set; }
public string Rarity { get; set; }
public int ExtraSizeLeft { get; set; }
public int ExtraSizeRight { get; set; }
public int ExtraSizeUp { get; set; }
public int ExtraSizeDown { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int? StackMinRandom { get; set; }
public int? StackMaxRandom { get; set; }
public List<Chamber> Chambers { get; set; }
public List<Slot> Slots { get; set; }
[JsonProperty("defAmmo")]
[JsonPropertyName("defAmmo")]
public string DefaultAmmo { get; set; }
}
public class Grid
{
[JsonProperty("_props")]
[JsonPropertyName("_props")]
public GridProps Props { get; set; }
}
public class GridProps
{
[JsonProperty("cellsH")]
[JsonPropertyName("cellsH")]
public int CellsHorizontal { get; set; }
[JsonProperty("cellsV")]
[JsonPropertyName("cellsV")]
public int CellsVertical { get; set; }
[JsonProperty("minCount")]
[JsonPropertyName("minCount")]
public int MinCount { get; set; }
[JsonProperty("maxCount")]
[JsonPropertyName("maxCount")]
public int MaxCount { get; set; }
[JsonProperty("filters")]
[JsonPropertyName("filters")]
public List<GridFilter> Filters { get; set; }
}
public class GridFilter
{
}
public class Chamber
{
[JsonProperty("_name")]
[JsonPropertyName("_name")]
public string Name { get; set; }
[JsonProperty("_id")]
[JsonPropertyName("_id")]
public string Id { get; set; }
[JsonProperty("_parent")]
[JsonPropertyName("_parent")]
public string Parent { get; set; }
[JsonProperty("_props")]
[JsonPropertyName("_props")]
public ChamberProps Props { get; set; }
[JsonProperty("_required")]
[JsonPropertyName("_required")]
public bool Required { get; set; }
[JsonProperty("_mergeSlotWithChildren")]
[JsonPropertyName("_mergeSlotWithChildren")]
public bool MergeSlotWithChildren { get; set; }
[JsonProperty("_proto")]
[JsonPropertyName("_proto")]
public string Proto { get; set; }
}
public class Slot
{
[JsonProperty("_name")]
[JsonPropertyName("_name")]
public string Name { get; set; }
[JsonProperty("_required")]
[JsonPropertyName("_required")]
public bool Required { get; set; }
}
public class ChamberProps
{
[JsonProperty("filters")]
[JsonPropertyName("filters")]
public List<FilterClass> Filters { get; set; }
}
public class FilterClass
{
[JsonProperty("filter")]
[JsonPropertyName("filter")]
public List<string> Filter { get; set; }
}

99
Model/Template.cs Normal file
View File

@ -0,0 +1,99 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Processor;
using LootDumpProcessor.Storage;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class Template : IKeyable, ICloneable
{
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public string __ID { get; } = Guid.NewGuid().ToString();
[JsonProperty("Id", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Id")]
public string? Id { get; set; }
[JsonProperty("IsContainer", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("IsContainer")]
public bool? IsContainer { get; set; }
[JsonProperty("useGravity", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("useGravity")]
public bool? UseGravity { get; set; }
[JsonProperty("randomRotation", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("randomRotation")]
public bool? RandomRotation { get; set; }
[JsonProperty("Position", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Position")]
public Vector3? Position { get; set; }
[JsonProperty("Rotation", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Rotation")]
public Vector3? Rotation { get; set; }
[JsonProperty("IsGroupPosition", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("IsGroupPosition")]
public bool? IsGroupPosition { get; set; }
[JsonProperty("GroupPositions", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("GroupPositions")]
public List<GroupPosition>? GroupPositions { get; set; }
[JsonProperty("IsAlwaysSpawn", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("IsAlwaysSpawn")]
public bool? IsAlwaysSpawn { get; set; }
[JsonProperty("Root")]
[JsonPropertyName("Root")]
public string? Root { get; set; }
[JsonProperty("Items", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Items")]
public List<Item>? Items { get; set; }
protected bool Equals(Template other)
{
return Id == other.Id;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Template)obj);
}
public override int GetHashCode()
{
return (Id != null ? Id.GetHashCode() : 0);
}
public IKey GetKey()
{
return new FlatUniqueKey(new[] { __ID });
}
public object Clone()
{
return new Template
{
Id = this.Id,
IsContainer = this.IsContainer,
UseGravity = this.UseGravity,
RandomRotation = this.RandomRotation,
Position = ProcessorUtil.Copy(this.Position),
Rotation = ProcessorUtil.Copy(this.Rotation),
IsGroupPosition = this.IsGroupPosition,
GroupPositions = ProcessorUtil.Copy(this.GroupPositions),
IsAlwaysSpawn = this.IsAlwaysSpawn,
Root = this.Root,
Items = ProcessorUtil.Copy(this.Items)
};
}
}
}

36
Model/Upd.cs Normal file
View File

@ -0,0 +1,36 @@
using System.Text.Json.Serialization;
using LootDumpProcessor.Process.Processor;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class Upd : ICloneable
{
[JsonProperty("StackObjectsCount", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("StackObjectsCount")]
public int? StackObjectsCount { get; set; }
[JsonProperty("FireMode", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("FireMode")]
public FireMode? FireMode { get; set; }
[JsonProperty("Foldable", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Foldable")]
public Foldable? Foldable { get; set; }
[JsonProperty("Repairable", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("Repairable")]
public Repairable? Repairable { get;set; }
public object Clone()
{
return new Upd
{
StackObjectsCount = this.StackObjectsCount,
FireMode = ProcessorUtil.Copy(this.FireMode),
Foldable = ProcessorUtil.Copy(this.Foldable),
Repairable = ProcessorUtil.Copy(this.Repairable)
};
}
}
}

30
Model/Vector3.cs Normal file
View File

@ -0,0 +1,30 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace LootDumpProcessor.Model
{
public class Vector3 : ICloneable
{
[JsonProperty("x", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("x")]
public double? X { get; set; }
[JsonProperty("y", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("y")]
public double? Y { get; set; }
[JsonProperty("z", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("z")]
public double? Z { get; set; }
public object Clone()
{
return new Vector3
{
X = this.X,
Y = this.Y,
Z = this.Z
};
}
}
}

View File

@ -0,0 +1,10 @@
namespace LootDumpProcessor.Process.Collector;
public static class CollectorFactory
{
public static ICollector GetInstance()
{
// TODO: implement real factory
return new HashSetCollector();
}
}

View File

@ -0,0 +1,28 @@
using LootDumpProcessor.Model.Processing;
namespace LootDumpProcessor.Process.Collector;
public class HashSetCollector : ICollector
{
private HashSet<PartialData> processedDumps = new HashSet<PartialData>();
private object lockObject = new object();
public void Setup()
{
}
public void Hold(PartialData outputData)
{
lock (lockObject)
{
processedDumps.Add(outputData);
}
}
public List<PartialData> Retrieve()
{
return processedDumps.ToList();
}
}

View File

@ -0,0 +1,12 @@
using LootDumpProcessor.Model.Config;
using LootDumpProcessor.Model.Processing;
namespace LootDumpProcessor.Process.Collector;
public interface ICollector
{
void Setup();
void Hold(PartialData parsedDump);
List<PartialData> Retrieve();
}

6
Process/FilesGatherer.cs Normal file
View File

@ -0,0 +1,6 @@
namespace LootDumpProcessor.Process;
public class FilesGatherer
{
}

6
Process/IPipeline.cs Normal file
View File

@ -0,0 +1,6 @@
namespace LootDumpProcessor.Process;
public interface IPipeline
{
void DoProcess();
}

View File

@ -0,0 +1,9 @@
namespace LootDumpProcessor.Process;
public enum OutputFileType
{
LooseLoot,
StaticContainer,
StaticLoot,
StaticAmmo
}

View File

@ -0,0 +1,10 @@
namespace LootDumpProcessor.Process;
public static class PipelineFactory
{
public static IPipeline GetInstance()
{
// implement actual factory at some point
return new QueuePipeline();
}
}

View File

@ -0,0 +1,12 @@
namespace LootDumpProcessor.Process.Processor.DumpProcessor;
public static class DumpProcessorFactory
{
public static IDumpProcessor GetInstance()
{
// Implement real factory
return new MultithreadSteppedDumpProcessor();
}
}

View File

@ -0,0 +1,8 @@
using LootDumpProcessor.Model.Processing;
namespace LootDumpProcessor.Process.Processor.DumpProcessor;
public interface IDumpProcessor
{
Dictionary<OutputFileType, object> ProcessDumps(List<PartialData> dumps);
}

View File

@ -0,0 +1,300 @@
using System.Collections.Concurrent;
using LootDumpProcessor.Logger;
using LootDumpProcessor.Model;
using LootDumpProcessor.Model.Input;
using LootDumpProcessor.Model.Output.StaticContainer;
using LootDumpProcessor.Model.Processing;
using LootDumpProcessor.Serializers.Json;
using LootDumpProcessor.Storage;
using LootDumpProcessor.Storage.Collections;
namespace LootDumpProcessor.Process.Processor.DumpProcessor;
public class MultithreadSteppedDumpProcessor : IDumpProcessor
{
private static IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance();
private static readonly List<Task> Runners = new();
private static readonly BlockingCollection<PartialData> _partialDataToProcess = new();
// if we need to, this variable can be moved to use the factory, but since the factory
// needs a locking mechanism to prevent dictionary access exceptions, its better to keep
// a reference to use here
private static readonly IDataStorage _dataStorage = DataStorageFactory.GetInstance();
public Dictionary<OutputFileType, object> ProcessDumps(List<PartialData> dumps)
{
LoggerFactory.GetInstance().Log("Starting final dump processing", LogLevel.Info);
var output = new Dictionary<OutputFileType, object>();
var dumpProcessData = GetDumpProcessData(dumps);
LoggerFactory.GetInstance().Log("Heavy processing done!", LogLevel.Info);
var staticContainers = new Dictionary<string, MapStaticLoot>();
var staticContainersLock = new object();
// We need to count how many dumps we have for each map
var mapDumpCounter = new Dictionary<string, int>();
var mapDumpCounterLock = new object();
// dictionary of maps, that has a dictionary of template and hit count
var mapStaticContainersAggregated = new Dictionary<string, Dictionary<Template, int>>();
var mapStaticContainersAggregatedLock = new object();
Runners.Clear();
// BSG changed the map data so static containers are now dynamic, so we need to scan all dumps for the static containers.
foreach (var dumped in dumps)
{
Runners.Add(
Task.Factory.StartNew(() =>
{
var data = _jsonSerializer.Deserialize<RootData>(File.ReadAllText(dumped.BasicInfo.FileName));
// the if statement below will keep track of how many dumps we have for each map
lock (mapDumpCounterLock)
{
if (mapDumpCounter.ContainsKey(data.Data.Name))
mapDumpCounter[data.Data.Name] += 1;
else
mapDumpCounter.Add(data.Data.Name, 1);
}
// the if statement below takes care of processing "forced" or real static data for each map, we only need
// to do this once per map, so we dont care about doing it again
lock (staticContainersLock)
{
if (!staticContainers.ContainsKey(data.Data.Name))
{
var mapStaticLoot = StaticLootProcessor.CreateRealStaticContainers(data);
staticContainers[mapStaticLoot.Item1] = mapStaticLoot.Item2;
}
}
// the section below takes care of finding how many "dynamic static containers" we have on the map
Dictionary<Template, int> mapAggregatedData;
lock (mapStaticContainersAggregatedLock)
{
if (!mapStaticContainersAggregated.TryGetValue(data.Data.Name, out mapAggregatedData))
{
mapAggregatedData = new Dictionary<Template, int>();
mapStaticContainersAggregated.Add(data.Data.Name, mapAggregatedData);
}
}
foreach (var dynamicStaticContainer in StaticLootProcessor.CreateDynamicStaticContainers(data))
{
lock (mapStaticContainersAggregatedLock)
{
if (mapAggregatedData.ContainsKey(dynamicStaticContainer))
mapAggregatedData[dynamicStaticContainer] += 1;
else
mapAggregatedData.Add(dynamicStaticContainer, 1);
}
}
GCHandler.Collect();
})
);
}
Task.WaitAll(Runners.ToArray());
// Aggregate and calculate the probability of a static container
mapStaticContainersAggregated.ToDictionary(
kv => kv.Key,
kv => kv.Value.Select(
td => new StaticDataPoint
{
Template = td.Key,
Probability = Math.Round((double)((decimal)td.Value / (decimal)mapDumpCounter[kv.Key]), 2)
}
).ToList()
).ToList().ForEach(kv => staticContainers[kv.Key].StaticContainers = kv.Value);
// Static containers
output.Add(OutputFileType.StaticContainer, staticContainers);
// Ammo distribution
output.Add(
OutputFileType.StaticAmmo,
StaticLootProcessor.CreateAmmoDistribution(dumpProcessData.ContainerCounts)
);
// Static loot distribution
output.Add(
OutputFileType.StaticLoot,
StaticLootProcessor.CreateStaticLootDistribution(dumpProcessData.ContainerCounts)
);
// Loose loot distribution
var looseLootDistribution = LooseLootProcessor.CreateLooseLootDistribution(
dumpProcessData.MapCounts,
dumpProcessData.LooseLootCounts
);
var loot = dumpProcessData.MapCounts
.Select(mapCount => mapCount.Key)
.ToDictionary(mi => mi, mi => looseLootDistribution[mi]);
output.Add(OutputFileType.LooseLoot, loot);
return output;
}
private DumpProcessData GetDumpProcessData(List<PartialData> dumps)
{
var dumpProcessData = new DumpProcessData();
dumps.GroupBy(x => x.BasicInfo.Map)
.ToList()
.ForEach(tuple =>
{
var mapi = tuple.Key;
var g = tuple.ToList();
LoggerFactory.GetInstance().Log(
$"Processing map {mapi}, total dump data to process: {g.Count}",
LogLevel.Info
);
dumpProcessData.MapCounts[mapi] = g.Count;
var lockObjectContainerCounts = new object();
var lockObjectCounts = new object();
var counts = new LooseLootCounts();
var lockObjectDictionaryCounts = new object();
var dictionaryCounts = new FlatKeyableDictionary<string, int>();
counts.Counts = dictionaryCounts.GetKey();
/*
var dictionaryItemCounts = new FlatKeyableDictionary<string, List<string>>();
counts.Items = dictionaryItemCounts.GetKey();
*/
var lockObjectDictionaryItemProperties = new object();
var dictionaryItemProperties = new FlatKeyableDictionary<string, FlatKeyableList<Template>>();
var actualDictionaryItemProperties = new FlatKeyableDictionary<string, IKey>();
counts.ItemProperties = actualDictionaryItemProperties.GetKey();
dumpProcessData.LooseLootCounts.Add(mapi, counts.GetKey());
// add the items to the queue
foreach (var gi in g)
{
_partialDataToProcess.Add(gi);
}
// Call GC before running threads
g = null;
tuple = null;
GCHandler.Collect();
// The data storage factory has a lock, we dont want the locks to occur when multithreading
for (int i = 0; i < LootDumpProcessorContext.GetConfig().Threads; i++)
{
Runners.Add(
Task.Factory.StartNew(
() =>
{
while (_partialDataToProcess.TryTake(out var partialData,
TimeSpan.FromMilliseconds(5000)))
{
try
{
var dumpData = _dataStorage.GetItem<ParsedDump>(partialData.ParsedDumpKey);
lock (lockObjectContainerCounts)
{
dumpProcessData.ContainerCounts.AddRange(dumpData.Containers);
}
// loose loot into ids on files
var loadedDictionary =
_dataStorage
.GetItem<SubdivisionedKeyableDictionary<string, List<Template>>>(
dumpData.LooseLoot.ItemProperties
);
foreach (var (k, v) in loadedDictionary)
{
var count = dumpData.LooseLoot.Counts[k];
lock (lockObjectDictionaryCounts)
{
if (dictionaryCounts.ContainsKey(k))
dictionaryCounts[k] += count;
else
dictionaryCounts[k] = count;
}
/*
var itemList = dumpData.LooseLoot.Items[k];
if (!dictionaryItemCounts.TryGetValue(k, out var itemCounts))
{
itemCounts = new List<string>();
dictionaryItemCounts.Add(k, itemCounts);
}
itemCounts.AddRange(itemList);
*/
lock (lockObjectDictionaryItemProperties)
{
if (!dictionaryItemProperties.TryGetValue(k, out var values))
{
values = new FlatKeyableList<Template>();
dictionaryItemProperties.Add(k, values);
actualDictionaryItemProperties.Add(k, values.GetKey());
}
values.AddRange(v);
}
}
lock (lockObjectCounts)
{
counts.MapSpawnpointCount.AddRange(new List<int>
{
dumpData.LooseLoot.MapSpawnpointCount
});
}
}
catch (Exception e)
{
LoggerFactory.GetInstance().Log(
$"ERROR OCCURRED:{e.Message}\n{e.StackTrace}",
LogLevel.Error
);
}
}
},
TaskCreationOptions.LongRunning)
);
}
// Wait until all runners are done processing
while (!Runners.All(r => r.IsCompleted))
{
LoggerFactory.GetInstance().Log(
$"One or more file processors are still processing files. Waiting {LootDumpProcessorContext.GetConfig().ThreadPoolingTimeoutMs}ms before checking again",
LogLevel.Info
);
Thread.Sleep(
TimeSpan.FromMilliseconds(LootDumpProcessorContext.GetConfig().ThreadPoolingTimeoutMs));
}
foreach (var (_, value) in dictionaryItemProperties)
{
_dataStorage.Store(value);
}
_dataStorage.Store(dictionaryCounts);
dictionaryCounts = null;
GCHandler.Collect();
/*
DataStorageFactory.GetInstance().Store(dictionaryItemCounts);
dictionaryItemCounts = null;
GC.Collect();
*/
_dataStorage.Store(actualDictionaryItemProperties);
actualDictionaryItemProperties = null;
GCHandler.Collect();
_dataStorage.Store(counts);
counts = null;
GCHandler.Collect();
});
return dumpProcessData;
}
}

View File

@ -0,0 +1,52 @@
using LootDumpProcessor.Logger;
using LootDumpProcessor.Model;
using LootDumpProcessor.Model.Processing;
using LootDumpProcessor.Process.Processor;
using LootDumpProcessor.Storage;
namespace LootDumpProcessor.Process.Impl;
public class FileProcessor : IFileProcessor
{
public PartialData Process(BasicInfo parsedData)
{
LoggerFactory.GetInstance().Log($"Processing file {parsedData.FileName}...", LogLevel.Info);
List<Template> looseLoot = new List<Template>();
List<Template> staticLoot = new List<Template>();
foreach (var item in parsedData.Data.Data.Loot)
{
if (item.IsContainer ?? false)
staticLoot.Add(item);
else
looseLoot.Add(item);
}
parsedData.Data = null;
var dumpData = new ParsedDump
{
BasicInfo = parsedData
};
PartialData data = new PartialData
{
BasicInfo = parsedData,
ParsedDumpKey = (AbstractKey)dumpData.GetKey()
};
if (!DataStorageFactory.GetInstance().Exists(dumpData.GetKey()))
{
LoggerFactory.GetInstance().Log(
$"Cached not found for {string.Join("/", dumpData.GetKey().GetLookupIndex())} processing.",
LogLevel.Info
);
dumpData.Containers = StaticLootProcessor.PreProcessStaticLoot(staticLoot);
dumpData.LooseLoot = LooseLootProcessor.PreProcessLooseLoot(looseLoot);
DataStorageFactory.GetInstance().Store(dumpData);
}
LoggerFactory.GetInstance().Log($"File {parsedData.FileName} finished processing!", LogLevel.Info);
return data;
}
}

View File

@ -0,0 +1,12 @@
using LootDumpProcessor.Process.Impl;
namespace LootDumpProcessor.Process.Processor;
public static class FileProcessorFactory
{
public static IFileProcessor GetInstance()
{
// implement actual factory someday
return new FileProcessor();
}
}

View File

@ -0,0 +1,8 @@
using LootDumpProcessor.Model.Processing;
namespace LootDumpProcessor.Process;
public interface IFileProcessor
{
PartialData Process(BasicInfo parsedData);
}

View File

@ -0,0 +1,262 @@
using LootDumpProcessor.Logger;
using LootDumpProcessor.Model;
using LootDumpProcessor.Model.Output;
using LootDumpProcessor.Model.Output.LooseLoot;
using LootDumpProcessor.Model.Processing;
using LootDumpProcessor.Storage;
using LootDumpProcessor.Storage.Collections;
using NumSharp;
namespace LootDumpProcessor.Process.Processor;
public class LooseLootProcessor
{
public static PreProcessedLooseLoot PreProcessLooseLoot(List<Template> looseloot)
{
var looseloot_ci = new PreProcessedLooseLoot
{
Counts = new Dictionary<string, int>()
};
var temporalItemProperties = new SubdivisionedKeyableDictionary<string, List<Template>>();
looseloot_ci.ItemProperties = (AbstractKey)temporalItemProperties.GetKey();
looseloot_ci.MapSpawnpointCount = looseloot.Count;
var uniqueIds = new Dictionary<string, object>();
// sometimes the rotation changes very slightly in the dumps for the same location / rotation spawnpoint
// use rounding to make sure it is not generated to two spawnpoint
foreach (var looseLootTemplate in looseloot)
{
// the bsg ids are insane.
// Sometimes the last 7 digits vary but they spawn the same item at the same position
// e.g. for the quest item "60a3b65c27adf161da7b6e14" at "loot_bunker_quest (3)555192"
// so the first approach was to remove the last digits.
// We then saw, that sometimes when the last digits differ for the same string, also the position
// differs.
// We decided to group over the position/rotation/useGravity since they make out a distinct spot
var saneId = looseLootTemplate.GetSaneId();
if (!uniqueIds.ContainsKey(saneId))
{
uniqueIds[saneId] = looseLootTemplate.Id;
if (looseloot_ci.Counts.ContainsKey(saneId))
looseloot_ci.Counts[saneId]++;
else
looseloot_ci.Counts[saneId] = 1;
}
if (!temporalItemProperties.TryGetValue(saneId, out var templates))
{
templates = new FlatKeyableList<Template>();
temporalItemProperties.Add(saneId, templates);
}
templates.Add(looseLootTemplate);
}
DataStorageFactory.GetInstance().Store(temporalItemProperties);
return looseloot_ci;
}
public static Dictionary<string, LooseLootRoot> CreateLooseLootDistribution(
Dictionary<string, int> map_counts,
Dictionary<string, IKey> looseloot_counts
)
{
var forcedConfidence = LootDumpProcessorContext.GetConfig().ProcessorConfig.SpawnPointToleranceForForced / 100;
var probabilities = new Dictionary<string, Dictionary<string, double>>();
var looseLootDistribution = new Dictionary<string, LooseLootRoot>();
foreach (var _tup_1 in map_counts)
{
var mapName = _tup_1.Key;
var mapCount = _tup_1.Value;
probabilities[mapName] = new Dictionary<string, double>();
var looseLootCounts = DataStorageFactory.GetInstance().GetItem<LooseLootCounts>(looseloot_counts[mapName]);
var counts = DataStorageFactory.GetInstance()
.GetItem<FlatKeyableDictionary<string, int>>(looseLootCounts.Counts);
foreach (var (idi, cnt) in counts)
{
probabilities[mapName][idi] = (double)((decimal)cnt / mapCount);
}
// No longer used, dispose
counts = null;
looseLootDistribution[mapName] = new LooseLootRoot
{
SpawnPointCount = new SpawnPointCount
{
Mean = np.mean(np.array(looseLootCounts.MapSpawnpointCount)),
Std = np.std(np.array(looseLootCounts.MapSpawnpointCount))
},
SpawnPointsForced = new List<SpawnPointsForced>(),
SpawnPoints = new List<SpawnPoint>()
};
var itemProperties = DataStorageFactory.GetInstance()
.GetItem<FlatKeyableDictionary<string, IKey>>(looseLootCounts.ItemProperties);
foreach (var (spawnPoint, itemList) in itemProperties)
{
var itemsCounts = new Dictionary<ComposedKey, int>();
var savedItemProperties = DataStorageFactory.GetInstance().GetItem<FlatKeyableList<Template>>(itemList);
foreach (var savedTemplateProperties in savedItemProperties)
{
var key = new ComposedKey(savedTemplateProperties);
if (itemsCounts.ContainsKey(key))
itemsCounts[key] += 1;
else
itemsCounts[key] = 1;
}
// Group by arguments to create possible positions / rotations per spawnpoint
// check if grouping is unique
var itemListSorted = savedItemProperties.Select(template => (template.GetSaneId(), template))
.GroupBy(g => g.Item1).ToList();
if (itemListSorted.Count > 1)
{
throw new Exception("More than one saneKey found");
}
var spawnPoints = itemListSorted.First().Select(v => v.template).ToList();
var locationId = spawnPoints[0].GetLocationId();
var template = ProcessorUtil.Copy(spawnPoints[0]);
//template.Root = null; // Why do we do this, not null in bsg data
var itemDistribution = itemsCounts.Select(kv => new ItemDistribution
{
ComposedKey = kv.Key,
RelativeProbability = kv.Value
}).ToList();
// If any of the items is a quest item or forced loose loot items, or the item normally appreas 99.5%
if (itemDistribution.Any(item =>
LootDumpProcessorContext.GetTarkovItems().IsQuestItem(item.ComposedKey?.FirstItem?.Tpl) ||
LootDumpProcessorContext.GetForcedLooseItems()[mapName].Contains(item.ComposedKey?.FirstItem?.Tpl))
)
{
var spawnPointToAdd = new SpawnPointsForced
{
LocationId = locationId,
Probability = probabilities[mapName][spawnPoint],
Template = template
};
looseLootDistribution[mapName].SpawnPointsForced.Add(spawnPointToAdd);
}
else if (probabilities[mapName][spawnPoint] > forcedConfidence)
{
var spawnPointToAdd = new SpawnPointsForced
{
LocationId = locationId,
Probability = probabilities[mapName][spawnPoint],
Template = template
};
looseLootDistribution[mapName].SpawnPointsForced.Add(spawnPointToAdd);
LoggerFactory.GetInstance().Log(
$"Item: {template.Id} has > {LootDumpProcessorContext.GetConfig().ProcessorConfig.SpawnPointToleranceForForced}% spawn chance in spawn point: {spawnPointToAdd.LocationId} but isn't in forced loot, adding to forced",
LogLevel.Warning
);
}
else // Normal spawn point, add to non-forced spawnpoint array
{
var spawnPointToAdd = new SpawnPoint
{
LocationId = locationId,
Probability = probabilities[mapName][spawnPoint],
Template = template,
ItemDistribution = itemDistribution
};
template.Items = new List<Item>();
var group = spawnPoints.GroupBy(template => new ComposedKey(template))
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var distribution in itemDistribution)
{
if (group.TryGetValue(distribution.ComposedKey, out var items))
{
// We need to reparent the IDs to match the composed key ID
var itemDistributionItemList = items.First().Items;
// Find the item with no parent id, this is essentially the "Root" of the actual item
var firstItemInTemplate =
itemDistributionItemList.Find(i => string.IsNullOrEmpty(i.ParentId));
// Save the original ID reference, we need to replace it on child items
var originalId = firstItemInTemplate.Id;
// Put the composed key instead
firstItemInTemplate.Id = distribution.ComposedKey.Key;
// Reparent any items with the original id on it
itemDistributionItemList.Where(i => i.ParentId == originalId)
.ToList()
.ForEach(i => i.ParentId = firstItemInTemplate.Id);
template.Items.AddRange(itemDistributionItemList);
}
else
{
LoggerFactory.GetInstance().Log(
$"Item template {distribution.ComposedKey?.FirstItem?.Tpl} was on loose loot distribution for spawn point {template.Id} but the spawn points didnt contain a template matching it.",
LogLevel.Error
);
}
}
looseLootDistribution[mapName].SpawnPoints.Add(spawnPointToAdd);
}
}
// # Test for duplicate position
// # we removed most of them by "rounding away" the jitter in rotation,
// # there are still a few duplicate locations with distinct difference in rotation left though
// group_fun = lambda x: (
// x["template"]["Position"]["x"],
// x["template"]["Position"]["y"],
// x["template"]["Position"]["z"],
// )
// test = sorted(loose_loot_distribution[mi]["spawnpoints"], key=group_fun)
// test_grouped = groupby(test, group_fun)
// test_len = []
// for k, g in test_grouped:
// gl = list(g)
// test_len.append(len(gl))
// if len(gl) > 1:
// print(gl)
//
// print(mi, np.unique(test_len, return_counts=True))
looseLootDistribution[mapName].SpawnPoints =
looseLootDistribution[mapName].SpawnPoints.OrderBy(x => x.Template.Id).ToList();
// Cross check with forced loot in dumps vs items defined in forced_loose.yaml
var forcedTplsInConfig = new HashSet<string>(
(from forceditem in LootDumpProcessorContext.GetForcedLooseItems()[mapName]
select forceditem).ToList());
var forcedTplsFound = new HashSet<string>(
(from forceditem in looseLootDistribution[mapName].SpawnPointsForced
select forceditem.Template.Items[0].Tpl).ToList());
// All the tpls that are defined in the forced_loose.yaml for this map that are not found as forced
foreach (var itemTpl in forcedTplsInConfig)
{
if (!forcedTplsFound.Contains(itemTpl))
{
LoggerFactory.GetInstance().Log(
$"Expected item: {itemTpl} defined in forced_loose.yaml config not found in forced loot",
LogLevel.Error
);
}
}
// All the tpls that are found as forced in output file but not in the forced_loose.yaml config
foreach (var itemTpl in forcedTplsFound)
{
if (!forcedTplsInConfig.Contains(itemTpl))
{
LoggerFactory.GetInstance().Log(
$"Map: {mapName} Item: {itemTpl} not defined in forced_loose.yaml config but was flagged as forced by code",
LogLevel.Warning
);
}
}
}
return looseLootDistribution;
}
}

View File

@ -0,0 +1,161 @@
using LootDumpProcessor.Model;
using LootDumpProcessor.Model.Input;
using LootDumpProcessor.Model.Output;
using LootDumpProcessor.Model.Output.StaticContainer;
using LootDumpProcessor.Model.Processing;
namespace LootDumpProcessor.Process.Processor;
public class StaticLootProcessor
{
public static List<PreProcessedStaticLoot> PreProcessStaticLoot(List<Template> staticloot)
{
var containers = new List<PreProcessedStaticLoot>();
foreach (var li in staticloot)
{
var tpl = li.Items[0].Tpl;
if (!LootDumpProcessorContext.GetStaticWeaponIds().Contains(tpl))
{
containers.Add(new PreProcessedStaticLoot
{
Type = tpl,
ContainerId = li.Items[0].Id,
Items = li.Items.Skip(1).ToList()
});
}
}
return containers;
}
public static Tuple<string, MapStaticLoot> CreateRealStaticContainers(RootData rawMapDump)
{
List<StaticForced> forcedStaticItems;
var mapName = rawMapDump.Data.Name;
var staticLootPositions = (from li in rawMapDump.Data.Loot
where li.IsContainer ?? false
select li).ToList();
var staticWeapons = new List<Template>();
staticLootPositions = staticLootPositions.OrderBy(x => x.Id).ToList();
foreach (var staticLootPosition in staticLootPositions)
{
var itemTpl = staticLootPosition.Items[0].Tpl;
if (LootDumpProcessorContext.GetStaticWeaponIds().Contains(itemTpl))
{
staticWeapons.Add(ProcessorUtil.Copy(staticLootPosition));
}
}
forcedStaticItems = LootDumpProcessorContext.GetForcedItems().ContainsKey(mapName)
? LootDumpProcessorContext.GetForcedItems()[mapName]
: new List<StaticForced>();
var mapStaticData = new MapStaticLoot
{
StaticWeapons = staticWeapons,
StaticForced = forcedStaticItems
};
return Tuple.Create(mapName, mapStaticData);
}
public static List<Template> CreateDynamicStaticContainers(RootData rawMapDump)
{
return (from li in rawMapDump.Data.Loot
where (li.IsContainer ?? false) && (!LootDumpProcessorContext.GetStaticWeaponIds().Contains(li.Id))
select li).ToList();
}
public static Dictionary<string, List<AmmoDistribution>> CreateAmmoDistribution(
List<PreProcessedStaticLoot> container_counts
)
{
var ammo = new List<string>();
foreach (var ci in container_counts)
{
ammo.AddRange(from item in ci.Items
where LootDumpProcessorContext.GetTarkovItems().IsBaseClass(item.Tpl, BaseClasses.Ammo)
select item.Tpl);
}
var ammo_counts = new List<CaliberTemplateCount>();
ammo_counts.AddRange(
ammo.GroupBy(a => a)
.Select(g => new CaliberTemplateCount
{
Caliber = LootDumpProcessorContext.GetTarkovItems().AmmoCaliber(g.Key),
Template = g.Key,
Count = g.Count()
})
);
ammo_counts = ammo_counts.OrderBy(x => x.Caliber).ToList();
var ammo_distribution = new Dictionary<string, List<AmmoDistribution>>();
foreach (var _tup_3 in ammo_counts.GroupBy(x => x.Caliber))
{
var k = _tup_3.Key;
var g = _tup_3.ToList();
ammo_distribution[k] = (from gi in g
select new AmmoDistribution
{
Tpl = gi.Template,
RelativeProbability = gi.Count
}).ToList();
}
return ammo_distribution;
}
public static Dictionary<string, StaticItemDistribution> CreateStaticLootDistribution(
List<PreProcessedStaticLoot> container_counts
)
{
var static_loot_distribution = new Dictionary<string, StaticItemDistribution>();
var types = Enumerable.Distinct((from ci in container_counts
select ci.Type).ToList());
foreach (var typei in types)
{
var container_counts_selected = (from ci in container_counts
where ci.Type == typei
select ci).ToList();
var itemscounts = new List<int>();
foreach (var ci in container_counts_selected)
{
itemscounts.Add((from cii in ci.Items
where cii.ParentId == ci.ContainerId
select cii).ToList().Count);
}
static_loot_distribution[typei] = new StaticItemDistribution();
static_loot_distribution[typei].ItemCountDistribution = itemscounts.GroupBy(i => i)
.Select(g => new ItemCountDistribution
{
Count = g.Key,
RelativeProbability = g.Count()
}).ToList();
// TODO: Change for different algo that splits items per parent once parentid = containerid, then compose
// TODO: key and finally create distribution based on composed Id instead
var itemsHitCounts = new Dictionary<string, int>();
foreach (var ci in container_counts_selected)
{
foreach (var cii in ci.Items)
{
if (cii.ParentId == ci.ContainerId)
{
if (itemsHitCounts.ContainsKey(cii.Tpl))
itemsHitCounts[cii.Tpl] += 1;
else
itemsHitCounts[cii.Tpl] = 1;
}
}
}
static_loot_distribution[typei].ItemDistribution = itemsHitCounts.Select(v => new StaticDistribution
{
Tpl = v.Key,
RelativeProbability = v.Value
}).ToList();
}
return static_loot_distribution;
}
}

200
Process/QueuePipeline.cs Normal file
View File

@ -0,0 +1,200 @@
using System.Collections.Concurrent;
using LootDumpProcessor.Logger;
using LootDumpProcessor.Process.Collector;
using LootDumpProcessor.Process.Processor;
using LootDumpProcessor.Process.Processor.DumpProcessor;
using LootDumpProcessor.Process.Reader;
using LootDumpProcessor.Process.Reader.Filters;
using LootDumpProcessor.Process.Reader.PreProcess;
using LootDumpProcessor.Process.Writer;
namespace LootDumpProcessor.Process;
public class QueuePipeline : IPipeline
{
private static readonly BlockingCollection<string> _filesToProcess = new();
private static readonly List<Task> Runners = new();
private static readonly Dictionary<string, IPreProcessReader> _preProcessReaders;
static QueuePipeline()
{
_preProcessReaders = LootDumpProcessorContext.GetConfig()
.ReaderConfig
.PreProcessorConfig
?.PreProcessors
?.ToDictionary(
t => PreProcessReaderFactory.GetInstance(t).GetHandleExtension().ToLower(),
PreProcessReaderFactory.GetInstance
) ?? new Dictionary<string, IPreProcessReader>();
}
public void DoProcess()
{
// Single collector instance to collect results
var collector = CollectorFactory.GetInstance();
collector.Setup();
// We add 2 more threads to the total count to account for subprocesses and others
int threads = LootDumpProcessorContext.GetConfig().Threads;
ThreadPool.SetMaxThreads(threads + 2, threads + 2);
try
{
// Gather all files, then add them into the processing queue
GatherFiles().ForEach(f => _filesToProcess.Add(f));
// We startup all the threads and collect them into a runners list
for (int i = 0; i < threads; i++)
{
Runners.Add(
Task.Factory.StartNew(
() =>
{
while (_filesToProcess.TryTake(out var file, TimeSpan.FromMilliseconds(5000)))
{
try
{
var reader = IntakeReaderFactory.GetInstance();
var processor = FileProcessorFactory.GetInstance();
if (reader.Read(file, out var basicInfo))
collector.Hold(processor.Process(basicInfo));
}
catch (Exception e)
{
LoggerFactory.GetInstance().Log(
$"Error occurred while processing file {file}\n{e.Message}\n{e.StackTrace}",
LogLevel.Error);
}
}
},
TaskCreationOptions.LongRunning)
);
}
// Wait until all runners are done processing
while (!Runners.All(r => r.IsCompleted))
{
LoggerFactory.GetInstance().Log(
$"One or more file processors are still processing files. Waiting {LootDumpProcessorContext.GetConfig().ThreadPoolingTimeoutMs}ms before checking again",
LogLevel.Info);
Thread.Sleep(TimeSpan.FromMilliseconds(LootDumpProcessorContext.GetConfig().ThreadPoolingTimeoutMs));
}
// Single writer instance to collect results
var writer = WriterFactory.GetInstance();
// Single collector instance to collect results
var dumpProcessor = DumpProcessorFactory.GetInstance();
writer.WriteAll(dumpProcessor.ProcessDumps(collector.Retrieve()));
}
finally
{
// use dispose on the preprocessreaders to eliminate any temporary files generated
foreach (var (_, value) in _preProcessReaders)
{
value.Dispose();
}
}
}
private List<string> GatherFiles()
{
// Read locations
var inputPath = LootDumpProcessorContext.GetConfig().ReaderConfig.DumpFilesLocation;
if (inputPath == null || inputPath.Count == 0)
throw new Exception("Reader dumpFilesLocations must be set to a valid value");
// We gather up all files into a queue
var queuedFilesToProcess = GetFileQueue(inputPath);
// Then we preprocess the files in the queue and get them ready for processing
return PreProcessQueuedFiles(queuedFilesToProcess);
}
private List<string> PreProcessQueuedFiles(Queue<string> queuedFilesToProcess)
{
var gatheredFiles = new List<string>();
if (queuedFilesToProcess.Count == 0)
throw new Exception("No files matched accepted extension types in configs");
var fileFilters = GetFileFilters() ?? new Dictionary<string, IFileFilter>();
while (queuedFilesToProcess.TryDequeue(out var file))
{
var extension = Path.GetExtension(file)[1..].ToLower();
// if there is a preprocessor, call it and preprocess the file, then add them to the queue
if (_preProcessReaders.TryGetValue(extension, out var preProcessor))
{
// if the preprocessor found something new to process or generated new files, add them to the queue
if (preProcessor.TryPreProcess(file, out var outputFiles, out var outputDirectories))
{
// all new directories need to be processed as well
GetFileQueue(outputDirectories).ToList()
.ForEach(queuedFilesToProcess.Enqueue);
// all output files need to be queued as well
outputFiles.ForEach(queuedFilesToProcess.Enqueue);
}
}
else
{
// if there is no preprocessor for the file, means its ready to filter or accept
if (fileFilters.TryGetValue(extension, out var filter))
{
if (filter.Accept(file))
gatheredFiles.Add(file);
}
else
{
gatheredFiles.Add(file);
}
}
}
return gatheredFiles;
}
private Queue<string> GetFileQueue(List<string> inputPath)
{
var queuedPathsToProcess = new Queue<string>();
var queuedFilesToProcess = new Queue<string>();
// Accepted file extensions on raw files
var acceptedFileExtension = LootDumpProcessorContext.GetConfig()
.ReaderConfig
.AcceptedFileExtensions
.Select(ex => ex.ToLower())
.ToHashSet();
inputPath.ForEach(p => queuedPathsToProcess.Enqueue(p));
while (queuedPathsToProcess.TryDequeue(out var path))
{
// Check the input file to be sure its going to have data on it.
if (!Directory.Exists(path))
throw new Exception($"Input directory \"{inputPath}\" could not be found");
// If we should process subfolder, queue them up as well
if (LootDumpProcessorContext.GetConfig().ReaderConfig.ProcessSubFolders)
foreach (var directory in Directory.GetDirectories(path))
queuedPathsToProcess.Enqueue(directory);
var files = Directory.GetFiles(path);
foreach (var file in files)
if (acceptedFileExtension.Contains(Path.GetExtension(file)[1..].ToLower()))
queuedFilesToProcess.Enqueue(file);
}
return queuedFilesToProcess;
}
private Dictionary<string, IFileFilter>? GetFileFilters()
{
return LootDumpProcessorContext.GetConfig()
.ReaderConfig
.FileFilters
?.ToDictionary(
t => FileFilterFactory.GetInstance(t).GetExtension(),
FileFilterFactory.GetInstance
);
}
}

View File

@ -0,0 +1,25 @@
namespace LootDumpProcessor.Process.Reader.Filters;
public static class FileFilterFactory
{
private static readonly Dictionary<FileFilterTypes, IFileFilter> _fileFilters = new();
private static object lockObject = new();
public static IFileFilter GetInstance(FileFilterTypes type)
{
lock (lockObject)
{
if (!_fileFilters.TryGetValue(type, out var filter))
{
filter = type switch
{
FileFilterTypes.JsonDump => new JsonDumpFileFilter(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
_fileFilters.Add(type, filter);
}
return filter;
}
}
}

View File

@ -0,0 +1,6 @@
namespace LootDumpProcessor.Process.Reader.Filters;
public enum FileFilterTypes
{
JsonDump
}

View File

@ -0,0 +1,7 @@
namespace LootDumpProcessor.Process.Reader.Filters;
public interface IFileFilter
{
string GetExtension();
bool Accept(string filename);
}

View File

@ -0,0 +1,40 @@
using System.Globalization;
using System.Text.RegularExpressions;
using LootDumpProcessor.Logger;
namespace LootDumpProcessor.Process.Reader.Filters;
public class JsonDumpFileFilter : IFileFilter
{
private static Regex FileNameDateRegex = new("([0-9]{4}(-[0-9]{2}){2}_((-){0,1}[0-9]{2}){3})");
private static DateTime parsedThresholdDate;
static JsonDumpFileFilter()
{
// Calculate parsed date from config threshold
if (string.IsNullOrEmpty(LootDumpProcessorContext.GetConfig().ReaderConfig.ThresholdDate))
{
LoggerFactory.GetInstance()
.Log($"ThresholdDate is null or empty in configs, defaulting to current day minus 30 days",
LogLevel.Warning);
parsedThresholdDate = (DateTime.Now - TimeSpan.FromDays(30));
}
else
{
parsedThresholdDate = DateTime.ParseExact(
LootDumpProcessorContext.GetConfig().ReaderConfig.ThresholdDate,
"yyyy-MM-dd",
CultureInfo.InvariantCulture
);
}
}
public string GetExtension() => "json";
public bool Accept(string filename)
{
var unparsedDate = FileNameDateRegex.Match(filename).Groups[1].Value;
var date = DateTime.ParseExact(unparsedDate, "yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture);
return date > parsedThresholdDate;
}
}

View File

@ -0,0 +1,8 @@
using LootDumpProcessor.Model.Processing;
namespace LootDumpProcessor.Process;
public interface IIntakeReader
{
bool Read(string file, out BasicInfo basicInfo);
}

View File

@ -0,0 +1,18 @@
using LootDumpProcessor.Process.Impl;
namespace LootDumpProcessor.Process.Reader;
public static class IntakeReaderFactory
{
public static IIntakeReader GetInstance()
{
return LootDumpProcessorContext.GetConfig().ReaderConfig.IntakeReaderConfig.IntakeReaderType switch
{
IntakeReaderTypes.Json => new JsonFileIntakeReader(),
_ => throw new ArgumentOutOfRangeException(
"IntakeReaderType",
"Value was not defined on IntakeReaderConfig"
)
};
}
}

View File

@ -0,0 +1,6 @@
namespace LootDumpProcessor.Process.Reader;
public enum IntakeReaderTypes
{
Json
}

View File

@ -0,0 +1,48 @@
using System.Globalization;
using System.Text.RegularExpressions;
using LootDumpProcessor.Logger;
using LootDumpProcessor.Model.Input;
using LootDumpProcessor.Model.Processing;
using LootDumpProcessor.Process.Processor;
using LootDumpProcessor.Serializers.Json;
namespace LootDumpProcessor.Process.Impl;
public class JsonFileIntakeReader : IIntakeReader
{
private static readonly IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance();
private static readonly HashSet<string>? _ignoredLocations =
LootDumpProcessorContext.GetConfig().ReaderConfig.IntakeReaderConfig?.IgnoredDumpLocations.ToHashSet();
private static Regex FileNameDateRegex = new("([0-9]{4}(-[0-9]{2}){2}_((-){0,1}[0-9]{2}){3})");
public bool Read(string file, out BasicInfo basicInfo)
{
var fileData = File.ReadAllText(file);
var unparsedDate = FileNameDateRegex.Match(file).Groups[1].Value;
var date = DateTime.ParseExact(unparsedDate, "yyyy-MM-dd_HH-mm-ss", CultureInfo.InvariantCulture);
var fi = _jsonSerializer.Deserialize<RootData>(fileData);
if (fi.Data?.Name != null && (!_ignoredLocations?.Contains(fi.Data.Name) ?? true))
{
basicInfo = new BasicInfo
{
Map = fi.Data.Name,
FileHash = ProcessorUtil.HashFile(fileData),
Data = fi,
Date = date,
FileName = file
};
LoggerFactory.GetInstance().Log($"File {file} fully read, returning data", LogLevel.Info);
return true;
}
LoggerFactory.GetInstance().Log(
$"File {file} was not eligible for dump data, it did not contain a location name or it was on ignored locations config",
LogLevel.Info
);
basicInfo = null;
return false;
}
}

View File

@ -0,0 +1,48 @@
using LootDumpProcessor.Logger;
namespace LootDumpProcessor.Process.Reader.PreProcess;
public abstract class AbstractPreProcessReader : IPreProcessReader
{
protected string _tempFolder;
public AbstractPreProcessReader()
{
var tempFolder = LootDumpProcessorContext.GetConfig().ReaderConfig.PreProcessorConfig?.PreProcessorTempFolder;
if (string.IsNullOrEmpty(tempFolder))
{
tempFolder = GetBaseDirectory();
LoggerFactory.GetInstance()
.Log(
$"No temp folder was assigned preProcessorTempFolder in PreProcessorConfig, defaulting to {tempFolder}",
LogLevel.Warning
);
}
// Cleanup the temp directory before starting the process
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, true);
}
Directory.CreateDirectory(tempFolder);
_tempFolder = tempFolder;
}
public abstract string GetHandleExtension();
public abstract bool TryPreProcess(string file, out List<string> files, out List<string> directories);
protected string GetBaseDirectory()
{
return $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\\SPT\\tmp\\PreProcessor";
}
public void Dispose()
{
if (LootDumpProcessorContext.GetConfig().ReaderConfig.PreProcessorConfig?.CleanupTempFolderAfterProcess ?? true)
{
Directory.Delete(_tempFolder, true);
}
}
}

View File

@ -0,0 +1,11 @@
namespace LootDumpProcessor.Process.Reader.PreProcess;
public interface IPreProcessReader
{
string GetHandleExtension();
bool TryPreProcess(string file, out List<string> files, out List<string> directories);
// Custom dispose, not IDisposable
void Dispose();
}

View File

@ -0,0 +1,22 @@
namespace LootDumpProcessor.Process.Reader.PreProcess;
public static class PreProcessReaderFactory
{
private static readonly Dictionary<PreProcessReaderTypes, IPreProcessReader> _proProcessReaders = new();
private static object lockObject = new object();
public static IPreProcessReader GetInstance(PreProcessReaderTypes type)
{
if (!_proProcessReaders.TryGetValue(type, out var preProcessReader))
{
preProcessReader = type switch
{
PreProcessReaderTypes.SevenZip => new SevenZipPreProcessReader(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
_proProcessReaders.Add(type, preProcessReader);
}
return preProcessReader;
}
}

View File

@ -0,0 +1,6 @@
namespace LootDumpProcessor.Process.Reader.PreProcess;
public enum PreProcessReaderTypes
{
SevenZip
}

View File

@ -0,0 +1,39 @@
using LootDumpProcessor.Logger;
using SevenZip;
using SevenZip.Sdk.Compression.Lzma;
namespace LootDumpProcessor.Process.Reader.PreProcess;
public class SevenZipPreProcessReader : AbstractPreProcessReader
{
public override string GetHandleExtension() => "7z";
static SevenZipPreProcessReader()
{
SevenZipBase.SetLibraryPath("./x64/7z.dll");
}
public override bool TryPreProcess(string file, out List<string> files, out List<string> directories)
{
Decoder decoder = new Decoder();
var fileRaw = Path.GetFileNameWithoutExtension(file);
// SevenZip library doesnt like forward slashes for some reason
var outPath = $"{_tempFolder}\\{fileRaw}".Replace("/", "\\");
LoggerFactory.GetInstance().Log(
$"Unzipping {file} into temp path {outPath}, this may take a while...",
LogLevel.Info);
var extractor = new SevenZipExtractor(file);
extractor.Extracting += (sender, args) =>
{
if (args.PercentDone % 10 == 0)
LoggerFactory.GetInstance().Log($"Unzip progress: {args.PercentDone}%", LogLevel.Info);
};
extractor.ExtractArchive(outPath);
LoggerFactory.GetInstance().Log($"Finished unzipping {file} into temp path {outPath}", LogLevel.Info);
files = Directory.GetFiles(outPath).ToList();
directories = Directory.GetDirectories(outPath).ToList();
return true;
}
}

45
Process/TarkovItems.cs Normal file
View File

@ -0,0 +1,45 @@
using LootDumpProcessor.Model.Tarkov;
using LootDumpProcessor.Serializers.Json;
namespace LootDumpProcessor.Process;
public class TarkovItems
{
private static readonly IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance();
private Dictionary<string, TemplateFileItem> _items;
private HandbookRoot _handbook;
public TarkovItems(string items, string handbook)
{
_items = _jsonSerializer.Deserialize<Dictionary<string, TemplateFileItem>>(File.ReadAllText(items));
_handbook = _jsonSerializer.Deserialize<HandbookRoot>(File.ReadAllText(handbook));
}
public virtual bool IsBaseClass(string tpl, string baseclass_id)
{
var item_template = _items[tpl];
if (string.IsNullOrEmpty(item_template.Parent))
return false;
return item_template.Parent == baseclass_id || IsBaseClass(item_template.Parent, baseclass_id);
}
public virtual bool IsQuestItem(string tpl)
{
var item_template = _items[tpl];
return item_template.Props.QuestItem;
}
public virtual string? MaxDurability(string tpl)
{
var item_template = _items[tpl];
return item_template.Props.MaxDurability?.ToString() ?? "";
}
public virtual string? AmmoCaliber(string tpl)
{
var item_template = _items[tpl];
return item_template.Props.Caliber;
}
}

View File

@ -0,0 +1,77 @@
using LootDumpProcessor.Model.Output;
using LootDumpProcessor.Model.Output.LooseLoot;
using LootDumpProcessor.Model.Output.StaticContainer;
using LootDumpProcessor.Serializers.Json;
namespace LootDumpProcessor.Process.Impl;
public class FileWriter : IWriter
{
private static readonly IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance();
private static string _outputPath;
static FileWriter()
{
var path = LootDumpProcessorContext.GetConfig().WriterConfig.OutputLocation;
if (string.IsNullOrEmpty(path))
throw new Exception("Output directory must be set in WriterConfigs");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
_outputPath = path;
}
public void WriteAll(Dictionary<OutputFileType, object> dumpData)
{
foreach (var (key, value) in dumpData)
{
Write(key, value);
}
}
public void Write(
OutputFileType type,
object data
)
{
if (!Directory.Exists($"{_outputPath}\\loot"))
Directory.CreateDirectory($"{_outputPath}\\loot");
switch (type)
{
case OutputFileType.LooseLoot:
var looseLootData = (Dictionary<string, LooseLootRoot>)data;
foreach (var (key, value) in looseLootData)
{
foreach (var s in LootDumpProcessorContext.GetDirectoryMappings()[key].Name)
{
if (!Directory.Exists($"{_outputPath}\\locations\\{s}"))
Directory.CreateDirectory($"{_outputPath}\\locations\\{s}");
File.WriteAllText($"{_outputPath}\\locations\\{s}\\looseLoot.json",
_jsonSerializer.Serialize(value));
}
}
break;
case OutputFileType.StaticContainer:
var staticContainer = (Dictionary<string, MapStaticLoot>)data;
File.WriteAllText($"{_outputPath}\\loot\\staticContainers.json",
_jsonSerializer.Serialize(staticContainer));
break;
case OutputFileType.StaticLoot:
var staticLoot = (Dictionary<string, StaticItemDistribution>)data;
File.WriteAllText($"{_outputPath}\\loot\\staticLoot.json",
_jsonSerializer.Serialize(staticLoot));
break;
case OutputFileType.StaticAmmo:
var staticAmmo = (Dictionary<string, List<AmmoDistribution>>)data;
File.WriteAllText($"{_outputPath}\\loot\\staticAmmo.json",
_jsonSerializer.Serialize(staticAmmo));
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
}

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