From 070ef29d24aa09e3e44c886e097be6300ff95920 Mon Sep 17 00:00:00 2001 From: p-kossa Date: Wed, 17 Jul 2024 13:38:19 -0400 Subject: [PATCH] better spawn point selection some fixes --- Bots/BotSpawn.cs | 147 ++++++++++++++++-------- Bots/ProfilePreparationComponent.cs | 65 ++++------- DefaultPluginVars.cs | 8 +- DonutComponent.cs | 166 ++++++++++++---------------- Models/BotSpawnInfo.cs | 2 +- Models/ZoneSpawnPoints.cs | 42 ++----- 6 files changed, 208 insertions(+), 222 deletions(-) diff --git a/Bots/BotSpawn.cs b/Bots/BotSpawn.cs index c6dd393..d28e23c 100644 --- a/Bots/BotSpawn.cs +++ b/Bots/BotSpawn.cs @@ -63,14 +63,14 @@ internal static async UniTask SpawnStartingBots(BotSpawnInfo botSpawnInfo, Cance foreach (var coordinate in coordinates) { Vector3? spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coordinate, maxSpawnTriesPerBot.Value, cancellationToken); - if (!spawnPosition.HasValue) + if (spawnPosition.HasValue) { - DonutComponent.Logger.LogDebug("No valid spawn position found - skipping this spawn"); + await ActivateStartingBots(cachedBotGroup, wildSpawnType, side, botCreator, botSpawnerClass, spawnPosition.Value, botDifficulty, groupSize, zone, cancellationToken); return; } - - await ActivateStartingBots(cachedBotGroup, wildSpawnType, side, botCreator, botSpawnerClass, coordinate, botDifficulty, groupSize, zone, cancellationToken); } + + DonutComponent.Logger.LogDebug($"All coordinates in zone {zone} failed for this spawn, skipping this spawn"); } internal static async UniTask ActivateStartingBots(BotCreationDataClass botCacheElement, WildSpawnType wildSpawnType, EPlayerSide side, IBotCreator ibotCreator, @@ -84,7 +84,7 @@ internal static async UniTask ActivateStartingBots(BotCreationDataClass botCache #if DEBUG DonutComponent.Logger.LogWarning($"Spawning bots at distance to player of: {Vector3.Distance(spawnPosition, gameWorld.MainPlayer.Position)} " + - $"of side: {botCacheElement.Side} and difficulty: {botDifficulty} in spawn zone: {zone}"); + $"of side: {botCacheElement.Side} and difficulty: {botDifficulty} in spawn zone: {zone}"); #endif var cancellationTokenSource = AccessTools.Field(typeof(BotSpawner), "_cancellationTokenSource").GetValue(botSpawnerClass) as CancellationTokenSource; @@ -96,7 +96,7 @@ internal static async UniTask ActivateStartingBots(BotCreationDataClass botCache } } - internal static async UniTask SpawnBots(BotWave botWave, string zone, Vector3 coordinate, string wildSpawnType, CancellationToken cancellationToken) + internal static async UniTask SpawnBots(BotWave botWave, string zone, Vector3 coordinate, string wildSpawnType, List coordinates, CancellationToken cancellationToken) { WildSpawnType actualWildSpawnType = DetermineWildSpawnType(wildSpawnType); int maxCount = DetermineMaxBotCount(wildSpawnType, botWave.MinGroupSize, botWave.MaxGroupSize); @@ -114,7 +114,7 @@ internal static async UniTask SpawnBots(BotWave botWave, string zone, Vector3 co { #if DEBUG DonutComponent.Logger.LogDebug("Max PMC respawns reached, skipping this spawn"); - #endif +#endif return; } else @@ -129,7 +129,7 @@ internal static async UniTask SpawnBots(BotWave botWave, string zone, Vector3 co { #if DEBUG DonutComponent.Logger.LogDebug("Max SCAV respawns reached, skipping this spawn"); - #endif +#endif return; } else @@ -147,7 +147,7 @@ internal static async UniTask SpawnBots(BotWave botWave, string zone, Vector3 co } bool isGroup = maxCount > 1; - await SetupSpawn(botWave, maxCount, isGroup, actualWildSpawnType, coordinate, zone, cancellationToken); + await SetupSpawn(botWave, maxCount, isGroup, actualWildSpawnType, coordinate, zone, coordinates, cancellationToken); } private static async UniTask AdjustMaxCountForHardCap(string wildSpawnType, int maxCount, CancellationToken cancellationToken) @@ -230,20 +230,20 @@ public static int DetermineMaxBotCount(string spawnType, int defaultMinCount, in return getActualBotCount(groupChance, defaultMinCount, defaultMaxCount); } - private static async UniTask SetupSpawn(BotWave botWave, int maxCount, bool isGroup, WildSpawnType wildSpawnType, Vector3 coordinate, string zone, CancellationToken cancellationToken) + private static async UniTask SetupSpawn(BotWave botWave, int maxCount, bool isGroup, WildSpawnType wildSpawnType, Vector3 coordinate, string zone, List coordinates, CancellationToken cancellationToken) { DonutComponent.Logger.LogDebug($"Attempting to spawn {(isGroup ? "group" : "solo")} with bot count {maxCount} in spawn zone {zone}"); if (isGroup) { - await SpawnGroupBots(botWave, maxCount, wildSpawnType, coordinate, zone, cancellationToken); + await SpawnGroupBots(botWave, maxCount, wildSpawnType, coordinate, zone, coordinates, cancellationToken); } else { - await SpawnSingleBot(botWave, wildSpawnType, coordinate, zone, cancellationToken); + await SpawnSingleBot(botWave, wildSpawnType, coordinate, zone, coordinates, cancellationToken); } } - private static async UniTask SpawnGroupBots(BotWave botWave, int count, WildSpawnType wildSpawnType, Vector3 coordinate, string zone, CancellationToken cancellationToken) + private static async UniTask SpawnGroupBots(BotWave botWave, int count, WildSpawnType wildSpawnType, Vector3 coordinate, string zone, List coordinates, CancellationToken cancellationToken) { #if DEBUG DonutComponent.Logger.LogDebug($"Spawning a group of {count} bots."); @@ -269,34 +269,36 @@ private static async UniTask SpawnGroupBots(BotWave botWave, int count, WildSpaw } var minSpawnDistFromPlayer = SpawnChecks.GetMinDistanceFromPlayer(); - Vector3? spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coordinate, maxSpawnTriesPerBot.Value, cancellationToken); - if (!spawnPosition.HasValue) + bool spawned = false; + + foreach (var coord in coordinates) { - DonutComponent.Logger.LogDebug("No valid spawn position found - skipping this spawn"); - return; + Vector3? spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coord, maxSpawnTriesPerBot.Value, cancellationToken); + if (spawnPosition.HasValue) + { + await SpawnBotForGroup(cachedBotGroup, wildSpawnType, side, botCreator, botSpawnerClass, spawnPosition.Value, cancellationTokenSource, botDifficulty, count, botWave, zone, cancellationToken); + spawned = true; + break; + } } - await SpawnBotForGroup(cachedBotGroup, wildSpawnType, side, botCreator, botSpawnerClass, spawnPosition.Value, cancellationTokenSource, botDifficulty, count, botWave, zone, cancellationToken); + if (!spawned) + { + DonutComponent.Logger.LogDebug("No valid spawn position found after retries - skipping this spawn"); + return; + } } - private static async UniTask SpawnSingleBot(BotWave botWave, WildSpawnType wildSpawnType, Vector3 coordinate, string zone, CancellationToken cancellationToken) + private static async UniTask SpawnSingleBot(BotWave botWave, WildSpawnType wildSpawnType, Vector3 coordinate, string zone, List coordinates, CancellationToken cancellationToken) { EPlayerSide side = GetSideForWildSpawnType(wildSpawnType, WildSpawnType.pmcUSEC, WildSpawnType.pmcBEAR); var cancellationTokenSource = AccessTools.Field(typeof(BotSpawner), "_cancellationTokenSource").GetValue(botSpawnerClass) as CancellationTokenSource; BotDifficulty botDifficulty = GetBotDifficulty(wildSpawnType, WildSpawnType.pmcUSEC, WildSpawnType.pmcBEAR); var BotCacheDataList = DonutsBotPrep.GetWildSpawnData(wildSpawnType, botDifficulty); - var minSpawnDistFromPlayer = SpawnChecks.GetMinDistanceFromPlayer(); - Vector3? spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coordinate, maxSpawnTriesPerBot.Value, cancellationToken); - - if (!spawnPosition.HasValue) - { - DonutComponent.Logger.LogDebug("No valid spawn position found - skipping this spawn"); - return; - } - - await SpawnBotFromCacheOrCreateNew(BotCacheDataList, wildSpawnType, side, botCreator, botSpawnerClass, spawnPosition.Value, cancellationTokenSource, botDifficulty, botWave, zone, cancellationToken); + await SpawnBotFromCacheOrCreateNew(BotCacheDataList, wildSpawnType, side, botCreator, botSpawnerClass, coordinate, cancellationTokenSource, botDifficulty, botWave, zone, coordinates, cancellationToken); } + private static WildSpawnType DetermineWildSpawnType(string spawnType) { WildSpawnType determinedSpawnType = GetWildSpawnType( @@ -396,37 +398,62 @@ internal static BotDifficulty grabOtherDifficulty() #endregion internal static async UniTask SpawnBotFromCacheOrCreateNew(List botCacheList, WildSpawnType wildSpawnType, EPlayerSide side, IBotCreator ibotCreator, - BotSpawner botSpawnerClass, Vector3 spawnPosition, CancellationTokenSource cancellationTokenSource, BotDifficulty botDifficulty, BotWave botWave, string zone, CancellationToken cancellationToken) + BotSpawner botSpawnerClass, Vector3 coordinate, CancellationTokenSource cancellationTokenSource, BotDifficulty botDifficulty, BotWave botWave, string zone, List coordinates, CancellationToken cancellationToken) { #if DEBUG - DonutComponent.Logger.LogDebug("Finding Cached Bot"); + DonutComponent.Logger.LogDebug("Finding Cached Bot"); #endif var botCacheElement = DonutsBotPrep.FindCachedBots(wildSpawnType, botDifficulty, 1); if (botCacheElement != null) { - await ActivateBotFromCache(botCacheElement, spawnPosition, cancellationTokenSource, botWave, zone, cancellationToken); + await ActivateBotFromCache(botCacheElement, coordinate, cancellationTokenSource, botWave, zone, coordinates, cancellationToken); } else { #if DEBUG DonutComponent.Logger.LogDebug("Bot Cache is empty for solo bot. Creating a new bot."); #endif - await CreateNewBot(wildSpawnType, side, ibotCreator, botSpawnerClass, spawnPosition, cancellationTokenSource, zone, cancellationToken); + await CreateNewBot(wildSpawnType, side, ibotCreator, botSpawnerClass, coordinate, cancellationTokenSource, zone, coordinates, cancellationToken); } } - private static async UniTask ActivateBotFromCache(BotCreationDataClass botCacheElement, Vector3 spawnPosition, CancellationTokenSource cancellationTokenSource, BotWave botWave, string zone, CancellationToken cancellationToken) + private static async UniTask ActivateBotFromCache(BotCreationDataClass botCacheElement, Vector3 coordinate, CancellationTokenSource cancellationTokenSource, BotWave botWave, string zone, List coordinates, CancellationToken cancellationToken) { - var closestBotZone = botSpawnerClass.GetClosestZone(spawnPosition, out float dist); - var closestCorePoint = GetClosestCorePoint(spawnPosition); - botCacheElement.AddPosition(spawnPosition, closestCorePoint.Id); + var minSpawnDistFromPlayer = SpawnChecks.GetMinDistanceFromPlayer(); + Vector3? spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coordinate, maxSpawnTriesPerBot.Value, cancellationToken); + + if (!spawnPosition.HasValue) + { + foreach (var coord in coordinates) + { + spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coord, maxSpawnTriesPerBot.Value, cancellationToken); + if (spawnPosition.HasValue) + { + break; + } + } + } + + if (spawnPosition.HasValue) + { + var closestBotZone = botSpawnerClass.GetClosestZone(spawnPosition.Value, out float dist); + var closestCorePoint = GetClosestCorePoint(spawnPosition.Value); + botCacheElement.AddPosition(spawnPosition.Value, closestCorePoint.Id); #if DEBUG - DonutComponent.Logger.LogWarning($"Spawning bot at distance to player of: {Vector3.Distance(spawnPosition, DonutComponent.gameWorld.MainPlayer.Position)} " + - $"of side: {botCacheElement.Side} in spawn zone {zone}"); + DonutComponent.Logger.LogWarning($"Spawning bot at distance to player of: {Vector3.Distance(spawnPosition.Value, DonutComponent.gameWorld.MainPlayer.Position)} " + + $"of side: {botCacheElement.Side} in spawn zone {zone}"); #endif - await ActivateBot(closestBotZone, botCacheElement, cancellationTokenSource, cancellationToken); + await ActivateBot(closestBotZone, botCacheElement, cancellationTokenSource, cancellationToken); + } + else + { +#if DEBUG + DonutComponent.Logger.LogDebug("No valid spawn position found - skipping this spawn"); + return; +#endif + } } internal static async UniTask SpawnBotForGroup(BotCreationDataClass botCacheElement, WildSpawnType wildSpawnType, EPlayerSide side, IBotCreator ibotCreator, @@ -451,22 +478,48 @@ internal static async UniTask SpawnBotForGroup(BotCreationDataClass botCacheElem } } - internal static async UniTask CreateNewBot(WildSpawnType wildSpawnType, EPlayerSide side, IBotCreator ibotCreator, BotSpawner botSpawnerClass, Vector3 spawnPosition, CancellationTokenSource cancellationTokenSource, string zone, CancellationToken cancellationToken) + internal static async UniTask CreateNewBot(WildSpawnType wildSpawnType, EPlayerSide side, IBotCreator ibotCreator, BotSpawner botSpawnerClass, Vector3 coordinate, CancellationTokenSource cancellationTokenSource, string zone, List coordinates, CancellationToken cancellationToken) { BotDifficulty botdifficulty = GetBotDifficulty(wildSpawnType, WildSpawnType.pmcUSEC, WildSpawnType.pmcBEAR); IProfileData botData = new IProfileData(side, wildSpawnType, botdifficulty, 0f, null); BotCreationDataClass bot = await BotCreationDataClass.Create(botData, ibotCreator, 1, botSpawnerClass); - var closestCorePoint = GetClosestCorePoint(spawnPosition); - bot.AddPosition(spawnPosition, closestCorePoint.Id); - var closestBotZone = botSpawnerClass.GetClosestZone(spawnPosition, out float dist); + var minSpawnDistFromPlayer = SpawnChecks.GetMinDistanceFromPlayer(); + Vector3? spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coordinate, maxSpawnTriesPerBot.Value, cancellationToken); + + if (!spawnPosition.HasValue) + { + foreach (var coord in coordinates) + { + spawnPosition = await SpawnChecks.GetValidSpawnPosition(minSpawnDistFromPlayer, 1, 1, coord, maxSpawnTriesPerBot.Value, cancellationToken); + if (spawnPosition.HasValue) + { + break; + } + } + } + + if (spawnPosition.HasValue) + { + var closestCorePoint = GetClosestCorePoint(spawnPosition.Value); + bot.AddPosition(spawnPosition.Value, closestCorePoint.Id); + + var closestBotZone = botSpawnerClass.GetClosestZone(spawnPosition.Value, out float dist); #if DEBUG - DonutComponent.Logger.LogWarning($"Spawning bot at distance to player of: {Vector3.Distance(spawnPosition, DonutComponent.gameWorld.MainPlayer.Position)} " + - $"of side: {bot.Side} and difficulty: {botdifficulty} in spawn zone {zone}"); + DonutComponent.Logger.LogWarning($"Spawning bot at distance to player of: {Vector3.Distance(spawnPosition.Value, DonutComponent.gameWorld.MainPlayer.Position)} " + + $"of side: {bot.Side} and difficulty: {botdifficulty} in spawn zone {zone}"); #endif - await ActivateBot(closestBotZone, bot, cancellationTokenSource, cancellationToken); + await ActivateBot(closestBotZone, bot, cancellationTokenSource, cancellationToken); + } + else + { +#if DEBUG + DonutComponent.Logger.LogDebug("No valid spawn position found - skipping this spawn"); + return; +#endif + } } internal static async UniTask ActivateBot(BotZone botZone, BotCreationDataClass botData, CancellationTokenSource cancellationTokenSource, CancellationToken cancellationToken) diff --git a/Bots/ProfilePreparationComponent.cs b/Bots/ProfilePreparationComponent.cs index ef31039..c8ad11b 100644 --- a/Bots/ProfilePreparationComponent.cs +++ b/Bots/ProfilePreparationComponent.cs @@ -26,7 +26,7 @@ internal static string maplocation { get { - if(Singleton.Instance == null) + if (Singleton.Instance == null) { return ""; } @@ -47,7 +47,7 @@ internal static string mapName { get { - switch(maplocation) + switch (maplocation) { case "bigmap": return "customs"; @@ -262,30 +262,24 @@ await UniTask.WhenAll( private async UniTask InitializeBotInfos(StartingBotConfig startingBotConfig, string maplocation, string botType, CancellationToken cancellationToken) { - if (DefaultPluginVars.forceAllBotType.Value == "PMC") + botType = DefaultPluginVars.forceAllBotType.Value switch { - botType = "PMC"; - } - else if (DefaultPluginVars.forceAllBotType.Value == "SCAV") - { - botType = "SCAV"; - } - - var difficultySetting = botType == "PMC" ? DefaultPluginVars.botDifficultiesPMC.Value.ToLower() : DefaultPluginVars.botDifficultiesSCAV.Value.ToLower(); + "PMC" => "PMC", + "SCAV" => "SCAV", + _ => botType + }; - // Get map bot configuration + string difficultySetting = botType == "PMC" ? DefaultPluginVars.botDifficultiesPMC.Value.ToLower() : DefaultPluginVars.botDifficultiesSCAV.Value.ToLower(); + maplocation = maplocation == "sandbox_high" ? "sandbox" : maplocation; var mapBotConfig = botType == "PMC" ? startingBotConfig.Maps[maplocation].PMC : startingBotConfig.Maps[maplocation].SCAV; var difficultiesForSetting = GetDifficultiesForSetting(difficultySetting); int maxBots = UnityEngine.Random.Range(mapBotConfig.MinCount, mapBotConfig.MaxCount + 1); - - if (botType == "PMC" && maxBots > Initialization.PMCBotLimit) - { - maxBots = Initialization.PMCBotLimit; - } - else if (botType == "SCAV" && maxBots > Initialization.SCAVBotLimit) + maxBots = botType switch { - maxBots = Initialization.SCAVBotLimit; - } + "PMC" when maxBots > Initialization.PMCBotLimit => Initialization.PMCBotLimit, + "SCAV" when maxBots > Initialization.SCAVBotLimit => Initialization.SCAVBotLimit, + _ => maxBots + }; Logger.LogDebug($"Max starting bots for {botType}: {maxBots}"); @@ -298,37 +292,22 @@ private async UniTask InitializeBotInfos(StartingBotConfig startingBotConfig, st while (totalBots < maxBots) { int groupSize = BotSpawn.DetermineMaxBotCount(botType.ToLower(), mapBotConfig.MinGroupSize, mapBotConfig.MaxGroupSize); - if ((totalBots + groupSize) > maxBots) - { - groupSize = maxBots - totalBots; - } + groupSize = Math.Min(groupSize, maxBots - totalBots); var wildSpawnType = botType == "PMC" ? GetPMCWildSpawnType(WildSpawnType.pmcUSEC, WildSpawnType.pmcBEAR) : WildSpawnType.assault; var side = botType == "PMC" ? GetPMCSide(wildSpawnType, WildSpawnType.pmcUSEC, WildSpawnType.pmcBEAR) : EPlayerSide.Savage; var difficulty = difficultiesForSetting[UnityEngine.Random.Range(0, difficultiesForSetting.Count)]; - var coordinates = new List(); - string selectedZone = null; - - var zoneKeys = spawnPointsDict.Keys.ToList(); - zoneKeys = zoneKeys.OrderBy(_ => random.Next()).ToList(); // Shuffle the list of zone keys - - foreach (var zone in zoneKeys) - { - if (!usedZones.Contains(zone) && spawnPointsDict.TryGetValue(zone, out var coord)) - { - coordinates.Add(coord); - selectedZone = zone; - usedZones.Add(zone); - break; - } - } - if (!coordinates.Any()) + var zoneKeys = spawnPointsDict.Keys.OrderBy(_ => random.Next()).ToList(); + string selectedZone = zoneKeys.FirstOrDefault(z => !usedZones.Contains(z)); + if (selectedZone == null) { Logger.LogError("No spawn points available for bot spawn."); break; } + var coordinates = spawnPointsDict[selectedZone]; + usedZones.Add(selectedZone); var botInfo = new PrepBotInfo(wildSpawnType, difficulty, side, groupSize > 1, groupSize); await CreateBot(botInfo, botInfo.IsGroup, botInfo.GroupSize, cancellationToken); @@ -343,7 +322,7 @@ private async UniTask InitializeBotInfos(StartingBotConfig startingBotConfig, st private WildSpawnType GetPMCWildSpawnType(WildSpawnType sptUsec, WildSpawnType sptBear) { - switch(DefaultPluginVars.pmcFaction.Value) + switch (DefaultPluginVars.pmcFaction.Value) { case "USEC": return WildSpawnType.pmcUSEC; @@ -356,7 +335,7 @@ private WildSpawnType GetPMCWildSpawnType(WildSpawnType sptUsec, WildSpawnType s private EPlayerSide GetPMCSide(WildSpawnType wildSpawnType, WildSpawnType sptUsec, WildSpawnType sptBear) { - switch(wildSpawnType) + switch (wildSpawnType) { case WildSpawnType.pmcUSEC: return EPlayerSide.Usec; diff --git a/DefaultPluginVars.cs b/DefaultPluginVars.cs index 8d3b3d8..d747466 100644 --- a/DefaultPluginVars.cs +++ b/DefaultPluginVars.cs @@ -397,8 +397,8 @@ static DefaultPluginVars() globalMinSpawnDistanceFromPlayerBool = new Setting( "Use Global Min Distance From Player", "If enabled, all spawns on all presets will use the global minimum spawn distance from player for each map defined below.", - false, - false); + true, + true); globalMinSpawnDistanceFromPlayerFactory = new Setting( "Factory", @@ -464,8 +464,8 @@ static DefaultPluginVars() globalMinSpawnDistanceFromOtherBotsBool = new Setting( "Use Global Min Distance From Other Bots", "If enabled, all spawns on all presets will use the global minimum spawn distance from player for each map defined below.", - false, - false); + true, + true); globalMinSpawnDistanceFromOtherBotsFactory = new Setting( "Factory", diff --git a/DonutComponent.cs b/DonutComponent.cs index 45c79b6..308ce3e 100644 --- a/DonutComponent.cs +++ b/DonutComponent.cs @@ -286,7 +286,6 @@ private async UniTask StartSpawnProcess(CancellationToken cancellationToken) private async UniTask SpawnBotWaves(MapBotWaves botWaves, CancellationToken cancellationToken) { - // Combine both lists and process in parallel for potential performance improvement var allBotWaves = botWaves.PMC.Concat(botWaves.SCAV).ToList(); while (!cancellationToken.IsCancellationRequested) @@ -299,34 +298,56 @@ private async UniTask SpawnBotWaves(MapBotWaves botWaves, CancellationToken canc { if (isInBattle && timeSinceLastHit < battleStateCoolDown.Value) { - break; // Skip spawn due to battle cooldown + Logger.LogDebug("In battle state cooldown, breaking the loop."); + break; } - // Get coordinates - var spawnPointsDict = DonutComponent.GetSpawnPointsForZones(DonutsBotPrep.allMapsZoneConfig, DonutsBotPrep.maplocation, botWave.Zones); + var wildSpawnType = botWaves.PMC.Contains(botWave) ? "pmc" : "scav"; - if (spawnPointsDict.Any()) + if (CanSpawn(botWave, wildSpawnType)) { - // Select a random coordinate from any zone - var randomZone = spawnPointsDict.Keys.ElementAt(UnityEngine.Random.Range(0, spawnPointsDict.Count)); - var coordinate = spawnPointsDict[randomZone]; + var spawnPointsDict = DonutComponent.GetSpawnPointsForZones(DonutsBotPrep.allMapsZoneConfig, DonutsBotPrep.maplocation, botWave.Zones); - var wildSpawnType = botWaves.PMC.Contains(botWave) ? "pmc" : "scav"; - - if (CanSpawn(botWave, randomZone, coordinate, wildSpawnType)) + if (spawnPointsDict.Any()) { - await TriggerSpawn(botWave, randomZone, coordinate, wildSpawnType, cancellationToken); - anySpawned = true; - break; // Spawned a bot, exit the loop to wait and retry + var random = new System.Random(); + var zoneKeys = spawnPointsDict.Keys.OrderBy(_ => random.Next()).ToList(); + + if (zoneKeys.Any()) + { + var randomZone = zoneKeys.First(); + var coordinates = spawnPointsDict[randomZone]; + + bool isHotspotZone = randomZone.IndexOf("hotspot", StringComparison.OrdinalIgnoreCase) >= 0; + + if ((isHotspotZone && wildSpawnType == "pmc" && hotspotBoostPMC.Value) || + (isHotspotZone && wildSpawnType == "scav" && hotspotBoostSCAV.Value)) + { + Logger.LogDebug($"{randomZone} is a hotspot; hotspot boost is enabled, setting spawn chance to 100"); + botWave.SpawnChance = 100; + } + + foreach (var coordinate in coordinates) + { + if (BotSpawn.IsWithinBotActivationDistance(botWave, coordinate)) + { + Logger.LogDebug($"Triggering spawn for botWave: {botWave} at {randomZone}, {coordinate}"); + await TriggerSpawn(botWave, randomZone, coordinate, wildSpawnType, coordinates, cancellationToken); + anySpawned = true; + break; + } + } + } } } + + // if CanSpawn if false then we need to reset the timers for this wave + ResetGroupTimers(botWave.GroupNum, wildSpawnType); } } - // Yield control to allow other tasks to execute await UniTask.Yield(PlayerLoopTiming.Update); - // If no bots were spawned, delay before retrying if (!anySpawned) { await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cancellationToken); @@ -335,25 +356,18 @@ private async UniTask SpawnBotWaves(MapBotWaves botWaves, CancellationToken canc } // Checks trigger distance and spawn chance - private bool CanSpawn(BotWave botWave, string zone, Vector3 coordinate, string wildSpawnType) + private bool CanSpawn(BotWave botWave, string wildSpawnType) { - if (BotSpawn.IsWithinBotActivationDistance(botWave, coordinate)) - { - bool isHotspotZone = zone.IndexOf("hotspot", StringComparison.OrdinalIgnoreCase) >= 0; + int randomValue = UnityEngine.Random.Range(0, 100); + bool canSpawn = randomValue < botWave.SpawnChance; - if ((isHotspotZone && wildSpawnType == "pmc" && hotspotBoostPMC.Value) || - (isHotspotZone && wildSpawnType == "scav" && hotspotBoostSCAV.Value)) - { - botWave.SpawnChance = 100; - } + Logger.LogDebug($"SpawnChance: {botWave.SpawnChance}, RandomValue: {randomValue}, CanSpawn: {canSpawn}"); - return UnityEngine.Random.Range(0, 100) < botWave.SpawnChance; - } - return false; + return canSpawn; } // Checks certain spawn options, reset groups timers - private async UniTask TriggerSpawn(BotWave botWave, string zone, Vector3 coordinate, string wildSpawnType, CancellationToken cancellationToken) + private async UniTask TriggerSpawn(BotWave botWave, string zone, Vector3 coordinate, string wildSpawnType, List coordinates, CancellationToken cancellationToken) { if (forceAllBotType.Value != "Disabled") { @@ -385,7 +399,7 @@ private async UniTask TriggerSpawn(BotWave botWave, string zone, Vector3 coordin botWave.TriggerCooldown(); } - await BotSpawn.SpawnBots(botWave, zone, coordinate, wildSpawnType, cancellationToken); + await BotSpawn.SpawnBots(botWave, zone, coordinate, wildSpawnType, coordinates, cancellationToken); } // Get the spawn wave configs from the waves json files @@ -471,90 +485,52 @@ public static StartingBotConfig GetStartingBotConfig(string selectionName) } } - // Gets a list of spawn points for defined zones. Checks for certain keywords. - public static Dictionary GetSpawnPointsForZones(AllMapsZoneConfig allMapsZoneConfig, string maplocation, List zones) + public static Dictionary> GetSpawnPointsForZones(AllMapsZoneConfig allMapsZoneConfig, string maplocation, List zoneNames) { - var spawnPointsDict = new Dictionary(); + var spawnPointsDict = new Dictionary>(); - if (allMapsZoneConfig == null) + if (!allMapsZoneConfig.Maps.TryGetValue(maplocation, out var mapZoneConfig)) { - Logger.LogError("allMapsZoneConfig is null."); + Logger.LogError($"Map location {maplocation} not found in zone configuration."); return spawnPointsDict; } - var lowerCaseZones = zones.Select(z => z.ToLowerInvariant()).ToList(); - - if (lowerCaseZones.Contains("start")) + foreach (var zoneName in zoneNames) { - if (!allMapsZoneConfig.StartZones.TryGetValue(maplocation, out var startZoneConfig)) - { - Logger.LogError($"Start zones for map location '{maplocation}' not found in allMapsZoneConfig."); - return spawnPointsDict; - } - - foreach (var zone in startZoneConfig) - { - var randomCoord = zone.Value.OrderBy(_ => UnityEngine.Random.value).FirstOrDefault(); - if (randomCoord != null) - { - spawnPointsDict[zone.Key] = new Vector3(randomCoord.x, randomCoord.y, randomCoord.z); - } - } - } - else if (lowerCaseZones.Contains("all")) - { - if (!allMapsZoneConfig.Maps.TryGetValue(maplocation, out var mapConfig)) - { - Logger.LogError($"Map location '{maplocation}' not found in allMapsZoneConfig."); - return spawnPointsDict; - } - - foreach (var zone in mapConfig.Zones) + if (zoneName == "all") { - var randomCoord = zone.Value.OrderBy(_ => UnityEngine.Random.value).FirstOrDefault(); - if (randomCoord != null) + foreach (var zone in mapZoneConfig.Zones) { - spawnPointsDict[zone.Key] = new Vector3(randomCoord.x, randomCoord.y, randomCoord.z); + if (!spawnPointsDict.ContainsKey(zone.Key)) + { + spawnPointsDict[zone.Key] = new List(); + } + spawnPointsDict[zone.Key].AddRange(zone.Value.Select(c => new Vector3(c.x, c.y, c.z))); } } - } - else if (lowerCaseZones.Contains("hotspot")) - { - if (!allMapsZoneConfig.Maps.TryGetValue(maplocation, out var mapConfig)) + else if (zoneName == "start" || zoneName == "hotspot") { - Logger.LogError($"Map location '{maplocation}' not found in allMapsZoneConfig."); - return spawnPointsDict; - } - - foreach (var zone in mapConfig.Zones) - { - if (zone.Key.ToLowerInvariant().Contains("hotspot")) + foreach (var zone in mapZoneConfig.Zones) { - var randomCoord = zone.Value.OrderBy(_ => UnityEngine.Random.value).FirstOrDefault(); - if (randomCoord != null) + if (zone.Key.IndexOf(zoneName, StringComparison.OrdinalIgnoreCase) >= 0) { - spawnPointsDict[zone.Key] = new Vector3(randomCoord.x, randomCoord.y, randomCoord.z); + if (!spawnPointsDict.ContainsKey(zone.Key)) + { + spawnPointsDict[zone.Key] = new List(); + } + spawnPointsDict[zone.Key].AddRange(zone.Value.Select(c => new Vector3(c.x, c.y, c.z))); } } } - } - else - { - if (!allMapsZoneConfig.Maps.TryGetValue(maplocation, out var mapConfig)) - { - Logger.LogError($"Map location '{maplocation}' not found in allMapsZoneConfig."); - return spawnPointsDict; - } - - foreach (var zoneName in lowerCaseZones) + else { - if (mapConfig.Zones.TryGetValue(zoneName, out var zonePoints)) + if (mapZoneConfig.Zones.TryGetValue(zoneName, out var coordinates)) { - var randomCoord = zonePoints.OrderBy(_ => UnityEngine.Random.value).FirstOrDefault(); - if (randomCoord != null) + if (!spawnPointsDict.ContainsKey(zoneName)) { - spawnPointsDict[zoneName] = new Vector3(randomCoord.x, randomCoord.y, randomCoord.z); + spawnPointsDict[zoneName] = new List(); } + spawnPointsDict[zoneName].AddRange(coordinates.Select(c => new Vector3(c.x, c.y, c.z))); } } } @@ -636,9 +612,9 @@ public void ResetGroupTimers(int groupNum, string wildSpawnType) if (botWave.GroupNum == groupNum) { botWave.ResetTimer(); - #if DEBUG +#if DEBUG DonutComponent.Logger.LogDebug($"Resetting timer for GroupNum: {groupNum}, WildSpawnType: {wildSpawnType}"); - #endif +#endif } } } diff --git a/Models/BotSpawnInfo.cs b/Models/BotSpawnInfo.cs index e98bfdd..9ed1a6d 100644 --- a/Models/BotSpawnInfo.cs +++ b/Models/BotSpawnInfo.cs @@ -9,7 +9,7 @@ public class BotSpawnInfo public List Coordinates { get; set; } public BotDifficulty Difficulty { get; set; } public EPlayerSide Faction { get; set; } - public string Zone { get; set; } + public string Zone { get; set; } public BotSpawnInfo(WildSpawnType botType, int groupSize, List coordinates, BotDifficulty difficulty, EPlayerSide faction, string zone) { diff --git a/Models/ZoneSpawnPoints.cs b/Models/ZoneSpawnPoints.cs index b24349b..98204d3 100644 --- a/Models/ZoneSpawnPoints.cs +++ b/Models/ZoneSpawnPoints.cs @@ -20,7 +20,6 @@ public class MapZoneConfig public class AllMapsZoneConfig { public Dictionary Maps { get; set; } = new Dictionary(); - public Dictionary>> StartZones { get; set; } = new Dictionary>>(); public static AllMapsZoneConfig LoadFromDirectory(string directoryPath) { @@ -34,44 +33,23 @@ public static AllMapsZoneConfig LoadFromDirectory(string directoryPath) if (mapConfig != null) { - if (file.EndsWith("_start.json")) + if (!allMapsConfig.Maps.ContainsKey(mapConfig.MapName)) { - var mapName = mapConfig.MapName; - if (!allMapsConfig.StartZones.ContainsKey(mapName)) + allMapsConfig.Maps[mapConfig.MapName] = new MapZoneConfig { - allMapsConfig.StartZones[mapName] = new Dictionary>(); - } - - foreach (var zone in mapConfig.Zones) - { - if (!allMapsConfig.StartZones[mapName].ContainsKey(zone.Key)) - { - allMapsConfig.StartZones[mapName][zone.Key] = new List(); - } - - allMapsConfig.StartZones[mapName][zone.Key].AddRange(zone.Value); - } + MapName = mapConfig.MapName, + Zones = new Dictionary>() + }; } - else + + foreach (var zone in mapConfig.Zones) { - if (!allMapsConfig.Maps.ContainsKey(mapConfig.MapName)) + if (!allMapsConfig.Maps[mapConfig.MapName].Zones.ContainsKey(zone.Key)) { - allMapsConfig.Maps[mapConfig.MapName] = new MapZoneConfig - { - MapName = mapConfig.MapName, - Zones = new Dictionary>() - }; + allMapsConfig.Maps[mapConfig.MapName].Zones[zone.Key] = new List(); } - foreach (var zone in mapConfig.Zones) - { - if (!allMapsConfig.Maps[mapConfig.MapName].Zones.ContainsKey(zone.Key)) - { - allMapsConfig.Maps[mapConfig.MapName].Zones[zone.Key] = new List(); - } - - allMapsConfig.Maps[mapConfig.MapName].Zones[zone.Key].AddRange(zone.Value); - } + allMapsConfig.Maps[mapConfig.MapName].Zones[zone.Key].AddRange(zone.Value); } } }