From 8ad953a224c8dcab0a5199130f369b2b7a3abdef Mon Sep 17 00:00:00 2001 From: Chomp Date: Thu, 6 Nov 2025 13:31:44 +0000 Subject: [PATCH] Added system to semi-randomly rotate goon spawns across various maps Removed knight from weekly boss system --- .../SPT_Data/configs/bot.json | 13 +++- .../Models/Spt/Config/BotConfig.cs | 15 +++++ .../Services/LocationLifecycleService.cs | 65 +++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json index 2218ec0b..51528993 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json @@ -2895,10 +2895,19 @@ "bossKilla", "bossKojaniy", "bossSanitar", - "bossKolontay", - "bossKnight" + "bossKolontay" ], "resetDay": "Monday" }, + "goonSpawnSystem": { + "enabled": true, + "locationPool": [ + "bigmap", + "woods", + "shoreline", + "lighthouse" + ], + "spawnChance": 35 + }, "replaceScavWith": "assault" } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs index 0bfddd17..498b4443 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs @@ -137,6 +137,21 @@ public record BotConfig : BaseConfig /// [JsonPropertyName("replaceScavWith")] public required WildSpawnType ReplaceScavWith { get; set; } + + [JsonPropertyName("goonSpawnSystem")] + public required GoonSpawnSystem GoonSpawnSystem { get; set; } +} + +public record GoonSpawnSystem +{ + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + [JsonPropertyName("locationPool")] + public IEnumerable LocationPool { get; set; } + + [JsonPropertyName("spawnChance")] + public double SpawnChance { get; set; } } public record WeeklyBossSettings diff --git a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs index ba94bcb1..7c68fdaa 100644 --- a/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/LocationLifecycleService.cs @@ -56,12 +56,15 @@ public class LocationLifecycleService( protected readonly RagfairConfig RagfairConfig = configServer.GetConfig(); protected readonly HideoutConfig HideoutConfig = configServer.GetConfig(); protected readonly PmcConfig PMCConfig = configServer.GetConfig(); + protected readonly BotConfig BotConfig = configServer.GetConfig(); protected readonly LostOnDeathConfig LostOnDeathConfig = configServer.GetConfig(); protected const string Pmc = "pmc"; protected const string Savage = "savage"; protected const string Scav = "scav"; + public object botConfig { get; private set; } + /// /// Check player type for pmc or scav /// @@ -320,6 +323,11 @@ public class LocationLifecycleService( return locationBaseClone; } + if (BotConfig.GoonSpawnSystem.Enabled) + { + AdjustGoonMapSpawns(); + } + // Add custom PMCs to map every time its run pmcWaveGenerator.ApplyWaveChangesToMap(locationBaseClone); @@ -348,6 +356,63 @@ public class LocationLifecycleService( return locationBaseClone; } + /// + /// Goons will spawn on one map each hour, changing randomly based on a consistent seed made from current utc year + utc hour + /// + /// LocationIds to always ignore when choosing a spawn + protected void AdjustGoonMapSpawns(HashSet? locationBlacklist = null) + { + locationBlacklist ??= ["hideout", "develop"]; + + // Reset all maps with goons to 0% spawn, ignore blacklisted locations + var allLocations = databaseService.GetLocations().GetDictionary(); + foreach (var (locationId, location) in allLocations) + { + if (!locationBlacklist.Contains(locationId) && location?.Base?.BossLocationSpawn is not null) + { + foreach (var goonSpawn in location.Base.BossLocationSpawn.Where(x => x.BossName == "bossKnight")) + { + goonSpawn.BossChance = 0; + } + } + } + + var now = DateTime.UtcNow; + + // Create consistent seed for hour (use prime) + var seed = (now.Year * 1009) + now.Hour; + + // Init Random class with unique seed + var random = new Random(seed); + + // Filter locations pool + var validLocationIds = BotConfig + .GoonSpawnSystem.LocationPool.Where(locationId => + !locationBlacklist.Contains(locationId) + && allLocations.TryGetValue(locationId, out var location) + && location?.Base?.BossLocationSpawn is not null + ) + .ToList(); + + if (validLocationIds.Count == 0) + { + logger.Error("Unable to adjust goon spawn chance, no valid locations found"); + + return; + } + + // Choose a spawn location for goons + var chosenMapId = validLocationIds[random.Next(0, validLocationIds.Count)]; + var chosenMap = allLocations[chosenMapId]; + + // "Where" just incase there's multiple knight spawns for some reason + var goonSpawns = chosenMap.Base.BossLocationSpawn.Where(x => x.BossName == "bossKnight"); + foreach (var goonSpawn in goonSpawns) + { + goonSpawn.BossChance = BotConfig.GoonSpawnSystem.SpawnChance; + } + } + /// /// Handle client/match/local/end ///