diff --git a/.vs/SAIN/FileContentIndex/1c05c128-fe22-4f21-ad45-84c2ee4b93d7.vsidx b/.vs/SAIN/FileContentIndex/1c05c128-fe22-4f21-ad45-84c2ee4b93d7.vsidx deleted file mode 100644 index d101eafd1..000000000 Binary files a/.vs/SAIN/FileContentIndex/1c05c128-fe22-4f21-ad45-84c2ee4b93d7.vsidx and /dev/null differ diff --git a/.vs/SAIN/FileContentIndex/21e2191e-a232-49d6-b04a-53ed8162d774.vsidx b/.vs/SAIN/FileContentIndex/21e2191e-a232-49d6-b04a-53ed8162d774.vsidx new file mode 100644 index 000000000..cf4709ad6 Binary files /dev/null and b/.vs/SAIN/FileContentIndex/21e2191e-a232-49d6-b04a-53ed8162d774.vsidx differ diff --git a/.vs/SAIN/FileContentIndex/50d9dac4-4219-442c-a540-7e4e3217aa6a.vsidx b/.vs/SAIN/FileContentIndex/50d9dac4-4219-442c-a540-7e4e3217aa6a.vsidx deleted file mode 100644 index 79941a24c..000000000 Binary files a/.vs/SAIN/FileContentIndex/50d9dac4-4219-442c-a540-7e4e3217aa6a.vsidx and /dev/null differ diff --git a/.vs/SAIN/FileContentIndex/c794518c-e9b8-41b9-94e1-dfe46c18c1a6.vsidx b/.vs/SAIN/FileContentIndex/c794518c-e9b8-41b9-94e1-dfe46c18c1a6.vsidx deleted file mode 100644 index 842fb09a1..000000000 Binary files a/.vs/SAIN/FileContentIndex/c794518c-e9b8-41b9-94e1-dfe46c18c1a6.vsidx and /dev/null differ diff --git a/.vs/SAIN/FileContentIndex/98fef9f1-e2eb-470e-bbbe-93588e5190b7.vsidx b/.vs/SAIN/FileContentIndex/ed110da1-8bdd-496e-bc74-be1a2e202ac4.vsidx similarity index 88% rename from .vs/SAIN/FileContentIndex/98fef9f1-e2eb-470e-bbbe-93588e5190b7.vsidx rename to .vs/SAIN/FileContentIndex/ed110da1-8bdd-496e-bc74-be1a2e202ac4.vsidx index d9ac335fc..1cd2caba5 100644 Binary files a/.vs/SAIN/FileContentIndex/98fef9f1-e2eb-470e-bbbe-93588e5190b7.vsidx and b/.vs/SAIN/FileContentIndex/ed110da1-8bdd-496e-bc74-be1a2e202ac4.vsidx differ diff --git a/.vs/SAIN/FileContentIndex/ed6547dc-e016-456a-bf20-4808a3895086.vsidx b/.vs/SAIN/FileContentIndex/ed6547dc-e016-456a-bf20-4808a3895086.vsidx new file mode 100644 index 000000000..c1674f9ed Binary files /dev/null and b/.vs/SAIN/FileContentIndex/ed6547dc-e016-456a-bf20-4808a3895086.vsidx differ diff --git a/.vs/SAIN/FileContentIndex/ef1d87ca-0c7f-463c-9c9e-32157fca18d4.vsidx b/.vs/SAIN/FileContentIndex/ef1d87ca-0c7f-463c-9c9e-32157fca18d4.vsidx new file mode 100644 index 000000000..8f94e698c Binary files /dev/null and b/.vs/SAIN/FileContentIndex/ef1d87ca-0c7f-463c-9c9e-32157fca18d4.vsidx differ diff --git a/.vs/SAIN/v17/.suo b/.vs/SAIN/v17/.suo index d7c106225..bfd9c0ea6 100644 Binary files a/.vs/SAIN/v17/.suo and b/.vs/SAIN/v17/.suo differ diff --git a/Components/EnemyLineOfSightManager.cs b/Components/EnemyLineOfSightManager.cs new file mode 100644 index 000000000..6925e8173 --- /dev/null +++ b/Components/EnemyLineOfSightManager.cs @@ -0,0 +1,266 @@ +using BepInEx.Logging; +using Comfort.Common; +using EFT; +using SAIN.Components; +using System.Collections.Generic; +using System.Linq; +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; +using UnityEngine.AI; +using static SAIN.UserSettings.VisionConfig; +using SAIN.Helpers; + +public class EnemyLineOfSightManager : MonoBehaviour +{ + private float SpherecastRadius = 0.05f; + private LayerMask SightLayers => LayerMaskClass.HighPolyWithTerrainMask; + private int MinJobSize = 1; + private List RegisteredPlayers = Singleton.Instance.RegisteredPlayers; + private Dictionary SAINComponents = new Dictionary(); + private List SAINComponentsList = new List(); + + private int Frames = 0; + + private void Awake() + { + Logger = BepInEx.Logging.Logger.CreateLogSource(GetType().Name); + } + + private ManualLogSource Logger; + + private void Update() + { + var game = Singleton.Instance; + if (game == null) + { + return; + } + if (!EnableVisionJobs.Value) + { + return; + } + + Frames++; + if (Frames == CheckFrameCount.Value) + { + Frames = 0; + + FindBotComponents(); + GlobalRaycastJob(); + EnemyRaycastJob(); + } + } + + private void FindBotComponents() + { + for (int i = 0; i < RegisteredPlayers.Count; i++) + { + var player = RegisteredPlayers[i]; + string Id = player.ProfileId; + if (SAINComponents.ContainsKey(Id)) + { + if (player.HealthController?.IsAlive == false || SAINComponents[Id] == null) + { + SAINComponents.Remove(Id); + } + } + else + { + if (player.HealthController?.IsAlive == true && player.IsAI) + { + var bot = player.AIData?.BotOwner; + if (bot != null && bot.Profile.Info.Settings.Role != WildSpawnType.marksman) + { + SAINComponents.Add(Id, bot.GetOrAddComponent()); + } + } + } + } + + SAINComponentsList = SAINComponents.Values.ToList(); + } + + private Vector3 HeadPos(Player player) + { + return player.MainParts[BodyPartType.head].Position; + } + + private Vector3 BodyPos(Player player) + { + return player.MainParts[BodyPartType.body].Position; + } + + private Vector3[] Parts(Player player) + { + return player.MainParts.Values.Select(item => item.Position).ToArray(); + } + + private void EnemyRaycastJob() + { + List botsWithEnemy = new List(); + foreach (var bot in SAINComponentsList) + { + if (bot.Enemy != null) + { + botsWithEnemy.Add(bot); + } + } + + NativeArray spherecastCommands = new NativeArray( + botsWithEnemy.Count * 6, + Allocator.TempJob + ); + NativeArray raycastHits = new NativeArray( + botsWithEnemy.Count * 6, + Allocator.TempJob + ); + + for (int i = 0; i < botsWithEnemy.Count; i++) + { + var bot = botsWithEnemy[i]; + Player enemy = bot.Enemy.EnemyPlayer; + Vector3 head = HeadPos(bot.BotOwner.GetPlayer); + var bodyParts = Parts(enemy); + for (int j = 0; j < bodyParts.Length - 1; j++) + { + Vector3 target = bodyParts[j]; + Vector3 direction = target - head; + float max = bot.BotOwner.Settings.Current.CurrentVisibleDistance; + float rayDistance = Mathf.Clamp(direction.magnitude, 0f, max); + spherecastCommands[j] = new SpherecastCommand( + head, + SpherecastRadius, + direction.normalized, + rayDistance, + SightLayers + ); + } + } + + JobHandle spherecastJob = SpherecastCommand.ScheduleBatch( + spherecastCommands, + raycastHits, + MinJobSize + ); + + spherecastJob.Complete(); + int visiblecount = 0; + + for (int i = 0; i < botsWithEnemy.Count; i++) + { + bool visible = false; + var bot = botsWithEnemy[i]; + Player enemy = bot.Enemy.EnemyPlayer; + var bodyParts = Parts(enemy); + for (int j = 0; j < bodyParts.Length - 1; j++) + { + if (raycastHits[j].collider == null) + { + visiblecount++; + bot.Enemy?.OnGainSight(); + if (DebugVision.Value) + { + DebugGizmos.SingleObjects.Line(bot.HeadPosition, bot.Enemy.EnemyPlayer.MainParts[BodyPartType.body].Position, Color.red, 0.01f, true, 0.1f, true); + } + break; + } + } + if (!visible) + { + bot.Enemy?.OnLoseSight(); + } + } + + if (DebugTimer < Time.time) + { + DebugTimer = Time.time + 5f; + Logger.LogInfo($"Raycast Job Enemy Complete [{spherecastCommands.Length}] raycasts finished for [{botsWithEnemy.Count}] bots with enemy. Found [{visiblecount}] visible enemies."); + } + + spherecastCommands.Dispose(); + raycastHits.Dispose(); + } + + private float DebugTimer = 0f; + + private void GlobalRaycastJob() + { + NativeArray allSpherecastCommands = new NativeArray( + SAINComponentsList.Count * RegisteredPlayers.Count, + Allocator.TempJob + ); + NativeArray allRaycastHits = new NativeArray( + SAINComponentsList.Count * RegisteredPlayers.Count, + Allocator.TempJob + ); + + int currentIndex = 0; + + for (int i = 0; i < SAINComponentsList.Count; i++) + { + var bot = SAINComponentsList[i]; + Vector3 head = HeadPos(bot.BotOwner.GetPlayer); + + for (int j = 0; j < RegisteredPlayers.Count; j++) + { + Vector3 target = BodyPos(RegisteredPlayers[j]); + Vector3 direction = target - head; + float max = bot.BotOwner.Settings.Current.CurrentVisibleDistance; + float rayDistance = Mathf.Clamp(direction.magnitude, 0f, max); + + allSpherecastCommands[currentIndex] = new SpherecastCommand( + head, + SpherecastRadius, + direction.normalized, + rayDistance, + SightLayers + ); + + currentIndex++; + } + } + + JobHandle spherecastJob = SpherecastCommand.ScheduleBatch( + allSpherecastCommands, + allRaycastHits, + MinJobSize + ); + int visiblecount = 0; + spherecastJob.Complete(); + + for (int i = 0; i < SAINComponentsList.Count; i++) + { + int startIndex = i * RegisteredPlayers.Count; + var visiblePlayers = SAINComponentsList[i].VisiblePlayers; + + for (int j = 0; j < RegisteredPlayers.Count; j++) + { + currentIndex = startIndex + j; + if (allRaycastHits[currentIndex].collider != null) + { + if (visiblePlayers.Contains(RegisteredPlayers[j])) + { + visiblePlayers.Remove(RegisteredPlayers[j]); + } + } + else + { + visiblecount++; + if (!visiblePlayers.Contains(RegisteredPlayers[j])) + { + visiblePlayers.Add(RegisteredPlayers[j]); + } + } + } + } + + if (DebugTimer < Time.time) + { + Logger.LogInfo($"Raycast Job Complete [{allSpherecastCommands.Length}] raycasts finished. Found [{visiblecount}] visible players."); + } + + allSpherecastCommands.Dispose(); + allRaycastHits.Dispose(); + } +} \ No newline at end of file diff --git a/Components/SAIN Bot Component/Classes/AILimitClass.cs b/Components/SAIN Bot Component/Classes/AILimitClass.cs index 873dbbc7a..7d0a7ba07 100644 --- a/Components/SAIN Bot Component/Classes/AILimitClass.cs +++ b/Components/SAIN Bot Component/Classes/AILimitClass.cs @@ -33,7 +33,7 @@ public void Update() float distanceToPlayer = SAIN.DistanceToMainPlayer; if (distanceToPlayer > 500f) { - TimeAdd = 3; + TimeAdd = 3f; } else if (distanceToPlayer > 300f) { diff --git a/Components/SAIN Bot Component/Classes/BotSquadClass.cs b/Components/SAIN Bot Component/Classes/BotSquadClass.cs index 6f444f20a..c555519ea 100644 --- a/Components/SAIN Bot Component/Classes/BotSquadClass.cs +++ b/Components/SAIN Bot Component/Classes/BotSquadClass.cs @@ -38,11 +38,13 @@ public void Update() FindSquadLeader(); } } - - if ((Leader == null || GroupSize != SquadMembers.Count) && TimeSinceLeaderDied == 0f) + if (SquadMembers != null) { - GroupSize = SquadMembers.Count; - FindSquadLeader(); + if ((Leader == null || GroupSize != SquadMembers.Count) && TimeSinceLeaderDied == 0f) + { + GroupSize = SquadMembers.Count; + FindSquadLeader(); + } } } @@ -97,6 +99,7 @@ private void AddLeader(SAINComponent sain) LeaderDieTime = 0f; IAmLeader = sain.ProfileId == BotOwner.ProfileId; LeaderComponent = sain; + Leader = sain.BotOwner; if (IAmLeader && SquadID == "None") { SquadID = Guid.NewGuid().ToString("N"); @@ -113,7 +116,8 @@ private void AddLeader(SAINComponent sain) public bool IAmLeader { get; private set; } = false; - public BotOwner Leader => LeaderComponent.BotOwner; + public BotOwner Leader { get; private set; } + public SAINComponent LeaderComponent { get; private set; } public bool BotInGroup => BotOwner.BotsGroup.MembersCount > 1; @@ -169,6 +173,12 @@ private void UpdateMembers() SquadPowerLevel = BotOwner.BotsGroup.GroupPower; SquadMembers = dictionary; + + if (Leader == null) + { + GroupSize = SquadMembers.Count; + FindSquadLeader(); + } } public bool MemberIsFallingBack { get; private set; } diff --git a/Components/SAIN Bot Component/Classes/Decision/Classes/SquadDecisionClass.cs b/Components/SAIN Bot Component/Classes/Decision/Classes/SquadDecisionClass.cs index 545b1200e..8ba98b981 100644 --- a/Components/SAIN Bot Component/Classes/Decision/Classes/SquadDecisionClass.cs +++ b/Components/SAIN Bot Component/Classes/Decision/Classes/SquadDecisionClass.cs @@ -12,15 +12,12 @@ public class SquadDecisionClass : SAINBot public SquadDecisionClass(BotOwner bot) : base(bot) { } protected ManualLogSource Logger => SAIN.Decision.Logger; + private SquadClass Squad => SAIN.BotSquad; public bool GetDecision(out SAINSquadDecision Decision) { Decision = SAINSquadDecision.None; - if (!SAIN.BotSquad.BotInGroup || SAIN.BotSquad.SquadMembers == null || SAIN.BotSquad.Leader?.IsDead == true) - { - return false; - } - if (!SAIN.HasEnemy ||! SAIN.HasGoalEnemy) + if (!Squad.BotInGroup || Squad.Leader?.IsDead == true) { return false; } @@ -29,15 +26,17 @@ public bool GetDecision(out SAINSquadDecision Decision) return false; } - if (!EnemyDecision(out Decision)) + if (EnemyDecision(out Decision)) { - if (StartRegroup()) - { - Decision = SAINSquadDecision.Regroup; - } + return true; + } + if (StartRegroup()) + { + Decision = SAINSquadDecision.Regroup; + return true; } - return Decision != SAINSquadDecision.None; + return false; } private bool EnemyDecision(out SAINSquadDecision Decision) diff --git a/Components/SAIN Bot Component/Classes/Objects/SAINEnemyClass.cs b/Components/SAIN Bot Component/Classes/Objects/SAINEnemyClass.cs index 88dfa875f..b91701218 100644 --- a/Components/SAIN Bot Component/Classes/Objects/SAINEnemyClass.cs +++ b/Components/SAIN Bot Component/Classes/Objects/SAINEnemyClass.cs @@ -48,9 +48,10 @@ private void FindEnemyComponent() { if (component != null) { - if (component.OwnerSquadId == SAIN.SquadId) + if (component.CheckIfComponentIsForGroup(SAIN)) { EnemyComponent = component; + component.AddOwner(SAIN); break; } } @@ -67,6 +68,7 @@ private void AddNewComponent() { Logger.LogDebug($"New Enemy Component added for enemy: [{EnemyPlayer.name}] for SquadID: [{SAIN.SquadId}]"); EnemyComponent = EnemyPlayer.gameObject.AddComponent(); + EnemyComponent.AddOwner(SAIN); } private readonly ManualLogSource Logger; @@ -150,6 +152,16 @@ private float GetMagnitudeToBot(Vector3 point) private float DistanceTimer = 0f; + public void OnGainSight() + { + + } + + public void OnLoseSight() + { + + } + private void CheckEnemyVisible() { bool lineOfSight = InLineOfSight; diff --git a/Components/SAIN Bot Component/SAINComponent.cs b/Components/SAIN Bot Component/SAINComponent.cs index 87464e989..e60b82ace 100644 --- a/Components/SAIN Bot Component/SAINComponent.cs +++ b/Components/SAIN Bot Component/SAINComponent.cs @@ -2,13 +2,16 @@ using Comfort.Common; using EFT; using SAIN.Classes; +using SAIN.Helpers; using System.Collections.Generic; using UnityEngine; +using static SAIN.UserSettings.VisionConfig; namespace SAIN.Components { public class SAINComponent : MonoBehaviour { + public List VisiblePlayers = new List(); private void Awake() { BotOwner = GetComponent(); @@ -46,10 +49,18 @@ private void Update() { if (GameHasEnded) { - Dispose(); + //Dispose(); } if (BotActive && !GameIsEnding) { + if (VisiblePlayers.Count > 0 && DebugVision.Value) + { + foreach (var player in VisiblePlayers) + { + DebugGizmos.SingleObjects.Line(HeadPosition, player.MainParts[BodyPartType.body].Position, Color.blue, 0.025f, true, 0.1f, true); + } + } + var goalEnemy = BotOwner.Memory.GoalEnemy; if (goalEnemy != null) { @@ -91,11 +102,11 @@ private void Update() //AILimit.Update(); if (AILimit.Enabled) { - return; + //return; } + BotSquad.Update(); Info.Update(); BotStuck.Update(); - BotSquad.Update(); Decision.Update(); Cover.Update(); Talk.Update(); diff --git a/Config/VisionConfig.cs b/Config/VisionConfig.cs index 6ae8a86bb..bafd43d95 100644 --- a/Config/VisionConfig.cs +++ b/Config/VisionConfig.cs @@ -4,6 +4,8 @@ namespace SAIN.UserSettings { internal class VisionConfig { + public static ConfigEntry EnableVisionJobs { get; private set; } + public static ConfigEntry CheckFrameCount { get; private set; } public static ConfigEntry AbsoluteMaxVisionDistance { get; private set; } public static ConfigEntry NoGlobalFog { get; private set; } public static ConfigEntry DebugWeather { get; private set; } @@ -13,6 +15,16 @@ public static void Init(ConfigFile Config) { string debugmode = "Vision Settings"; + EnableVisionJobs = Config.Bind(debugmode, "Enable Vision Job System", true, + new ConfigDescription("", + null, + new ConfigurationManagerAttributes { IsAdvanced = false, Order = 3 })); + + CheckFrameCount = Config.Bind(debugmode, "CheckFrameCount", 5, + new ConfigDescription("", + new AcceptableValueRange(1, 30), + new ConfigurationManagerAttributes { IsAdvanced = false, Order = 3 })); + NoGlobalFog = Config.Bind(debugmode, "Is Global Fog Disabled?", false, new ConfigDescription("This does not disable global fog. Only enable this if you have global fog disabled through mods like AmandsGraphics. This Allows AI to see much further to reflect the lack of any global fog, and scales weather modifier accordingly.", null, diff --git a/Layers/SAIN Combat/Actions/MoveToCoverAction.cs b/Layers/SAIN Combat/Actions/MoveToCoverAction.cs index 62f623465..cce5cd5f4 100644 --- a/Layers/SAIN Combat/Actions/MoveToCoverAction.cs +++ b/Layers/SAIN Combat/Actions/MoveToCoverAction.cs @@ -39,7 +39,6 @@ public override void Start() public override void Stop() { - MoveToCover.ToggleSprint(false); } private MoveToCoverObject MoveToCover; diff --git a/Plugin.cs b/Plugin.cs index bd944a487..4ca19e0e1 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -109,6 +109,8 @@ private void Update() return; } + Singleton.Instance.GetOrAddComponent(); + // Add Components to main player if (!ComponentAdded) { diff --git a/SAIN.csproj b/SAIN.csproj index e2a8d3dc6..af91980d2 100644 --- a/SAIN.csproj +++ b/SAIN.csproj @@ -198,6 +198,7 @@ + @@ -306,6 +307,6 @@ copy "$(TargetPath)" "C:\Games\spt 3.5.7\BepInEx\plugins\$(TargetName)-$(ConfigurationName).dll" -copy "$(TargetPath)" "$(SolutionDir)Release\$(TargetName)\BepInEx\plugins\$(TargetName)-$(ConfigurationName).dll" + \ No newline at end of file diff --git a/bin/x86/3.5.7/SAIN.dll b/bin/x86/3.5.7/SAIN.dll index 400e3e09a..ce2a0f6c5 100644 Binary files a/bin/x86/3.5.7/SAIN.dll and b/bin/x86/3.5.7/SAIN.dll differ diff --git a/bin/x86/3.5.7/SAIN.pdb b/bin/x86/3.5.7/SAIN.pdb index 00b11fc4d..839fb0d13 100644 Binary files a/bin/x86/3.5.7/SAIN.pdb and b/bin/x86/3.5.7/SAIN.pdb differ diff --git a/obj/x86/3.5.7/DesignTimeResolveAssemblyReferencesInput.cache b/obj/x86/3.5.7/DesignTimeResolveAssemblyReferencesInput.cache index 21ce1abd2..7c4ac3fca 100644 Binary files a/obj/x86/3.5.7/DesignTimeResolveAssemblyReferencesInput.cache and b/obj/x86/3.5.7/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/obj/x86/3.5.7/SAIN.csproj.AssemblyReference.cache b/obj/x86/3.5.7/SAIN.csproj.AssemblyReference.cache index 4be8beb58..26e7d5628 100644 Binary files a/obj/x86/3.5.7/SAIN.csproj.AssemblyReference.cache and b/obj/x86/3.5.7/SAIN.csproj.AssemblyReference.cache differ diff --git a/obj/x86/3.5.7/SAIN.csproj.CoreCompileInputs.cache b/obj/x86/3.5.7/SAIN.csproj.CoreCompileInputs.cache index e561148ec..389cdd68a 100644 --- a/obj/x86/3.5.7/SAIN.csproj.CoreCompileInputs.cache +++ b/obj/x86/3.5.7/SAIN.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -dd73e0037e7d581675d8a787a3b361588238b464 +664bf187ecfd85cf4bdaeb25e4218c139ce554db diff --git a/obj/x86/3.5.7/SAIN.dll b/obj/x86/3.5.7/SAIN.dll index 400e3e09a..ce2a0f6c5 100644 Binary files a/obj/x86/3.5.7/SAIN.dll and b/obj/x86/3.5.7/SAIN.dll differ diff --git a/obj/x86/3.5.7/SAIN.pdb b/obj/x86/3.5.7/SAIN.pdb index 00b11fc4d..839fb0d13 100644 Binary files a/obj/x86/3.5.7/SAIN.pdb and b/obj/x86/3.5.7/SAIN.pdb differ