From 7080a19f3b85a5f5c6fd40f16dbb2072badf38ac Mon Sep 17 00:00:00 2001 From: Shynd Date: Sat, 25 May 2024 19:51:42 -0400 Subject: [PATCH 1/5] add ability to toggle whether name plates and pings are displayed with pip optic --- Fika.Core/Coop/Custom/FikaHealthBar.cs | 2 +- Fika.Core/Coop/Factories/PingFactory.cs | 2 +- Fika.Core/FikaPlugin.cs | 2 +- Fika.Core/Utils/WorldToScreen.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Fika.Core/Coop/Custom/FikaHealthBar.cs b/Fika.Core/Coop/Custom/FikaHealthBar.cs index cb2dc73a..e638956a 100644 --- a/Fika.Core/Coop/Custom/FikaHealthBar.cs +++ b/Fika.Core/Coop/Custom/FikaHealthBar.cs @@ -100,7 +100,7 @@ private void UpdateScreenSpacePosition(bool throttleUpdate) float processedDistance = Mathf.Clamp(sqrDistance / 625, 0.6f, 1f); Vector3 position = new(currentPlayer.PlayerBones.Neck.position.x, currentPlayer.PlayerBones.Neck.position.y + (1f * processedDistance), currentPlayer.PlayerBones.Neck.position.z); - if (!WorldToScreen.GetScreenPoint(position, mainPlayer, out Vector3 screenPoint)) + if (!WorldToScreen.GetScreenPoint(position, mainPlayer, out Vector3 screenPoint, FikaPlugin.NamePlateUseOpticZoom.Value)) { UpdateColorTextMeshProUGUI(playerPlate.playerNameScreen, 0); UpdateColorImage(playerPlate.healthBarScreen, 0); diff --git a/Fika.Core/Coop/Factories/PingFactory.cs b/Fika.Core/Coop/Factories/PingFactory.cs index 3a47c5b5..ef011555 100644 --- a/Fika.Core/Coop/Factories/PingFactory.cs +++ b/Fika.Core/Coop/Factories/PingFactory.cs @@ -83,7 +83,7 @@ protected void Update() screenScale = outputWidth / inputWidth; } - if (WorldToScreen.GetScreenPoint(hitPoint, mainPlayer, out Vector3 screenPoint)) + if (WorldToScreen.GetScreenPoint(hitPoint, mainPlayer, out Vector3 screenPoint, FikaPlugin.PingUseOpticZoom.Value)) { float distanceToCenter = Vector3.Distance(screenPoint, new Vector3(Screen.width, Screen.height, 0) / 2); diff --git a/Fika.Core/FikaPlugin.cs b/Fika.Core/FikaPlugin.cs index daf2a0ce..b0cadd98 100644 --- a/Fika.Core/FikaPlugin.cs +++ b/Fika.Core/FikaPlugin.cs @@ -292,7 +292,7 @@ private void SetupConfig() UsePlateFactionSide = Config.Bind("Coop | Name Plates", "Show Player Faction Icon", true, new ConfigDescription("Shows the player faction icon next to the HP bar.", tags: new ConfigurationManagerAttributes() { Order = 7 })); - HideNamePlateInOptic = Config.Bind("Coop | Name Plates", "Hide Name Plate in Optic", true, new ConfigDescription("Hides the name plate when viewing through PiP scopes since it's kinda janky.", tags: new ConfigurationManagerAttributes() { Order = 6 })); + NamePlateUseOpticZoom = Config.Bind("Coop | Name Plates", "Name Plates Use Optic Zoom", true, new ConfigDescription("If name plate location should be displayed using the PiP optic camera.", tags: new ConfigurationManagerAttributes() { Order = 6, IsAdvanced = true })); DecreaseOpacityNotLookingAt = Config.Bind("Coop | Name Plates", "Decrease Opacity In Peripheral", true, new ConfigDescription("Decreases the opacity of the name plates when not looking at a player.", tags: new ConfigurationManagerAttributes() { Order = 5 })); diff --git a/Fika.Core/Utils/WorldToScreen.cs b/Fika.Core/Utils/WorldToScreen.cs index d40b6b98..b93f212d 100644 --- a/Fika.Core/Utils/WorldToScreen.cs +++ b/Fika.Core/Utils/WorldToScreen.cs @@ -8,7 +8,7 @@ namespace Fika.Core.Utils { public static class WorldToScreen { - public static bool GetScreenPoint(Vector3 worldPosition, CoopPlayer mainPlayer, out Vector3 screenPoint) + public static bool GetScreenPoint(Vector3 worldPosition, CoopPlayer mainPlayer, out Vector3 screenPoint, bool useOpticCamera = true) { CameraClass worldCameraInstance = CameraClass.Instance; Camera worldCamera = worldCameraInstance.Camera; @@ -22,7 +22,7 @@ public static bool GetScreenPoint(Vector3 worldPosition, CoopPlayer mainPlayer, ProceduralWeaponAnimation weaponAnimation = mainPlayer.ProceduralWeaponAnimation; - if (weaponAnimation != null) + if (useOpticCamera && weaponAnimation != null) { if (weaponAnimation.IsAiming && weaponAnimation.CurrentScope.IsOptic) { From 442c27902155b92199ddc83183b079eff5f2fbd9 Mon Sep 17 00:00:00 2001 From: Shynd Date: Sat, 25 May 2024 19:52:38 -0400 Subject: [PATCH 2/5] add option for minimum ping opacity --- Fika.Core/Coop/Factories/PingFactory.cs | 2 +- Fika.Core/FikaPlugin.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Fika.Core/Coop/Factories/PingFactory.cs b/Fika.Core/Coop/Factories/PingFactory.cs index ef011555..207c4e25 100644 --- a/Fika.Core/Coop/Factories/PingFactory.cs +++ b/Fika.Core/Coop/Factories/PingFactory.cs @@ -89,7 +89,7 @@ protected void Update() if (distanceToCenter < 200) { - image.color = new Color(_pingColor.r, _pingColor.g, _pingColor.b, Mathf.Max(0.05f, distanceToCenter / 200)); + image.color = new Color(_pingColor.r, _pingColor.g, _pingColor.b, Mathf.Max(FikaPlugin.PingMinimumOpacity.Value, distanceToCenter / 200)); } else { diff --git a/Fika.Core/FikaPlugin.cs b/Fika.Core/FikaPlugin.cs index b0cadd98..f9547ba8 100644 --- a/Fika.Core/FikaPlugin.cs +++ b/Fika.Core/FikaPlugin.cs @@ -320,7 +320,7 @@ private void SetupConfig() PlayPingAnimation = Config.Bind("Coop | Custom", "Play Ping Animation", false, new ConfigDescription("Plays the pointing animation automatically when pinging. Can interfere with gameplay.", tags: new ConfigurationManagerAttributes() { Order = 2 })); - ShowPingDuringOptics = Config.Bind("Coop | Custom", "Show Ping During Optics", false, new ConfigDescription("If pings should be displayed while aiming down an optics scope.", tags: new ConfigurationManagerAttributes() { Order = 1 })); + PingMinimumOpacity = Config.Bind("Coop | Custom", "Ping Minimum Opacity", 0.05f, new ConfigDescription("The minimum opacity of pings when looking straight at them.", new AcceptableValueRange(0f, 0.5f), new ConfigurationManagerAttributes() { Order = 0, IsAdvanced = true })); // Coop | Debug From acac5435bc0c566715950d941c964de460cc57da Mon Sep 17 00:00:00 2001 From: Shynd Date: Sat, 25 May 2024 19:53:42 -0400 Subject: [PATCH 3/5] add option to disable ping scaling with distance --- Fika.Core/Coop/Factories/PingFactory.cs | 11 ++++++++++- Fika.Core/FikaPlugin.cs | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Fika.Core/Coop/Factories/PingFactory.cs b/Fika.Core/Coop/Factories/PingFactory.cs index 207c4e25..d53604d0 100644 --- a/Fika.Core/Coop/Factories/PingFactory.cs +++ b/Fika.Core/Coop/Factories/PingFactory.cs @@ -108,7 +108,16 @@ public virtual void Initialize(ref Vector3 point, Object userObject, Color pingC float distance = Mathf.Clamp(Vector3.Distance(CameraClass.Instance.Camera.transform.position, transform.position) / 100, 0.4f, 0.6f); float pingSize = FikaPlugin.PingSize.Value; - image.rectTransform.localScale = new Vector3(pingSize, pingSize, pingSize) * distance; + Vector3 scaledSize = new Vector3(pingSize, pingSize, pingSize); + if (FikaPlugin.PingScaleWithDistance.Value == true) + { + scaledSize *= distance; + } + else + { + scaledSize *= 0.5f; + } + image.rectTransform.localScale = scaledSize; } } diff --git a/Fika.Core/FikaPlugin.cs b/Fika.Core/FikaPlugin.cs index f9547ba8..840d9f92 100644 --- a/Fika.Core/FikaPlugin.cs +++ b/Fika.Core/FikaPlugin.cs @@ -318,7 +318,7 @@ private void SetupConfig() PingTime = Config.Bind("Coop | Custom", "Ping Time", 3, new ConfigDescription("How long pings should be displayed.", new AcceptableValueRange(2, 10), new ConfigurationManagerAttributes() { Order = 3 })); - PlayPingAnimation = Config.Bind("Coop | Custom", "Play Ping Animation", false, new ConfigDescription("Plays the pointing animation automatically when pinging. Can interfere with gameplay.", tags: new ConfigurationManagerAttributes() { Order = 2 })); + PingScaleWithDistance = Config.Bind("Coop | Custom", "Ping Scale With Distance", true, new ConfigDescription("If ping size should scale with distance from player.", tags: new ConfigurationManagerAttributes() { Order = 1, IsAdvanced = true })); PingMinimumOpacity = Config.Bind("Coop | Custom", "Ping Minimum Opacity", 0.05f, new ConfigDescription("The minimum opacity of pings when looking straight at them.", new AcceptableValueRange(0f, 0.5f), new ConfigurationManagerAttributes() { Order = 0, IsAdvanced = true })); From 92cec3744d836e6ea60a587ae93dad0a4d5775f7 Mon Sep 17 00:00:00 2001 From: Shynd Date: Sat, 25 May 2024 19:54:12 -0400 Subject: [PATCH 4/5] reorder plugin options --- Fika.Core/FikaPlugin.cs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/Fika.Core/FikaPlugin.cs b/Fika.Core/FikaPlugin.cs index 840d9f92..f7486fad 100644 --- a/Fika.Core/FikaPlugin.cs +++ b/Fika.Core/FikaPlugin.cs @@ -115,6 +115,7 @@ public class FikaPlugin : BaseUnityPlugin public static ConfigEntry UseHealthNumber { get; set; } public static ConfigEntry UsePlateFactionSide { get; set; } public static ConfigEntry HideNamePlateInOptic { get; set; } + public static ConfigEntry NamePlateUseOpticZoom { get; set; } public static ConfigEntry DecreaseOpacityNotLookingAt { get; set; } public static ConfigEntry NamePlateScale { get; set; } public static ConfigEntry OpacityInADS { get; set; } @@ -130,6 +131,9 @@ public class FikaPlugin : BaseUnityPlugin public static ConfigEntry PingTime { get; set; } public static ConfigEntry PlayPingAnimation { get; set; } public static ConfigEntry ShowPingDuringOptics { get; set; } + public static ConfigEntry PingUseOpticZoom { get; set; } + public static ConfigEntry PingScaleWithDistance { get; set; } + public static ConfigEntry PingMinimumOpacity { get; set; } // Coop | Debug public static ConfigEntry FreeCamButton { get; set; } @@ -284,13 +288,15 @@ private void SetupConfig() // Coop | Name Plates - UseNamePlates = Config.Bind("Coop | Name Plates", "Show Player Name Plates", false, new ConfigDescription("Toggle Health-Bars & Names.", tags: new ConfigurationManagerAttributes() { Order = 10 })); + UseNamePlates = Config.Bind("Coop | Name Plates", "Show Player Name Plates", false, new ConfigDescription("Toggle Health-Bars & Names.", tags: new ConfigurationManagerAttributes() { Order = 11 })); - HideHealthBar = Config.Bind("Coop | Name Plates", "Hide Health Bar", false, new ConfigDescription("Completely hides the health bar.", tags: new ConfigurationManagerAttributes() { Order = 9 })); + HideHealthBar = Config.Bind("Coop | Name Plates", "Hide Health Bar", false, new ConfigDescription("Completely hides the health bar.", tags: new ConfigurationManagerAttributes() { Order = 10 })); - UseHealthNumber = Config.Bind("Coop | Name Plates", "Show HP% instead of bar", false, new ConfigDescription("Shows health in % amount instead of using the bar.", tags: new ConfigurationManagerAttributes() { Order = 8 })); + UseHealthNumber = Config.Bind("Coop | Name Plates", "Show HP% instead of bar", false, new ConfigDescription("Shows health in % amount instead of using the bar.", tags: new ConfigurationManagerAttributes() { Order = 9 })); - UsePlateFactionSide = Config.Bind("Coop | Name Plates", "Show Player Faction Icon", true, new ConfigDescription("Shows the player faction icon next to the HP bar.", tags: new ConfigurationManagerAttributes() { Order = 7 })); + UsePlateFactionSide = Config.Bind("Coop | Name Plates", "Show Player Faction Icon", true, new ConfigDescription("Shows the player faction icon next to the HP bar.", tags: new ConfigurationManagerAttributes() { Order = 8 })); + + HideNamePlateInOptic = Config.Bind("Coop | Name Plates", "Hide Name Plate in Optic", true, new ConfigDescription("Hides the name plate when viewing through PiP scopes.", tags: new ConfigurationManagerAttributes() { Order = 7 })); NamePlateUseOpticZoom = Config.Bind("Coop | Name Plates", "Name Plates Use Optic Zoom", true, new ConfigDescription("If name plate location should be displayed using the PiP optic camera.", tags: new ConfigurationManagerAttributes() { Order = 6, IsAdvanced = true })); @@ -308,15 +314,21 @@ private void SetupConfig() // Coop | Custom - UsePingSystem = Config.Bind("Coop | Custom", "Ping System", false, new ConfigDescription("Toggle Ping System. If enabled you can receive and send pings by pressing the ping key.", tags: new ConfigurationManagerAttributes() { Order = 7 })); + UsePingSystem = Config.Bind("Coop | Custom", "Ping System", false, new ConfigDescription("Toggle Ping System. If enabled you can receive and send pings by pressing the ping key.", tags: new ConfigurationManagerAttributes() { Order = 9 })); + + PingButton = Config.Bind("Coop | Custom", "Ping Button", new KeyboardShortcut(KeyCode.U), new ConfigDescription("Button used to send pings.", tags: new ConfigurationManagerAttributes() { Order = 8 })); + + PingColor = Config.Bind("Coop | Custom", "Ping Color", Color.white, new ConfigDescription("The color of your pings when displayed for other players.", tags: new ConfigurationManagerAttributes() { Order = 7 })); + + PingSize = Config.Bind("Coop | Custom", "Ping Size", 1f, new ConfigDescription("The multiplier of the ping size.", new AcceptableValueRange(0.1f, 2f), new ConfigurationManagerAttributes() { Order = 6 })); - PingButton = Config.Bind("Coop | Custom", "Ping Button", new KeyboardShortcut(KeyCode.U), new ConfigDescription("Button used to send pings.", tags: new ConfigurationManagerAttributes() { Order = 6 })); + PingTime = Config.Bind("Coop | Custom", "Ping Time", 3, new ConfigDescription("How long pings should be displayed.", new AcceptableValueRange(2, 10), new ConfigurationManagerAttributes() { Order = 5 })); - PingColor = Config.Bind("Coop | Custom", "Ping Color", Color.white, new ConfigDescription("The color of your pings when displayed for other players.", tags: new ConfigurationManagerAttributes() { Order = 5 })); + PlayPingAnimation = Config.Bind("Coop | Custom", "Play Ping Animation", false, new ConfigDescription("Plays the pointing animation automatically when pinging. Can interfere with gameplay.", tags: new ConfigurationManagerAttributes() { Order = 4 })); - PingSize = Config.Bind("Coop | Custom", "Ping Size", 1f, new ConfigDescription("The multiplier of the ping size.", new AcceptableValueRange(0.1f, 2f), new ConfigurationManagerAttributes() { Order = 4 })); + ShowPingDuringOptics = Config.Bind("Coop | Custom", "Show Ping During Optics", false, new ConfigDescription("If pings should be displayed while aiming down an optics scope.", tags: new ConfigurationManagerAttributes() { Order = 3 })); - PingTime = Config.Bind("Coop | Custom", "Ping Time", 3, new ConfigDescription("How long pings should be displayed.", new AcceptableValueRange(2, 10), new ConfigurationManagerAttributes() { Order = 3 })); + PingUseOpticZoom = Config.Bind("Coop | Custom", "Ping Use Optic Zoom", true, new ConfigDescription("If ping location should be displayed using the PiP optic camera.", tags: new ConfigurationManagerAttributes() { Order = 2, IsAdvanced = true })); PingScaleWithDistance = Config.Bind("Coop | Custom", "Ping Scale With Distance", true, new ConfigDescription("If ping size should scale with distance from player.", tags: new ConfigurationManagerAttributes() { Order = 1, IsAdvanced = true })); From de7fcde7fd66e90d1ca16db8c003fe52552d9800 Mon Sep 17 00:00:00 2001 From: Lacyway <20912169+Lacyway@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:05:11 +0200 Subject: [PATCH 5/5] Move pinger to earlier stage, preventing timeouts when loading --- Fika.Core/Coop/Components/CoopHandler.cs | 1226 ++++++++--------- Fika.Core/Coop/Components/FikaPinger.cs | 47 + Fika.Core/Coop/GameMode/CoopGame.cs | 2 + ...arkovApplication_LocalGameCreator_Patch.cs | 10 +- Fika.Core/Coop/Utils/NetManagerUtils.cs | 26 + Fika.Core/Networking/Models/PingRequest.cs | 22 +- 6 files changed, 689 insertions(+), 644 deletions(-) create mode 100644 Fika.Core/Coop/Components/FikaPinger.cs diff --git a/Fika.Core/Coop/Components/CoopHandler.cs b/Fika.Core/Coop/Components/CoopHandler.cs index 96369912..2a509fbd 100644 --- a/Fika.Core/Coop/Components/CoopHandler.cs +++ b/Fika.Core/Coop/Components/CoopHandler.cs @@ -8,8 +8,6 @@ using Fika.Core.Coop.Matchmaker; using Fika.Core.Coop.Players; using Fika.Core.Networking; -using Fika.Core.Networking.Http; -using Fika.Core.Networking.Http.Models; using LiteNetLib; using LiteNetLib.Utils; using System; @@ -21,633 +19,599 @@ namespace Fika.Core.Coop.Components { - /// - /// CoopHandler is the User 1-2-1 communication to the Server. This can be seen as an extension component to CoopGame. - /// - public class CoopHandler : MonoBehaviour - { - #region Fields/Properties - public Dictionary ListOfInteractiveObjects { get; private set; } = []; - public string ServerId { get; set; } = null; - /// - /// ProfileId to Player instance - /// - public Dictionary Players { get; } = new(); - public int HumanPlayers = 1; - public List ExtractedPlayers { get; set; } = []; - ManualLogSource Logger; - public CoopPlayer MyPlayer => (CoopPlayer)Singleton.Instance.MainPlayer; - - public List queuedProfileIds = []; - private Queue spawnQueue = new(50); - - //private Thread loopThread; - //private CancellationTokenSource loopToken; - - public class SpawnObject(Profile profile, Vector3 position, bool isAlive, bool isAI, int netId) - { - public Profile Profile { get; set; } = profile; - public Vector3 Position { get; set; } = position; - public bool IsAlive { get; set; } = isAlive; - public bool IsAI { get; set; } = isAI; - public int NetId { get; set; } = netId; - } - - public bool RunAsyncTasks { get; set; } = true; - - internal FikaBTRManager_Client clientBTR = null; - internal FikaBTRManager_Host serverBTR = null; - - internal static GameObject CoopHandlerParent; - - private Coroutine PingRoutine; - - #endregion - - #region Public Voids - - public static CoopHandler GetCoopHandler() - { - if (CoopHandlerParent == null) - { - return null; - } - - CoopHandler coopHandler = CoopHandler.CoopHandlerParent.GetComponent(); - if (coopHandler != null) - { - return coopHandler; - } - - return null; - } - - public static bool TryGetCoopHandler(out CoopHandler coopHandler) - { - coopHandler = GetCoopHandler(); - return coopHandler != null; - } - - public static string GetServerId() - { - CoopHandler coopGC = GetCoopHandler(); - if (coopGC == null) - { - return MatchmakerAcceptPatches.GetGroupId(); - } - - return coopGC.ServerId; - } - #endregion - - #region Unity Component Methods - - /// - /// Unity Component Awake Method - /// - protected void Awake() - { - // ---------------------------------------------------- - // Create a BepInEx Logger for CoopHandler - Logger = BepInEx.Logging.Logger.CreateLogSource("CoopHandler"); - } - - /// - /// Unity Component Start Method - /// - protected void Start() - { - if (MatchmakerAcceptPatches.IsServer) - { - PingRoutine = StartCoroutine(PingServer()); - } - - if (MatchmakerAcceptPatches.IsClient) - { - _ = Task.Run(ReadFromServerCharactersLoop); - } - - StartCoroutine(ProcessSpawnQueue()); - - WorldInteractiveObject[] interactiveObjects = FindObjectsOfType(); - foreach (WorldInteractiveObject interactiveObject in interactiveObjects) - { - ListOfInteractiveObjects.Add(interactiveObject.Id, interactiveObject); - } - } - - private IEnumerator PingServer() - { - //string serialized = new PingRequest().ToJson(); - PingRequest pingRequest = new(); - - while (true) - { - yield return new WaitForSeconds(30); - //RequestHandler.PutJson("/fika/update/ping", serialized); - Task pingTask = FikaRequestHandler.UpdatePing(pingRequest); - while (!pingTask.IsCompleted) - { - yield return null; - } - } - } - - protected void OnDestroy() - { - Players.Clear(); - - RunAsyncTasks = false; - - StopCoroutine(ProcessSpawnQueue()); - if (PingRoutine != null) - { - StopCoroutine(PingRoutine); - } - } - - private bool requestQuitGame = false; - - /// - /// The state your character or game is in to Quit. - /// - public enum EQuitState - { - NONE = -1, - YouAreDead, - YouHaveExtracted - } - - public EQuitState GetQuitState() - { - EQuitState quitState = EQuitState.NONE; - - if (!Singleton.Instantiated) - { - return quitState; - } - - IFikaGame coopGame = Singleton.Instance; - if (coopGame == null) - { - return quitState; - } - - if (Players == null) - { - return quitState; - } - - if (coopGame.ExtractedPlayers == null) - { - return quitState; - } - - if (MyPlayer == null) - { - return quitState; - } - - // Check alive status - if (!MyPlayer.HealthController.IsAlive) - { - quitState = EQuitState.YouAreDead; - } - - // Extractions - if (coopGame.ExtractedPlayers.Contains(MyPlayer.NetId)) - { - quitState = EQuitState.YouHaveExtracted; - } - - return quitState; - } - - /// - /// This handles the ways of exiting the active game session - /// - void ProcessQuitting() - { - EQuitState quitState = GetQuitState(); - - if (FikaPlugin.ExtractKey.Value.IsDown() && quitState != EQuitState.NONE && !requestQuitGame) - { - requestQuitGame = true; - - // If you are the server / host - if (MatchmakerAcceptPatches.IsServer) - { - // A host needs to wait for the team to extract or die! - if ((Singleton.Instance.NetServer.ConnectedPeersCount > 0) && quitState != EQuitState.NONE) - { - NotificationManagerClass.DisplayWarningNotification("HOSTING: You cannot exit the game until all clients have disconnected."); - requestQuitGame = false; - return; - } - else if (Singleton.Instance.NetServer.ConnectedPeersCount == 0 && Singleton.Instance.timeSinceLastPeerDisconnected > DateTime.Now.AddSeconds(-5) && Singleton.Instance.hasHadPeer) - { - NotificationManagerClass.DisplayWarningNotification($"HOSTING: Please wait at least 5 seconds after the last peer disconnected before quitting."); - requestQuitGame = false; - return; - } - else - { - Singleton.Instance.Stop(Singleton.Instance.MainPlayer.ProfileId, - Singleton.Instance.MyExitStatus, - MyPlayer.ActiveHealthController.IsAlive ? Singleton.Instance.MyExitLocation : null, 0); - } - } - else - { - Singleton.Instance.Stop(Singleton.Instance.MainPlayer.ProfileId, - Singleton.Instance.MyExitStatus, - MyPlayer.ActiveHealthController.IsAlive ? Singleton.Instance.MyExitLocation : null, 0); - } - return; - } - } - - protected private void Update() - { - if (!Singleton.Instantiated) - { - return; - } - - ProcessQuitting(); - } - - #endregion - - private async Task ReadFromServerCharactersLoop() - { - while (RunAsyncTasks) - { - CoopGame coopGame = (CoopGame)Singleton.Instance; - int waitTime = 2500; - if (coopGame.Status == GameStatus.Started) - { - waitTime = 15000; - } - await Task.Delay(waitTime); - - if (Players == null) - { - continue; - - } - - ReadFromServerCharacters(); - } - } - - private void ReadFromServerCharacters() - { - AllCharacterRequestPacket requestPacket = new(MyPlayer.ProfileId); - - if (Players.Count > 0) - { - requestPacket.HasCharacters = true; - requestPacket.Characters = [.. Players.Values.Select(p => p.ProfileId), .. queuedProfileIds]; - } - - NetDataWriter writer = Singleton.Instance.DataWriter; - if (writer != null) - { - writer.Reset(); - Singleton.Instance.SendData(writer, ref requestPacket, DeliveryMethod.ReliableOrdered); - } - } - - private async void SpawnPlayer(SpawnObject spawnObject) - { - if (Singleton.Instance.RegisteredPlayers.Any(x => x.ProfileId == spawnObject.Profile.ProfileId)) - { - return; - } - - if (Singleton.Instance.AllAlivePlayersList.Any(x => x.ProfileId == spawnObject.Profile.ProfileId)) - { - return; - } - - int playerId = Players.Count + Singleton.Instance.RegisteredPlayers.Count + 1; - if (spawnObject.Profile == null) - { - Logger.LogError("SpawnPlayer Profile is NULL!"); - queuedProfileIds.Remove(spawnObject.Profile.ProfileId); - return; - } - - IEnumerable allPrefabPaths = spawnObject.Profile.GetAllPrefabPaths(); - if (allPrefabPaths.Count() == 0) - { - Logger.LogError($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::PrefabPaths are empty!"); - return; - } - - await Singleton.Instance.LoadBundlesAndCreatePools(PoolManager.PoolsCategory.Raid, - PoolManager.AssemblyType.Local, - allPrefabPaths.ToArray(), - JobPriority.General).ContinueWith(x => - { - if (x.IsCompleted) - { - Logger.LogDebug($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::Load Complete"); - } - else if (x.IsFaulted) - { - Logger.LogError($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::Load Failed"); - } - else if (x.IsCanceled) - { - Logger.LogError($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::Load Cancelled"); - } - }); - - ObservedCoopPlayer otherPlayer = SpawnObservedPlayer(spawnObject.Profile, spawnObject.Position, playerId, spawnObject.IsAI, spawnObject.NetId); - - if (!spawnObject.IsAlive) - { - // TODO: Spawn them as corpses? - } - - if (MatchmakerAcceptPatches.IsServer) - { - if (LocalGameInstance != null) - { - CoopGame coopGame = (CoopGame)LocalGameInstance; - BotsController botController = coopGame.BotsController; - if (botController != null) - { - // Start Coroutine as botController might need a while to start sometimes... - Logger.LogInfo("Starting AddClientToBotEnemies routine."); - StartCoroutine(AddClientToBotEnemies(botController, otherPlayer)); - } - else - { - Logger.LogError("botController was null when trying to add player to enemies!"); - } - } - else - { - Logger.LogError("LocalGameInstance was null when trying to add player to enemies!"); - } - } - - queuedProfileIds.Remove(spawnObject.Profile.ProfileId); - } - - private IEnumerator ProcessSpawnQueue() - { - while (true) - { - yield return new WaitForSeconds(1f); - - if (Singleton.Instantiated) - { - if (spawnQueue.Count > 0) - { - SpawnPlayer(spawnQueue.Dequeue()); - } - else - { - yield return new WaitForSeconds(2); - } - } - else - { - yield return new WaitForSeconds(1); - } - } - } - - public void QueueProfile(Profile profile, Vector3 position, int netId, bool isAlive = true, bool isAI = false) - { - if (Singleton.Instance.RegisteredPlayers.Any(x => x.ProfileId == profile.ProfileId)) - { - return; - } - - if (Singleton.Instance.AllAlivePlayersList.Any(x => x.ProfileId == profile.ProfileId)) - { - return; - } - - if (queuedProfileIds.Contains(profile.ProfileId)) - { - return; - } - - queuedProfileIds.Add(profile.ProfileId); - Logger.LogInfo($"Queueing profile: {profile.Nickname}, {profile.ProfileId}"); - spawnQueue.Enqueue(new SpawnObject(profile, position, isAlive, isAI, netId)); - } - - public WorldInteractiveObject GetInteractiveObject(string objectId, out WorldInteractiveObject worldInteractiveObject) - { - if (ListOfInteractiveObjects.TryGetValue(objectId, out worldInteractiveObject)) - { - return worldInteractiveObject; - } - return null; - } - - private ObservedCoopPlayer SpawnObservedPlayer(Profile profile, Vector3 position, int playerId, bool isAI, int netId) - { - ObservedCoopPlayer otherPlayer = ObservedCoopPlayer.CreateObservedPlayer(playerId, position, Quaternion.identity, - "Player", isAI == true ? "Bot_" : $"Player_{profile.Nickname}_", EPointOfView.ThirdPerson, profile, isAI, - EUpdateQueue.Update, Player.EUpdateMode.Manual, Player.EUpdateMode.Auto, - GClass548.Config.CharacterController.ObservedPlayerMode, - () => Singleton.Instance.Control.Settings.MouseSensitivity, - () => Singleton.Instance.Control.Settings.MouseAimingSensitivity, - GClass1456.Default).Result; - - if (otherPlayer == null) - { - return null; - } - - otherPlayer.NetId = netId; - Logger.LogInfo($"SpawnObservedPlayer: {profile.Nickname} spawning with NetId {netId}"); - if (!isAI) - { - HumanPlayers++; - } - - if (!Players.ContainsKey(netId)) - { - Players.Add(netId, otherPlayer); - } - else - { - Logger.LogError($"Trying to add {otherPlayer.Profile.Nickname} to list of players but it was already there!"); - } - - if (!Singleton.Instance.RegisteredPlayers.Any(x => x.Profile.ProfileId == profile.ProfileId)) - { - Singleton.Instance.RegisteredPlayers.Add(otherPlayer); - } - - foreach (CoopPlayer player in Players.Values) - { - if (player is not ObservedCoopPlayer) - { - continue; - } - - Collider playerCollider = otherPlayer.GetCharacterControllerCommon().GetCollider(); - Collider otherCollider = player.GetCharacterControllerCommon().GetCollider(); - - if (playerCollider != null && otherCollider != null) - { - GClass649.IgnoreCollision(playerCollider, otherCollider); - } - } - - if (isAI) - { - if (profile.Info.Side is EPlayerSide.Bear or EPlayerSide.Usec) - { - Item backpack = profile.Inventory.Equipment.GetSlot(EquipmentSlot.Backpack).ContainedItem; - backpack?.GetAllItems().Where(i => i != backpack).ExecuteForEach(i => i.SpawnedInSession = true); - - // We still want DogTags to be 'FiR' - Item item = otherPlayer.Inventory.Equipment.GetSlot(EquipmentSlot.Dogtag).ContainedItem; - if (item != null) - { - item.SpawnedInSession = true; - } - } - } - else if (profile.Info.Side != EPlayerSide.Savage)// Make Player PMC items are all not 'FiR' - { - profile.SetSpawnedInSession(false); - - // We still want DogTags to be 'FiR' - Item item = otherPlayer.Inventory.Equipment.GetSlot(EquipmentSlot.Dogtag).ContainedItem; - if (item != null) - { - item.SpawnedInSession = true; - } - } - - otherPlayer.InitObservedPlayer(); - - Logger.LogDebug($"CreateLocalPlayer::{profile.Info.Nickname}::Spawned."); - - SetWeaponInHandsOfNewPlayer(otherPlayer, () => { }); - - return otherPlayer; - } - - private IEnumerator AddClientToBotEnemies(BotsController botController, LocalPlayer playerToAdd) - { - CoopGame coopGame = (CoopGame)LocalGameInstance; - - Logger.LogInfo($"AddClientToBotEnemies: " + playerToAdd.Profile.Nickname); - - while (coopGame.Status != GameStatus.Running && !botController.IsEnable) - { - yield return null; - } - - while (coopGame.BotsController.BotSpawner == null) - { - yield return null; - } - - Logger.LogInfo($"Adding Client {playerToAdd.Profile.Nickname} to enemy list"); - botController.AddActivePLayer(playerToAdd); - - bool found = false; - - for (int i = 0; i < botController.BotSpawner.PlayersCount; i++) - { - if (botController.BotSpawner.GetPlayer(i) == playerToAdd) - { - found = true; - break; - } - } - - if (found) - { - Logger.LogInfo($"Verified that {playerToAdd.Profile.Nickname} was added to the enemy list."); - } - else - { - Logger.LogInfo($"Failed to add {playerToAdd.Profile.Nickname} to the enemy list."); - } - } - - /// - /// Attempts to set up the New Player with the current weapon after spawning - /// - /// The player to set the item on - public void SetWeaponInHandsOfNewPlayer(Player person, Action successCallback) - { - EquipmentClass equipment = person.Profile.Inventory.Equipment; - if (equipment == null) - { - Logger.LogError($"SetWeaponInHandsOfNewPlayer: {person.Profile.ProfileId} has no Equipment!"); - } - Item item = null; - - if (equipment.GetSlot(EquipmentSlot.FirstPrimaryWeapon).ContainedItem != null) - { - item = equipment.GetSlot(EquipmentSlot.FirstPrimaryWeapon).ContainedItem; - } - - if (item == null && equipment.GetSlot(EquipmentSlot.SecondPrimaryWeapon).ContainedItem != null) - { - item = equipment.GetSlot(EquipmentSlot.SecondPrimaryWeapon).ContainedItem; - } - - if (item == null && equipment.GetSlot(EquipmentSlot.Holster).ContainedItem != null) - { - item = equipment.GetSlot(EquipmentSlot.Holster).ContainedItem; - } - - if (item == null && equipment.GetSlot(EquipmentSlot.Scabbard).ContainedItem != null) - { - item = equipment.GetSlot(EquipmentSlot.Scabbard).ContainedItem; - } - - if (item == null) - { - Logger.LogError($"SetWeaponInHandsOfNewPlayer:Unable to find any weapon for {person.Profile.ProfileId}"); - } - - person.SetItemInHands(item, (IResult) => - { - if (IResult.Failed == true) - { - Logger.LogError($"SetWeaponInHandsOfNewPlayer:Unable to set item {item} in hands for {person.Profile.ProfileId}"); - } - - if (IResult.Succeed == true) - { - successCallback?.Invoke(); - } - - if (person.TryGetItemInHands() != null) - { - successCallback?.Invoke(); - } - }); - } - - public BaseLocalGame LocalGameInstance { get; internal set; } - } - - public enum ESpawnState - { - None = 0, - Loading = 1, - Spawning = 2, - Spawned = 3, - Ignore = 98, - Error = 99, - } + /// + /// CoopHandler is the User 1-2-1 communication to the Server. This can be seen as an extension component to CoopGame. + /// + public class CoopHandler : MonoBehaviour + { + #region Fields/Properties + public Dictionary ListOfInteractiveObjects { get; private set; } = []; + public string ServerId { get; set; } = null; + public Dictionary Players { get; } = new(); + public int HumanPlayers = 1; + public List ExtractedPlayers { get; set; } = []; + ManualLogSource Logger; + public CoopPlayer MyPlayer => (CoopPlayer)Singleton.Instance.MainPlayer; + + public List queuedProfileIds = []; + private Queue spawnQueue = new(50); + + public class SpawnObject(Profile profile, Vector3 position, bool isAlive, bool isAI, int netId) + { + public Profile Profile { get; set; } = profile; + public Vector3 Position { get; set; } = position; + public bool IsAlive { get; set; } = isAlive; + public bool IsAI { get; set; } = isAI; + public int NetId { get; set; } = netId; + } + + public bool RunAsyncTasks { get; set; } = true; + + internal FikaBTRManager_Client clientBTR = null; + internal FikaBTRManager_Host serverBTR = null; + + internal static GameObject CoopHandlerParent; + + #endregion + + #region Public Voids + + public static CoopHandler GetCoopHandler() + { + if (CoopHandlerParent == null) + { + return null; + } + + CoopHandler coopHandler = CoopHandler.CoopHandlerParent.GetComponent(); + if (coopHandler != null) + { + return coopHandler; + } + + return null; + } + + public static bool TryGetCoopHandler(out CoopHandler coopHandler) + { + coopHandler = GetCoopHandler(); + return coopHandler != null; + } + + public static string GetServerId() + { + CoopHandler coopGC = GetCoopHandler(); + if (coopGC == null) + { + return MatchmakerAcceptPatches.GetGroupId(); + } + + return coopGC.ServerId; + } + #endregion + + #region Unity Component Methods + + /// + /// Unity Component Awake Method + /// + protected void Awake() + { + // ---------------------------------------------------- + // Create a BepInEx Logger for CoopHandler + Logger = BepInEx.Logging.Logger.CreateLogSource("CoopHandler"); + } + + /// + /// Unity Component Start Method + /// + protected void Start() + { + if (MatchmakerAcceptPatches.IsClient) + { + _ = Task.Run(ReadFromServerCharactersLoop); + } + + StartCoroutine(ProcessSpawnQueue()); + + WorldInteractiveObject[] interactiveObjects = FindObjectsOfType(); + foreach (WorldInteractiveObject interactiveObject in interactiveObjects) + { + ListOfInteractiveObjects.Add(interactiveObject.Id, interactiveObject); + } + } + + protected void OnDestroy() + { + Players.Clear(); + + RunAsyncTasks = false; + + StopCoroutine(ProcessSpawnQueue()); + } + + private bool requestQuitGame = false; + + /// + /// The state your character or game is in to Quit. + /// + public enum EQuitState + { + NONE = -1, + YouAreDead, + YouHaveExtracted + } + + public EQuitState GetQuitState() + { + EQuitState quitState = EQuitState.NONE; + + if (!Singleton.Instantiated) + { + return quitState; + } + + IFikaGame coopGame = Singleton.Instance; + if (coopGame == null) + { + return quitState; + } + + if (Players == null) + { + return quitState; + } + + if (coopGame.ExtractedPlayers == null) + { + return quitState; + } + + if (MyPlayer == null) + { + return quitState; + } + + // Check alive status + if (!MyPlayer.HealthController.IsAlive) + { + quitState = EQuitState.YouAreDead; + } + + // Extractions + if (coopGame.ExtractedPlayers.Contains(MyPlayer.NetId)) + { + quitState = EQuitState.YouHaveExtracted; + } + + return quitState; + } + + /// + /// This handles the ways of exiting the active game session + /// + void ProcessQuitting() + { + EQuitState quitState = GetQuitState(); + + if (FikaPlugin.ExtractKey.Value.IsDown() && quitState != EQuitState.NONE && !requestQuitGame) + { + requestQuitGame = true; + + // If you are the server / host + if (MatchmakerAcceptPatches.IsServer) + { + // A host needs to wait for the team to extract or die! + if ((Singleton.Instance.NetServer.ConnectedPeersCount > 0) && quitState != EQuitState.NONE) + { + NotificationManagerClass.DisplayWarningNotification("HOSTING: You cannot exit the game until all clients have disconnected."); + requestQuitGame = false; + return; + } + else if (Singleton.Instance.NetServer.ConnectedPeersCount == 0 && Singleton.Instance.timeSinceLastPeerDisconnected > DateTime.Now.AddSeconds(-5) && Singleton.Instance.hasHadPeer) + { + NotificationManagerClass.DisplayWarningNotification($"HOSTING: Please wait at least 5 seconds after the last peer disconnected before quitting."); + requestQuitGame = false; + return; + } + else + { + Singleton.Instance.Stop(Singleton.Instance.MainPlayer.ProfileId, + Singleton.Instance.MyExitStatus, + MyPlayer.ActiveHealthController.IsAlive ? Singleton.Instance.MyExitLocation : null, 0); + } + } + else + { + Singleton.Instance.Stop(Singleton.Instance.MainPlayer.ProfileId, + Singleton.Instance.MyExitStatus, + MyPlayer.ActiveHealthController.IsAlive ? Singleton.Instance.MyExitLocation : null, 0); + } + return; + } + } + + protected private void Update() + { + if (!Singleton.Instantiated) + { + return; + } + + ProcessQuitting(); + } + + #endregion + + private async Task ReadFromServerCharactersLoop() + { + while (RunAsyncTasks) + { + CoopGame coopGame = (CoopGame)Singleton.Instance; + int waitTime = 2500; + if (coopGame.Status == GameStatus.Started) + { + waitTime = 15000; + } + await Task.Delay(waitTime); + + if (Players == null) + { + continue; + + } + + ReadFromServerCharacters(); + } + } + + private void ReadFromServerCharacters() + { + AllCharacterRequestPacket requestPacket = new(MyPlayer.ProfileId); + + if (Players.Count > 0) + { + requestPacket.HasCharacters = true; + requestPacket.Characters = [.. Players.Values.Select(p => p.ProfileId), .. queuedProfileIds]; + } + + NetDataWriter writer = Singleton.Instance.DataWriter; + if (writer != null) + { + writer.Reset(); + Singleton.Instance.SendData(writer, ref requestPacket, DeliveryMethod.ReliableOrdered); + } + } + + private async void SpawnPlayer(SpawnObject spawnObject) + { + if (Singleton.Instance.RegisteredPlayers.Any(x => x.ProfileId == spawnObject.Profile.ProfileId)) + { + return; + } + + if (Singleton.Instance.AllAlivePlayersList.Any(x => x.ProfileId == spawnObject.Profile.ProfileId)) + { + return; + } + + int playerId = Players.Count + Singleton.Instance.RegisteredPlayers.Count + 1; + if (spawnObject.Profile == null) + { + Logger.LogError("SpawnPlayer Profile is NULL!"); + queuedProfileIds.Remove(spawnObject.Profile.ProfileId); + return; + } + + IEnumerable allPrefabPaths = spawnObject.Profile.GetAllPrefabPaths(); + if (allPrefabPaths.Count() == 0) + { + Logger.LogError($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::PrefabPaths are empty!"); + return; + } + + await Singleton.Instance.LoadBundlesAndCreatePools(PoolManager.PoolsCategory.Raid, + PoolManager.AssemblyType.Local, + allPrefabPaths.ToArray(), + JobPriority.General).ContinueWith(x => + { + if (x.IsCompleted) + { + Logger.LogDebug($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::Load Complete"); + } + else if (x.IsFaulted) + { + Logger.LogError($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::Load Failed"); + } + else if (x.IsCanceled) + { + Logger.LogError($"SpawnPlayer::{spawnObject.Profile.Info.Nickname}::Load Cancelled"); + } + }); + + ObservedCoopPlayer otherPlayer = SpawnObservedPlayer(spawnObject.Profile, spawnObject.Position, playerId, spawnObject.IsAI, spawnObject.NetId); + + if (!spawnObject.IsAlive) + { + // TODO: Spawn them as corpses? + } + + if (MatchmakerAcceptPatches.IsServer) + { + if (LocalGameInstance != null) + { + CoopGame coopGame = (CoopGame)LocalGameInstance; + BotsController botController = coopGame.BotsController; + if (botController != null) + { + // Start Coroutine as botController might need a while to start sometimes... + Logger.LogInfo("Starting AddClientToBotEnemies routine."); + StartCoroutine(AddClientToBotEnemies(botController, otherPlayer)); + } + else + { + Logger.LogError("botController was null when trying to add player to enemies!"); + } + } + else + { + Logger.LogError("LocalGameInstance was null when trying to add player to enemies!"); + } + } + + queuedProfileIds.Remove(spawnObject.Profile.ProfileId); + } + + private IEnumerator ProcessSpawnQueue() + { + while (true) + { + yield return new WaitForSeconds(1f); + + if (Singleton.Instantiated) + { + if (spawnQueue.Count > 0) + { + SpawnPlayer(spawnQueue.Dequeue()); + } + else + { + yield return new WaitForSeconds(2); + } + } + else + { + yield return new WaitForSeconds(1); + } + } + } + + public void QueueProfile(Profile profile, Vector3 position, int netId, bool isAlive = true, bool isAI = false) + { + if (Singleton.Instance.RegisteredPlayers.Any(x => x.ProfileId == profile.ProfileId)) + { + return; + } + + if (Singleton.Instance.AllAlivePlayersList.Any(x => x.ProfileId == profile.ProfileId)) + { + return; + } + + if (queuedProfileIds.Contains(profile.ProfileId)) + { + return; + } + + queuedProfileIds.Add(profile.ProfileId); + Logger.LogInfo($"Queueing profile: {profile.Nickname}, {profile.ProfileId}"); + spawnQueue.Enqueue(new SpawnObject(profile, position, isAlive, isAI, netId)); + } + + public WorldInteractiveObject GetInteractiveObject(string objectId, out WorldInteractiveObject worldInteractiveObject) + { + if (ListOfInteractiveObjects.TryGetValue(objectId, out worldInteractiveObject)) + { + return worldInteractiveObject; + } + return null; + } + + private ObservedCoopPlayer SpawnObservedPlayer(Profile profile, Vector3 position, int playerId, bool isAI, int netId) + { + ObservedCoopPlayer otherPlayer = ObservedCoopPlayer.CreateObservedPlayer(playerId, position, Quaternion.identity, + "Player", isAI == true ? "Bot_" : $"Player_{profile.Nickname}_", EPointOfView.ThirdPerson, profile, isAI, + EUpdateQueue.Update, Player.EUpdateMode.Manual, Player.EUpdateMode.Auto, + GClass548.Config.CharacterController.ObservedPlayerMode, + () => Singleton.Instance.Control.Settings.MouseSensitivity, + () => Singleton.Instance.Control.Settings.MouseAimingSensitivity, + GClass1456.Default).Result; + + if (otherPlayer == null) + { + return null; + } + + otherPlayer.NetId = netId; + Logger.LogInfo($"SpawnObservedPlayer: {profile.Nickname} spawning with NetId {netId}"); + if (!isAI) + { + HumanPlayers++; + } + + if (!Players.ContainsKey(netId)) + { + Players.Add(netId, otherPlayer); + } + else + { + Logger.LogError($"Trying to add {otherPlayer.Profile.Nickname} to list of players but it was already there!"); + } + + if (!Singleton.Instance.RegisteredPlayers.Any(x => x.Profile.ProfileId == profile.ProfileId)) + { + Singleton.Instance.RegisteredPlayers.Add(otherPlayer); + } + + foreach (CoopPlayer player in Players.Values) + { + if (player is not ObservedCoopPlayer) + { + continue; + } + + Collider playerCollider = otherPlayer.GetCharacterControllerCommon().GetCollider(); + Collider otherCollider = player.GetCharacterControllerCommon().GetCollider(); + + if (playerCollider != null && otherCollider != null) + { + GClass649.IgnoreCollision(playerCollider, otherCollider); + } + } + + if (isAI) + { + if (profile.Info.Side is EPlayerSide.Bear or EPlayerSide.Usec) + { + Item backpack = profile.Inventory.Equipment.GetSlot(EquipmentSlot.Backpack).ContainedItem; + backpack?.GetAllItems().Where(i => i != backpack).ExecuteForEach(i => i.SpawnedInSession = true); + + // We still want DogTags to be 'FiR' + Item item = otherPlayer.Inventory.Equipment.GetSlot(EquipmentSlot.Dogtag).ContainedItem; + if (item != null) + { + item.SpawnedInSession = true; + } + } + } + else if (profile.Info.Side != EPlayerSide.Savage)// Make Player PMC items are all not 'FiR' + { + profile.SetSpawnedInSession(false); + + // We still want DogTags to be 'FiR' + Item item = otherPlayer.Inventory.Equipment.GetSlot(EquipmentSlot.Dogtag).ContainedItem; + if (item != null) + { + item.SpawnedInSession = true; + } + } + + otherPlayer.InitObservedPlayer(); + + Logger.LogDebug($"CreateLocalPlayer::{profile.Info.Nickname}::Spawned."); + + SetWeaponInHandsOfNewPlayer(otherPlayer, () => { }); + + return otherPlayer; + } + + private IEnumerator AddClientToBotEnemies(BotsController botController, LocalPlayer playerToAdd) + { + CoopGame coopGame = (CoopGame)LocalGameInstance; + + Logger.LogInfo($"AddClientToBotEnemies: " + playerToAdd.Profile.Nickname); + + while (coopGame.Status != GameStatus.Running && !botController.IsEnable) + { + yield return null; + } + + while (coopGame.BotsController.BotSpawner == null) + { + yield return null; + } + + Logger.LogInfo($"Adding Client {playerToAdd.Profile.Nickname} to enemy list"); + botController.AddActivePLayer(playerToAdd); + + bool found = false; + + for (int i = 0; i < botController.BotSpawner.PlayersCount; i++) + { + if (botController.BotSpawner.GetPlayer(i) == playerToAdd) + { + found = true; + break; + } + } + + if (found) + { + Logger.LogInfo($"Verified that {playerToAdd.Profile.Nickname} was added to the enemy list."); + } + else + { + Logger.LogInfo($"Failed to add {playerToAdd.Profile.Nickname} to the enemy list."); + } + } + + /// + /// Attempts to set up the New Player with the current weapon after spawning + /// + /// The player to set the item on + public void SetWeaponInHandsOfNewPlayer(Player person, Action successCallback) + { + EquipmentClass equipment = person.Profile.Inventory.Equipment; + if (equipment == null) + { + Logger.LogError($"SetWeaponInHandsOfNewPlayer: {person.Profile.ProfileId} has no Equipment!"); + } + Item item = null; + + if (equipment.GetSlot(EquipmentSlot.FirstPrimaryWeapon).ContainedItem != null) + { + item = equipment.GetSlot(EquipmentSlot.FirstPrimaryWeapon).ContainedItem; + } + + if (item == null && equipment.GetSlot(EquipmentSlot.SecondPrimaryWeapon).ContainedItem != null) + { + item = equipment.GetSlot(EquipmentSlot.SecondPrimaryWeapon).ContainedItem; + } + + if (item == null && equipment.GetSlot(EquipmentSlot.Holster).ContainedItem != null) + { + item = equipment.GetSlot(EquipmentSlot.Holster).ContainedItem; + } + + if (item == null && equipment.GetSlot(EquipmentSlot.Scabbard).ContainedItem != null) + { + item = equipment.GetSlot(EquipmentSlot.Scabbard).ContainedItem; + } + + if (item == null) + { + Logger.LogError($"SetWeaponInHandsOfNewPlayer:Unable to find any weapon for {person.Profile.ProfileId}"); + } + + person.SetItemInHands(item, (IResult) => + { + if (IResult.Failed == true) + { + Logger.LogError($"SetWeaponInHandsOfNewPlayer:Unable to set item {item} in hands for {person.Profile.ProfileId}"); + } + + if (IResult.Succeed == true) + { + successCallback?.Invoke(); + } + + if (person.TryGetItemInHands() != null) + { + successCallback?.Invoke(); + } + }); + } + + public BaseLocalGame LocalGameInstance { get; internal set; } + } + + public enum ESpawnState + { + None = 0, + Loading = 1, + Spawning = 2, + Spawned = 3, + Ignore = 98, + Error = 99, + } } diff --git a/Fika.Core/Coop/Components/FikaPinger.cs b/Fika.Core/Coop/Components/FikaPinger.cs new file mode 100644 index 00000000..bef9477a --- /dev/null +++ b/Fika.Core/Coop/Components/FikaPinger.cs @@ -0,0 +1,47 @@ +using Fika.Core.Networking.Http; +using Fika.Core.Networking.Http.Models; +using System.Collections; +using System.Threading.Tasks; +using UnityEngine; + +namespace Fika.Core.Coop.Components +{ + public class FikaPinger : MonoBehaviour + { + private Coroutine pingRoutine; + + public void StartPingRoutine() + { + pingRoutine = StartCoroutine(PingServer()); + } + + public void StopPingRoutine() + { + if (pingRoutine != null) + { + StopCoroutine(pingRoutine); + pingRoutine = null; + } + } + + private IEnumerator PingServer() + { + PingRequest pingRequest = new(); + + while (true) + { + Task pingTask = FikaRequestHandler.UpdatePing(pingRequest); + while (!pingTask.IsCompleted) + { + yield return null; + } + yield return new WaitForSeconds(30); + } + } + + private void OnDestroy() + { + StopPingRoutine(); + } + } +} diff --git a/Fika.Core/Coop/GameMode/CoopGame.cs b/Fika.Core/Coop/GameMode/CoopGame.cs index a068bcb6..977ca35e 100644 --- a/Fika.Core/Coop/GameMode/CoopGame.cs +++ b/Fika.Core/Coop/GameMode/CoopGame.cs @@ -1920,6 +1920,8 @@ public override void Dispose() Destroy(newDynamicAI); } + NetManagerUtils.StopPinger(); + FikaPlugin.DynamicAI.SettingChanged -= DynamicAI_SettingChanged; FikaPlugin.DynamicAIRate.SettingChanged -= DynamicAIRate_SettingChanged; } diff --git a/Fika.Core/Coop/Patches/LocalGame/TarkovApplication_LocalGameCreator_Patch.cs b/Fika.Core/Coop/Patches/LocalGame/TarkovApplication_LocalGameCreator_Patch.cs index 224cadaf..e28a2457 100644 --- a/Fika.Core/Coop/Patches/LocalGame/TarkovApplication_LocalGameCreator_Patch.cs +++ b/Fika.Core/Coop/Patches/LocalGame/TarkovApplication_LocalGameCreator_Patch.cs @@ -76,6 +76,8 @@ public static async Task Postfix(Task __result, TarkovApplication __instance, Ti throw new ArgumentNullException("timeHasComeScreenController"); } + bool isServer = MatchmakerAcceptPatches.IsServer; + LocationSettingsClass.Location location = ____raidSettings.SelectedLocation; MatchmakerAcceptPatches.GClass3182 = timeHasComeScreenController; @@ -86,6 +88,10 @@ public static async Task Postfix(Task __result, TarkovApplication __instance, Ti } NetManagerUtils.CreateNetManager(MatchmakerAcceptPatches.IsServer); + if (isServer) + { + NetManagerUtils.StartPinger(); + } ISession session = CurrentSession; @@ -99,7 +105,7 @@ public static async Task Postfix(Task __result, TarkovApplication __instance, Ti await session.SendRaidSettings(____raidSettings); - if (MatchmakerAcceptPatches.IsClient) + if (!isServer) { timeHasComeScreenController.ChangeStatus("Joining coop game..."); @@ -125,7 +131,7 @@ public static async Task Postfix(Task __result, TarkovApplication __instance, Ti Singleton.Create(coopGame); FikaEventDispatcher.DispatchEvent(new AbstractGameCreatedEvent(coopGame)); - if (MatchmakerAcceptPatches.IsClient) + if (!isServer) { coopGame.SetMatchmakerStatus("Coop game joined"); } diff --git a/Fika.Core/Coop/Utils/NetManagerUtils.cs b/Fika.Core/Coop/Utils/NetManagerUtils.cs index f3f700a7..be1f8bbd 100644 --- a/Fika.Core/Coop/Utils/NetManagerUtils.cs +++ b/Fika.Core/Coop/Utils/NetManagerUtils.cs @@ -1,5 +1,6 @@ using BepInEx.Logging; using Comfort.Common; +using Fika.Core.Coop.Components; using Fika.Core.Coop.Players; using Fika.Core.Networking; using System.Threading.Tasks; @@ -95,5 +96,30 @@ public static Task SetupGameVariables(bool isServer, CoopPlayer coopPlayer) return Task.CompletedTask; } + + public static void StartPinger() + { + if (FikaGameObject != null) + { + FikaPinger fikaPinger = FikaGameObject.AddComponent(); + fikaPinger.StartPingRoutine(); + } + } + + public static void StopPinger() + { + if (FikaGameObject != null) + { + FikaPinger fikaPinger = FikaGameObject.GetComponent(); + if (fikaPinger != null) + { + Object.Destroy(fikaPinger); + } + else + { + logger.LogError("StopPinger: Could not find FikaPinger!"); + } + } + } } } \ No newline at end of file diff --git a/Fika.Core/Networking/Models/PingRequest.cs b/Fika.Core/Networking/Models/PingRequest.cs index 1f858f19..49be16ed 100644 --- a/Fika.Core/Networking/Models/PingRequest.cs +++ b/Fika.Core/Networking/Models/PingRequest.cs @@ -1,17 +1,17 @@ -using Fika.Core.Coop.Components; +using Fika.Core.Coop.Matchmaker; using System.Runtime.Serialization; namespace Fika.Core.Networking.Http.Models { - [DataContract] - public struct PingRequest - { - [DataMember(Name = "serverId")] - public string ServerId; + [DataContract] + public struct PingRequest + { + [DataMember(Name = "serverId")] + public string ServerId; - public PingRequest() - { - ServerId = CoopHandler.GetServerId(); - } - } + public PingRequest() + { + ServerId = MatchmakerAcceptPatches.GetGroupId(); + } + } } \ No newline at end of file