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()