diff --git a/Build.props b/Build.props index 0e02c66b..fe2a13ef 100644 --- a/Build.props +++ b/Build.props @@ -1,7 +1,7 @@ - 4.0.7 + 4.0.8 a12b34 0000000000 LOCAL diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/seasonalevents.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/seasonalevents.json index 819f7e33..cb298be6 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/seasonalevents.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/seasonalevents.json @@ -9929,9 +9929,10 @@ "settings": { "adjustBotAppearances": true, "enableChristmasHideout": true, - "enableSanta": true + "enableSanta": true, + "enableRundansEvent": true }, - "startDay": "12", + "startDay": "13", "startMonth": "12", "type": "CHRISTMAS" }, diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json index 94142bef..0601fe72 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/weather.json @@ -287,13 +287,13 @@ "name": "AUTUMN_LATE", "startDay": "1", "startMonth": "11", - "endDay": "21", + "endDay": "13", "endMonth": "12" }, { "seasonType": 2, "name": "WINTER_START", - "startDay": "21", + "startDay": "13", "startMonth": "12", "endDay": "31", "endMonth": "12" diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bear.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bear.json index b66bfc3d..ba94c730 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bear.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bear.json @@ -3215,7 +3215,6 @@ "5ede474b0c226a66f5402622": 1, "5ede475339ee016e8c534742": 1, "5ede475b549eed7c6d5c18fb": 1, - "5ede47641cf3836a88318df1": 1, "5f0c892565703e5c461894e9": 1 }, "Caliber46x30": { diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bosskilla.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bosskilla.json index 3aa14df1..7c3228f0 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bosskilla.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/bosskilla.json @@ -2808,6 +2808,32 @@ "6575ea6760703324250610de" ] }, + "674d91ce6e862d5a95059ed6": { + "Back_plate": [ + "656efaf54772930db4031ff5" + ], + "Collar": [ + "6575ea719c7cad336508e418" + ], + "Front_plate": [ + "656f611f94b480b8a500c0db" + ], + "Groin": [ + "6575ea7c60703324250610e2" + ], + "Soft_armor_back": [ + "6575ea4cf6a13a7b7100adc4" + ], + "Soft_armor_front": [ + "6575ea3060703324250610da" + ], + "Soft_armor_left": [ + "6575ea5cf6a13a7b7100adc8" + ], + "soft_armor_right": [ + "6575ea6760703324250610de" + ] + }, "5c0e874186f7745dc7616606": { "Helmet_back": [ "6571138e818110db4600aa71" @@ -2822,6 +2848,20 @@ "5c0e842486f77443a74d2976" ] }, + "6759af0f9c8a538dd70bfae6": { + "Helmet_back": [ + "6571138e818110db4600aa71" + ], + "Helmet_ears": [ + "657112fa818110db4600aa6b" + ], + "Helmet_top": [ + "6571133d22996eaf11088200" + ], + "mod_equipment": [ + "5c0e842486f77443a74d2976" + ] + }, "65ae4f57e343f0acc00824da": { "mod_foregrip": [ "588226d124597767ad33f787", diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/usec.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/usec.json index 8052449f..3a2ad17f 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/usec.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/bots/types/usec.json @@ -3206,7 +3206,6 @@ "5ede474b0c226a66f5402622": 1, "5ede475339ee016e8c534742": 1, "5ede475b549eed7c6d5c18fb": 1, - "5ede47641cf3836a88318df1": 1, "5f0c892565703e5c461894e9": 1 }, "Caliber46x30": { diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/templates/profiles.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/templates/profiles.json index 7ac43a5b..dcad9798 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/templates/profiles.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/templates/profiles.json @@ -1626,7 +1626,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 6 + "HpResource": 3 } } }, @@ -4784,7 +4784,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 6 + "HpResource": 3 } } }, @@ -7798,7 +7798,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 6 + "HpResource": 3 } } }, @@ -8396,7 +8396,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -11638,7 +11638,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 6 + "HpResource": 3 } } }, @@ -12127,7 +12127,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -15071,7 +15071,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 6 + "HpResource": 3 } } }, @@ -15664,7 +15664,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -16089,7 +16089,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -16106,7 +16106,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -16138,7 +16138,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -19394,7 +19394,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 6 + "HpResource": 3 } } }, @@ -19980,7 +19980,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -20056,7 +20056,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -20073,7 +20073,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, @@ -20238,7 +20238,7 @@ "slotId": "hideout", "upd": { "MedKit": { - "HpResource": 5 + "HpResource": 3 } } }, diff --git a/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs index b6522a86..1718999d 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/RagfairAssortGenerator.cs @@ -18,6 +18,7 @@ public class RagfairAssortGenerator( DatabaseService databaseService, PresetHelper presetHelper, SeasonalEventService seasonalEventService, + ItemFilterService itemFilterService, ConfigServer configServer, ICloner cloner ) @@ -44,7 +45,10 @@ public class RagfairAssortGenerator( IEnumerable> results = []; // Get cloned items from db - var dbItems = databaseService.GetItems().Where(item => !string.Equals(item.Value.Type, "Node", StringComparison.OrdinalIgnoreCase)); + var blacklist = itemFilterService.GetBlacklistedItems(); + var dbItems = databaseService + .GetItems() + .Where(item => !string.Equals(item.Value.Type, "Node", StringComparison.OrdinalIgnoreCase) && !blacklist.Contains(item.Key)); // Store processed preset tpls so we don't add them when processing non-preset items HashSet processedArmorItems = []; diff --git a/Libraries/SPTarkov.Server.Core/Helpers/PrestigeHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/PrestigeHelper.cs index ae025796..5d8d6f3e 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/PrestigeHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/PrestigeHelper.cs @@ -125,6 +125,17 @@ public class PrestigeHelper( ); } + // Copy over existing unlocked hideout customisation unlocks to new profile that player doesn't already have + newProfile.CustomisationUnlocks ??= []; + foreach (var oldUnlock in oldProfile.CustomisationUnlocks ?? []) + { + if (newProfile.CustomisationUnlocks.FirstOrDefault(unlock => unlock.Id == oldUnlock.Id) is null) + { + newProfile.CustomisationUnlocks.Add(oldUnlock); + } + } + + // Set prestige level on new profile newProfile.CharacterData!.PmcData!.Info!.PrestigeLevel = prestige.PrestigeLevel; } diff --git a/Libraries/SPTarkov.Server.Core/Loaders/BundleLoader.cs b/Libraries/SPTarkov.Server.Core/Loaders/BundleLoader.cs index f5328aa2..c7641ffe 100644 --- a/Libraries/SPTarkov.Server.Core/Loaders/BundleLoader.cs +++ b/Libraries/SPTarkov.Server.Core/Loaders/BundleLoader.cs @@ -65,6 +65,8 @@ public class BundleLoader(ISptLogger logger, JsonUtil jsonUtil, Bu AddBundle(bundleManifest.Key, new BundleInfo(relativeModPath, bundleManifest, bundleHash)); } + + await bundleHashCacheService.WriteCache(); } /// diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/SeasonalEventConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/SeasonalEventConfig.cs index f241eb1d..b60278f5 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/SeasonalEventConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/SeasonalEventConfig.cs @@ -140,6 +140,9 @@ public record SeasonalEventSettings [JsonPropertyName("disableWaves")] public List? DisableWaves { get; set; } + + [JsonPropertyName("enableRundansEvent")] + public bool? EnableRundansEvent { get; set; } } public record ZombieSettings diff --git a/Libraries/SPTarkov.Server.Core/Services/BackupService.cs b/Libraries/SPTarkov.Server.Core/Services/BackupService.cs index 4b450f68..d2300a50 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BackupService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BackupService.cs @@ -30,6 +30,16 @@ public class BackupService protected readonly TimeUtil TimeUtil; protected readonly IReadOnlyList LoadedMods; + private static readonly CultureInfo[] Cultures = + [ + CultureInfo.InvariantCulture, + new CultureInfo("fa-IR") { DateTimeFormat = { Calendar = new PersianCalendar() } }, + new CultureInfo("ar-SA") { DateTimeFormat = { Calendar = new HijriCalendar() } }, + new CultureInfo("he-IL") { DateTimeFormat = { Calendar = new HebrewCalendar() } }, + new CultureInfo("th-TH") { DateTimeFormat = { Calendar = new ThaiBuddhistCalendar() } }, + new CultureInfo("ja-JP") { DateTimeFormat = { Calendar = new JapaneseCalendar() } }, + ]; + public BackupService( ISptLogger logger, IReadOnlyList loadedMods, @@ -233,9 +243,9 @@ public class BackupService } } - protected SortedDictionary GetBackupPathsWithCreationTimestamp(IEnumerable backupPaths) + protected SortedDictionary GetBackupPathsWithCreationTimestamp(IEnumerable backupPaths) { - var result = new SortedDictionary(); + var result = new SortedDictionary(); foreach (var backupPath in backupPaths) { var date = ExtractDateFromFolderName(backupPath); @@ -244,7 +254,7 @@ public class BackupService continue; } - result.Add(date.Value.ToFileTimeUtc(), backupPath); + result.Add(date.Value, backupPath); } return result; @@ -297,7 +307,7 @@ public class BackupService return 0; // Skip comparison if either date is invalid. } - return (int)(dateA.Value.ToFileTimeUtc() - dateB.Value.ToFileTimeUtc()); + return dateA.Value.CompareTo(dateB.Value); } /// @@ -308,11 +318,29 @@ public class BackupService protected DateTime? ExtractDateFromFolderName(string folderPath) { var folderName = Path.GetFileName(folderPath); - const string format = "yyyy-MM-dd_HH-mm-ss"; - if (DateTime.TryParseExact(folderName, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTime)) + + var now = DateTime.UtcNow; + var minDate = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var maxDate = now.AddYears(5); + + foreach (var culture in Cultures) { - return dateTime; + if ( + DateTime.TryParseExact( + folderName, + format, + culture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, + out var dt + ) + ) + { + if (dt >= minDate && dt <= maxDate) + { + return DateTime.SpecifyKind(dt, DateTimeKind.Utc); + } + } } Logger.Warning($"Invalid backup folder name format: {folderPath}, [{folderName}]"); diff --git a/Libraries/SPTarkov.Server.Core/Services/BotEquipmentFilterService.cs b/Libraries/SPTarkov.Server.Core/Services/BotEquipmentFilterService.cs index bb61c163..54c961db 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BotEquipmentFilterService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BotEquipmentFilterService.cs @@ -270,7 +270,7 @@ public class BotEquipmentFilterService( /// Filtered bot file protected void FilterCartridges(BotType baseBotNode, EquipmentFilterDetails? blacklist, EquipmentFilterDetails? whitelist) { - if (whitelist is not null) + if (whitelist is not null && whitelist.Cartridge is not null) { // Loop over each caliber + cartridges of that type foreach (var (caliber, cartridges) in baseBotNode.BotInventory.Ammo) @@ -281,16 +281,13 @@ public class BotEquipmentFilterService( continue; } - // Loop over each cartridge + weight - // Clear all cartridges ready for whitelist to be added - foreach (var ammoKvP in cartridges) - // Cartridge not on whitelist + // Get all cartridges that aren't on the whitelist + var cartridgesToRemove = cartridges.Keys.Where(cartridge => !matchingWhitelist.Contains(cartridge)).ToList(); + + // Remove said cartridges from the original dictionary + foreach (var cartridge in cartridgesToRemove) { - if (!matchingWhitelist.Contains(ammoKvP.Key)) - // Remove - { - cartridges.Remove(ammoKvP.Key); - } + cartridges.Remove(cartridge); } } diff --git a/Libraries/SPTarkov.Server.Core/Services/BundleHashCacheService.cs b/Libraries/SPTarkov.Server.Core/Services/BundleHashCacheService.cs index 86f21806..05f56432 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BundleHashCacheService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BundleHashCacheService.cs @@ -1,4 +1,5 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils; @@ -10,6 +11,7 @@ public class BundleHashCacheService(ISptLogger logger, J protected const string _bundleHashCachePath = "./user/cache/"; protected const string _cacheName = "bundleHashCache.json"; protected Dictionary _bundleHashes = []; + private readonly SemaphoreSlim _writeLock = new(1, 1); public async Task HydrateCache() { @@ -29,6 +31,27 @@ public class BundleHashCacheService(ISptLogger logger, J _bundleHashes = await jsonUtil.DeserializeFromFileAsync>(fullCachePath) ?? []; } + public async Task WriteCache() + { + await _writeLock.WaitAsync(); + + try + { + var bundleHashesSerialized = jsonUtil.Serialize(_bundleHashes); + + if (bundleHashesSerialized is null) + { + return; + } + + await fileUtil.WriteFileAsync(Path.Join(_bundleHashCachePath, _cacheName), bundleHashesSerialized); + } + finally + { + _writeLock.Release(); + } + } + protected uint GetStoredValue(string key) { if (!_bundleHashes.TryGetValue(key, out var value)) @@ -43,16 +66,7 @@ public class BundleHashCacheService(ISptLogger logger, J { _bundleHashes[bundlePath] = hash; - var bundleHashesSerialized = jsonUtil.Serialize(_bundleHashes); - - if (bundleHashesSerialized is null) - { - return; - } - - await fileUtil.WriteFileAsync(Path.Join(_bundleHashCachePath, _cacheName), bundleHashesSerialized); - - logger.Debug($"Bundle: {bundlePath} hash stored in: ${_bundleHashCachePath}"); + logger.Debug($"Bundle: {bundlePath} hash stored in cache"); } /// diff --git a/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs b/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs index 13345891..3656a68a 100644 --- a/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/SeasonalEventService.cs @@ -514,9 +514,20 @@ public class SeasonalEventService( AdjustBotAppearanceValues(eventType.Type); } + if (eventType.Settings?.EnableRundansEvent ?? false) + { + EnableRunnansEvent(databaseService.GetGlobals()); + } + ChangeBtrToTarColaSkin(); } + protected void EnableRunnansEvent(Globals globals) + { + globals.Configuration.RunddansSettings.Active = true; + globals.Configuration.RunddansSettings.ActivePVE = true; + } + private void ChangeBtrToTarColaSkin() { var btrSettings = databaseService.GetGlobals().Configuration.BTRSettings; diff --git a/Libraries/SPTarkov.Server.Core/Utils/DatabaseImporter.cs b/Libraries/SPTarkov.Server.Core/Utils/DatabaseImporter.cs index 755267b2..9a380831 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/DatabaseImporter.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/DatabaseImporter.cs @@ -55,6 +55,11 @@ public class DatabaseImporter( var bsgPath = $"/{newBasePath}/{filePathNoExtension}".Replace("\\", "/"); imageRouter.AddRoute(bsgPath, fileNameWithPath); + + if (fileNameWithNoSPTPath.Contains("icon.ico")) + { + imageRouter.AddRoute("/favicon", fileNameWithPath); + } } } diff --git a/README.md b/README.md index 62be3207..ef897b6d 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,7 @@ In Rider, after installing the CSharpier plugin: We have a number of tests that are run automatically when you submit a pull request. You can run these tests locally by running The unit test sub-project. If you're adding a new feature or fixing a bug, please consider adding tests to cover your changes so that we can ensure they don't break in the future. + ## License This project is licensed under the NCSA Open Source License. See the [LICENSE](LICENSE) file for details. diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index 62058cbc..43d22c0c 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -59,6 +59,7 @@ public static class Program "=========================================================================================================" ); + Console.ReadLine(); return; }