Fix more warnings... (#543)

* Fix more warnings...

* Fix mistake
This commit is contained in:
Cj
2025-08-12 11:28:54 -04:00
committed by GitHub
parent 4a081a7ac3
commit e54dcbd4d1
16 changed files with 435 additions and 164 deletions
@@ -187,7 +187,7 @@ public class HideoutController(
{
foreach (var bonus in bonuses)
{
hideoutHelper.ApplyPlayerUpgradesBonuses(pmcData, bonus);
hideoutHelper.ApplyPlayerUpgradesBonus(pmcData, bonus);
}
}
@@ -937,7 +937,7 @@ public class RagfairController(
}
return paymentHelper.IsMoneyTpl(requirement.Template)
? handbookHelper.InRUB(requirement.Count.Value, requirement.Template)
? handbookHelper.InRoubles(requirement.Count.Value, requirement.Template)
: itemHelper.GetDynamicItemPrice(requirement.Template).Value * requirement.Count.Value;
});
}
@@ -0,0 +1,10 @@
namespace SPTarkov.Server.Core.Exceptions.Helpers;
public class HandbookHelperException : Exception
{
public HandbookHelperException(string message)
: base(message) { }
public HandbookHelperException(string message, Exception innerException)
: base(message, innerException) { }
}
@@ -0,0 +1,10 @@
namespace SPTarkov.Server.Core.Exceptions.Helpers;
public class HealthHelperException : Exception
{
public HealthHelperException(string message)
: base(message) { }
public HealthHelperException(string message, Exception innerException)
: base(message, innerException) { }
}
@@ -0,0 +1,10 @@
namespace SPTarkov.Server.Core.Exceptions.Helpers;
public class HideoutHelperException : Exception
{
public HideoutHelperException(string message)
: base(message) { }
public HideoutHelperException(string message, Exception innerException)
: base(message, innerException) { }
}
@@ -1,4 +1,4 @@
namespace SPTarkov.Server.Core.Exceptions.Items;
namespace SPTarkov.Server.Core.Exceptions.Helpers;
public class ItemHelperException : Exception
{
@@ -228,7 +228,7 @@ public class RagfairOfferGenerator(
return currencyCount;
}
return handbookHelper.InRUB(currencyCount, currencyType);
return handbookHelper.InRoubles(currencyCount, currencyType);
}
/// <summary>
@@ -634,7 +634,7 @@ public class RepeatableQuestRewardGenerator(
var currency = traderId == Traders.PEACEKEEPER || traderId == Traders.FENCE ? Money.EUROS : Money.ROUBLES;
// Convert reward amount to Euros if necessary
var rewardAmountToGivePlayer = currency == Money.EUROS ? handbookHelper.FromRUB(rewardRoubles, Money.EUROS) : rewardRoubles;
var rewardAmountToGivePlayer = currency == Money.EUROS ? handbookHelper.FromRoubles(rewardRoubles, Money.EUROS) : rewardRoubles;
// Get chosen currency + amount and return
return GenerateItemReward(currency, rewardAmountToGivePlayer, rewardIndex, false);
@@ -1,8 +1,11 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Exceptions.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
using SPTarkov.Server.Core.Models.Enums;
using SPTarkov.Server.Core.Models.Spt.Config;
using SPTarkov.Server.Core.Models.Spt.Logging;
using SPTarkov.Server.Core.Models.Utils;
using SPTarkov.Server.Core.Servers;
using SPTarkov.Server.Core.Services;
using SPTarkov.Server.Core.Utils.Cloners;
@@ -10,7 +13,7 @@ using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Core.Helpers;
[Injectable(InjectionType.Singleton)]
public class HandbookHelper(DatabaseService databaseService, ConfigServer configServer, ICloner cloner)
public class HandbookHelper(ISptLogger<HandbookHelper> logger, DatabaseService databaseService, ConfigServer configServer, ICloner cloner)
{
private LookupCollection? _handbookPriceCache;
protected virtual LookupCollection HandbookPriceCache
@@ -18,7 +21,7 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
get { return _handbookPriceCache ??= HydrateHandbookCache(); }
}
protected readonly ItemConfig _itemConfig = configServer.GetConfig<ItemConfig>();
protected readonly ItemConfig ItemConfig = configServer.GetConfig<ItemConfig>();
/// <summary>
/// Create an in-memory cache of all items with associated handbook price in handbookPriceCache class
@@ -28,7 +31,7 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
var result = new LookupCollection();
var handbook = databaseService.GetHandbook();
// Add handbook overrides found in items.json config into db
foreach (var (key, priceOverride) in _itemConfig.HandbookPriceOverride)
foreach (var (key, priceOverride) in ItemConfig.HandbookPriceOverride)
{
var itemToUpdate = handbook.Items.FirstOrDefault(item => item.Id == key);
if (itemToUpdate is null)
@@ -44,11 +47,11 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
itemToUpdate = handbook.Items.FirstOrDefault(item => item.Id == key);
}
itemToUpdate.Price = priceOverride.Price;
itemToUpdate!.Price = priceOverride.Price;
itemToUpdate.ParentId = priceOverride.ParentId;
}
var handbookDbClone = cloner.Clone(handbook);
var handbookDbClone = cloner.Clone(handbook)!;
foreach (var handbookItem in handbookDbClone.Items)
{
result.Items.ById.TryAdd(handbookItem.Id, handbookItem.Price ?? 0);
@@ -57,13 +60,23 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
result.Items.ByParent.TryAdd(handbookItem.ParentId, []);
}
result.Items.ByParent.TryGetValue(handbookItem.ParentId, out var itemIds);
if (!result.Items.ByParent.TryGetValue(handbookItem.ParentId, out var itemIds))
{
throw new HandbookHelperException(
$"Cannot add item id `{handbookItem.Id}` to parent id `{handbookItem.ParentId}`. Parent does not exist."
);
}
itemIds.Add(handbookItem.Id);
}
foreach (var handbookCategory in handbookDbClone.Categories)
{
result.Categories.ById.TryAdd(handbookCategory.Id, handbookCategory.ParentId);
if (!result.Categories.ById.TryAdd(handbookCategory.Id, handbookCategory.ParentId))
{
throw new HandbookHelperException($"Unable to add `{handbookCategory.Id}`. Key already exists.");
}
if (handbookCategory.ParentId is not null)
{
if (!result.Categories.ByParent.TryGetValue(handbookCategory.ParentId.Value, out _))
@@ -71,7 +84,12 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
result.Categories.ByParent.TryAdd(handbookCategory.ParentId.Value, []);
}
result.Categories.ByParent.TryGetValue(handbookCategory.ParentId.Value, out var itemIds);
if (!result.Categories.ByParent.TryGetValue(handbookCategory.ParentId.Value, out var itemIds))
{
throw new HandbookHelperException(
$"Cannot add item id `{handbookCategory.Id}` to parent id `{handbookCategory.ParentId.Value}`. Parent does not exist."
);
}
itemIds.Add(handbookCategory.Id);
}
@@ -123,7 +141,7 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
/// <returns></returns>
public double GetTemplatePriceForItems(IEnumerable<Item> items)
{
return items.Where(item => item?.Template != null).Sum(item => GetTemplatePrice(item.Template));
return items.Sum(item => GetTemplatePrice(item.Template));
}
/// <summary>
@@ -133,9 +151,17 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
/// <returns>string array</returns>
public List<MongoId> TemplatesWithParent(MongoId parentId)
{
HandbookPriceCache.Items.ByParent.TryGetValue(parentId, out var template);
if (HandbookPriceCache.Items.ByParent.TryGetValue(parentId, out var templates))
{
return templates;
}
return template ?? [];
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Template ids with parent id `{parentId}` not found when trying to get templates by parent");
}
return [];
}
/// <summary>
@@ -153,10 +179,19 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
/// </summary>
/// <param name="categoryParent"></param>
/// <returns>string array</returns>
public List<string> ChildrenCategories(string categoryParent)
public List<string> ChildrenCategories(MongoId categoryParent)
{
HandbookPriceCache.Categories.ByParent.TryGetValue(categoryParent, out var category);
return category ?? [];
if (HandbookPriceCache.Categories.ByParent.TryGetValue(categoryParent, out var childrenCategories))
{
return childrenCategories;
}
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Children categories with parent id `{categoryParent}` not found when trying to get children categories");
}
return [];
}
/// <summary>
@@ -165,7 +200,7 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
/// <param name="nonRoubleCurrencyCount">Currency count to convert</param>
/// <param name="currencyTypeFrom">What current currency is</param>
/// <returns>Count in roubles</returns>
public double InRUB(double nonRoubleCurrencyCount, MongoId currencyTypeFrom)
public double InRoubles(double nonRoubleCurrencyCount, MongoId currencyTypeFrom)
{
return currencyTypeFrom == Money.ROUBLES
? nonRoubleCurrencyCount
@@ -178,7 +213,7 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
/// <param name="roubleCurrencyCount">roubles to convert</param>
/// <param name="currencyTypeTo">Currency to convert roubles into</param>
/// <returns>currency count in desired type</returns>
public double FromRUB(double roubleCurrencyCount, MongoId currencyTypeTo)
public double FromRoubles(double roubleCurrencyCount, MongoId currencyTypeTo)
{
if (currencyTypeTo == Money.ROUBLES)
{
@@ -190,7 +225,7 @@ public class HandbookHelper(DatabaseService databaseService, ConfigServer config
return price > 0 ? Math.Max(1, Math.Round(roubleCurrencyCount / price)) : 0;
}
public HandbookCategory GetCategoryById(MongoId handbookId)
public HandbookCategory? GetCategoryById(MongoId handbookId)
{
return databaseService.GetHandbook().Categories.FirstOrDefault(category => category.Id == handbookId);
}
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Exceptions.Helpers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Eft.Common.Tables;
@@ -10,34 +11,44 @@ using BodyPartHealth = SPTarkov.Server.Core.Models.Eft.Common.Tables.BodyPartHea
namespace SPTarkov.Server.Core.Helpers;
[Injectable]
public class HealthHelper(TimeUtil timeUtil, SaveServer saveServer, ProfileHelper profileHelper, ConfigServer configServer)
public class HealthHelper(TimeUtil timeUtil, ConfigServer configServer)
{
protected readonly HealthConfig _healthConfig = configServer.GetConfig<HealthConfig>();
protected readonly HealthConfig HealthConfig = configServer.GetConfig<HealthConfig>();
protected readonly HashSet<string> EffectsToSkip = ["Dehydration", "Exhaustion"];
/// <summary>
/// Update player profile vitality values with changes from client request object
/// </summary>
/// <param name="sessionID">Session id</param>
/// <param name="sessionId">Session id</param>
/// <param name="pmcProfileToUpdate">Player profile to apply changes to</param>
/// <param name="healthChanges">Changes to apply </param>
/// <param name="isDead">OPTIONAL - Is player dead</param>
public void ApplyHealthChangesToProfile(MongoId sessionID, PmcData pmcProfileToUpdate, BotBaseHealth healthChanges, bool isDead = false)
public void ApplyHealthChangesToProfile(MongoId sessionId, PmcData pmcProfileToUpdate, BotBaseHealth healthChanges)
{
var fullProfile = saveServer.GetProfile(sessionID);
var profileEdition = fullProfile.ProfileInfo.Edition;
var profileSide = fullProfile.CharacterData.PmcData.Info.Side;
/* TODO: Not used here, need to check node or a live profile, commented out for now to avoid the potential alloc - Cj
var fullProfile = saveServer.GetProfile(sessionId);
var profileEdition = fullProfile.ProfileInfo?.Edition;
var profileSide = fullProfile.CharacterData?.PmcData?.Info?.Side;
// Get matching 'side' e.g. USEC
var matchingSide = profileHelper.GetProfileTemplateForSide(profileEdition, profileSide);
var defaultTemperature = matchingSide?.Character?.Health?.Temperature ?? new CurrentMinMax { Current = 36.6 };
*/
if (healthChanges.BodyParts is null)
{
throw new HealthHelperException("healthChanges.BodyParts is null when trying to apply health changes");
}
// Alter saved profiles Health with values from post-raid client data
ModifyProfileHealthProperties(pmcProfileToUpdate, healthChanges.BodyParts, ["Dehydration", "Exhaustion"]);
ModifyProfileHealthProperties(pmcProfileToUpdate, healthChanges.BodyParts, EffectsToSkip);
// Adjust hydration/energy/temperature
AdjustProfileHydrationEnergyTemperature(pmcProfileToUpdate, healthChanges);
if (pmcProfileToUpdate.Health is null)
{
throw new HealthHelperException("pmcProfileToUpdate.Health is null when trying to apply health changes");
}
// Update last edited timestamp
pmcProfileToUpdate.Health.UpdateTime = timeUtil.GetTimeStamp();
}
@@ -56,27 +67,36 @@ public class HealthHelper(TimeUtil timeUtil, SaveServer saveServer, ProfileHelpe
{
foreach (var (partName, partProperties) in bodyPartChanges)
{
if (!profileToAdjust.Health.BodyParts.TryGetValue(partName, out var matchingProfilePart))
// Pattern matching null and false because otherwise the compiler throws a fit because `matchingProfilePart`
// might not be initialized, very cool
if (profileToAdjust.Health?.BodyParts?.TryGetValue(partName, out var matchingProfilePart) is null or false)
{
continue;
}
if (_healthConfig.Save.Health)
if (partProperties.Health is null || matchingProfilePart.Health is null)
{
throw new HealthHelperException(
"partProperties.Health or matchingBodyPart.Health is null when trying to modify profile health properties"
);
}
if (HealthConfig.Save.Health)
{
// Apply hp changes to profile
matchingProfilePart.Health.Current =
partProperties.Health.Current == 0
? partProperties.Health.Maximum * _healthConfig.HealthMultipliers.Blacked
? partProperties.Health.Maximum * HealthConfig.HealthMultipliers.Blacked
: partProperties.Health.Current;
matchingProfilePart.Health.Maximum = partProperties.Health.Maximum;
}
// Process each effect for each part
foreach (var (key, effectDetails) in partProperties.Effects)
foreach (var (key, effectDetails) in partProperties.Effects ?? [])
{
// Null guard
matchingProfilePart.Effects ??= new Dictionary<string, BodyPartEffectProperties>();
matchingProfilePart.Effects ??= new Dictionary<string, BodyPartEffectProperties?>();
// Effect already exists on limb in server profile, skip
if (matchingProfilePart.Effects.ContainsKey(key))
@@ -96,7 +116,7 @@ public class HealthHelper(TimeUtil timeUtil, SaveServer saveServer, ProfileHelpe
continue;
}
var effectToAdd = new BodyPartEffectProperties { Time = effectDetails.Time ?? -1 };
var effectToAdd = new BodyPartEffectProperties { Time = effectDetails?.Time ?? -1 };
// Add effect to server profile
if (matchingProfilePart.Effects.TryAdd(key, effectToAdd))
{
@@ -1,4 +1,5 @@
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Exceptions.Helpers;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
@@ -27,32 +28,37 @@ public class HideoutHelper(
ProfileHelper profileHelper,
InventoryHelper inventoryHelper,
ItemHelper itemHelper,
NotificationSendHelper notificationSendHelper,
NotifierHelper notifierHelper,
ICloner cloner
ICloner cloner,
MathUtil mathUtil
)
{
public static readonly MongoId BitcoinProductionId = new("5d5c205bd582a50d042a3c0e");
public static readonly MongoId WaterCollectorId = new("5d5589c1f934db045e6c5492");
public const int MaxSkillPoint = 5000;
/// <summary>
/// Add production to profiles' Hideout.Production array
/// </summary>
/// <param name="pmcData">Profile to add production to</param>
/// <param name="productionRequest">Production request</param>
/// <param name="sessionID">Session id</param>
/// <param name="sessionId">Session id</param>
/// <returns>client response</returns>
public void RegisterProduction(PmcData pmcData, HideoutSingleProductionStartRequestData productionRequest, MongoId sessionID)
public void RegisterProduction(PmcData pmcData, HideoutSingleProductionStartRequestData productionRequest, MongoId sessionId)
{
var recipe = databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault(production => production.Id == productionRequest.RecipeId);
.Production.Recipes?.FirstOrDefault(production => production.Id == productionRequest.RecipeId);
if (recipe is null)
{
logger.Error(serverLocalisationService.GetText("hideout-missing_recipe_in_db", productionRequest.RecipeId));
httpResponseUtil.AppendErrorToOutput(eventOutputHolder.GetOutput(sessionID));
httpResponseUtil.AppendErrorToOutput(eventOutputHolder.GetOutput(sessionId));
return;
}
if (pmcData.Hideout is null)
{
throw new HideoutHelperException($"Hideout is null when trying to register production for recipe id `{recipe.Id}`");
}
// @Important: Here we need to be very exact:
@@ -71,7 +77,7 @@ public class HideoutHelper(
foreach (var tool in productionRequest.Tools)
{
var toolItem = cloner.Clone(pmcData.Inventory.Items.FirstOrDefault(x => x.Id == tool.Id));
var toolItem = cloner.Clone(pmcData.Inventory?.Items?.FirstOrDefault(x => x.Id == tool.Id));
if (toolItem is null)
{
logger.Warning($"Unable to find tool item: {tool.Id}");
@@ -82,7 +88,7 @@ public class HideoutHelper(
// Make sure we only return as many as we took
toolItem.AddUpd();
toolItem.Upd.StackObjectsCount = tool.Count;
toolItem.Upd!.StackObjectsCount = tool.Count;
production.SptRequiredTools.Add(
new Item
@@ -103,18 +109,32 @@ public class HideoutHelper(
/// </summary>
/// <param name="pmcData">Profile to add production to</param>
/// <param name="productionRequest">Production request</param>
/// <param name="sessionID">Session id</param>
/// <param name="sessionId">Session id</param>
/// <returns>client response</returns>
public void RegisterProduction(PmcData pmcData, HideoutContinuousProductionStartRequestData productionRequest, MongoId sessionID)
public void RegisterProduction(PmcData pmcData, HideoutContinuousProductionStartRequestData productionRequest, MongoId sessionId)
{
if (!productionRequest.RecipeId.HasValue)
{
logger.Error("RecipeId sent from client is null, skipping continuous production registration");
return;
}
var recipe = databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault(production => production.Id == productionRequest.RecipeId);
.Production.Recipes?.FirstOrDefault(production => production.Id == productionRequest.RecipeId);
if (recipe is null)
{
logger.Error(serverLocalisationService.GetText("hideout-missing_recipe_in_db", productionRequest.RecipeId));
httpResponseUtil.AppendErrorToOutput(eventOutputHolder.GetOutput(sessionID));
httpResponseUtil.AppendErrorToOutput(eventOutputHolder.GetOutput(sessionId));
return;
}
if (pmcData.Hideout is null)
{
throw new HideoutHelperException(
$"Hideout is null when trying to register production for recipe id `{productionRequest.RecipeId.Value}`"
);
}
// @Important: Here we need to be very exact:
@@ -156,23 +176,31 @@ public class HideoutHelper(
/// </summary>
/// <param name="profileData">Profile to add bonus to</param>
/// <param name="bonus">Bonus to add to profile</param>
public void ApplyPlayerUpgradesBonuses(PmcData profileData, Bonus bonus)
public void ApplyPlayerUpgradesBonus(PmcData profileData, Bonus bonus)
{
if (!bonus.TemplateId.HasValue)
{
logger.Error("Bonus template id is null");
return;
}
// Handle additional changes some bonuses need before being added
switch (bonus.Type)
{
case BonusType.StashSize:
{
// Find stash item and adjust tpl to new tpl from bonus
var stashItem = profileData.Inventory.Items.FirstOrDefault(x => x.Id == profileData.Inventory.Stash);
var stashItem = profileData.Inventory?.Items?.FirstOrDefault(x => x.Id == profileData.Inventory.Stash);
if (stashItem is null)
{
logger.Warning(
logger.Error(
serverLocalisationService.GetText(
"hideout-unable_to_apply_stashsize_bonus_no_stash_found",
profileData.Inventory.Stash
profileData.Inventory?.Stash
)
);
return;
}
stashItem.Template = bonus.TemplateId.Value;
@@ -180,6 +208,11 @@ public class HideoutHelper(
break;
}
case BonusType.MaximumEnergyReserve:
if (profileData.Health?.Energy is null)
{
throw new HideoutHelperException("Profile Energy is null when trying to apply MaximumEnergyReserve");
}
// Amend max energy in profile
profileData.Health.Energy.Maximum += bonus.Value;
break;
@@ -189,6 +222,17 @@ public class HideoutHelper(
bonus.IsProduction = null;
bonus.IsVisible = null;
break;
default:
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Unhandled bonus type `{bonus.Type}` when trying to apply player upgrade bonus");
}
break;
}
if (profileData.Bonuses is null)
{
throw new HideoutHelperException($"Profile bonuses are null when trying to add: {bonus.Type}");
}
// Add bonus to player bonuses array in profile
@@ -207,9 +251,14 @@ public class HideoutHelper(
/// <param name="sessionID">Session id</param>
public void UpdatePlayerHideout(MongoId sessionID)
{
var pmcData = profileHelper.GetPmcProfile(sessionID);
var pmcData = profileHelper.GetPmcProfile(sessionID)!;
var hideoutProperties = GetHideoutProperties(pmcData);
if (pmcData.Hideout is null)
{
throw new HideoutHelperException("Hideout is null when trying to update player hideout");
}
pmcData.Hideout.SptUpdateLastRunTimestamp ??= timeUtil.GetTimeStamp();
UpdateAreasWithResources(sessionID, pmcData, hideoutProperties);
@@ -224,16 +273,15 @@ public class HideoutHelper(
/// <returns>Hideout-related values</returns>
protected HideoutProperties GetHideoutProperties(PmcData pmcData)
{
var bitcoinFarm = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.BitcoinFarm);
var bitcoinCount = (bitcoinFarm?.Slots).Count(slot => slot.Items is not null); // Get slots with an item property
var bitcoinFarm = pmcData.Hideout?.Areas?.FirstOrDefault(area => area.Type == HideoutAreas.BitcoinFarm);
var bitcoinCount = bitcoinFarm?.Slots?.Count(slot => slot.Items is not null); // Get slots with an item property
var waterCollector = pmcData.Hideout?.Areas?.FirstOrDefault(area => area.Type == HideoutAreas.WaterCollector);
var hideoutProperties = new HideoutProperties
{
BtcFarmGcs = bitcoinCount,
IsGeneratorOn = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.Generator)?.Active ?? false,
WaterCollectorHasFilter = DoesWaterCollectorHaveFilter(
pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.WaterCollector)
),
IsGeneratorOn = pmcData.Hideout?.Areas?.FirstOrDefault(area => area.Type == HideoutAreas.Generator)?.Active ?? false,
WaterCollectorHasFilter = DoesWaterCollectorHaveFilter(waterCollector),
};
return hideoutProperties;
@@ -244,13 +292,19 @@ public class HideoutHelper(
/// </summary>
/// <param name="waterCollector">Hideout area to check</param>
/// <returns></returns>
protected static bool DoesWaterCollectorHaveFilter(BotHideoutArea waterCollector)
protected bool DoesWaterCollectorHaveFilter(BotHideoutArea? waterCollector)
{
// Water collector not built
if (waterCollector is null)
{
return false;
}
// Can put filters in from L3
if (waterCollector.Level == 3)
// Has filter in at least one slot
{
return waterCollector.Slots.Any(slot => slot.Items is not null);
return waterCollector.Slots?.Any(slot => slot.Items is not null) ?? false;
}
// No Filter
@@ -267,20 +321,21 @@ public class HideoutHelper(
var recipes = databaseService.GetHideout().Production;
// Check each production and handle edge cases if necessary
foreach (var prodId in pmcData.Hideout?.Production)
foreach (var prodId in pmcData.Hideout?.Production ?? [])
{
if (pmcData.Hideout.Production.TryGetValue(prodId.Key, out var craft) && craft is null)
// Pattern matching null or false to shut the compiler up
if (pmcData.Hideout?.Production?.TryGetValue(prodId.Key, out var craft) is null or false)
{
// Craft value is undefined, get rid of it (could be from cancelling craft that needs cleaning up)
pmcData.Hideout.Production.Remove(prodId.Key);
pmcData.Hideout?.Production?.Remove(prodId.Key);
continue;
}
if (craft.Progress == null)
if (craft?.Progress is null)
{
logger.Warning(serverLocalisationService.GetText("hideout-craft_has_undefined_progress_value_defaulting", prodId));
craft.Progress = 0;
craft!.Progress = 0;
}
// Skip processing (Don't skip continuous crafts like bitcoin farm or cultist circle)
@@ -344,12 +399,24 @@ public class HideoutHelper(
/// <param name="pmcData">profile to update</param>
/// <param name="productionId">id of water collection production to update</param>
/// <param name="hideoutProperties">Hideout properties</param>
protected void UpdateWaterCollectorProductionTimer(PmcData pmcData, string productionId, HideoutProperties hideoutProperties)
protected void UpdateWaterCollectorProductionTimer(PmcData pmcData, MongoId productionId, HideoutProperties hideoutProperties)
{
if (pmcData.Hideout?.Production is null)
{
throw new HideoutHelperException("Hideout productions are null when trying to update water collector production timer");
}
if (!pmcData.Hideout.Production.TryGetValue(productionId, out var production) || production is null)
{
logger.Error($"production id: {productionId.ToString()} not found in hideout productions, what are we trying to update?");
return;
}
var timeElapsed = GetTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.IsGeneratorOn);
if (hideoutProperties.WaterCollectorHasFilter)
{
pmcData.Hideout.Production[productionId].Progress += timeElapsed;
production.Progress += timeElapsed;
}
}
@@ -360,8 +427,13 @@ public class HideoutHelper(
/// <param name="prodId">Production id being crafted</param>
/// <param name="recipe">Recipe data being crafted</param>
/// <param name="hideoutProperties"></param>
protected void UpdateProductionProgress(PmcData pmcData, string prodId, HideoutProduction recipe, HideoutProperties hideoutProperties)
protected void UpdateProductionProgress(PmcData pmcData, MongoId prodId, HideoutProduction recipe, HideoutProperties hideoutProperties)
{
if (pmcData.Hideout?.Production is null)
{
throw new HideoutHelperException("Hideout productions are null when trying to update production progress");
}
// Production is complete, no need to do any calculations
if (DoesProgressMatchProductionTime(pmcData, prodId))
{
@@ -372,7 +444,11 @@ public class HideoutHelper(
var timeElapsed = GetTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.IsGeneratorOn, recipe);
// Increment progress by time passed
pmcData.Hideout.Production.TryGetValue(prodId, out var production);
if (!pmcData.Hideout.Production.TryGetValue(prodId, out var production) || production is null)
{
logger.Error($"production id: {prodId.ToString()} not found in hideout productions, what are we trying to update?");
return;
}
// Some items NEED power to craft (e.g. DSP)
if (production.needFuelForAllProductionTime.GetValueOrDefault() && hideoutProperties.IsGeneratorOn)
@@ -393,9 +469,18 @@ public class HideoutHelper(
}
}
protected void UpdateCultistCircleCraftProgress(PmcData pmcData, string prodId)
protected void UpdateCultistCircleCraftProgress(PmcData pmcData, MongoId prodId)
{
pmcData.Hideout.Production.TryGetValue(prodId, out var production);
if (pmcData.Hideout?.Production is null)
{
throw new HideoutHelperException("Hideout productions are null when trying to update cultist progress");
}
if (!pmcData.Hideout.Production.TryGetValue(prodId, out var production) || production is null)
{
logger.Error($"Production id `{prodId.ToString()}` not found in profile, what are we trying to update?");
return;
}
// Check if we're already complete, skip
if ((production.AvailableForFinish ?? false) && !production.InProgress.GetValueOrDefault(false))
@@ -440,9 +525,29 @@ public class HideoutHelper(
/// <param name="pmcData">Player profile</param>
/// <param name="prodId">Production id</param>
/// <returns>progress matches productionTime from recipe</returns>
protected static bool DoesProgressMatchProductionTime(PmcData pmcData, string prodId)
protected bool DoesProgressMatchProductionTime(PmcData pmcData, MongoId prodId)
{
return pmcData.Hideout.Production[prodId].Progress == pmcData.Hideout.Production[prodId].ProductionTime;
if (pmcData.Hideout?.Production is null)
{
throw new HideoutHelperException("Hideout productions are null when trying to match production progress");
}
// Production doesn't exist or progress or production time is null
if (
!pmcData.Hideout.Production.TryGetValue(prodId, out var production)
|| production?.Progress is null
|| production.ProductionTime is null
)
{
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"ProductionId: {prodId} Trying to match progress to production time that does not exist.");
}
return false;
}
return production.Progress.Value.Approx(production.ProductionTime.Value);
}
/// <summary>
@@ -450,25 +555,39 @@ public class HideoutHelper(
/// </summary>
/// <param name="pmcData">Profile to update</param>
/// <param name="productionId">Id of scav case production to update</param>
protected void UpdateScavCaseProductionTimer(PmcData pmcData, string productionId)
protected void UpdateScavCaseProductionTimer(PmcData pmcData, MongoId productionId)
{
var timeElapsed =
timeUtil.GetTimeStamp()
- pmcData.Hideout.Production[productionId].StartTimestamp
- pmcData.Hideout.Production[productionId].Progress;
if (pmcData.Hideout?.Production is null)
{
throw new HideoutHelperException("Hideout productions are null when trying to update scav case production timer");
}
pmcData.Hideout.Production[productionId].Progress += timeElapsed;
if (!pmcData.Hideout.Production.TryGetValue(productionId, out var production) || production is null)
{
logger.Error($"Production id `{productionId.ToString()}` not found in profile, what are we trying to update?");
return;
}
var currentTime = timeUtil.GetTimeStamp();
var timeElapsed = currentTime - production.StartTimestamp - production.Progress;
production.Progress += timeElapsed;
}
/// <summary>
/// Iterate over hideout areas that use resources (fuel/filters etc) and update associated values
/// </summary>
/// <param name="sessionID">Session id</param>
/// <param name="sessionId">Session id</param>
/// <param name="pmcData">Profile to update areas of</param>
/// <param name="hideoutProperties">hideout properties</param>
protected void UpdateAreasWithResources(MongoId sessionID, PmcData pmcData, HideoutProperties hideoutProperties)
protected void UpdateAreasWithResources(MongoId sessionId, PmcData pmcData, HideoutProperties hideoutProperties)
{
foreach (var area in pmcData.Hideout.Areas)
if (pmcData.Hideout is null)
{
throw new HideoutHelperException("Hideout is null when trying update areas with resources");
}
foreach (var area in pmcData.Hideout.Areas ?? [])
{
switch (area.Type)
{
@@ -477,18 +596,21 @@ public class HideoutHelper(
{
UpdateFuel(area, pmcData, hideoutProperties.IsGeneratorOn);
}
break;
case HideoutAreas.WaterCollector:
UpdateWaterCollector(sessionID, pmcData, area, hideoutProperties);
UpdateWaterCollector(sessionId, pmcData, area, hideoutProperties);
break;
case HideoutAreas.AirFilteringUnit:
if (hideoutProperties.IsGeneratorOn)
{
UpdateAirFilters(area, pmcData, hideoutProperties.IsGeneratorOn);
}
break;
default:
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Unhandled area type {area.Type} when trying to update areas with resources");
}
break;
}
}
@@ -527,9 +649,10 @@ public class HideoutHelper(
fuelUsedSinceLastTick *= combinedBonus;
var hasFuelRemaining = false;
double pointsConsumed;
for (var i = 0; i < generatorArea.Slots.Count; i++)
for (var i = 0; i < generatorArea.Slots?.Count; i++)
{
double pointsConsumed;
var generatorSlot = generatorArea.Slots[i];
if (generatorSlot?.Items is null)
// No item in slot, skip
@@ -545,24 +668,25 @@ public class HideoutHelper(
}
var fuelRemaining = fuelItemInSlot.Upd?.Resource?.Value;
if (fuelRemaining == 0)
// No fuel left, skip
{
continue;
}
switch (fuelRemaining)
{
// No fuel left, skip
case 0:
continue;
// Undefined fuel, fresh fuel item and needs its max fuel amount looked up
if (fuelRemaining is null)
case null:
{
var fuelItemTemplate = itemHelper.GetItem(fuelItemInSlot.Template).Value;
pointsConsumed = fuelUsedSinceLastTick ?? 0;
fuelRemaining = fuelItemTemplate.Properties.MaxResource - fuelUsedSinceLastTick;
break;
}
else
{
default:
// Fuel exists already, deduct fuel from item remaining value
pointsConsumed = (double)((fuelItemInSlot.Upd.Resource.UnitsConsumed ?? 0) + fuelUsedSinceLastTick);
fuelRemaining -= fuelUsedSinceLastTick;
break;
}
// Round values to keep accuracy
@@ -584,7 +708,7 @@ public class HideoutHelper(
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Profile: {pmcData.Id} Generator has: {fuelRemaining} fuel left in slot {i + 1}");
logger.Debug($"Profile: {pmcData!.Id} Generator has: {fuelRemaining} fuel left in slot {i + 1}");
}
hasFuelRemaining = true;
@@ -598,7 +722,7 @@ public class HideoutHelper(
fuelUsedSinceLastTick = Math.Abs(fuelRemaining ?? 0);
if (logger.IsLogEnabled(LogLevel.Debug))
{
logger.Debug($"Profile: {pmcData.Id} Generator ran out of fuel");
logger.Debug($"Profile: {pmcData!.Id} Generator ran out of fuel");
}
}
@@ -624,8 +748,8 @@ public class HideoutHelper(
// Canister with purified water craft exists
if (
pmcData.Hideout.Production.TryGetValue(WaterCollectorId, out var purifiedWaterCraft)
&& purifiedWaterCraft.GetType() == typeof(Production)
pmcData.Hideout?.Production?.TryGetValue(WaterCollectorId, out var purifiedWaterCraft) is true
&& purifiedWaterCraft?.GetType() == typeof(Production)
)
{
// Update craft time to account for increases in players craft time skill
@@ -661,7 +785,7 @@ public class HideoutHelper(
{
var globalSkillsDb = databaseService.GetGlobals().Configuration.SkillsSettings;
var recipe = databaseService.GetHideout().Production.Recipes.FirstOrDefault(production => production.Id == recipeId);
var recipe = databaseService.GetHideout().Production.Recipes?.FirstOrDefault(production => production.Id == recipeId);
if (recipe is null)
{
logger.Error(serverLocalisationService.GetText("hideout-missing_recipe_in_db", recipeId));
@@ -695,7 +819,7 @@ public class HideoutHelper(
}
var modifiedProductionTime = recipe.ProductionTime - timeReductionSeconds;
if (modifiedProductionTime > 0 && profileHelper.IsDeveloperAccount(pmcData.Id.Value))
if (modifiedProductionTime > 0 && profileHelper.IsDeveloperAccount(pmcData.Id!.Value))
{
modifiedProductionTime = 40;
}
@@ -737,7 +861,7 @@ public class HideoutHelper(
}
// Check all slots that take water filters until we find one with filter in it
for (var i = 0; i < waterFilterArea.Slots.Count; i++)
for (var i = 0; i < waterFilterArea.Slots?.Count; i++)
{
// No water filter in slot, skip
if (waterFilterArea.Slots[i].Items is null)
@@ -745,10 +869,15 @@ public class HideoutHelper(
continue;
}
var waterFilterItemInSlot = waterFilterArea.Slots[i].Items.FirstOrDefault();
var waterFilterItemInSlot = waterFilterArea.Slots[i].Items?.FirstOrDefault();
if (waterFilterItemInSlot is null)
{
logger.Warning($"Could not find water filter in slot index `{i}` when trying to update water filters");
continue;
}
// How many units of filter are left
var resourceValue = waterFilterItemInSlot?.Upd?.Resource?.Value;
var resourceValue = waterFilterItemInSlot.Upd?.Resource?.Value;
double pointsConsumed;
if (resourceValue is null)
{
@@ -758,7 +887,7 @@ public class HideoutHelper(
}
else
{
pointsConsumed = (waterFilterItemInSlot.Upd.Resource.UnitsConsumed ?? 0) + filterDrainRate;
pointsConsumed = (waterFilterItemInSlot.Upd?.Resource?.UnitsConsumed ?? 0) + filterDrainRate;
resourceValue -= filterDrainRate;
}
@@ -859,7 +988,7 @@ public class HideoutHelper(
/// <returns>Seconds to produce item</returns>
protected double GetTotalProductionTimeSeconds(MongoId prodId)
{
return databaseService.GetHideout().Production.Recipes.FirstOrDefault(prod => prod.Id == prodId)?.ProductionTime ?? 0;
return databaseService.GetHideout().Production.Recipes?.FirstOrDefault(prod => prod.Id == prodId)?.ProductionTime ?? 0;
}
/// <summary>
@@ -893,15 +1022,15 @@ public class HideoutHelper(
var hideoutManagementConsumptionBonus = 1.0 - GetHideoutManagementConsumptionBonus(pmcData);
filterDrainRate *= hideoutManagementConsumptionBonus;
for (var i = 0; i < airFilterArea.Slots.Count; i++)
for (var i = 0; i < airFilterArea.Slots?.Count; i++)
{
if (airFilterArea.Slots[i]?.Items is null)
if (airFilterArea.Slots[i].Items is null)
{
continue;
}
var resourceValue = airFilterArea.Slots[i].Items[0].Upd?.Resource is not null
? airFilterArea.Slots[i].Items[0].Upd.Resource.Value
var resourceValue = airFilterArea.Slots[i].Items?[0].Upd?.Resource is not null
? airFilterArea.Slots[i].Items?[0].Upd?.Resource?.Value
: null;
double pointsConsumed;
@@ -912,7 +1041,7 @@ public class HideoutHelper(
}
else
{
pointsConsumed = (airFilterArea.Slots[i].Items[0].Upd.Resource.UnitsConsumed ?? 0) + filterDrainRate ?? 0;
pointsConsumed = (airFilterArea.Slots[i].Items?[0].Upd?.Resource?.UnitsConsumed ?? 0) + filterDrainRate ?? 0;
resourceValue -= filterDrainRate;
}
@@ -928,7 +1057,7 @@ public class HideoutHelper(
if (resourceValue > 0)
{
airFilterArea.Slots[i].Items[0].Upd = new Upd
airFilterArea.Slots[i].Items![0].Upd = new Upd
{
StackObjectsCount = 1,
Resource = new UpdResource { Value = resourceValue, UnitsConsumed = pointsConsumed },
@@ -1013,7 +1142,7 @@ public class HideoutHelper(
var coinSlotCount = GetBTCSlots(pmcData);
// Full of bitcoins, halt progress
if (btcProduction.Products.Count >= coinSlotCount)
if (btcProduction.Products?.Count >= coinSlotCount)
{
// Set progress to 0
btcProduction.Progress = 0;
@@ -1023,11 +1152,17 @@ public class HideoutHelper(
var bitcoinProdData = databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault(production => production.Id == BitcoinProductionId);
.Production.Recipes?.FirstOrDefault(production => production.Id == BitcoinProductionId);
if (bitcoinProdData is null)
{
logger.Error("Bitcoin production data is null when trying to update bitcoin farm");
return;
}
// BSG finally fixed their settings, they now get loaded from the settings and used in the client
var adjustedCraftTime =
(profileHelper.IsDeveloperAccount(pmcData.SessionId.Value) ? 40 : bitcoinProdData.ProductionTime)
(profileHelper.IsDeveloperAccount(pmcData.SessionId!.Value) ? 40 : bitcoinProdData.ProductionTime)
/ (1 + (btcFarmCGs - 1) * databaseService.GetHideout().Settings.GpuBoostRate);
// The progress should be adjusted based on the GPU boost rate, but the target is still the base productionTime
@@ -1037,7 +1172,7 @@ public class HideoutHelper(
while (btcProduction.Progress >= bitcoinProdData.ProductionTime)
{
if (btcProduction.Products.Count < coinSlotCount)
if (btcProduction.Products?.Count < coinSlotCount)
// Has space to add a coin to production rewards
{
AddBtcToProduction(btcProduction, bitcoinProdData.ProductionTime ?? 0);
@@ -1059,7 +1194,7 @@ public class HideoutHelper(
/// <param name="coinCraftTimeSeconds">Time to craft a bitcoin</param>
protected void AddBtcToProduction(Production btcProd, double coinCraftTimeSeconds)
{
btcProd.Products.Add(
btcProd.Products?.Add(
new Item
{
Id = new MongoId(),
@@ -1081,29 +1216,34 @@ public class HideoutHelper(
/// <returns>Amount of time elapsed in seconds</returns>
protected long? GetTimeElapsedSinceLastServerTick(PmcData pmcData, bool isGeneratorOn, HideoutProduction? recipe = null)
{
if (pmcData.Hideout is null)
{
throw new HideoutHelperException("Pmc Hideout is null when trying get last elasped server tick");
}
// Reduce time elapsed (and progress) when generator is off
var timeElapsed = timeUtil.GetTimeStamp() - pmcData.Hideout.SptUpdateLastRunTimestamp;
if (recipe is not null)
{
var hideoutArea = databaseService.GetHideout().Areas.FirstOrDefault(area => area.Type == recipe.AreaType);
if (!(hideoutArea.NeedsFuel ?? false))
if (!(hideoutArea?.NeedsFuel ?? false))
// e.g. Lavatory works at 100% when power is on / off
{
return timeElapsed;
}
}
if (!isGeneratorOn)
if (!isGeneratorOn && timeElapsed.HasValue)
{
timeElapsed = (long)(timeElapsed * databaseService.GetHideout().Settings.GeneratorSpeedWithoutFuel);
timeElapsed = (long)(timeElapsed * databaseService.GetHideout().Settings.GeneratorSpeedWithoutFuel!.Value);
}
return timeElapsed;
}
/// <summary>
/// Get a count of how many possible BTC can be gathered by the profile
/// Get a count of how much possible BTC can be gathered by the profile
/// </summary>
/// <param name="pmcData">Profile to look up</param>
/// <returns>Coin slot count</returns>
@@ -1111,7 +1251,7 @@ public class HideoutHelper(
{
var bitcoinProductions = databaseService
.GetHideout()
.Production.Recipes.FirstOrDefault(production => production.Id == BitcoinProductionId);
.Production.Recipes?.FirstOrDefault(production => production.Id == BitcoinProductionId);
var productionSlots = bitcoinProductions?.ProductionLimitCount ?? 3; // Default to 3 if none found
var hasManagementSkillSlots = profileHelper.HasEliteSkillLevel(SkillTypes.HideoutManagement, pmcData);
var managementSlotsCount = GetEliteSkillAdditionalBitcoinSlotCount() ?? 2;
@@ -1145,7 +1285,7 @@ public class HideoutHelper(
// at level 1 you already get 0.5%, so it goes up until level 50. For some reason the wiki
// says that it caps at level 51 with 25% but as per dump data that is incorrect apparently
var roundedLevel = Math.Floor(hideoutManagementSkill.Progress / 100);
roundedLevel = roundedLevel == 51 ? roundedLevel - 1 : roundedLevel;
roundedLevel = roundedLevel.Approx(51d) ? roundedLevel - 1 : roundedLevel;
return roundedLevel
* databaseService.GetGlobals().Configuration.SkillsSettings.HideoutManagement.ConsumptionReductionPerLevel
@@ -1171,7 +1311,7 @@ public class HideoutHelper(
// at level 1 you already get 0.5%, so it goes up until level 50. For some reason the wiki
// says that it caps at level 51 with 25% but as per dump data that is incorrect apparently
var roundedLevel = Math.Floor(profileSkill.Progress / 100);
roundedLevel = roundedLevel == 51 ? roundedLevel - 1 : roundedLevel;
roundedLevel = roundedLevel.Approx(51d) ? roundedLevel - 1 : roundedLevel;
return roundedLevel * valuePerLevel / 100;
}
@@ -1200,8 +1340,18 @@ public class HideoutHelper(
/// <param name="output">Output object to update</param>
public void GetBTC(PmcData pmcData, HideoutTakeProductionRequestData request, MongoId sessionId, ItemEventRouterResponse output)
{
if (pmcData.Hideout?.Production is null)
{
throw new HideoutHelperException("Hideout productions are null when trying to retrieve bitcoin productions");
}
if (pmcData.Hideout?.Production?.TryGetValue(BitcoinProductionId, out var bitcoinCraft) is null or false)
{
logger.Error("Bitcoin production does not exist when trying to retrieve bitcoin productions");
return;
}
// Get how many coins were crafted and ready to pick up
pmcData.Hideout.Production.TryGetValue(BitcoinProductionId, out var bitcoinCraft);
var craftedCoinCount = bitcoinCraft?.Products?.Count;
if (bitcoinCraft is null || craftedCoinCount is null)
{
@@ -1246,15 +1396,15 @@ public class HideoutHelper(
// Is at max capacity + we collected all coins - reset production start time
var coinSlotCount = GetBTCSlots(pmcData);
if (pmcData.Hideout.Production[BitcoinProductionId].Products.Count >= coinSlotCount)
if (bitcoinCraft.Products?.Count >= coinSlotCount)
// Set start to now
{
pmcData.Hideout.Production[BitcoinProductionId].StartTimestamp = timeUtil.GetTimeStamp();
bitcoinCraft.StartTimestamp = timeUtil.GetTimeStamp();
}
// Remove crafted coins from production in profile now they've been collected
// Can only collect all coins, not individually
pmcData.Hideout.Production[BitcoinProductionId].Products = [];
bitcoinCraft.Products = [];
}
/// <summary>
@@ -1264,7 +1414,7 @@ public class HideoutHelper(
/// <returns>true if complete</returns>
protected bool HideoutImprovementIsComplete(HideoutImprovement improvement)
{
return improvement?.Completed ?? false;
return improvement.Completed ?? false;
}
/// <summary>
@@ -1273,14 +1423,14 @@ public class HideoutHelper(
/// <param name="profileData">Profile to adjust</param>
public void SetHideoutImprovementsToCompleted(PmcData profileData)
{
foreach (var improvementId in profileData.Hideout.Improvements)
foreach (var improvementId in profileData.Hideout?.Improvements ?? [])
{
if (!profileData.Hideout.Improvements.TryGetValue(improvementId.Key, out var improvementDetails))
if (profileData.Hideout?.Improvements?.TryGetValue(improvementId.Key, out var improvementDetails) is null or false)
{
continue;
}
if (improvementDetails.Completed == false && improvementDetails.ImproveCompleteTimestamp < timeUtil.GetTimeStamp())
if (improvementDetails?.Completed == false && improvementDetails.ImproveCompleteTimestamp < timeUtil.GetTimeStamp())
{
improvementDetails.Completed = true;
}
@@ -1293,28 +1443,58 @@ public class HideoutHelper(
/// <param name="pmcData">Player profile</param>
public void ApplyPlaceOfFameDogtagBonus(PmcData pmcData)
{
var fameAreaProfile = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.PlaceOfFame);
var fameAreaProfile = pmcData.Hideout?.Areas?.FirstOrDefault(area => area.Type == HideoutAreas.PlaceOfFame);
if (fameAreaProfile is null)
{
logger.Error("Could not locate fame area in profile when trying to apply dogtag bonus");
return;
}
// Get hideout area 16 bonus array
var fameAreaDb = databaseService.GetHideout().Areas.FirstOrDefault(area => area.Type == HideoutAreas.PlaceOfFame);
if (fameAreaDb is null)
{
logger.Error("Could not locate fame area in database when trying to apply dogtag bonus");
return;
}
if (fameAreaDb.Stages?.TryGetValue(fameAreaProfile.Level?.ToString() ?? string.Empty, out var stage) is null or false)
{
logger.Error($"Could not locate stage: {fameAreaProfile.Level?.ToString() ?? "`Level is null`"} in fame area");
return;
}
// Get SkillGroupLevelingBoost object
var combatBoostBonusDb = fameAreaDb
.Stages[fameAreaProfile.Level.ToString()]
.Bonuses.FirstOrDefault(bonus => bonus.Type.ToString() == "SkillGroupLevelingBoost");
var combatBoostBonusDb = stage.Bonuses?.FirstOrDefault(bonus => bonus.Type.ToString() == "SkillGroupLevelingBoost");
// Get SkillGroupLevelingBoost object in profile
var combatBonusProfile = pmcData.Bonuses.FirstOrDefault(bonus => bonus.Id == combatBoostBonusDb.Id);
var combatBonusProfile = pmcData.Bonuses?.FirstOrDefault(bonus => bonus.Id == combatBoostBonusDb?.Id);
if (combatBonusProfile is null)
{
logger.Error("Could not locate: ");
return;
}
// Get all slotted dogtag items
var activeDogtags = pmcData.Inventory.Items.Where(item => item?.SlotId?.StartsWith("dogtag") ?? false);
var activeDogtags = pmcData.Inventory?.Items?.Where(item => item.SlotId?.StartsWith("dogtag") ?? false);
if (activeDogtags is null)
{
logger.Warning("Could not locate any dogtag in the hall of fame when trying to apply dogtag bonus");
return;
}
// Calculate bonus percent (apply hideoutManagement bonus)
var hideoutManagementSkill = pmcData.GetSkillFromProfile(SkillTypes.HideoutManagement);
if (hideoutManagementSkill is null)
{
logger.Error("Could not locate hideout management skill in profile when trying to apply dogtag bonus");
return;
}
var hideoutManagementSkillBonusPercent = 1 + hideoutManagementSkill.Progress / 10000; // 5100 becomes 0.51, add 1 to it, 1.51
var bonus = GetDogtagCombatSkillBonusPercent(pmcData, activeDogtags) * hideoutManagementSkillBonusPercent;
// Update bonus value to above calcualted value
// Update bonus value to above calculated value
combatBonusProfile.Value = Math.Round(bonus, 2);
}
@@ -1357,7 +1537,12 @@ public class HideoutHelper(
public void RemoveHideoutWallBuffsAndDebuffs(HideoutArea wallAreaDb, PmcData pmcData)
{
// Smush all stage bonuses into one array for easy iteration
var wallBonuses = wallAreaDb.Stages.SelectMany(stage => stage.Value.Bonuses);
var wallBonuses = wallAreaDb.Stages?.SelectMany(stage => stage.Value.Bonuses!);
if (wallBonuses is null)
{
logger.Warning("Could not locate wall bonuses in wall area, what are we trying to remove?");
return;
}
// Get all bonus Ids that the wall adds
HashSet<string> bonusIdsToRemove = [];
@@ -1372,6 +1557,6 @@ public class HideoutHelper(
}
// Remove the wall bonuses from profile by id
pmcData.Bonuses = pmcData.Bonuses.Where(bonus => !bonusIdsToRemove.Contains(bonus.Id)).ToList();
pmcData.Bonuses = pmcData.Bonuses?.Where(bonus => !bonusIdsToRemove.Contains(bonus.Id)).ToList();
}
}
@@ -1,5 +1,6 @@
using System.Collections.Frozen;
using SPTarkov.DI.Annotations;
using SPTarkov.Server.Core.Exceptions.Helpers;
using SPTarkov.Server.Core.Exceptions.Items;
using SPTarkov.Server.Core.Extensions;
using SPTarkov.Server.Core.Models.Common;
@@ -744,7 +744,7 @@ public class LocationLifecycleService(
MergePmcAndScavEncyclopedias(serverPmcProfile, scavProfile);
// Handle temp, hydration, limb hp/effects
healthHelper.ApplyHealthChangesToProfile(sessionId, serverPmcProfile, postRaidProfile.Health, isDead);
healthHelper.ApplyHealthChangesToProfile(sessionId, serverPmcProfile, postRaidProfile.Health);
// Required when player loses limb in-raid and fixes it, max now stuck at 50% or less if lost multiple times
var profileTemplate = profileHelper.GetProfileTemplateForSide(fullServerProfile.ProfileInfo.Edition, serverPmcProfile.Info.Side);
@@ -105,8 +105,8 @@ public class PaymentService(
if (payToTrader)
{
// Convert the amount to the trader's currency and update the sales sum.
var costOfPurchaseInCurrency = handbookHelper.FromRUB(
handbookHelper.InRUB(currencyAmount, currencyTpl),
var costOfPurchaseInCurrency = handbookHelper.FromRoubles(
handbookHelper.InRoubles(currencyAmount, currencyTpl),
trader.Currency.Value.GetCurrencyTpl()
);
@@ -121,7 +121,7 @@ public class PaymentService(
logger.Debug(serverLocalisationService.GetText("payment-zero_price_no_payment"));
// Convert the handbook price to the trader's currency and update the sales sum.
var costOfPurchaseInCurrency = handbookHelper.FromRUB(
var costOfPurchaseInCurrency = handbookHelper.FromRoubles(
GetTraderItemHandbookPriceRouble(request.ItemId, requestTransactionId) ?? 0,
trader.Currency.Value.GetCurrencyTpl()
);
@@ -190,7 +190,7 @@ public class PaymentService(
}
var currencyTpl = trader.Currency.Value.GetCurrencyTpl();
var calcAmount = handbookHelper.FromRUB(handbookHelper.InRUB(amountToSend ?? 0, currencyTpl), currencyTpl);
var calcAmount = handbookHelper.FromRoubles(handbookHelper.InRoubles(amountToSend ?? 0, currencyTpl), currencyTpl);
var currencyMaxStackSize = itemHelper.GetItem(currencyTpl).Value.Properties?.StackMaxSize;
if (currencyMaxStackSize is null)
{
@@ -565,7 +565,7 @@ public class ProfileFixerService(
{
// no bonus, add to profile
logger.Debug($"Profile has level {level} area {profileArea.Type} but no bonus found, adding {bonus.Type}");
hideoutHelper.ApplyPlayerUpgradesBonuses(pmcProfile, bonus);
hideoutHelper.ApplyPlayerUpgradesBonus(pmcProfile, bonus);
}
}
}
@@ -268,7 +268,7 @@ public class RagfairPriceService(
// Convert to different currency if required.
if (desiredCurrency != Money.ROUBLES)
{
price = handbookHelper.FromRUB(price, desiredCurrency);
price = handbookHelper.FromRoubles(price, desiredCurrency);
}
if (price <= 0)