Merge tag '4.0.8'

This commit is contained in:
Archangel
2025-12-12 15:52:02 +01:00
18 changed files with 167 additions and 51 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- SPT specific -->
<SptVersion Condition="'$(SptVersion)' == ''">4.0.7</SptVersion>
<SptVersion Condition="'$(SptVersion)' == ''">4.0.8</SptVersion>
<SptCommit Condition="'$(SptCommit)' == ''">a12b34</SptCommit>
<SptBuildTime Condition="'$(SptBuildTime)' == ''">0000000000</SptBuildTime>
<SptBuildType Condition="'$(SptBuildType)' == ''">LOCAL</SptBuildType>
@@ -9929,9 +9929,10 @@
"settings": {
"adjustBotAppearances": true,
"enableChristmasHideout": true,
"enableSanta": true
"enableSanta": true,
"enableRundansEvent": true
},
"startDay": "12",
"startDay": "13",
"startMonth": "12",
"type": "CHRISTMAS"
},
@@ -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"
@@ -3215,7 +3215,6 @@
"5ede474b0c226a66f5402622": 1,
"5ede475339ee016e8c534742": 1,
"5ede475b549eed7c6d5c18fb": 1,
"5ede47641cf3836a88318df1": 1,
"5f0c892565703e5c461894e9": 1
},
"Caliber46x30": {
@@ -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",
@@ -3206,7 +3206,6 @@
"5ede474b0c226a66f5402622": 1,
"5ede475339ee016e8c534742": 1,
"5ede475b549eed7c6d5c18fb": 1,
"5ede47641cf3836a88318df1": 1,
"5f0c892565703e5c461894e9": 1
},
"Caliber46x30": {
@@ -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
}
}
},
@@ -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<List<Item>> 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<MongoId> processedArmorItems = [];
@@ -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;
}
@@ -65,6 +65,8 @@ public class BundleLoader(ISptLogger<BundleLoader> logger, JsonUtil jsonUtil, Bu
AddBundle(bundleManifest.Key, new BundleInfo(relativeModPath, bundleManifest, bundleHash));
}
await bundleHashCacheService.WriteCache();
}
/// <summary>
@@ -140,6 +140,9 @@ public record SeasonalEventSettings
[JsonPropertyName("disableWaves")]
public List<string>? DisableWaves { get; set; }
[JsonPropertyName("enableRundansEvent")]
public bool? EnableRundansEvent { get; set; }
}
public record ZombieSettings
@@ -30,6 +30,16 @@ public class BackupService
protected readonly TimeUtil TimeUtil;
protected readonly IReadOnlyList<SptMod> 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<BackupService> logger,
IReadOnlyList<SptMod> loadedMods,
@@ -233,9 +243,9 @@ public class BackupService
}
}
protected SortedDictionary<long, string> GetBackupPathsWithCreationTimestamp(IEnumerable<string> backupPaths)
protected SortedDictionary<DateTime, string> GetBackupPathsWithCreationTimestamp(IEnumerable<string> backupPaths)
{
var result = new SortedDictionary<long, string>();
var result = new SortedDictionary<DateTime, string>();
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);
}
/// <summary>
@@ -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}]");
@@ -270,7 +270,7 @@ public class BotEquipmentFilterService(
/// <returns>Filtered bot file</returns>
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);
}
}
@@ -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<BundleHashCacheService> logger, J
protected const string _bundleHashCachePath = "./user/cache/";
protected const string _cacheName = "bundleHashCache.json";
protected Dictionary<string, uint> _bundleHashes = [];
private readonly SemaphoreSlim _writeLock = new(1, 1);
public async Task HydrateCache()
{
@@ -29,6 +31,27 @@ public class BundleHashCacheService(ISptLogger<BundleHashCacheService> logger, J
_bundleHashes = await jsonUtil.DeserializeFromFileAsync<Dictionary<string, uint>>(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<BundleHashCacheService> 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");
}
/// <summary>
@@ -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;
@@ -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);
}
}
}
+1
View File
@@ -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.
+1
View File
@@ -59,6 +59,7 @@ public static class Program
"========================================================================================================="
);
Console.ReadLine();
return;
}