From a597c0e2a377f96981794f4a16c8ff15a552964d Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 8 Jun 2025 11:18:54 +0100 Subject: [PATCH] Improved dictionary access to prevent errors when production isn't found Made some methods static Comment improvements Added keywords to spelling whitelist Usings cleanup Removed unused injections #374 --- .../Helpers/HideoutHelper.cs | 141 +++++++++--------- server-csharp.sln.DotSettings | 2 + 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/HideoutHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/HideoutHelper.cs index ac93ebc7..5d00cf93 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/HideoutHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/HideoutHelper.cs @@ -5,10 +5,8 @@ using SPTarkov.Server.Core.Models.Eft.Hideout; using SPTarkov.Server.Core.Models.Eft.Inventory; using SPTarkov.Server.Core.Models.Eft.ItemEvent; using SPTarkov.Server.Core.Models.Enums; -using SPTarkov.Server.Core.Models.Spt.Config; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Routers; -using SPTarkov.Server.Core.Servers; using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; using SPTarkov.Server.Core.Utils.Cloners; @@ -27,9 +25,7 @@ public class HideoutHelper( HttpResponseUtil _httpResponseUtil, ProfileHelper _profileHelper, InventoryHelper _inventoryHelper, - PlayerService _playerService, ItemHelper _itemHelper, - ConfigServer _configServer, ICloner _cloner ) { @@ -39,26 +35,25 @@ public class HideoutHelper( public const string WaterCollector = "5d5589c1f934db045e6c5492"; public const int MaxSkillPoint = 5000; protected HashSet _idCheck = [BitcoinFarm, CultistCircleCraftId]; - protected HideoutConfig hideoutConfig = _configServer.GetConfig(); /// /// Add production to profiles' Hideout.Production array /// - /// Profile to add production to + /// Profile to add production to /// Production request - /// Session id + /// Session id /// client response public void RegisterProduction( PmcData pmcData, - HideoutSingleProductionStartRequestData body, + HideoutSingleProductionStartRequestData productionRequest, string sessionID) { var recipe = _databaseService .GetHideout() - .Production.Recipes.FirstOrDefault(production => production.Id == body.RecipeId); + .Production.Recipes.FirstOrDefault(production => production.Id == productionRequest.RecipeId); if (recipe is null) { - _logger.Error(_localisationService.GetText("hideout-missing_recipe_in_db", body.RecipeId)); + _logger.Error(_localisationService.GetText("hideout-missing_recipe_in_db", productionRequest.RecipeId)); _httpResponseUtil.AppendErrorToOutput(_eventOutputHolder.GetOutput(sessionID)); } @@ -71,20 +66,20 @@ public class HideoutHelper( pmcData.Hideout.Production = new Dictionary(); } - var modifiedProductionTime = GetAdjustedCraftTimeWithSkills(pmcData, body.RecipeId); + var modifiedProductionTime = GetAdjustedCraftTimeWithSkills(pmcData, productionRequest.RecipeId); var production = InitProduction( - body.RecipeId, + productionRequest.RecipeId, modifiedProductionTime ?? 0, recipe.NeedFuelForAllProductionTime ); // Store the tools used for this production, so we can return them later - if (body is not null && body.Tools?.Count > 0) + if (productionRequest.Tools?.Count > 0) { production.SptRequiredTools = []; - foreach (var tool in body.Tools) + foreach (var tool in productionRequest.Tools) { var toolItem = _cloner.Clone(pmcData.Inventory.Items.FirstOrDefault(x => x.Id == tool.Id)); @@ -104,27 +99,27 @@ public class HideoutHelper( } } - pmcData.Hideout.Production[body.RecipeId] = production; + pmcData.Hideout.Production[productionRequest.RecipeId] = production; } /// /// Add production to profiles' Hideout.Production array /// - /// Profile to add production to + /// Profile to add production to /// Production request - /// Session id + /// Session id /// client response public void RegisterProduction( PmcData pmcData, - HideoutContinuousProductionStartRequestData body, + HideoutContinuousProductionStartRequestData productionRequest, string sessionID) { var recipe = _databaseService .GetHideout() - .Production.Recipes.FirstOrDefault(production => production.Id == body.RecipeId); + .Production.Recipes.FirstOrDefault(production => production.Id == productionRequest.RecipeId); if (recipe is null) { - _logger.Error(_localisationService.GetText("hideout-missing_recipe_in_db", body.RecipeId)); + _logger.Error(_localisationService.GetText("hideout-missing_recipe_in_db", productionRequest.RecipeId)); _httpResponseUtil.AppendErrorToOutput(_eventOutputHolder.GetOutput(sessionID)); } @@ -137,15 +132,15 @@ public class HideoutHelper( pmcData.Hideout.Production = new Dictionary(); } - var modifiedProductionTime = GetAdjustedCraftTimeWithSkills(pmcData, body.RecipeId); + var modifiedProductionTime = GetAdjustedCraftTimeWithSkills(pmcData, productionRequest.RecipeId); var production = InitProduction( - body.RecipeId, + productionRequest.RecipeId, modifiedProductionTime ?? 0, recipe.NeedFuelForAllProductionTime ); - pmcData.Hideout.Production[body.RecipeId] = production; + pmcData.Hideout.Production[productionRequest.RecipeId] = production; } /// @@ -221,7 +216,7 @@ public class HideoutHelper( /// /// Process a players hideout, update areas that use resources + increment production timers /// - /// Session id + /// Session id public void UpdatePlayerHideout(string sessionID) { var pmcData = _profileHelper.GetPmcProfile(sessionID); @@ -237,8 +232,8 @@ public class HideoutHelper( /// /// Get various properties that will be passed to hideout update-related functions /// - /// Player profile - /// Properties + /// Player profile + /// Hideout-related values protected HideoutProperties GetHideoutProperties(PmcData pmcData) { var bitcoinFarm = pmcData.Hideout.Areas.FirstOrDefault(area => area.Type == HideoutAreas.BitcoinFarm); @@ -261,7 +256,7 @@ public class HideoutHelper( /// /// Hideout area to check /// - protected bool DoesWaterCollectorHaveFilter(BotHideoutArea waterCollector) + protected static bool DoesWaterCollectorHaveFilter(BotHideoutArea waterCollector) { // Can put filters in from L3 if (waterCollector.Level == 3) @@ -288,8 +283,7 @@ public class HideoutHelper( // Check each production and handle edge cases if necessary foreach (var prodId in pmcData.Hideout?.Production) { - var craft = pmcData.Hideout.Production[prodId.Key]; - if (craft is null) + if (pmcData.Hideout.Production.TryGetValue(prodId.Key, out var craft) && craft is null) { // Craft value is undefined, get rid of it (could be from cancelling craft that needs cleaning up) pmcData.Hideout.Production.Remove(prodId.Key); @@ -305,7 +299,7 @@ public class HideoutHelper( craft.Progress = 0; } - // Skip processing (Don't skip continious crafts like bitcoin farm or cultist circle) + // Skip processing (Don't skip continuous crafts like bitcoin farm or cultist circle) if (IsCraftComplete(craft)) { continue; @@ -331,7 +325,7 @@ public class HideoutHelper( { UpdateBitcoinFarm( pmcData, - pmcData.Hideout.Production[prodId.Key], + pmcData.Hideout.Production.GetValueOrDefault(prodId.Key), hideoutProperties.BtcFarmGcs, hideoutProperties.IsGeneratorOn ); @@ -437,7 +431,7 @@ public class HideoutHelper( var timeElapsed = GetTimeElapsedSinceLastServerTick(pmcData, hideoutProperties.IsGeneratorOn, recipe); // Increment progress by time passed - var production = pmcData.Hideout.Production[prodId]; + pmcData.Hideout.Production.TryGetValue(prodId, out var production); // Some items NEED power to craft (e.g. DSP) if (production.needFuelForAllProductionTime.GetValueOrDefault() && hideoutProperties.IsGeneratorOn) @@ -450,9 +444,9 @@ public class HideoutHelper( production.Progress += timeElapsed; } - // Limit progress to total production time if progress is over (dont run for continious crafts)) + // Limit progress to total production time if progress is over (don't run for continuous crafts) if (!(recipe.Continuous ?? false)) - // If progress is larger than prod time, return ProductionTime, hard cap the vaue + // If progress is larger than prod time, return ProductionTime, hard cap the value { production.Progress = Math.Min(production.Progress ?? 0, production.ProductionTime ?? 0); } @@ -460,7 +454,7 @@ public class HideoutHelper( protected void UpdateCultistCircleCraftProgress(PmcData pmcData, string prodId) { - var production = pmcData.Hideout.Production[prodId]; + pmcData.Hideout.Production.TryGetValue(prodId, out var production); // Check if we're already complete, skip if ((production.AvailableForFinish ?? false) && !production.InProgress.GetValueOrDefault(false)) @@ -489,9 +483,9 @@ public class HideoutHelper( FlagCultistCircleCraftAsComplete(production); } - protected void FlagCultistCircleCraftAsComplete(Production production) + protected static void FlagCultistCircleCraftAsComplete(Production production) { - // Craft is complete, flas as such + // Craft is complete, flag as such production.AvailableForFinish = true; // The client expects `Progress` to be 0, and `inProgress` to be false when a circle is complete @@ -504,9 +498,8 @@ public class HideoutHelper( /// /// Player profile /// Production id - /// Recipe being crafted /// progress matches productionTime from recipe - protected bool DoesProgressMatchProductionTime(PmcData pmcData, string prodId) + protected static bool DoesProgressMatchProductionTime(PmcData pmcData, string prodId) { return pmcData.Hideout.Production[prodId].Progress == pmcData.Hideout.Production[prodId].ProductionTime; } @@ -600,7 +593,7 @@ public class HideoutHelper( fuelUsedSinceLastTick *= combinedBonus; var hasFuelRemaining = false; - var pointsConsumed = 0D; + double pointsConsumed; for (var i = 0; i < generatorArea.Slots.Count; i++) { var generatorSlot = generatorArea.Slots[i]; @@ -610,7 +603,7 @@ public class HideoutHelper( continue; } - var fuelItemInSlot = generatorSlot?.Items[0]; + var fuelItemInSlot = generatorSlot?.Items.FirstOrDefault(); if (fuelItemInSlot is null) // No item in slot, skip { @@ -698,10 +691,9 @@ public class HideoutHelper( { return; } - + // Canister with purified water craft exists - var purifiedWaterCraft = pmcData.Hideout.Production[WaterCollector]; - if (purifiedWaterCraft is not null && purifiedWaterCraft.GetType() == typeof(Production)) + if (pmcData.Hideout.Production.TryGetValue(WaterCollector, out var purifiedWaterCraft) && purifiedWaterCraft.GetType() == typeof(Production)) { // Update craft time to account for increases in players craft time skill purifiedWaterCraft.ProductionTime = GetAdjustedCraftTimeWithSkills( @@ -732,7 +724,7 @@ public class HideoutHelper( /// /// Get craft time and make adjustments to account for dev profile + crafting skill level /// - /// Player profile making craft + /// Player profile making craft /// Recipe being crafted /// Should the hideout management bonus be applied to the calculation /// Items craft time with bonuses subtracted @@ -799,7 +791,7 @@ public class HideoutHelper( /// Water filter area to update /// Production object /// Is generator enabled - /// Player profile + /// Player profile protected void UpdateWaterFilters( BotHideoutArea waterFilterArea, Production production, @@ -820,7 +812,7 @@ public class HideoutHelper( // Production hasn't completed var pointsConsumed = 0D; - // Check progress against the productions craft time (dont use base time as it doesnt include any time bonuses profile has) + // Check progress against the productions craft time (don't use base time as it doesn't include any time bonuses profile has) if (production.Progress > production.ProductionTime) // Craft is complete nothing to do { @@ -868,7 +860,7 @@ public class HideoutHelper( // Filter has some fuel left in it after our adjustment if (resourceValue > 0) { - var isWaterFilterFoundInRaid = waterFilterItemInSlot.Upd.SpawnedInSession ?? false; + var isWaterFilterFoundInRaid = waterFilterItemInSlot.Upd?.SpawnedInSession ?? false; // Set filters consumed amount waterFilterItemInSlot.Upd = GetAreaUpdObject( @@ -886,7 +878,6 @@ public class HideoutHelper( } // Filter ran out / used up - // biome-ignore lint/performance/noDelete: Delete is fine here, as we're seeking to entirely delete the water filter. waterFilterArea.Slots[i].Items = null; // Update remaining resources to be subtracted filterDrainRate = Math.Abs(resourceValue ?? 0); @@ -902,7 +893,7 @@ public class HideoutHelper( /// How far water collector has progressed /// Base drain rate /// Drain rate (adjusted) - protected double GetTimeAdjustedWaterFilterDrainRate( + protected static double GetTimeAdjustedWaterFilterDrainRate( long secondsSinceServerTick, double totalProductionTime, double productionProgress, @@ -920,7 +911,7 @@ public class HideoutHelper( /// /// Get the water filter drain rate based on hideout bonuses player has /// - /// Player profile + /// Player profile /// Drain rate protected double GetWaterFilterDrainRate(PmcData pmcData) { @@ -965,11 +956,12 @@ public class HideoutHelper( } /// - /// Create a upd object using passed in parameters + /// Create an upd object using passed in parameters /// /// /// /// + /// /// Upd protected Upd GetAreaUpdObject( double stackCount, @@ -1003,7 +995,7 @@ public class HideoutHelper( // Hideout management resource consumption bonus: var hideoutManagementConsumptionBonus = 1.0 - GetHideoutManagementConsumptionBonus(pmcData); filterDrainRate *= hideoutManagementConsumptionBonus; - var pointsConsumed = 0D; + double pointsConsumed; for (var i = 0; i < airFilterArea.Slots.Count; i++) { @@ -1060,12 +1052,26 @@ public class HideoutHelper( } } + /// + /// Increment bitcoin farm progress + /// + /// Player profile + /// Hideout btc craft + /// + /// Is hideout generator powered protected void UpdateBitcoinFarm( PmcData pmcData, - Production btcProduction, + Production? btcProduction, int? btcFarmCGs, bool isGeneratorOn) { + if (btcProduction is null) + { + _logger.Error("Bitcoin farm production was null, skipping"); + + return; + } + var isBtcProd = btcProduction.GetType() == typeof(Production); if (!isBtcProd) { @@ -1181,14 +1187,14 @@ public class HideoutHelper( /// /// Get number of ticks that have passed since hideout areas were last processed, reduced when generator is off /// - /// Player profile + /// Player profile /// Is the generator on for the duration of elapsed time /// Hideout production recipe being crafted we need the ticks for /// Amount of time elapsed in seconds protected long? GetTimeElapsedSinceLastServerTick( PmcData pmcData, bool isGeneratorOn, - HideoutProduction recipe = null) + HideoutProduction? recipe = null) { // Reduce time elapsed (and progress) when generator is off var timeElapsed = _timeUtil.GetTimeStamp() - pmcData.Hideout.SptUpdateLastRunTimestamp; @@ -1214,7 +1220,7 @@ public class HideoutHelper( /// /// Get a count of how many possible BTC can be gathered by the profile /// - /// Profile to look up + /// Profile to look up /// Coin slot count protected double GetBTCSlots(PmcData pmcData) { @@ -1242,7 +1248,7 @@ public class HideoutHelper( /// HideoutManagement skill gives a consumption bonus the higher the level /// 0.5% per level per 1-51, (25.5% at max) /// - /// Profile to get hideout consumption level from + /// Profile to get hideout consumption level from /// Consumption bonus protected double? GetHideoutManagementConsumptionBonus(PmcData pmcData) { @@ -1268,7 +1274,7 @@ public class HideoutHelper( /// /// Get a multiplier based on player's skill level and value per level /// - /// Player profile + /// Player profile /// Player skill from profile /// Value from globals.config.SkillsSettings - `PerLevel` /// Multiplier from 0 to 1 @@ -1311,7 +1317,7 @@ public class HideoutHelper( /// Gather crafted BTC from hideout area and add to inventory /// Reset production start timestamp if hideout area at full coin capacity /// - /// Player profile + /// Player profile /// Take production request /// Session id /// Output object to update @@ -1322,8 +1328,9 @@ public class HideoutHelper( ItemEventRouterResponse output) { // Get how many coins were crafted and ready to pick up - var craftedCoinCount = pmcData.Hideout.Production[BitcoinFarm]?.Products?.Count; - if (craftedCoinCount is null) + pmcData.Hideout.Production.TryGetValue(BitcoinFarm, out var bitcoinCraft); + var craftedCoinCount = bitcoinCraft?.Products?.Count; + if (bitcoinCraft is null || craftedCoinCount is null) { var errorMsg = _localisationService.GetText("hideout-no_bitcoins_to_collect"); _logger.Error(errorMsg); @@ -1377,7 +1384,7 @@ public class HideoutHelper( } // Remove crafted coins from production in profile now they've been collected - // Can only collect all coins, not individially + // Can only collect all coins, not individually pmcData.Hideout.Production[BitcoinFarm].Products = []; } @@ -1474,10 +1481,10 @@ public class HideoutHelper( /// Calculate the raw dogtag combat skill bonus for place of fame based on number of dogtags /// Reverse engineered from client code /// - /// Player profile + /// Player profile /// Active dogtags in place of fame dogtag slots /// Combat bonus - protected double GetDogtagCombatSkillBonusPercent(PmcData pmcData, List activeDogtags) + protected static double GetDogtagCombatSkillBonusPercent(PmcData pmcData, List activeDogtags) { // Not own dogtag // Side = opposite of player @@ -1504,8 +1511,8 @@ public class HideoutHelper( /// The wall pollutes a profile with various temp buffs/debuffs, /// Remove them all /// - /// Hideout area data - /// Player profile + /// Hideout area data + /// Player profile public void RemoveHideoutWallBuffsAndDebuffs(HideoutArea wallAreaDb, PmcData pmcData) { // Smush all stage bonuses into one array for easy iteration diff --git a/server-csharp.sln.DotSettings b/server-csharp.sln.DotSettings index d74ccc82..5454b259 100644 --- a/server-csharp.sln.DotSettings +++ b/server-csharp.sln.DotSettings @@ -1,7 +1,9 @@  DO_NOT_SHOW DO_NOT_SHOW + BTC PMC + True True True True