From f3dedfaaad018afcbffd78d25d25918c955151f1 Mon Sep 17 00:00:00 2001 From: Chomp Date: Fri, 2 Jan 2026 14:54:58 +0000 Subject: [PATCH 01/21] Updated copyright year --- Libraries/SPTarkov.Common/SPTarkov.Common.csproj | 2 +- Libraries/SPTarkov.DI/SPTarkov.DI.csproj | 2 +- Libraries/SPTarkov.Reflection/SPTarkov.Reflection.csproj | 4 ++-- .../SPTarkov.Server.Assets/SPTarkov.Server.Assets.csproj | 4 ++-- Libraries/SPTarkov.Server.Core/SPTarkov.Server.Core.csproj | 4 ++-- Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj | 4 ++-- SPTarkov.Server/SPTarkov.Server.csproj | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Libraries/SPTarkov.Common/SPTarkov.Common.csproj b/Libraries/SPTarkov.Common/SPTarkov.Common.csproj index 1d913e12..378d0a2a 100644 --- a/Libraries/SPTarkov.Common/SPTarkov.Common.csproj +++ b/Libraries/SPTarkov.Common/SPTarkov.Common.csproj @@ -4,7 +4,7 @@ SPTarkov.Common Single Player Tarkov Common shared library for the Single Player Tarkov projects. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp diff --git a/Libraries/SPTarkov.DI/SPTarkov.DI.csproj b/Libraries/SPTarkov.DI/SPTarkov.DI.csproj index bc264fd0..39a899c9 100644 --- a/Libraries/SPTarkov.DI/SPTarkov.DI.csproj +++ b/Libraries/SPTarkov.DI/SPTarkov.DI.csproj @@ -4,7 +4,7 @@ SPTarkov.DI Single Player Tarkov Dependency injection shared library for the Single Player Tarkov projects. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp diff --git a/Libraries/SPTarkov.Reflection/SPTarkov.Reflection.csproj b/Libraries/SPTarkov.Reflection/SPTarkov.Reflection.csproj index d9f08b18..ef00be8b 100644 --- a/Libraries/SPTarkov.Reflection/SPTarkov.Reflection.csproj +++ b/Libraries/SPTarkov.Reflection/SPTarkov.Reflection.csproj @@ -1,10 +1,10 @@ - + SPTarkov.Reflection Single Player Tarkov Reflection library for the Single Player Tarkov server. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp diff --git a/Libraries/SPTarkov.Server.Assets/SPTarkov.Server.Assets.csproj b/Libraries/SPTarkov.Server.Assets/SPTarkov.Server.Assets.csproj index 005fe28a..5cf34c17 100644 --- a/Libraries/SPTarkov.Server.Assets/SPTarkov.Server.Assets.csproj +++ b/Libraries/SPTarkov.Server.Assets/SPTarkov.Server.Assets.csproj @@ -1,10 +1,10 @@ - + SPTarkov.Server.Assets Single Player Tarkov Asset library for the Single Player Tarkov server. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp diff --git a/Libraries/SPTarkov.Server.Core/SPTarkov.Server.Core.csproj b/Libraries/SPTarkov.Server.Core/SPTarkov.Server.Core.csproj index 50d59c2b..f00613f6 100644 --- a/Libraries/SPTarkov.Server.Core/SPTarkov.Server.Core.csproj +++ b/Libraries/SPTarkov.Server.Core/SPTarkov.Server.Core.csproj @@ -1,10 +1,10 @@ - + SPTarkov.Server.Core Single Player Tarkov Core library for the Single Player Tarkov server. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp diff --git a/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj b/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj index 11188d4b..75e8539a 100644 --- a/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj +++ b/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj @@ -1,10 +1,10 @@ - + SPTarkov.Server.Web Single Player Tarkov Common shared library for the Single Player Tarkov projects. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp diff --git a/SPTarkov.Server/SPTarkov.Server.csproj b/SPTarkov.Server/SPTarkov.Server.csproj index e3f0e007..defb7b77 100644 --- a/SPTarkov.Server/SPTarkov.Server.csproj +++ b/SPTarkov.Server/SPTarkov.Server.csproj @@ -4,7 +4,7 @@ SPTarkov.Server Single Player Tarkov Single Player Tarkov server launcher. - Copyright (c) Single Player Tarkov 2025 + Copyright (c) Single Player Tarkov 2026 LICENSE https://sp-tarkov.com https://github.com/sp-tarkov/server-csharp From 4ae789ce289d83d18a644e00fe5cc30741b58373 Mon Sep 17 00:00:00 2001 From: Archangel Date: Mon, 5 Jan 2026 16:52:25 +0100 Subject: [PATCH 02/21] Set release loglevel to Info --- SPTarkov.Server/sptLogger.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPTarkov.Server/sptLogger.json b/SPTarkov.Server/sptLogger.json index bbe8dcb0..8ac51817 100644 --- a/SPTarkov.Server/sptLogger.json +++ b/SPTarkov.Server/sptLogger.json @@ -2,7 +2,7 @@ "loggers": [ { "type": "File", - "logLevel": "Trace", + "logLevel": "Info", "format": "[%date% %time%][%level%][%logger%] %message%", "filePath": "./user/logs/spt/", "filePattern": "spt%DATE%.log", @@ -23,7 +23,7 @@ }, { "type": "File", - "logLevel": "Trace", + "logLevel": "Info", "format": "[%date% %time%][%level%][%logger%] %message%", "filePath": "./user/logs/requests/", "filePattern": "requests%DATE%.log", From 9ed310538cb671582eddc7d9888edf179a7a13d4 Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 6 Jan 2026 10:09:57 +0000 Subject: [PATCH 03/21] Fixed invalid logging inside `WeaponModLimitReached()` #721 --- .../Services/BotWeaponModLimitService.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs b/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs index 1d0746fa..2916e680 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs @@ -113,7 +113,7 @@ public class BotWeaponModLimitService(ISptLogger logge var modIsLightOrLaser = itemHelper.IsOfBaseclasses(modTemplate.Id, modLimits.FlashlightLaserBaseTypes); if (modIsLightOrLaser) { - return WeaponModLimitReached(modTemplate.Id, modLimits.FlashlightLaser, modLimits.FlashlightLaserMax ?? 0, botRole); + return WeaponModLimitReached(modTemplate.Id, modLimits.FlashlightLaser, modLimits.FlashlightLaserMax ?? 0, botRole, "light/laser"); } // Mod is a mount that can hold only flashlights ad limit is reached (don't want to add empty mounts if limit is reached) @@ -137,10 +137,11 @@ public class BotWeaponModLimitService(ISptLogger logge /// current number of this item on gun /// mod limit allowed /// role of bot we're checking weapon of + /// OPTIONAL: Type of mod, scope or lightlaser /// true if limit reached - protected bool WeaponModLimitReached(MongoId modTpl, ItemCount currentCount, int? maxLimit, string botRole) + protected bool WeaponModLimitReached(MongoId modTpl, ItemCount currentCount, int? maxLimit, string botRole, string modType = "scope") { - // No limit + // No limit, ignore if (maxLimit is null or 0) { return false; @@ -151,13 +152,13 @@ public class BotWeaponModLimitService(ISptLogger logge { if (logger.IsLogEnabled(LogLevel.Debug)) { - logger.Debug($"[{botRole}] scope limit reached! tried to add {modTpl} but scope count is {currentCount.Count}"); + logger.Debug($"[{botRole}] {modType} limit reached! tried to add: {modTpl} but scope count is: {currentCount.Count}"); } return true; } - // Increment scope count + // Increment mod count limit currentCount.Count++; return false; From 82effe6b1c7bb8f864db17279198622b823ceeb1 Mon Sep 17 00:00:00 2001 From: sp-tarkov-bot Date: Tue, 6 Jan 2026 10:10:52 +0000 Subject: [PATCH 04/21] Format Style Fixes --- .../Services/BotWeaponModLimitService.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs b/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs index 2916e680..db969b94 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs @@ -113,7 +113,13 @@ public class BotWeaponModLimitService(ISptLogger logge var modIsLightOrLaser = itemHelper.IsOfBaseclasses(modTemplate.Id, modLimits.FlashlightLaserBaseTypes); if (modIsLightOrLaser) { - return WeaponModLimitReached(modTemplate.Id, modLimits.FlashlightLaser, modLimits.FlashlightLaserMax ?? 0, botRole, "light/laser"); + return WeaponModLimitReached( + modTemplate.Id, + modLimits.FlashlightLaser, + modLimits.FlashlightLaserMax ?? 0, + botRole, + "light/laser" + ); } // Mod is a mount that can hold only flashlights ad limit is reached (don't want to add empty mounts if limit is reached) From 265855f9311e3e5ef7debd07652fdd307284a9ff Mon Sep 17 00:00:00 2001 From: Chomp Date: Tue, 6 Jan 2026 10:13:13 +0000 Subject: [PATCH 05/21] Improved logging inside `FlagOfferForRemoval()` #722 --- .../SPTarkov.Server.Core/Controllers/RagfairController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs index cbd30a25..738714e0 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs @@ -1033,7 +1033,10 @@ public class RagfairController( playerOffer.EndTime = (long?)Math.Round((double)newEndTime); } - logger.Debug($"Flagged player offer: {offerId} for expiry in: {TimeSpan.FromTicks(playerOffer.EndTime.Value).ToString()}"); + if (logger.IsLogEnabled(LogLevel.Debug)) + { + logger.Debug($"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(playerOffer.EndTime.Value).ToString()}"); + } return output; } From 4e4b608c084ff0341dc568c598fb30bc699bc393 Mon Sep 17 00:00:00 2001 From: sp-tarkov-bot Date: Tue, 6 Jan 2026 10:14:59 +0000 Subject: [PATCH 06/21] Format Style Fixes --- .../SPTarkov.Server.Core/Controllers/RagfairController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs index 738714e0..f7ea7cc8 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs @@ -1035,7 +1035,9 @@ public class RagfairController( if (logger.IsLogEnabled(LogLevel.Debug)) { - logger.Debug($"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(playerOffer.EndTime.Value).ToString()}"); + logger.Debug( + $"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(playerOffer.EndTime.Value).ToString()}" + ); } return output; From 432dd2e7335c6e416f46f71058a8deb2ed213898 Mon Sep 17 00:00:00 2001 From: acidphantasm <127812106+acidphantasm@users.noreply.github.com> Date: Tue, 6 Jan 2026 05:52:48 -0600 Subject: [PATCH 07/21] Furtherfix ragfair modlimits (#724) * Further fix to mod limit debug log * Small improvements on FlagOfferForRemoval to correctly fix logging instead of FromSeconds using the unixtimestamp --------- Co-authored-by: acidphantasm --- .../Controllers/RagfairController.cs | 16 +++++++++++----- .../Services/BotWeaponModLimitService.cs | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs index f7ea7cc8..7bf4da99 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs @@ -1025,18 +1025,24 @@ public class RagfairController( } // Only reduce time to end if time remaining is greater than what we would set it to - var differenceInSeconds = playerOffer.EndTime - timeUtil.GetTimeStamp(); - if (differenceInSeconds > RagfairConfig.Sell.ExpireSeconds) + var now = timeUtil.GetTimeStamp(); + var configExpireSeconds = RagfairConfig.Sell.ExpireSeconds; + + var differenceInSeconds = playerOffer.EndTime - now; + if (differenceInSeconds > configExpireSeconds) { // `expireSeconds` Default is 71 seconds - var newEndTime = RagfairConfig.Sell.ExpireSeconds + timeUtil.GetTimeStamp(); + // TODO: RagfairConfig.Sell.ExpireSeconds should not exist as it should use + // Globals.Configuration.RagFair.OfferDurationTimeInHourAfterRemove (the value actually used by client) + var newEndTime = configExpireSeconds + now; playerOffer.EndTime = (long?)Math.Round((double)newEndTime); + differenceInSeconds = configExpireSeconds; } - if (logger.IsLogEnabled(LogLevel.Debug)) + if (logger.IsLogEnabled(LogLevel.Debug) && differenceInSeconds is { } remaining) { logger.Debug( - $"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(playerOffer.EndTime.Value).ToString()}" + $"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(remaining).ToString()}" ); } diff --git a/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs b/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs index db969b94..7b83341b 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotWeaponModLimitService.cs @@ -158,7 +158,7 @@ public class BotWeaponModLimitService(ISptLogger logge { if (logger.IsLogEnabled(LogLevel.Debug)) { - logger.Debug($"[{botRole}] {modType} limit reached! tried to add: {modTpl} but scope count is: {currentCount.Count}"); + logger.Debug($"[{botRole}] {modType} limit reached! tried to add: {modTpl} but {modType} count is: {currentCount.Count}"); } return true; From fd41e5530c0b3ba2ae2ffaff75e11cdbf5363eaf Mon Sep 17 00:00:00 2001 From: sp-tarkov-bot Date: Tue, 6 Jan 2026 11:53:39 +0000 Subject: [PATCH 08/21] Format Style Fixes --- .../SPTarkov.Server.Core/Controllers/RagfairController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs index 7bf4da99..11983856 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/RagfairController.cs @@ -1041,9 +1041,7 @@ public class RagfairController( if (logger.IsLogEnabled(LogLevel.Debug) && differenceInSeconds is { } remaining) { - logger.Debug( - $"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(remaining).ToString()}" - ); + logger.Debug($"Flagged player: {sessionId} offer: {offerId} for expiry in: {TimeSpan.FromSeconds(remaining).ToString()}"); } return output; From 729d7c0b63f14d87e33d0dc67c4f1bdc485c935e Mon Sep 17 00:00:00 2001 From: Archangel Date: Tue, 6 Jan 2026 16:12:05 +0100 Subject: [PATCH 09/21] Bump SPT version --- Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build.props b/Build.props index c9ffda60..f2f94437 100644 --- a/Build.props +++ b/Build.props @@ -1,7 +1,7 @@ - 4.0.10 + 4.0.12 a12b34 0000000000 LOCAL From a2225f14fead29c8acfe2519d6e53b66a0c038cb Mon Sep 17 00:00:00 2001 From: Archangel Date: Thu, 8 Jan 2026 13:33:06 +0100 Subject: [PATCH 10/21] Add head type to customisation unlocks --- Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs index 491896d4..2cf2017b 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/ProfileHelper.cs @@ -684,6 +684,9 @@ public class ProfileHelper( case CustomisationTypeId.UPPER: rewardToStore.Type = CustomisationType.UPPER; break; + case CustomisationTypeId.HEAD: + rewardToStore.Type = CustomisationType.HEAD; + break; default: logger.Error($"Unhandled customisation unlock type: {template.Parent} not added to profile"); return; From 61a70ed727e9c6218543b4e2e46b2285ae536e30 Mon Sep 17 00:00:00 2001 From: Archangel Date: Thu, 8 Jan 2026 13:55:06 +0100 Subject: [PATCH 11/21] If prestige gives head, give head early --- .../Controllers/PrestigeController.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs b/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs index e3f7d6a4..0ee1aae4 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using SPTarkov.DI.Annotations; using SPTarkov.Server.Core.Helpers; using SPTarkov.Server.Core.Models.Common; @@ -67,6 +68,32 @@ public class PrestigeController(ProfileHelper profileHelper, DatabaseService dat profile.SptData.PendingPrestige = pendingPrestige; profile.ProfileInfo.IsWiped = true; + var prestigeLevels = databaseService.GetTemplates().Prestige?.Elements ?? []; + + var prestigeRewards = prestigeLevels + .Slice(0, pendingPrestige.PrestigeLevel.Value) + .SelectMany(prestigeInner => prestigeInner.Rewards); + + var customisationTemplateDb = databaseService.GetTemplates().Customization; + + foreach (var reward in prestigeRewards) + { + if (!MongoId.IsValidMongoId(reward.Target)) + { + continue; + } + + if (!customisationTemplateDb.TryGetValue(reward.Target, out var template)) + { + continue; + } + + if (template.Parent == CustomisationTypeId.HEAD) + { + profileHelper.AddHideoutCustomisationUnlock(profile, reward, CustomisationSource.PRESTIGE); + } + } + await saveServer.SaveProfileAsync(sessionId); } } From f6eb626d7362a094cc82eef309402f409a0a978c Mon Sep 17 00:00:00 2001 From: Archangel Date: Thu, 8 Jan 2026 13:57:48 +0100 Subject: [PATCH 12/21] Add comment --- Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs b/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs index 0ee1aae4..2618e867 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/PrestigeController.cs @@ -88,6 +88,7 @@ public class PrestigeController(ProfileHelper profileHelper, DatabaseService dat continue; } + // This has to be done before the profile is wiped, as the user can only select a new head during the wipe if (template.Parent == CustomisationTypeId.HEAD) { profileHelper.AddHideoutCustomisationUnlock(profile, reward, CustomisationSource.PRESTIGE); From 0523bd07a2ac4cfc49e6c111188033e4bb6e1480 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sat, 10 Jan 2026 10:43:58 +0000 Subject: [PATCH 13/21] Fixed bot generation working with stale data when transiting #726 --- .../Services/LocationLifecycleService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs index 59dbe831..23af24e4 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs @@ -505,7 +505,11 @@ public class LocationLifecycleService( // TODO - Persist each players last visited location history over multiple transits, e.g. using InMemoryCacheService, need to take care to not let data get stored forever // Store transfer data for later use in `startLocalRaid()` when next raid starts request.LocationTransit.SptExitName = request.Results.ExitName; - profileActivityService.GetProfileActivityRaidData(sessionId).LocationTransit = request.LocationTransit; + + // Update raid data with new location data + var profileActivityRaidData = profileActivityService.GetProfileActivityRaidData(sessionId); + profileActivityRaidData.LocationTransit = request.LocationTransit; + profileActivityRaidData.RaidConfiguration?.Location = locationName; } if (!isPmc) From 2842ed1622c9fdd9c447153cc35292cfa90d0842 Mon Sep 17 00:00:00 2001 From: KnotScripts <139625288+PMouse23@users.noreply.github.com> Date: Sat, 10 Jan 2026 13:17:28 +0100 Subject: [PATCH 14/21] Check level first preventing profile from breaking. (#727) * Check level first preventing profile from breaking. --------- Co-authored-by: Archangel Co-authored-by: sp-tarkov-bot Co-authored-by: Chomp --- .../Controllers/HideoutController.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs b/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs index 93b6c5e6..c770bf01 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/HideoutController.cs @@ -162,10 +162,7 @@ public class HideoutController( return; } - // Upgrade profile values - profileHideoutArea.Level++; - profileHideoutArea.CompleteTime = 0; - profileHideoutArea.Constructing = false; + var nextLevel = profileHideoutArea.Level + 1; var hideoutData = hideout.Areas.FirstOrDefault(area => area.Type == profileHideoutArea.Type); if (hideoutData is null) @@ -177,12 +174,18 @@ public class HideoutController( } // Apply bonuses - if (!hideoutData.Stages.TryGetValue(profileHideoutArea.Level.ToString(), out var hideoutStage)) + if (!hideoutData.Stages.TryGetValue(nextLevel.ToString(), out var hideoutStage)) { - logger.Error($"Stage level: {profileHideoutArea.Level} not found for area: {request.AreaType}"); + logger.Error($"Stage level: {nextLevel} not found for area: {request.AreaType}"); return; } + + // Upgrade profile values + profileHideoutArea.Level = nextLevel; + profileHideoutArea.CompleteTime = 0; + profileHideoutArea.Constructing = false; + var bonuses = hideoutStage.Bonuses; if (bonuses?.Count > 0) { From 1bbb58f8a4358e77bd8342ad3bee2ac488a16017 Mon Sep 17 00:00:00 2001 From: Archangel Date: Mon, 12 Jan 2026 18:24:43 +0100 Subject: [PATCH 15/21] Add GUID to log --- SPTarkov.Server/Modding/ModValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPTarkov.Server/Modding/ModValidator.cs b/SPTarkov.Server/Modding/ModValidator.cs index 40ee8700..74be15e5 100644 --- a/SPTarkov.Server/Modding/ModValidator.cs +++ b/SPTarkov.Server/Modding/ModValidator.cs @@ -199,7 +199,7 @@ public class ModValidator(ISptLogger logger, ServerLocalisationSer new { name = mod.ModMetadata.Name, - version = $"{mod.ModMetadata.Version} (targets SPT: {mod.ModMetadata.SptVersion})", + version = $"{mod.ModMetadata.Version} (GUID: {mod.ModMetadata.ModGuid} | targets SPT: {mod.ModMetadata.SptVersion})", author = mod.ModMetadata.Author, } ) From ad3b37c17a4dc9f74fcca6eec995518d8559fb1e Mon Sep 17 00:00:00 2001 From: Archangel Date: Mon, 12 Jan 2026 18:29:32 +0100 Subject: [PATCH 16/21] Update console exception log.. Again.. (#728) --- SPTarkov.Server/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index 5e91a48d..cb34f4e1 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -71,7 +71,7 @@ public static class Program ShowRedConsoleMessage( e, - "The server has unexpectedly stopped, reach out to #spt-support in our Discord server. Include a screenshot of this message + the below error" + "The server has unexpectedly stopped, reach out to #mod-questions-4-0 in our Discord server. Include a screenshot of this message and the surrounding error(s) above and below" ); Console.WriteLine("Press any key to exit..."); Console.ReadLine(); From d8881d1329533358dae6920abecaa951b9697af5 Mon Sep 17 00:00:00 2001 From: Archangel Date: Thu, 15 Jan 2026 20:22:44 +0100 Subject: [PATCH 17/21] Write json files with linux newlines --- Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs b/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs index c5434228..d217c635 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs @@ -24,6 +24,7 @@ public class JsonUtil UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, #endif Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + NewLine = "\n", }; foreach (var registrator in registrators) From b12c7ddbd05efc5aa1b3d39338a952d4f9591cad Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 15 Jan 2026 20:56:05 +0100 Subject: [PATCH 18/21] Harden AddTaskItemRequirementsToRewardPool against NullReferenceExceptions (#729) * Sanitize AddTaskItemRequirementsToRewardPool * add log --------- Co-authored-by: rootdarkarchon --- .../Services/CircleOfCultistService.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs index d0d38f71..d0fde891 100644 --- a/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/CircleOfCultistService.cs @@ -683,14 +683,19 @@ public class CircleOfCultistService( /// Pool to add items to protected void AddTaskItemRequirementsToRewardPool(PmcData pmcData, HashSet itemRewardBlacklist, HashSet rewardPool) { - var activeTasks = pmcData.Quests.Where(quest => quest.Status == QuestStatusEnum.Started); - foreach (var task in activeTasks) + var activeTasks = pmcData.Quests?.Where(quest => quest.Status == QuestStatusEnum.Started); + foreach (var task in activeTasks ?? []) { var questData = questHelper.GetQuestFromDb(task.QId, pmcData); - var handoverConditions = questData.Conditions.AvailableForFinish.Where(condition => condition.ConditionType == "HandoverItem"); - foreach (var condition in handoverConditions) + if (questData is null) { - foreach (var neededItem in condition.Target.List) + logger.Warning($"Could not get quest data for QId {task.QId}."); + continue; + } + var handoverConditions = questData.Conditions.AvailableForFinish?.Where(condition => condition.ConditionType == "HandoverItem"); + foreach (var condition in handoverConditions ?? []) + { + foreach (var neededItem in condition?.Target?.List ?? []) { if (itemRewardBlacklist.Contains(neededItem) || !itemHelper.IsValidItem(neededItem)) { From c37a9f37eb928e5904e2d9a538eadd6a5a91fd7f Mon Sep 17 00:00:00 2001 From: DrakiaXYZ <565558+DrakiaXYZ@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:06:11 -0800 Subject: [PATCH 19/21] Fix colors not working on mod load failure (#734) Move console mode set to beginning of StartServer to allow colors earlier in the console output Co-authored-by: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com> --- SPTarkov.Server/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index cb34f4e1..a4265c83 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -93,6 +93,7 @@ public static class Program public static async Task StartServer(string[] args) { Console.OutputEncoding = Encoding.UTF8; + SetConsoleOutputMode(); // Some users don't know how to create a shortcut... if (!IsRunFromInstallationFolder()) @@ -157,8 +158,6 @@ public static class Program forwardedHeadersOptions.KnownProxies.Clear(); app.UseForwardedHeaders(forwardedHeadersOptions); - SetConsoleOutputMode(); - await app.Services.GetRequiredService().Startup(); await app.RunAsync(); From 8de0102b037294fd82a1cda5ac1c69ce28788be0 Mon Sep 17 00:00:00 2001 From: Tyfon Date: Wed, 28 Jan 2026 01:47:45 -0800 Subject: [PATCH 20/21] Implement limit and time parameters for /client/mail/dialog/view, aka pagination (#735) Co-authored-by: Tyfon <29051038+tyfon7@users.noreply.github.com> --- .../Controllers/DialogueController.cs | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs b/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs index 9edc2b95..7d0de32b 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs @@ -248,7 +248,7 @@ public class DialogueController( var fullProfile = saveServer.GetProfile(sessionId); var dialogue = GetDialogByIdFromProfile(fullProfile, request); - if (dialogue.Messages?.Count == 0) + if (dialogue.Messages == null || dialogue.Messages.Count == 0) { return new GetMailDialogViewResponseData { @@ -266,9 +266,9 @@ public class DialogueController( return new GetMailDialogViewResponseData { - Messages = dialogue.Messages, + Messages = GetLimitedMessages(dialogue.Messages, request.Limit, request.Time), Profiles = GetProfilesForMail(fullProfile, dialogue.Users), - HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages!), + HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages), }; } @@ -410,6 +410,49 @@ public class DialogueController( return messages.Any(message => (message.Items?.Data?.Count ?? 0) > 0); } + /// + /// Gets a subset of messages from before a certain time + /// + /// The superset of messages + /// The maximum number of messages to return, null/0 means all + /// Limit to messages before this Unix time (seconds since epoch), null/0 means all + /// List of matching messages + protected List GetLimitedMessages(List allMessages, int? limit, decimal? time) + { + if ((time == null || time == 0) && (limit == null || limit == 0 || limit >= allMessages.Count)) + { + return allMessages; + } + + if (time == null || time == 0) + { + time = timeUtil.GetTimeStamp(); + } + + if (limit == null || limit == 0) + { + limit = int.MaxValue; + } + + List results = []; + for (var i = allMessages.Count - 1; i >= 0; i--) + { + var message = allMessages[i]; + if (message.DateTime <= time) + { + results.Add(message); + + if (results.Count >= limit) + { + break; + } + } + } + + results.Reverse(); // Since we iterated from newest to oldest, reverse so the result is in order + return results; + } + /// /// Handle client/mail/dialog/remove /// Remove an entire dialog with an entity (trader/user) From b006ff9b51080d777fcf449d3fd3cc90205bcb74 Mon Sep 17 00:00:00 2001 From: Archangel Date: Thu, 5 Feb 2026 14:43:12 +0100 Subject: [PATCH 21/21] Use long instead of int (Fixes: #732) --- .../Migration/Migrations/Fixes/InvalidRepeatableQuestFix.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/SPTarkov.Server.Core/Migration/Migrations/Fixes/InvalidRepeatableQuestFix.cs b/Libraries/SPTarkov.Server.Core/Migration/Migrations/Fixes/InvalidRepeatableQuestFix.cs index b829cf25..5fd0eaee 100644 --- a/Libraries/SPTarkov.Server.Core/Migration/Migrations/Fixes/InvalidRepeatableQuestFix.cs +++ b/Libraries/SPTarkov.Server.Core/Migration/Migrations/Fixes/InvalidRepeatableQuestFix.cs @@ -33,7 +33,7 @@ public sealed class InvalidRepeatableQuestFix : AbstractProfileMigration } var endTimeNode = quest["endTime"]; - var endTime = endTimeNode?.GetValue() ?? 0; + var endTime = endTimeNode?.GetValue() ?? 0; if (endTime != 0 && quest["changeRequirement"] is null) { @@ -56,7 +56,7 @@ public sealed class InvalidRepeatableQuestFix : AbstractProfileMigration continue; } - var endTime = quest["endTime"]?.GetValue() ?? 0; + var endTime = quest["endTime"]?.GetValue() ?? 0; if (endTime != 0 && quest["changeRequirement"] is null) {