From e6b791179b329c53cef31b47ff84aa5c10cc7237 Mon Sep 17 00:00:00 2001 From: Chomp Date: Sun, 29 Jun 2025 18:58:23 +0100 Subject: [PATCH] Added `Boss of the week` system Similar to live where a boss is picked at random and will have a guaranteed spawn on a map. This is indicated with a skull on the map selection screen Configurable via `config/bot.json/weeklyBoss` --- .../SPT_Data/configs/bot.json | 12 +++ .../database/locations/factory4_day/base.json | 2 +- .../Extensions/DateTimeExtensions.cs | 18 +++++ .../Models/Eft/Common/LocationBase.cs | 2 + .../Models/Spt/Config/BotConfig.cs | 16 ++++ .../Services/PostDbLoadService.cs | 77 +++++++++++++++++++ 6 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json index d9317210..081e7c47 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/configs/bot.json @@ -2916,5 +2916,17 @@ "5783c43d2459774bbe137486", "60b0f6c058e0b0481a09ad11" ] + }, + "weeklyBoss": { + "enabled": true, + "bossPool": [ + "bossBully", + "bossTagilla", + "bossGluhar", + "bossKilla", + "bossKojaniy", + "bossSanitar", + "bossKolontay" + ] } } diff --git a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locations/factory4_day/base.json b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locations/factory4_day/base.json index 560c87ba..2ea7ecfe 100644 --- a/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locations/factory4_day/base.json +++ b/Libraries/SPTarkov.Server.Assets/SPT_Data/database/locations/factory4_day/base.json @@ -109,7 +109,7 @@ "IgnoreMaxBots": true, "RandomTimeSpawn": false, "ShowOnTarkovMap": false, - "ShowOnTarkovMapPvE": true, + "ShowOnTarkovMapPvE": false, "SpawnMode": [ "regular", "pve" diff --git a/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs b/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs index 9142f7f6..0b35c730 100644 --- a/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs +++ b/Libraries/SPTarkov.Server.Core/Extensions/DateTimeExtensions.cs @@ -106,5 +106,23 @@ return dateToCheck >= eventStartDate && dateToCheck <= eventEndDate; } + + /// + /// Get the closest monday to passed in datetime + /// + /// Date to get closest monday of + /// Starting day of week - Default = Monday + /// Monday as DateTime + public static DateTime GetStartOfWeek( + this DateTime dateTime, + DayOfWeek startDay = DayOfWeek.Monday + ) + { + // Calculate difference from current day to Monday + var diff = (7 + (dateTime.DayOfWeek - startDay)) % 7; + + // Subtract difference to get date of most recent Monday + return dateTime.AddDays(-1 * diff).Date; + } } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/LocationBase.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/LocationBase.cs index 255d559f..7eb76b40 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Common/LocationBase.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Common/LocationBase.cs @@ -1144,6 +1144,8 @@ public record Area public XYZ? Size { get; set; } } +[EftEnumConverter] +[EftListEnumConverter] public enum WildSpawnType { marksman, diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs index 4b188d57..7ca520b5 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Config/BotConfig.cs @@ -1,5 +1,6 @@ using System.Text.Json.Serialization; using SPTarkov.Server.Core.Models.Common; +using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Eft.Common.Tables; using SPTarkov.Server.Core.Models.Enums; @@ -138,6 +139,21 @@ public record BotConfig : BaseConfig /// [JsonPropertyName("botRolesThatMustHaveUniqueName")] public required HashSet BotRolesThatMustHaveUniqueName { get; set; } + + /// + /// Bot roles that must have a unique name when generated vs other bots in raid + /// + [JsonPropertyName("weeklyBoss")] + public required WeeklyBossSettings WeeklyBoss { get; set; } +} + +public record WeeklyBossSettings +{ + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + [JsonPropertyName("bossPool")] + public List BossPool { get; set; } } /// diff --git a/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs b/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs index adbfce5e..fec267a1 100644 --- a/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/PostDbLoadService.cs @@ -1,4 +1,5 @@ using SPTarkov.DI.Annotations; +using SPTarkov.Server.Core.Extensions; using SPTarkov.Server.Core.Models.Eft.Common; using SPTarkov.Server.Core.Models.Enums; using SPTarkov.Server.Core.Models.Spt.Config; @@ -126,6 +127,82 @@ public class PostDbLoadService( var currentSeason = _seasonalEventService.GetActiveWeatherSeason(); _raidWeatherService.GenerateWeather(currentSeason); + + if (_botConfig.WeeklyBoss.Enabled) + { + var chosenBoss = GetWeeklyBoss(_botConfig.WeeklyBoss.BossPool); + FlagMapAsGuaranteedBoss(chosenBoss); + } + } + + /// + /// Choose a boss that will spawn at 100% on a map from a predefined collection of bosses + /// + /// Pool of bosses to pick from + /// Boss to spawn for this week + protected WildSpawnType GetWeeklyBoss(List bosses) + { + // Get closest monday to today + var startOfWeek = DateTime.Today.GetStartOfWeek(); + + // Create a consistent seed for the week using the year and the day of the year of above monday chosen + // This results in seed being identical for the week + var seed = startOfWeek.Year * 1000 + startOfWeek.DayOfYear; + + // Init Random class with unique seed + var random = new Random(seed); + + // First number generated by random.Next() will always be the same because of the seed + return bosses[random.Next(0, bosses.Count)]; + } + + /// + /// Given the provided boss, flag them as 100% spawn and add skull to the map they spawn on + /// + /// Boss to flag + protected void FlagMapAsGuaranteedBoss(WildSpawnType boss) + { + // Get the corresponding map for the provided boss + var locations = _databaseService.GetLocations(); + Location? location; + switch (boss) + { + case WildSpawnType.bossBully: + location = locations.Bigmap; + break; + case WildSpawnType.bossGluhar: + location = locations.RezervBase; + break; + case WildSpawnType.bossKilla: + location = locations.Interchange; + break; + case WildSpawnType.bossKojaniy: + location = locations.Woods; + break; + case WildSpawnType.bossSanitar: + location = locations.Shoreline; + break; + case WildSpawnType.bossKolontay: + location = locations.TarkovStreets; + break; + default: + _logger.Warning($"Unknown boss type: {boss}. Unable to set as weekly. Skipping"); + return; + } + + var bossSpawn = location.Base.BossLocationSpawn.FirstOrDefault(x => + x.BossName == boss.ToString() + ); + if (bossSpawn is null) + { + _logger.Warning($"Boss: {boss} not found on map, unable to set as weekly. Skipping"); + return; + } + + _logger.Debug($"{boss} is boss of the week"); + bossSpawn.BossChance = 100; + bossSpawn.ShowOnTarkovMap = true; + bossSpawn.ShowOnTarkovMapPvE = true; } private void MergeCustomHideoutAreas()