Merge branch 'develop' into linux-build-changes
This commit is contained in:
@@ -22,7 +22,6 @@ public class LocationLootGenerator(
|
||||
MathUtil _mathUtil,
|
||||
HashUtil _hashUtil,
|
||||
ItemHelper _itemHelper,
|
||||
InventoryHelper _inventoryHelper,
|
||||
DatabaseService _databaseService,
|
||||
ContainerHelper _containerHelper,
|
||||
PresetHelper _presetHelper,
|
||||
@@ -33,8 +32,8 @@ public class LocationLootGenerator(
|
||||
ICloner _cloner
|
||||
)
|
||||
{
|
||||
protected LocationConfig _locationConfig = _configServer.GetConfig<LocationConfig>();
|
||||
protected SeasonalEventConfig _seasonalEventConfig = _configServer.GetConfig<SeasonalEventConfig>();
|
||||
protected readonly LocationConfig _locationConfig = _configServer.GetConfig<LocationConfig>();
|
||||
protected readonly SeasonalEventConfig _seasonalEventConfig = _configServer.GetConfig<SeasonalEventConfig>();
|
||||
|
||||
/// Create a list of container objects with randomised loot
|
||||
/// <param name="locationBase">Map base to generate containers for</param>
|
||||
@@ -155,9 +154,10 @@ public class LocationLootGenerator(
|
||||
return result;
|
||||
}
|
||||
|
||||
var mapping = GetGroupIdToContainerMappings(mapData.Statics, staticRandomisableContainersOnMap);
|
||||
|
||||
|
||||
// For each of the container groups, choose from the pool of containers, hydrate container with loot and add to result array
|
||||
var mapping = GetGroupIdToContainerMappings(mapData.Statics, staticRandomisableContainersOnMap);
|
||||
foreach (var (key, data) in mapping)
|
||||
{
|
||||
// Count chosen was 0, skip
|
||||
@@ -177,7 +177,7 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
// EDGE CASE: These are containers without a group and have a probability < 100%
|
||||
if (key == "")
|
||||
if (key == string.Empty)
|
||||
{
|
||||
var containerIdsCopy = _cloner.Clone(data.ContainerIdsWithProbability);
|
||||
// Roll each containers probability, if it passes, it gets added
|
||||
@@ -235,7 +235,6 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
_logger.Success($"A total of: {staticLootItemCount} static items spawned");
|
||||
|
||||
_logger.Success(
|
||||
_localisationService.GetText("location-containers_generated_success", staticContainerCount)
|
||||
);
|
||||
@@ -254,7 +253,7 @@ public class LocationLootGenerator(
|
||||
staticContainer.Probability != 1 &&
|
||||
!staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false) &&
|
||||
!_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
|
||||
staticContainer.Template.Items[0].Template
|
||||
staticContainer.Template.Items.FirstOrDefault().Template
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
@@ -271,14 +270,14 @@ public class LocationLootGenerator(
|
||||
staticContainer.Probability == 1 ||
|
||||
staticContainer.Template.IsAlwaysSpawn.GetValueOrDefault(false) ||
|
||||
_locationConfig.ContainerRandomisationSettings.ContainerTypesToNotRandomise.Contains(
|
||||
staticContainer.Template.Items[0].Template
|
||||
staticContainer.Template.Items.FirstOrDefault().Template
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choose a number of containers based on their probabilty value to fulfil the desired count in
|
||||
/// Choose a number of containers based on their probability value to fulfil the desired count in
|
||||
/// containerData.chosenCount
|
||||
/// </summary>
|
||||
/// <param name="groupId">Name of the group the containers are being collected for</param>
|
||||
@@ -346,12 +345,12 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
// Add an empty group for containers without a group id but still have a < 100% chance to spawn
|
||||
// Likely bad BSG data, will be fixed...eventually, example of the groupids: `NEED_TO_BE_FIXED1`,`NEED_TO_BE_FIXED_SE02`, `NEED_TO_BE_FIXED_NW_01`
|
||||
mapping[""] = new ContainerGroupCount
|
||||
// Likely bad BSG data, will be fixed...eventually, example of the groupIds: `NEED_TO_BE_FIXED1`,`NEED_TO_BE_FIXED_SE02`, `NEED_TO_BE_FIXED_NW_01`
|
||||
mapping.Add(string.Empty, new ContainerGroupCount
|
||||
{
|
||||
ContainerIdsWithProbability = new Dictionary<string, double>(),
|
||||
ChosenCount = -1
|
||||
};
|
||||
});
|
||||
|
||||
// Iterate over all containers and add to group keyed by groupId
|
||||
// Containers without a group go into a group with empty key ""
|
||||
@@ -413,12 +412,12 @@ public class LocationLootGenerator(
|
||||
)
|
||||
{
|
||||
var containerClone = _cloner.Clone(staticContainer);
|
||||
var containerTpl = containerClone.Template.Items[0].Template;
|
||||
var containerTpl = containerClone.Template.Items.FirstOrDefault().Template;
|
||||
|
||||
// Create new unique parent id to prevent any collisions
|
||||
var parentId = _hashUtil.Generate();
|
||||
containerClone.Template.Root = parentId;
|
||||
containerClone.Template.Items[0].Id = parentId;
|
||||
containerClone.Template.Items.FirstOrDefault().Id = parentId;
|
||||
|
||||
var containerMap = _itemHelper.GetContainerMapping(containerTpl);
|
||||
|
||||
@@ -432,7 +431,7 @@ public class LocationLootGenerator(
|
||||
// Get all possible loot items for container
|
||||
var containerLootPool = GetPossibleLootItemsForContainer(containerTpl, staticLootDist);
|
||||
|
||||
// Some containers need to have items forced into it (quest keys etc)
|
||||
// Some containers need to have items forced into it (quest keys etc.)
|
||||
var tplsForced = staticForced
|
||||
.Where(forcedStaticProp => forcedStaticProp.ContainerId == containerClone.Template.Id)
|
||||
.Select(x => x.ItemTpl);
|
||||
@@ -463,14 +462,9 @@ public class LocationLootGenerator(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tplToAdd == "5bf3e0490db83400196199af")
|
||||
{
|
||||
Console.WriteLine("yo");
|
||||
}
|
||||
|
||||
// Check if item should have children removed
|
||||
var items = _locationConfig.TplsToStripChildItemsFrom.Contains(tplToAdd)
|
||||
? [chosenItemWithChildren.Items[0]] // Strip children from parent
|
||||
? [chosenItemWithChildren.Items.FirstOrDefault()] // Strip children from parent
|
||||
: chosenItemWithChildren.Items;
|
||||
|
||||
// look for open slot to put chosen item into
|
||||
@@ -516,7 +510,7 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up a containers itemcountDistribution data and choose an item count based on the found weights
|
||||
/// Look up a containers itemCountDistribution data and choose an item count based on the found weights
|
||||
/// </summary>
|
||||
/// <param name="containerTypeId">Container to get item count for</param>
|
||||
/// <param name="staticLootDist">staticLoot.json</param>
|
||||
@@ -546,8 +540,8 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
foreach (var itemCountDistribution in countDistribution)
|
||||
// Add each count of items into array
|
||||
{
|
||||
// Add each count of items into array
|
||||
itemCountArray.Add(
|
||||
new ProbabilityObject<int, float?>(
|
||||
itemCountDistribution.Count.Value,
|
||||
@@ -588,14 +582,14 @@ public class LocationLootGenerator(
|
||||
foreach (var icd in itemContainerDistribution)
|
||||
{
|
||||
if (!seasonalEventActive && seasonalItemTplBlacklist.Contains(icd.Tpl))
|
||||
// Skip seasonal event items if they're not enabled
|
||||
{
|
||||
// Skip seasonal event items if they're not enabled
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
|
||||
if (_itemFilterService.IsLootableItemBlacklisted(icd.Tpl))
|
||||
{
|
||||
// Ensure no blacklisted lootable items are in pool
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -653,12 +647,11 @@ public class LocationLootGenerator(
|
||||
// Add forced loot
|
||||
AddForcedLoot(loot, dynamicForcedSpawnPoints, locationName, staticAmmoDist);
|
||||
|
||||
var allDynamicSpawnpoints = dynamicLootDist.Spawnpoints;
|
||||
var allDynamicSpawnPoints = dynamicLootDist.Spawnpoints;
|
||||
|
||||
// Draw from random distribution
|
||||
var desiredSpawnpointCount = Math.Round(
|
||||
GetLooseLootMultiplierForLocation(locationName) *
|
||||
_randomUtil.GetNormallyDistributedRandomNumber(
|
||||
var desiredSpawnPointCount = Math.Round(
|
||||
GetLooseLootMultiplierForLocation(locationName) * _randomUtil.GetNormallyDistributedRandomNumber(
|
||||
(double) dynamicLootDist.SpawnpointCount.Mean,
|
||||
(double) dynamicLootDist.SpawnpointCount.Std
|
||||
)
|
||||
@@ -667,59 +660,59 @@ public class LocationLootGenerator(
|
||||
// Positions not in forced but have 100% chance to spawn
|
||||
List<Spawnpoint> guaranteedLoosePoints = [];
|
||||
|
||||
var blacklistedSpawnpoints = _locationConfig.LooseLootBlacklist.GetValueOrDefault(locationName);
|
||||
var spawnpointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
|
||||
var blacklistedSpawnPoints = _locationConfig.LooseLootBlacklist.GetValueOrDefault(locationName);
|
||||
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
|
||||
|
||||
foreach (var spawnpoint in allDynamicSpawnpoints)
|
||||
foreach (var spawnPoint in allDynamicSpawnPoints)
|
||||
{
|
||||
// Point is blacklisted, skip
|
||||
if (blacklistedSpawnpoints?.Contains(spawnpoint.Template.Id) ?? false)
|
||||
if (blacklistedSpawnPoints?.Contains(spawnPoint.Template.Id) ?? false)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Ignoring loose loot location: {spawnpoint.Template.Id}");
|
||||
_logger.Debug($"Ignoring loose loot location: {spawnPoint.Template.Id}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've handled IsAlwaysSpawn above, so skip them
|
||||
if (spawnpoint.Template.IsAlwaysSpawn ?? false)
|
||||
if (spawnPoint.Template.IsAlwaysSpawn ?? false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 100%, add it to guaranteed
|
||||
if (spawnpoint.Probability == 1)
|
||||
if (spawnPoint.Probability == 1)
|
||||
{
|
||||
guaranteedLoosePoints.Add(spawnpoint);
|
||||
guaranteedLoosePoints.Add(spawnPoint);
|
||||
continue;
|
||||
}
|
||||
|
||||
spawnpointArray.Add(new ProbabilityObject<string, Spawnpoint>(spawnpoint.Template.Id, spawnpoint.Probability ?? 0, spawnpoint));
|
||||
spawnPointArray.Add(new ProbabilityObject<string, Spawnpoint>(spawnPoint.Template.Id, spawnPoint.Probability ?? 0, spawnPoint));
|
||||
}
|
||||
|
||||
// Select a number of spawn points to add loot to
|
||||
// Add ALL loose loot with 100% chance to pool
|
||||
List<Spawnpoint> chosenSpawnpoints = [];
|
||||
chosenSpawnpoints.AddRange(guaranteedLoosePoints);
|
||||
List<Spawnpoint> chosenSpawnPoints = [];
|
||||
chosenSpawnPoints.AddRange(guaranteedLoosePoints);
|
||||
|
||||
var randomSpawnpointCount = desiredSpawnpointCount - chosenSpawnpoints.Count;
|
||||
var randomSpawnPointCount = desiredSpawnPointCount - chosenSpawnPoints.Count;
|
||||
// Only draw random spawn points if needed
|
||||
if (randomSpawnpointCount > 0 && spawnpointArray.Count > 0)
|
||||
if (randomSpawnPointCount > 0 && spawnPointArray.Count > 0)
|
||||
// Add randomly chosen spawn points
|
||||
{
|
||||
foreach (var si in spawnpointArray.Draw((int) randomSpawnpointCount, false))
|
||||
foreach (var si in spawnPointArray.Draw((int) randomSpawnPointCount, false))
|
||||
{
|
||||
chosenSpawnpoints.Add(spawnpointArray.Data(si));
|
||||
chosenSpawnPoints.Add(spawnPointArray.Data(si));
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out duplicate locationIds // prob can be done better
|
||||
chosenSpawnpoints = chosenSpawnpoints.GroupBy(spawnpoint => spawnpoint.LocationId).Select(group => group.First()).ToList();
|
||||
chosenSpawnPoints = chosenSpawnPoints.GroupBy(spawnPoint => spawnPoint.LocationId).Select(group => group.First()).ToList();
|
||||
|
||||
// Do we have enough items in pool to fulfill requirement
|
||||
var tooManySpawnPointsRequested = desiredSpawnpointCount - chosenSpawnpoints.Count > 0;
|
||||
var tooManySpawnPointsRequested = desiredSpawnPointCount - chosenSpawnPoints.Count > 0;
|
||||
if (tooManySpawnPointsRequested)
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
@@ -729,8 +722,8 @@ public class LocationLootGenerator(
|
||||
"location-spawn_point_count_requested_vs_found",
|
||||
new
|
||||
{
|
||||
requested = desiredSpawnpointCount + guaranteedLoosePoints.Count,
|
||||
found = chosenSpawnpoints.Count,
|
||||
requested = desiredSpawnPointCount + guaranteedLoosePoints.Count,
|
||||
found = chosenSpawnPoints.Count,
|
||||
mapName = locationName
|
||||
}
|
||||
)
|
||||
@@ -738,12 +731,12 @@ public class LocationLootGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over spawnpoints
|
||||
// Iterate over spawnPoints
|
||||
var seasonalEventActive = _seasonalEventService.SeasonalEventEnabled();
|
||||
var seasonalItemTplBlacklist = _seasonalEventService.GetInactiveSeasonalEventItems();
|
||||
foreach (var spawnPoint in chosenSpawnpoints)
|
||||
foreach (var spawnPoint in chosenSpawnPoints)
|
||||
{
|
||||
// Spawnpoint is invalid, skip it
|
||||
// SpawnPoint is invalid, skip it
|
||||
if (spawnPoint.Template is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
@@ -840,9 +833,8 @@ public class LocationLootGenerator(
|
||||
foreach (var itemTpl in lootToForceSingleAmountOnMap)
|
||||
{
|
||||
// Get all spawn positions for item tpl in forced loot array
|
||||
var items = forcedSpawnPoints.Where(forcedSpawnPoint => forcedSpawnPoint.Template.Items[0].Template == itemTpl
|
||||
);
|
||||
if (items is null || !items.Any())
|
||||
var items = forcedSpawnPoints.Where(forcedSpawnPoint => forcedSpawnPoint.Template.Items.FirstOrDefault().Template == itemTpl);
|
||||
if (!items.Any())
|
||||
{
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
@@ -853,15 +845,15 @@ public class LocationLootGenerator(
|
||||
}
|
||||
|
||||
// Create probability array of all spawn positions for this spawn id
|
||||
var spawnpointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
|
||||
var spawnPointArray = new ProbabilityObjectArray<string, Spawnpoint>(_mathUtil, _cloner);
|
||||
foreach (var si in items)
|
||||
// use locationId as template.Id is the same across all items
|
||||
{
|
||||
spawnpointArray.Add(new ProbabilityObject<string, Spawnpoint>(si.LocationId, si.Probability ?? 0, si));
|
||||
spawnPointArray.Add(new ProbabilityObject<string, Spawnpoint>(si.LocationId, si.Probability ?? 0, si));
|
||||
}
|
||||
|
||||
// Choose 1 out of all found spawn positions for spawn id and add to loot array
|
||||
foreach (var spawnPointLocationId in spawnpointArray.Draw(1, false))
|
||||
foreach (var spawnPointLocationId in spawnPointArray.Draw(1, false))
|
||||
{
|
||||
var itemToAdd = items.FirstOrDefault(item => item.LocationId == spawnPointLocationId);
|
||||
var lootItem = itemToAdd?.Template;
|
||||
@@ -1032,14 +1024,14 @@ public class LocationLootGenerator(
|
||||
if (_locationConfig.TplsToStripChildItemsFrom.Contains(chosenItem.Template))
|
||||
// Strip children from parent before adding
|
||||
{
|
||||
itemWithChildren = [itemWithChildren[0]];
|
||||
itemWithChildren = [itemWithChildren.FirstOrDefault()];
|
||||
}
|
||||
|
||||
itemWithMods.AddRange(itemWithChildren);
|
||||
}
|
||||
|
||||
// Get inventory size of item
|
||||
var size = _itemHelper.GetItemSize(itemWithMods, itemWithMods[0].Id);
|
||||
var size = _itemHelper.GetItemSize(itemWithMods, itemWithMods.FirstOrDefault().Id);
|
||||
|
||||
return new ContainerItem
|
||||
{
|
||||
@@ -1100,13 +1092,13 @@ public class LocationLootGenerator(
|
||||
// No spawn point, use default template
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.WEAPON))
|
||||
{
|
||||
rootItem = CreateWeaponItems(chosenTpl, staticAmmoDist, parentId, ref items);
|
||||
rootItem = CreateWeaponRootAndChildren(chosenTpl, staticAmmoDist, parentId, ref items);
|
||||
|
||||
var size = _itemHelper.GetItemSize(items, rootItem.Id);
|
||||
width = size.Width;
|
||||
height = size.Height;
|
||||
}
|
||||
// No spawnpoint to fall back on, generate manually
|
||||
// No spawnPoint to fall back on, generate manually
|
||||
else if (_itemHelper.IsOfBaseclass(chosenTpl, BaseClasses.AMMO_BOX))
|
||||
{
|
||||
_itemHelper.AddCartridgesToAmmoBox(items, itemTemplate);
|
||||
@@ -1141,7 +1133,7 @@ public class LocationLootGenerator(
|
||||
_itemHelper.RemapRootItemId(presetAndMods);
|
||||
|
||||
// Use original items parentId otherwise item doesn't get added to container correctly
|
||||
presetAndMods[0].ParentId = rootItem.ParentId;
|
||||
presetAndMods.FirstOrDefault().ParentId = rootItem.ParentId;
|
||||
items = presetAndMods;
|
||||
}
|
||||
else
|
||||
@@ -1160,15 +1152,30 @@ public class LocationLootGenerator(
|
||||
return items;
|
||||
}
|
||||
|
||||
protected Item? CreateWeaponItems(string chosenTpl, Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist, string? parentId, ref List<Item> items)
|
||||
/// <summary>
|
||||
/// Attempt to find default preset for passed in tpl and construct a weapon with children.
|
||||
/// If no preset found, return chosenTpl as Item object
|
||||
/// </summary>
|
||||
/// <param name="chosenTpl">Tpl of item to get preset for</param>
|
||||
/// <param name="cartridgePool">Pool of cartridges to pick from</param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <param name="items">Root item + children</param>
|
||||
/// <returns>Root Item</returns>
|
||||
protected Item? CreateWeaponRootAndChildren(
|
||||
string chosenTpl,
|
||||
Dictionary<string,List<StaticAmmoDetails>> cartridgePool,
|
||||
string? parentId,
|
||||
ref List<Item> items)
|
||||
{
|
||||
List<Item> children = [];
|
||||
|
||||
// Look up a default preset for desired weapon tpl
|
||||
var defaultPreset = _cloner.Clone(_presetHelper.GetDefaultPreset(chosenTpl));
|
||||
if (defaultPreset?.Items is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
children = _itemHelper.ReparentItemAndChildren(defaultPreset.Items[0], defaultPreset.Items);
|
||||
children = _itemHelper.ReparentItemAndChildren(defaultPreset.Items.FirstOrDefault(), defaultPreset.Items);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1194,7 +1201,7 @@ public class LocationLootGenerator(
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesn't have any default presets and kills this code below as it has no chidren to reparent
|
||||
// RSP30 (62178be9d0050232da3485d9/624c0b3340357b5f566e8766/6217726288ed9f0845317459) doesn't have any default presets and kills this code below as it has no children to re-parent
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"createStaticLootItem() No preset found for weapon: {chosenTpl}");
|
||||
@@ -1259,7 +1266,7 @@ public class LocationLootGenerator(
|
||||
_itemHelper.FillMagazineWithRandomCartridge(
|
||||
magazineWithCartridges,
|
||||
magTemplate,
|
||||
staticAmmoDist,
|
||||
cartridgePool,
|
||||
weaponTemplate.Properties.AmmoCaliber,
|
||||
0.25,
|
||||
defaultWeapon.Properties.DefAmmo,
|
||||
@@ -1274,7 +1281,10 @@ public class LocationLootGenerator(
|
||||
return rootItem;
|
||||
}
|
||||
|
||||
protected void GenerateStaticMagazineItem(Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist, Item? rootItem, TemplateItem itemTemplate,
|
||||
protected void GenerateStaticMagazineItem(
|
||||
Dictionary<string, List<StaticAmmoDetails>> staticAmmoDist,
|
||||
Item? rootItem,
|
||||
TemplateItem itemTemplate,
|
||||
List<Item> items)
|
||||
{
|
||||
List<Item> magazineWithCartridges = [rootItem];
|
||||
@@ -1292,7 +1302,7 @@ public class LocationLootGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
public class ContainerGroupCount
|
||||
public record ContainerGroupCount
|
||||
{
|
||||
[JsonPropertyName("containerIdsWithProbability")]
|
||||
public Dictionary<string, double>? ContainerIdsWithProbability
|
||||
|
||||
@@ -4,32 +4,16 @@ using SPTarkov.Server.Core.Models.Spt.Config;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
using SPTarkov.Server.Core.Servers;
|
||||
using SPTarkov.Server.Core.Services;
|
||||
using SPTarkov.Server.Core.Utils;
|
||||
|
||||
namespace SPTarkov.Server.Core.Generators;
|
||||
|
||||
[Injectable]
|
||||
public class PmcWaveGenerator
|
||||
public class PmcWaveGenerator(
|
||||
ISptLogger<PmcWaveGenerator> logger,
|
||||
DatabaseService databaseService,
|
||||
ConfigServer configServer)
|
||||
{
|
||||
protected ConfigServer _configServer;
|
||||
protected DatabaseService _databaseService;
|
||||
protected ISptLogger<PmcWaveGenerator> _logger;
|
||||
protected PmcConfig _pmcConfig;
|
||||
protected RandomUtil _randomUtil;
|
||||
|
||||
public PmcWaveGenerator(
|
||||
ISptLogger<PmcWaveGenerator> _logger,
|
||||
RandomUtil _randomUtil,
|
||||
DatabaseService _databaseService,
|
||||
ConfigServer _configServer
|
||||
)
|
||||
{
|
||||
this._logger = _logger;
|
||||
this._randomUtil = _randomUtil;
|
||||
this._databaseService = _databaseService;
|
||||
this._configServer = _configServer;
|
||||
_pmcConfig = _configServer.GetConfig<PmcConfig>();
|
||||
}
|
||||
protected readonly PmcConfig _pmcConfig = configServer.GetConfig<PmcConfig>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a pmc wave to a map
|
||||
@@ -63,13 +47,8 @@ public class PmcWaveGenerator
|
||||
return;
|
||||
}
|
||||
|
||||
var location = _databaseService.GetLocation(name);
|
||||
if (location is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
location.Base.BossLocationSpawn.AddRange(pmcWavesToAdd);
|
||||
var location = databaseService.GetLocation(name);
|
||||
location?.Base.BossLocationSpawn.AddRange(pmcWavesToAdd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
using SPTarkov.Server.Core.Utils;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace SPTarkov.Server.Core.Services.Cache;
|
||||
|
||||
[Injectable]
|
||||
public class BundleHashCacheService(
|
||||
ISptLogger<BundleHashCacheService> _logger,
|
||||
HashUtil _hashUtil,
|
||||
JsonUtil _jsonUtil,
|
||||
FileUtil _fileUtil
|
||||
)
|
||||
{
|
||||
protected static readonly string _bundleHashCachePath = "./user/cache/bundleHashCache.json";
|
||||
protected Dictionary<string, string> _bundleHashes = new();
|
||||
|
||||
public string GetStoredValue(string key)
|
||||
{
|
||||
_bundleHashes.TryGetValue(key, out var value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void StoreValue(string key, string value)
|
||||
{
|
||||
_bundleHashes.Add(key, value);
|
||||
|
||||
_fileUtil.WriteFile(_bundleHashCachePath, _jsonUtil.Serialize(_bundleHashes));
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Bundle {key} hash stored in {_bundleHashCachePath}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchWithStoredHash(string bundlePath, string hash)
|
||||
{
|
||||
return GetStoredValue(bundlePath) == hash;
|
||||
}
|
||||
|
||||
public bool CalculateAndMatchHash(string bundlePath)
|
||||
{
|
||||
var fileContents = _fileUtil.ReadFile(bundlePath);
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, fileContents);
|
||||
|
||||
return MatchWithStoredHash(bundlePath, generatedHash);
|
||||
}
|
||||
|
||||
public void CalculateAndStoreHash(string bundlePath)
|
||||
{
|
||||
var fileContents = _fileUtil.ReadFile(bundlePath);
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.MD5, fileContents);
|
||||
|
||||
StoreValue(bundlePath, generatedHash);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using SPTarkov.DI.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
using SPTarkov.Server.Core.Utils;
|
||||
using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
|
||||
|
||||
namespace SPTarkov.Server.Core.Services.Cache;
|
||||
|
||||
[Injectable]
|
||||
public class ModHashCacheService(
|
||||
ISptLogger<ModHashCacheService> _logger,
|
||||
JsonUtil _jsonUtil,
|
||||
HashUtil _hashUtil,
|
||||
FileUtil _fileUtil
|
||||
)
|
||||
{
|
||||
protected readonly string _modCachePath = "./user/cache/modCache.json";
|
||||
protected readonly Dictionary<string, string> _modHashes = new();
|
||||
|
||||
public string? GetStoredValue(string key)
|
||||
{
|
||||
_modHashes.TryGetValue(key, out var value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void StoreValue(string key, string value)
|
||||
{
|
||||
_modHashes.TryAdd(key, value);
|
||||
|
||||
_fileUtil.WriteFile(_modCachePath, _jsonUtil.Serialize(_modHashes));
|
||||
|
||||
if (_logger.IsLogEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.Debug($"Mod {key} hash stored in: {_modCachePath}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool MatchWithStoredHash(string modName, string hash)
|
||||
{
|
||||
return GetStoredValue(modName) == hash;
|
||||
}
|
||||
|
||||
public bool CalculateAndCompareHash(string modName, string modContent)
|
||||
{
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.SHA1, modContent);
|
||||
|
||||
return MatchWithStoredHash(modName, generatedHash);
|
||||
}
|
||||
|
||||
public void CalculateAndStoreHash(string modName, string modContent)
|
||||
{
|
||||
var generatedHash = _hashUtil.GenerateHashForData(HashingAlgorithm.SHA1, modContent);
|
||||
|
||||
StoreValue(modName, generatedHash);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class DatabaseService(
|
||||
HashUtil _hashUtil
|
||||
)
|
||||
{
|
||||
protected bool isDataValid = true;
|
||||
private bool _isDataValid = true;
|
||||
|
||||
/// <returns> assets/database/ </returns>
|
||||
public DatabaseTables GetTables()
|
||||
@@ -105,13 +105,15 @@ public class DatabaseService(
|
||||
/// </summary>
|
||||
/// <param name="locationId"> Desired location ID </param>
|
||||
/// <returns> assets/database/locations/ </returns>
|
||||
public Location GetLocation(string locationId)
|
||||
public Location? GetLocation(string locationId)
|
||||
{
|
||||
var locations = GetLocations();
|
||||
var desiredLocation = locations.GetByJsonProp<Location>(locationId.ToLower());
|
||||
if (desiredLocation == null)
|
||||
{
|
||||
throw new Exception(_localisationService.GetText("database-no_location_found_with_id", locationId));
|
||||
_logger.Error(_localisationService.GetText("database-no_location_found_with_id", locationId));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return desiredLocation;
|
||||
@@ -297,12 +299,14 @@ public class DatabaseService(
|
||||
/// </summary>
|
||||
/// <param name="traderId"> Desired trader ID </param>
|
||||
/// <returns> assets/database/traders/ </returns>
|
||||
public Trader GetTrader(string traderId)
|
||||
public Trader? GetTrader(string traderId)
|
||||
{
|
||||
var traders = GetTraders();
|
||||
if (!traders.TryGetValue(traderId, out var desiredTrader))
|
||||
{
|
||||
throw new Exception(_localisationService.GetText("database-no_trader_found_with_id", traderId));
|
||||
_logger.Error(_localisationService.GetText("database-no_trader_found_with_id", traderId));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return desiredTrader;
|
||||
@@ -326,13 +330,13 @@ public class DatabaseService(
|
||||
{
|
||||
var start = Stopwatch.StartNew();
|
||||
|
||||
isDataValid =
|
||||
_isDataValid =
|
||||
ValidateTable(GetQuests(), "quest") &&
|
||||
ValidateTable(GetTraders(), "trader") &&
|
||||
ValidateTable(GetItems(), "item") &&
|
||||
ValidateTable(GetCustomization(), "customization");
|
||||
|
||||
if (!isDataValid)
|
||||
if (!_isDataValid)
|
||||
{
|
||||
_logger.Error(_localisationService.GetText("database-invalid_data"));
|
||||
}
|
||||
@@ -370,6 +374,6 @@ public class DatabaseService(
|
||||
/// <returns> True if the database contains valid data, false otherwise </returns>
|
||||
public bool IsDatabaseValid()
|
||||
{
|
||||
return isDataValid;
|
||||
return _isDataValid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,10 @@ public class LocaleService(
|
||||
ConfigServer _configServer
|
||||
)
|
||||
{
|
||||
// we have to LazyLoad the data from the database and then combine it with the custom data before returning it
|
||||
protected readonly LocaleConfig _localeConfig = _configServer.GetConfig<LocaleConfig>();
|
||||
protected readonly Dictionary<string, Dictionary<string, string>> customClientLocales = new();
|
||||
|
||||
/// <summary>
|
||||
/// Get the eft globals db file based on the configured locale in config/locale.json, if not found, fall back to 'en'
|
||||
/// This will contain Custom locales added by mods
|
||||
/// </summary>
|
||||
/// <returns> Dictionary of locales for desired language - en/fr/cn </returns>
|
||||
public Dictionary<string, string> GetLocaleDb(string? language = null)
|
||||
@@ -29,23 +26,21 @@ public class LocaleService(
|
||||
: language;
|
||||
|
||||
// if it can't get locales for language provided, default to en
|
||||
if (TryGetLocaleDbWithCustomLocales(languageToUse, out var localeToReturn) ||
|
||||
TryGetLocaleDbWithCustomLocales("en", out localeToReturn))
|
||||
if (TryGetLocaleDb(languageToUse, out var localeToReturn) || TryGetLocaleDb("en", out localeToReturn))
|
||||
{
|
||||
// TODO: need to see if this needs to be cloned
|
||||
return RemovePraporTestMessage(localeToReturn);
|
||||
return localeToReturn;
|
||||
}
|
||||
|
||||
throw new Exception($"unable to get locales from either {languageToUse} or en");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the locale database for the specified language key, including custom locales if available.
|
||||
/// Attempts to retrieve the locale database for the specified language key
|
||||
/// </summary>
|
||||
/// <param name="languageKey">The language key for which the locale database should be retrieved.</param>
|
||||
/// <param name="localeToReturn">The resulting locale database as a dictionary, or null if the operation fails.</param>
|
||||
/// <returns>True if the locale database was successfully retrieved, otherwise false.</returns>
|
||||
protected bool TryGetLocaleDbWithCustomLocales(string languageKey, out Dictionary<string, string>? localeToReturn)
|
||||
protected bool TryGetLocaleDb(string languageKey, out Dictionary<string, string>? localeToReturn)
|
||||
{
|
||||
localeToReturn = null;
|
||||
if (!_databaseServer.GetTables().Locales.Global.TryGetValue(languageKey, out var keyedLocales))
|
||||
@@ -55,42 +50,9 @@ public class LocaleService(
|
||||
|
||||
localeToReturn = keyedLocales.Value;
|
||||
|
||||
if (customClientLocales.TryGetValue(languageKey, out var customClientLocale))
|
||||
{
|
||||
// there were custom locales for this language
|
||||
localeToReturn = CombineDbWithCustomLocales(localeToReturn, customClientLocale);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Combines the provided database locales with custom locales, ensuring that all entries are merged into a single dictionary.
|
||||
/// Custom locale entries will overwrite existing keys from the database locales if conflicts occur.
|
||||
/// </summary>
|
||||
/// <param name="dbLocales">The dictionary containing locale entries from the database.</param>
|
||||
/// <param name="customLocales">The dictionary containing custom locale entries to be merged.</param>
|
||||
/// <returns>A dictionary representing the merged result of database and custom locales.</returns>
|
||||
protected Dictionary<string, string> CombineDbWithCustomLocales(Dictionary<string, string> dbLocales, Dictionary<string, string> customLocales)
|
||||
{
|
||||
try
|
||||
{
|
||||
return dbLocales
|
||||
.Concat(customLocales)
|
||||
.GroupBy(kvp => kvp.Key)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group => group.Last().Value
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game locale key from the locale.json file,
|
||||
/// if value is 'system' get system-configured locale
|
||||
@@ -239,33 +201,6 @@ public class LocaleService(
|
||||
return GetLocaleDb().Keys.Where(x => x.StartsWith(partialKey)).ToList();
|
||||
}
|
||||
|
||||
public void AddCustomClientLocale(string locale, string localeKey, string localeValue)
|
||||
{
|
||||
AddToDictionary(locale, localeKey, localeValue, customClientLocales);
|
||||
}
|
||||
|
||||
public void RemoveCustomClientLocale(string locale, string localeKey)
|
||||
{
|
||||
customClientLocales.Remove(localeKey);
|
||||
}
|
||||
|
||||
private void AddToDictionary(string locale, string localeKey, string localeValue,
|
||||
Dictionary<string, Dictionary<string, string>> dictionaryToAddTo)
|
||||
{
|
||||
dictionaryToAddTo.TryAdd(locale, new Dictionary<string, string>());
|
||||
if (!dictionaryToAddTo.TryGetValue(locale, out var localeDictToAddTo))
|
||||
{
|
||||
_logger.Error($"Unable to get custom locale dictionary keyed by: {locale}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!localeDictToAddTo.TryAdd(localeKey, localeValue))
|
||||
{
|
||||
localeDictToAddTo[localeKey] = localeValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blank out the "test" mail message from prapor
|
||||
/// </summary>
|
||||
|
||||
@@ -243,9 +243,17 @@ public class CustomItemService(
|
||||
|
||||
newLocaleDetails ??= localeDetails[localeDetails.Keys.FirstOrDefault()];
|
||||
|
||||
localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} Name", newLocaleDetails.Name);
|
||||
localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} ShortName", newLocaleDetails.ShortName);
|
||||
localeService.AddCustomClientLocale(shortNameKey.Key, $"{newItemId} Description", newLocaleDetails.Description);
|
||||
if (databaseService.GetLocales().Global.TryGetValue(shortNameKey.Key, out var lazyLoad))
|
||||
{
|
||||
lazyLoad.AddTransformer(localeData =>
|
||||
{
|
||||
localeData.Add($"{newItemId} Name", newLocaleDetails.Name);
|
||||
localeData.Add($"{newItemId} ShortName", newLocaleDetails.ShortName);
|
||||
localeData.Add($"{newItemId} Description", newLocaleDetails.Description);
|
||||
|
||||
return localeData;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,8 @@ public class PostDbLoadService(
|
||||
|
||||
RemoveNewBeginningRequirementFromPrestige();
|
||||
|
||||
RemovePraporTestMessage();
|
||||
|
||||
ValidateQuestAssortUnlocksExist();
|
||||
|
||||
if (_seasonalEventService.IsAutomaticEventDetectionEnabled())
|
||||
@@ -191,6 +193,19 @@ public class PostDbLoadService(
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePraporTestMessage()
|
||||
{
|
||||
foreach((var locale, var lazyLoad) in _databaseService.GetLocales().Global)
|
||||
{
|
||||
lazyLoad.AddTransformer(lazyloadedData =>
|
||||
{
|
||||
lazyloadedData["61687e2c3e526901fa76baf9"] = "";
|
||||
|
||||
return lazyloadedData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void CloneExistingCraftsAndAddNew()
|
||||
{
|
||||
var hideoutCraftDb = _databaseService.GetHideout().Production;
|
||||
@@ -264,34 +279,38 @@ public class PostDbLoadService(
|
||||
continue;
|
||||
}
|
||||
|
||||
var mapLooseLoot = _databaseService.GetLocation(mapId).LooseLoot.Value;
|
||||
if (mapLooseLoot is null)
|
||||
_databaseService.GetLocation(mapId).LooseLoot.AddTransformer(looselootData =>
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var positionToAdd in positionsToAdd)
|
||||
{
|
||||
// Exists already, add new items to existing positions pool
|
||||
var existingLootPosition = mapLooseLoot.Spawnpoints.FirstOrDefault(x =>
|
||||
x.Template.Id == positionToAdd.Template.Id
|
||||
);
|
||||
|
||||
if (existingLootPosition is not null)
|
||||
if (looselootData is null)
|
||||
{
|
||||
existingLootPosition.Template.Items.AddRange(positionToAdd.Template.Items);
|
||||
existingLootPosition.ItemDistribution.AddRange(positionToAdd.ItemDistribution);
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
return looselootData;
|
||||
}
|
||||
|
||||
// New position, add entire object
|
||||
mapLooseLoot.Spawnpoints.Add(positionToAdd);
|
||||
}
|
||||
foreach (var positionToAdd in positionsToAdd)
|
||||
{
|
||||
// Exists already, add new items to existing positions pool
|
||||
var existingLootPosition = looselootData.Spawnpoints.FirstOrDefault(x =>
|
||||
x.Template.Id == positionToAdd.Template.Id
|
||||
);
|
||||
|
||||
if (existingLootPosition is not null)
|
||||
{
|
||||
existingLootPosition.Template.Items.AddRange(positionToAdd.Template.Items);
|
||||
existingLootPosition.ItemDistribution.AddRange(positionToAdd.ItemDistribution);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// New position, add entire object
|
||||
looselootData.Spawnpoints.Add(positionToAdd);
|
||||
}
|
||||
|
||||
return looselootData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,35 +415,39 @@ public class PostDbLoadService(
|
||||
|
||||
foreach (var (mapId, mapAdjustments) in _lootConfig.LooseLootSpawnPointAdjustments)
|
||||
{
|
||||
var mapLooseLootData = _databaseService.GetLocation(mapId).LooseLoot.Value;
|
||||
if (mapLooseLootData is null)
|
||||
_databaseService.GetLocation(mapId).LooseLoot.AddTransformer(looselootData =>
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (lootKey, newChanceValue) in mapAdjustments)
|
||||
{
|
||||
var lootPostionToAdjust = mapLooseLootData.Spawnpoints.FirstOrDefault(spawnPoint =>
|
||||
spawnPoint.Template.Id == lootKey
|
||||
);
|
||||
if (lootPostionToAdjust is null)
|
||||
if (looselootData is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_adjust_loot_position_on_map",
|
||||
new { lootKey, mapId }
|
||||
)
|
||||
_localisationService.GetText("location-map_has_no_loose_loot_data", mapId)
|
||||
);
|
||||
|
||||
continue;
|
||||
return looselootData;
|
||||
}
|
||||
|
||||
lootPostionToAdjust.Probability = newChanceValue;
|
||||
}
|
||||
foreach (var (lootKey, newChanceValue) in mapAdjustments)
|
||||
{
|
||||
var lootPostionToAdjust = looselootData.Spawnpoints.FirstOrDefault(spawnPoint =>
|
||||
spawnPoint.Template.Id == lootKey
|
||||
);
|
||||
if (lootPostionToAdjust is null)
|
||||
{
|
||||
_logger.Warning(
|
||||
_localisationService.GetText(
|
||||
"location-unable_to_adjust_loot_position_on_map",
|
||||
new { lootKey, mapId }
|
||||
)
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
lootPostionToAdjust.Probability = newChanceValue;
|
||||
}
|
||||
|
||||
return looselootData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,11 +541,11 @@ public class PostDbLoadService(
|
||||
}
|
||||
|
||||
foreach (var area in _databaseService.GetHideout().Areas)
|
||||
foreach (var (_, stage) in area.Stages)
|
||||
// Only adjust crafts ABOVE the override
|
||||
{
|
||||
stage.ConstructionTime = Math.Min(stage.ConstructionTime.Value, overrideSeconds);
|
||||
}
|
||||
foreach (var (_, stage) in area.Stages)
|
||||
// Only adjust crafts ABOVE the override
|
||||
{
|
||||
stage.ConstructionTime = Math.Min(stage.ConstructionTime.Value, overrideSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UnlockHideoutLootCrateCrafts()
|
||||
|
||||
@@ -1030,8 +1030,16 @@ public class SeasonalEventService(
|
||||
|
||||
protected void RenameBitcoin()
|
||||
{
|
||||
_localeService.AddCustomClientLocale("en", $"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name", "Physical SPT Coin");
|
||||
_localeService.AddCustomClientLocale("en", $"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName", "0.2SPT");
|
||||
if(_databaseService.GetLocales().Global.TryGetValue("en", out var lazyLoad))
|
||||
{
|
||||
lazyLoad.AddTransformer(localeData =>
|
||||
{
|
||||
localeData[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} Name"] = "Physical SPT Coin";
|
||||
localeData[$"{ItemTpl.BARTER_PHYSICAL_BITCOIN} ShortName"] = "0.2SPT";
|
||||
|
||||
return localeData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
public class LazyLoad<T>(Func<T> deserialize)
|
||||
{
|
||||
private readonly List<Func<T?, T?>> _lazyLoadTransformers = [];
|
||||
private readonly ReaderWriterLockSlim _lazyLoadTransformersLock = new();
|
||||
private static readonly TimeSpan _autoCleanerTimeout = TimeSpan.FromSeconds(30);
|
||||
private bool _isLoaded;
|
||||
private T? _result;
|
||||
@@ -9,10 +11,23 @@ public class LazyLoad<T>(Func<T> deserialize)
|
||||
private Timer? autoCleanerTimeout;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="OnLazyLoad" /> can be subscribed to for mods to modify. It is fired right after lazy loading is complete
|
||||
/// and any modification passed to <see cref="OnLazyLoadEventArgs.Value" /> will stay for the duration of this <see cref="LazyLoad{T}"/>'s lifecycle
|
||||
/// Adds a transformer to modify the value during lazy loading. Transformers execute
|
||||
/// in registration order and the final result is cached until auto-cleanup.
|
||||
/// </summary>
|
||||
public event EventHandler<OnLazyLoadEventArgs<T>>? OnLazyLoad;
|
||||
/// <param name="transformer">Function that transforms the value</param>
|
||||
public void AddTransformer(Func<T?, T?> transformer)
|
||||
{
|
||||
_lazyLoadTransformersLock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
_lazyLoadTransformers.Add(transformer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lazyLoadTransformersLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public T? Value
|
||||
{
|
||||
@@ -23,12 +38,21 @@ public class LazyLoad<T>(Func<T> deserialize)
|
||||
_result = deserialize();
|
||||
_isLoaded = true;
|
||||
|
||||
OnLazyLoadEventArgs<T> args = new(_result);
|
||||
OnLazyLoad?.Invoke(this, args);
|
||||
|
||||
if (args.Value != null)
|
||||
_lazyLoadTransformersLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
_result = args.Value;
|
||||
foreach (var transform in _lazyLoadTransformers)
|
||||
{
|
||||
_result = transform(_result);
|
||||
}
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lazyLoadTransformersLock.ExitReadLock();
|
||||
}
|
||||
|
||||
autoCleanerTimeout = new Timer(
|
||||
@@ -43,15 +67,10 @@ public class LazyLoad<T>(Func<T> deserialize)
|
||||
_autoCleanerTimeout,
|
||||
Timeout.InfiniteTimeSpan
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
autoCleanerTimeout?.Change(_autoCleanerTimeout, Timeout.InfiniteTimeSpan);
|
||||
return _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OnLazyLoadEventArgs<T>(T value) : EventArgs
|
||||
{
|
||||
public T Value { get; set; } = value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user