mirror of
https://github.com/sp-tarkov/loot-dump-processor.git
synced 2025-02-13 06:50:45 -05:00
Refactored loose loot processor to use dependency injection
This commit is contained in:
parent
9be5d6e342
commit
65b49defc9
@ -5,6 +5,7 @@ using LootDumpProcessor.Model.Input;
|
|||||||
using LootDumpProcessor.Model.Output.StaticContainer;
|
using LootDumpProcessor.Model.Output.StaticContainer;
|
||||||
using LootDumpProcessor.Model.Processing;
|
using LootDumpProcessor.Model.Processing;
|
||||||
using LootDumpProcessor.Process.Processor.v2.AmmoProcessor;
|
using LootDumpProcessor.Process.Processor.v2.AmmoProcessor;
|
||||||
|
using LootDumpProcessor.Process.Processor.v2.LooseLootProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.v2.StaticContainersProcessor;
|
using LootDumpProcessor.Process.Processor.v2.StaticContainersProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.v2.StaticLootProcessor;
|
using LootDumpProcessor.Process.Processor.v2.StaticLootProcessor;
|
||||||
using LootDumpProcessor.Serializers.Json;
|
using LootDumpProcessor.Serializers.Json;
|
||||||
@ -16,7 +17,7 @@ namespace LootDumpProcessor.Process.Processor.DumpProcessor;
|
|||||||
|
|
||||||
public class MultithreadSteppedDumpProcessor(
|
public class MultithreadSteppedDumpProcessor(
|
||||||
IStaticLootProcessor staticLootProcessor, IStaticContainersProcessor staticContainersProcessor,
|
IStaticLootProcessor staticLootProcessor, IStaticContainersProcessor staticContainersProcessor,
|
||||||
IAmmoProcessor ammoProcessor
|
IAmmoProcessor ammoProcessor, ILooseLootProcessor looseLootProcessor
|
||||||
) : IDumpProcessor
|
) : IDumpProcessor
|
||||||
{
|
{
|
||||||
private readonly IStaticLootProcessor _staticLootProcessor =
|
private readonly IStaticLootProcessor _staticLootProcessor =
|
||||||
@ -28,6 +29,9 @@ public class MultithreadSteppedDumpProcessor(
|
|||||||
private readonly IAmmoProcessor _ammoProcessor =
|
private readonly IAmmoProcessor _ammoProcessor =
|
||||||
ammoProcessor ?? throw new ArgumentNullException(nameof(ammoProcessor));
|
ammoProcessor ?? throw new ArgumentNullException(nameof(ammoProcessor));
|
||||||
|
|
||||||
|
private readonly ILooseLootProcessor _looseLootProcessor =
|
||||||
|
looseLootProcessor ?? throw new ArgumentNullException(nameof(looseLootProcessor));
|
||||||
|
|
||||||
private static IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance();
|
private static IJsonSerializer _jsonSerializer = JsonSerializerFactory.GetInstance();
|
||||||
|
|
||||||
private static readonly List<Task> Runners = new();
|
private static readonly List<Task> Runners = new();
|
||||||
@ -203,7 +207,7 @@ public class MultithreadSteppedDumpProcessor(
|
|||||||
if (LoggerFactory.GetInstance().CanBeLogged(LogLevel.Info))
|
if (LoggerFactory.GetInstance().CanBeLogged(LogLevel.Info))
|
||||||
LoggerFactory.GetInstance().Log("Processing loose loot distribution", LogLevel.Info);
|
LoggerFactory.GetInstance().Log("Processing loose loot distribution", LogLevel.Info);
|
||||||
// Loose loot distribution
|
// Loose loot distribution
|
||||||
var looseLootDistribution = LooseLootProcessor.CreateLooseLootDistribution(
|
var looseLootDistribution = _looseLootProcessor.CreateLooseLootDistribution(
|
||||||
dumpProcessData.MapCounts,
|
dumpProcessData.MapCounts,
|
||||||
dumpProcessData.LooseLootCounts
|
dumpProcessData.LooseLootCounts
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
using LootDumpProcessor.Logger;
|
using LootDumpProcessor.Logger;
|
||||||
using LootDumpProcessor.Model;
|
using LootDumpProcessor.Model;
|
||||||
using LootDumpProcessor.Model.Processing;
|
using LootDumpProcessor.Model.Processing;
|
||||||
|
using LootDumpProcessor.Process.Processor.v2.LooseLootProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.v2.StaticLootProcessor;
|
using LootDumpProcessor.Process.Processor.v2.StaticLootProcessor;
|
||||||
using LootDumpProcessor.Storage;
|
using LootDumpProcessor.Storage;
|
||||||
|
|
||||||
namespace LootDumpProcessor.Process.Processor.FileProcessor;
|
namespace LootDumpProcessor.Process.Processor.FileProcessor;
|
||||||
|
|
||||||
public class FileProcessor(IStaticLootProcessor staticLootProcessor) : IFileProcessor
|
public class FileProcessor(IStaticLootProcessor staticLootProcessor, ILooseLootProcessor looseLootProcessor)
|
||||||
|
: IFileProcessor
|
||||||
{
|
{
|
||||||
private readonly IStaticLootProcessor _staticLootProcessor = staticLootProcessor ?? throw new ArgumentNullException(nameof(staticLootProcessor));
|
private readonly IStaticLootProcessor _staticLootProcessor =
|
||||||
|
staticLootProcessor ?? throw new ArgumentNullException(nameof(staticLootProcessor));
|
||||||
|
|
||||||
|
private readonly ILooseLootProcessor _looseLootProcessor =
|
||||||
|
looseLootProcessor ?? throw new ArgumentNullException(nameof(looseLootProcessor));
|
||||||
|
|
||||||
public PartialData Process(BasicInfo parsedData)
|
public PartialData Process(BasicInfo parsedData)
|
||||||
{
|
{
|
||||||
@ -47,7 +53,7 @@ public class FileProcessor(IStaticLootProcessor staticLootProcessor) : IFileProc
|
|||||||
LogLevel.Debug
|
LogLevel.Debug
|
||||||
);
|
);
|
||||||
dumpData.Containers = _staticLootProcessor.PreProcessStaticLoot(staticLoot);
|
dumpData.Containers = _staticLootProcessor.PreProcessStaticLoot(staticLoot);
|
||||||
dumpData.LooseLoot = LooseLootProcessor.PreProcessLooseLoot(looseLoot);
|
dumpData.LooseLoot = _looseLootProcessor.PreProcessLooseLoot(looseLoot);
|
||||||
DataStorageFactory.GetInstance().Store(dumpData);
|
DataStorageFactory.GetInstance().Store(dumpData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,279 +0,0 @@
|
|||||||
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 LootDumpProcessor.Utils;
|
|
||||||
using NumSharp;
|
|
||||||
|
|
||||||
namespace LootDumpProcessor.Process.Processor;
|
|
||||||
|
|
||||||
public static 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;
|
|
||||||
|
|
||||||
// we want to cleanup the data, so we calculate the mean for the values we get raw
|
|
||||||
// For whatever reason, we sometimes get dumps that have A LOT more loose loot point than
|
|
||||||
// the average
|
|
||||||
var values = looseLootCounts.MapSpawnpointCount.Select(Convert.ToDouble);
|
|
||||||
var initialMean = np.mean(np.array(values)).ToArray<double>().First();
|
|
||||||
var looseLootCountTolerancePercentage = LootDumpProcessorContext.GetConfig().ProcessorConfig.LooseLootCountTolerancePercentage / 100;
|
|
||||||
// We calculate here a high point to check, anything above this value will be ignored
|
|
||||||
// The data that was inside those loose loot points still counts for them though!
|
|
||||||
var high = initialMean * (1 + looseLootCountTolerancePercentage);
|
|
||||||
looseLootCounts.MapSpawnpointCount = looseLootCounts.MapSpawnpointCount.Where(v => v <= high).ToList();
|
|
||||||
|
|
||||||
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%
|
|
||||||
// Only add position to forced loot if it has only 1 item in the array.
|
|
||||||
if (itemDistribution.Count == 1 && 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);
|
|
||||||
if (LoggerFactory.GetInstance().CanBeLogged(LogLevel.Warning))
|
|
||||||
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
|
|
||||||
{
|
|
||||||
if (LoggerFactory.GetInstance().CanBeLogged(LogLevel.Error))
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
if (LoggerFactory.GetInstance().CanBeLogged(LogLevel.Error))
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
if (LoggerFactory.GetInstance().CanBeLogged(LogLevel.Warning))
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
using LootDumpProcessor.Model;
|
||||||
|
using LootDumpProcessor.Model.Output.LooseLoot;
|
||||||
|
using LootDumpProcessor.Model.Processing;
|
||||||
|
using LootDumpProcessor.Storage;
|
||||||
|
|
||||||
|
namespace LootDumpProcessor.Process.Processor.v2.LooseLootProcessor
|
||||||
|
{
|
||||||
|
public interface ILooseLootProcessor
|
||||||
|
{
|
||||||
|
PreProcessedLooseLoot PreProcessLooseLoot(List<Template> looseLoot);
|
||||||
|
|
||||||
|
Dictionary<string, LooseLootRoot> CreateLooseLootDistribution(
|
||||||
|
Dictionary<string, int> mapCounts,
|
||||||
|
Dictionary<string, IKey> looseLootCounts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
using LootDumpProcessor.Model;
|
||||||
|
using LootDumpProcessor.Model.Output;
|
||||||
|
using LootDumpProcessor.Model.Output.LooseLoot;
|
||||||
|
using LootDumpProcessor.Model.Processing;
|
||||||
|
using LootDumpProcessor.Storage;
|
||||||
|
using LootDumpProcessor.Storage.Collections;
|
||||||
|
using LootDumpProcessor.Utils;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NumSharp;
|
||||||
|
|
||||||
|
namespace LootDumpProcessor.Process.Processor.v2.LooseLootProcessor
|
||||||
|
{
|
||||||
|
public class LooseLootProcessor(ILogger<LooseLootProcessor> logger, IDataStorage dataStorage)
|
||||||
|
: ILooseLootProcessor
|
||||||
|
{
|
||||||
|
private readonly ILogger<LooseLootProcessor> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
private readonly IDataStorage _dataStorage = dataStorage ?? throw new ArgumentNullException(nameof(dataStorage));
|
||||||
|
|
||||||
|
public PreProcessedLooseLoot PreProcessLooseLoot(List<Template> looseLoot)
|
||||||
|
{
|
||||||
|
var preProcessedLoot = new PreProcessedLooseLoot
|
||||||
|
{
|
||||||
|
Counts = new Dictionary<string, int>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var itemPropertiesDictionary = new SubdivisionedKeyableDictionary<string, List<Template>>();
|
||||||
|
preProcessedLoot.ItemProperties = (AbstractKey)itemPropertiesDictionary.GetKey();
|
||||||
|
preProcessedLoot.MapSpawnpointCount = looseLoot.Count;
|
||||||
|
|
||||||
|
var uniqueLootIds = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
// Rounding rotation to prevent duplicate spawnpoints due to slight variations
|
||||||
|
foreach (var template in looseLoot)
|
||||||
|
{
|
||||||
|
var sanitizedId = template.GetSaneId();
|
||||||
|
if (!uniqueLootIds.ContainsKey(sanitizedId))
|
||||||
|
{
|
||||||
|
uniqueLootIds[sanitizedId] = template.Id;
|
||||||
|
preProcessedLoot.Counts[sanitizedId] = preProcessedLoot.Counts.ContainsKey(sanitizedId)
|
||||||
|
? preProcessedLoot.Counts[sanitizedId] + 1
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemPropertiesDictionary.TryGetValue(sanitizedId, out var templates))
|
||||||
|
{
|
||||||
|
templates = new FlatKeyableList<Template>();
|
||||||
|
itemPropertiesDictionary.Add(sanitizedId, templates);
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Add(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dataStorage.Store(itemPropertiesDictionary);
|
||||||
|
return preProcessedLoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, LooseLootRoot> CreateLooseLootDistribution(
|
||||||
|
Dictionary<string, int> mapCounts,
|
||||||
|
Dictionary<string, IKey> looseLootCounts
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var config = LootDumpProcessorContext.GetConfig();
|
||||||
|
var spawnPointTolerance = config.ProcessorConfig.SpawnPointToleranceForForced / 100;
|
||||||
|
var looseLootDistribution = new Dictionary<string, LooseLootRoot>();
|
||||||
|
|
||||||
|
foreach (var map in mapCounts)
|
||||||
|
{
|
||||||
|
var mapName = map.Key;
|
||||||
|
var mapCount = map.Value;
|
||||||
|
|
||||||
|
var probabilities = new Dictionary<string, double>();
|
||||||
|
var looseLootCountsItem = _dataStorage.GetItem<LooseLootCounts>(looseLootCounts[mapName]);
|
||||||
|
|
||||||
|
var counts = _dataStorage.GetItem<FlatKeyableDictionary<string, int>>(looseLootCountsItem.Counts);
|
||||||
|
foreach (var (itemId, count) in counts)
|
||||||
|
{
|
||||||
|
probabilities[itemId] = (double)count / mapCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spawnPointCount = looseLootCountsItem.MapSpawnpointCount.Select(Convert.ToDouble);
|
||||||
|
var initialMean = np.mean(np.array(spawnPointCount)).ToArray<double>().First();
|
||||||
|
var tolerancePercentage = config.ProcessorConfig.LooseLootCountTolerancePercentage / 100;
|
||||||
|
var highThreshold = initialMean * (1 + tolerancePercentage);
|
||||||
|
|
||||||
|
looseLootCountsItem.MapSpawnpointCount = looseLootCountsItem.MapSpawnpointCount
|
||||||
|
.Where(count => count <= highThreshold)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
looseLootDistribution[mapName] = new LooseLootRoot
|
||||||
|
{
|
||||||
|
SpawnPointCount = new SpawnPointCount
|
||||||
|
{
|
||||||
|
Mean = np.mean(np.array(looseLootCountsItem.MapSpawnpointCount)),
|
||||||
|
Std = np.std(np.array(looseLootCountsItem.MapSpawnpointCount))
|
||||||
|
},
|
||||||
|
SpawnPointsForced = new List<SpawnPointsForced>(),
|
||||||
|
SpawnPoints = new List<SpawnPoint>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var itemProperties = _dataStorage.GetItem<FlatKeyableDictionary<string, IKey>>(looseLootCountsItem.ItemProperties);
|
||||||
|
foreach (var (spawnPoint, itemListKey) in itemProperties)
|
||||||
|
{
|
||||||
|
var itemCounts = new Dictionary<ComposedKey, int>();
|
||||||
|
var savedTemplates = _dataStorage.GetItem<FlatKeyableList<Template>>(itemListKey);
|
||||||
|
|
||||||
|
foreach (var template in savedTemplates)
|
||||||
|
{
|
||||||
|
var composedKey = new ComposedKey(template);
|
||||||
|
if (!itemCounts.TryAdd(composedKey, 1))
|
||||||
|
itemCounts[composedKey]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupedTemplates = savedTemplates
|
||||||
|
.Select(t => (t.GetSaneId(), t))
|
||||||
|
.GroupBy(g => g.Item1)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (groupedTemplates.Count > 1)
|
||||||
|
{
|
||||||
|
throw new Exception("Multiple sanitized IDs found for a single spawn point.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var spawnPoints = groupedTemplates.First().Select(g => g.t).ToList();
|
||||||
|
var locationId = spawnPoints[0].GetLocationId();
|
||||||
|
var templateCopy = ProcessorUtil.Copy(spawnPoints[0]);
|
||||||
|
var itemDistributions = itemCounts.Select(kv => new ItemDistribution
|
||||||
|
{
|
||||||
|
ComposedKey = kv.Key,
|
||||||
|
RelativeProbability = kv.Value
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (itemDistributions.Count == 1 &&
|
||||||
|
(LootDumpProcessorContext.GetTarkovItems().IsQuestItem(itemDistributions[0].ComposedKey?.FirstItem?.Tpl) ||
|
||||||
|
LootDumpProcessorContext.GetForcedLooseItems()[mapName].Contains(itemDistributions[0].ComposedKey?.FirstItem?.Tpl)))
|
||||||
|
{
|
||||||
|
var forcedSpawnPoint = new SpawnPointsForced
|
||||||
|
{
|
||||||
|
LocationId = locationId,
|
||||||
|
Probability = probabilities[spawnPoint],
|
||||||
|
Template = templateCopy
|
||||||
|
};
|
||||||
|
looseLootDistribution[mapName].SpawnPointsForced.Add(forcedSpawnPoint);
|
||||||
|
}
|
||||||
|
else if (probabilities[spawnPoint] > spawnPointTolerance)
|
||||||
|
{
|
||||||
|
var forcedSpawnPoint = new SpawnPointsForced
|
||||||
|
{
|
||||||
|
LocationId = locationId,
|
||||||
|
Probability = probabilities[spawnPoint],
|
||||||
|
Template = templateCopy
|
||||||
|
};
|
||||||
|
looseLootDistribution[mapName].SpawnPointsForced.Add(forcedSpawnPoint);
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Item: {ItemId} has > {Tolerance}% spawn chance in spawn point: {LocationId} but isn't in forced loot, adding to forced",
|
||||||
|
templateCopy.Id,
|
||||||
|
config.ProcessorConfig.SpawnPointToleranceForForced,
|
||||||
|
forcedSpawnPoint.LocationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var spawnPointEntry = new SpawnPoint
|
||||||
|
{
|
||||||
|
LocationId = locationId,
|
||||||
|
Probability = probabilities[spawnPoint],
|
||||||
|
Template = templateCopy,
|
||||||
|
ItemDistribution = itemDistributions
|
||||||
|
};
|
||||||
|
|
||||||
|
templateCopy.Items = new List<Item>();
|
||||||
|
|
||||||
|
var groupedByKey = spawnPoints
|
||||||
|
.GroupBy(t => new ComposedKey(t))
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
foreach (var distribution in itemDistributions)
|
||||||
|
{
|
||||||
|
if (groupedByKey.TryGetValue(distribution.ComposedKey, out var items))
|
||||||
|
{
|
||||||
|
var itemList = items.First().Items;
|
||||||
|
var originalItem = itemList.Find(i => string.IsNullOrEmpty(i.ParentId));
|
||||||
|
var originalId = originalItem.Id;
|
||||||
|
originalItem.Id = distribution.ComposedKey.Key;
|
||||||
|
foreach (var childItem in itemList.Where(i => i.ParentId == originalId))
|
||||||
|
{
|
||||||
|
childItem.ParentId = originalItem.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
templateCopy.Items.AddRange(itemList);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"Item template {TemplateId} on loose loot distribution for spawn point {SpawnPointId} not found in spawn points.",
|
||||||
|
distribution.ComposedKey?.FirstItem?.Tpl,
|
||||||
|
templateCopy.Id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
looseLootDistribution[mapName].SpawnPoints.Add(spawnPointEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
looseLootDistribution[mapName].SpawnPoints = looseLootDistribution[mapName].SpawnPoints
|
||||||
|
.OrderBy(x => x.Template.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var configuredForcedTemplates = new HashSet<string>(LootDumpProcessorContext.GetForcedLooseItems()[mapName].Select(item => item));
|
||||||
|
var foundForcedTemplates = new HashSet<string>(looseLootDistribution[mapName].SpawnPointsForced.Select(fp => fp.Template.Items[0].Tpl));
|
||||||
|
|
||||||
|
foreach (var expectedTpl in configuredForcedTemplates)
|
||||||
|
{
|
||||||
|
if (!foundForcedTemplates.Contains(expectedTpl))
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
"Expected item: {ItemTpl} defined in forced_loose.yaml config not found in forced loot",
|
||||||
|
expectedTpl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var foundTpl in foundForcedTemplates)
|
||||||
|
{
|
||||||
|
if (!configuredForcedTemplates.Contains(foundTpl))
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Map: {MapName} Item: {ItemTpl} not defined in forced_loose.yaml config but was flagged as forced by code",
|
||||||
|
mapName,
|
||||||
|
foundTpl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return looseLootDistribution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ using LootDumpProcessor.Process;
|
|||||||
using LootDumpProcessor.Process.Processor.DumpProcessor;
|
using LootDumpProcessor.Process.Processor.DumpProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.FileProcessor;
|
using LootDumpProcessor.Process.Processor.FileProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.v2.AmmoProcessor;
|
using LootDumpProcessor.Process.Processor.v2.AmmoProcessor;
|
||||||
|
using LootDumpProcessor.Process.Processor.v2.LooseLootProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.v2.StaticContainersProcessor;
|
using LootDumpProcessor.Process.Processor.v2.StaticContainersProcessor;
|
||||||
using LootDumpProcessor.Process.Processor.v2.StaticLootProcessor;
|
using LootDumpProcessor.Process.Processor.v2.StaticLootProcessor;
|
||||||
using LootDumpProcessor.Storage;
|
using LootDumpProcessor.Storage;
|
||||||
@ -21,7 +22,9 @@ public static class Program
|
|||||||
services.AddTransient<IStaticLootProcessor, StaticLootProcessor>();
|
services.AddTransient<IStaticLootProcessor, StaticLootProcessor>();
|
||||||
services.AddTransient<IStaticContainersProcessor, StaticContainersProcessor>();
|
services.AddTransient<IStaticContainersProcessor, StaticContainersProcessor>();
|
||||||
services.AddTransient<IAmmoProcessor, AmmoProcessor>();
|
services.AddTransient<IAmmoProcessor, AmmoProcessor>();
|
||||||
services.AddTransient<StaticLootProcessor>();
|
services.AddTransient<ILooseLootProcessor, LooseLootProcessor>();
|
||||||
|
|
||||||
|
services.AddSingleton<IDataStorage>(_ => DataStorageFactory.GetInstance());
|
||||||
|
|
||||||
services.AddTransient<IFileProcessor, FileProcessor>();
|
services.AddTransient<IFileProcessor, FileProcessor>();
|
||||||
services.AddTransient<IDumpProcessor, MultithreadSteppedDumpProcessor>();
|
services.AddTransient<IDumpProcessor, MultithreadSteppedDumpProcessor>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user