diff --git a/Fika.Core/Coop/Custom/FikaHealthBar.cs b/Fika.Core/Coop/Custom/FikaHealthBar.cs index 1b8cca51..6564eb61 100644 --- a/Fika.Core/Coop/Custom/FikaHealthBar.cs +++ b/Fika.Core/Coop/Custom/FikaHealthBar.cs @@ -2,6 +2,7 @@ using Comfort.Common; using EFT; +using EFT.Animations; using EFT.UI; using Fika.Core.Bundles; using Fika.Core.Coop.Players; @@ -21,6 +22,8 @@ public class FikaHealthBar : MonoBehaviour private CoopPlayer mainPlayer; private PlayerPlateUI playerPlate; private float screenScale = 1f; + private int frameCounter = 0; + private readonly int throttleInterval = 60; // throttle to 1 update per 60 frames protected void Awake() { @@ -33,40 +36,53 @@ protected void Update() { if (currentPlayer != null) { - if (!FikaPlugin.UseNamePlates.Value) + bool throttleUpdate = IsThrottleUpdate(); + if (throttleUpdate) { - playerPlate.gameObject.SetActive(false); - return; - } - else if (playerPlate.gameObject.active == false) - { - playerPlate.gameObject.SetActive(true); - } - UpdateScreenSpacePosition(); - float currentHealth = currentPlayer.HealthController.GetBodyPartHealth(EBodyPart.Common, true).Current; - float maxHealth = currentPlayer.HealthController.GetBodyPartHealth(EBodyPart.Common, true).Maximum; - if (FikaPlugin.UseHealthNumber.Value) - { - if (!playerPlate.healthNumberBackgroundScreen.gameObject.activeSelf) + // Handling the visibility of elements + if (!FikaPlugin.UseNamePlates.Value) + { + playerPlate.gameObject.SetActive(false); + return; + } + else if (playerPlate.gameObject.active == false) { - playerPlate.healthNumberBackgroundScreen.gameObject.SetActive(true); - playerPlate.healthBarBackgroundScreen.gameObject.SetActive(false); + playerPlate.gameObject.SetActive(true); } - int healthNumberPercentage = (int)Math.Round((currentHealth / maxHealth) * 100); - playerPlate.SetHealthNumberText($"{healthNumberPercentage}%"); + SetPlayerPlateFactionVisibility(FikaPlugin.UsePlateFactionSide.Value); + SetPlayerPlateHealthVisibility(FikaPlugin.HideHealthBar.Value); } - else + // Updating the health bar + if (playerPlate.healthBarScreen.gameObject.activeSelf) { - if (!playerPlate.healthBarBackgroundScreen.gameObject.active) + float currentHealth = currentPlayer.HealthController.GetBodyPartHealth(EBodyPart.Common, true).Current; + float maxHealth = currentPlayer.HealthController.GetBodyPartHealth(EBodyPart.Common, true).Maximum; + if (FikaPlugin.UseHealthNumber.Value) { - playerPlate.healthNumberBackgroundScreen.gameObject.SetActive(false); - playerPlate.healthBarBackgroundScreen.gameObject.SetActive(true); + if (!playerPlate.healthNumberBackgroundScreen.gameObject.activeSelf) + { + playerPlate.healthNumberBackgroundScreen.gameObject.SetActive(true); + playerPlate.healthBarBackgroundScreen.gameObject.SetActive(false); + } + int healthNumberPercentage = (int)Math.Round((currentHealth / maxHealth) * 100); + playerPlate.SetHealthNumberText($"{healthNumberPercentage}%"); } + else + { + if (!playerPlate.healthBarBackgroundScreen.gameObject.active) + { + playerPlate.healthNumberBackgroundScreen.gameObject.SetActive(false); + playerPlate.healthBarBackgroundScreen.gameObject.SetActive(true); + } - float normalizedHealth = Mathf.Clamp01(currentHealth / maxHealth); - playerPlate.healthBarScreen.fillAmount = normalizedHealth; - UpdateHealthBarColor(normalizedHealth); + float normalizedHealth = Mathf.Clamp01(currentHealth / maxHealth); + playerPlate.healthBarScreen.fillAmount = normalizedHealth; + UpdateHealthBarColor(normalizedHealth); + } } + // Finally, update the screen space position + UpdateScreenSpacePosition(throttleUpdate); + // Destroy if this player is dead if (!currentPlayer.HealthController.IsAlive) { Destroy(this); @@ -78,79 +94,99 @@ protected void Update() } } - private void UpdateScreenSpacePosition() + private void UpdateScreenSpacePosition(bool throttleUpdate) { - if (mainPlayer.HealthController.IsAlive && mainPlayer.ProceduralWeaponAnimation.IsAiming) + // ADS opacity handling + float opacityMultiplier = 1f; + ProceduralWeaponAnimation proceduralWeaponAnimation = mainPlayer.ProceduralWeaponAnimation; + if (mainPlayer.HealthController.IsAlive && proceduralWeaponAnimation.IsAiming) { - if (mainPlayer.ProceduralWeaponAnimation.CurrentScope.IsOptic) + if (proceduralWeaponAnimation.CurrentScope.IsOptic && FikaPlugin.HideNamePlateInOptic.Value) { playerPlate.ScalarObjectScreen.active = false; return; } + opacityMultiplier = FikaPlugin.OpacityInADS.Value; } - else if (playerPlate.ScalarObjectScreen.active == false) + CameraClass cameraInstance = CameraClass.Instance; + Camera camera = cameraInstance.Camera; + + // Distance check + Vector3 direction = camera.transform.position - currentPlayer.Position; + float sqrDistance = direction.sqrMagnitude; + float maxDistanceToShow = FikaPlugin.MaxDistanceToShow.Value * FikaPlugin.MaxDistanceToShow.Value; + if (sqrDistance > maxDistanceToShow) { - playerPlate.ScalarObjectScreen.active = true; + playerPlate.ScalarObjectScreen.active = false; + return; } - Camera camera = CameraClass.Instance.Camera; + // If we're here, we can show the name plate + playerPlate.ScalarObjectScreen.active = true; + + 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); + Vector3 screenPoint = camera.WorldToScreenPoint(position); + + if (screenPoint.z <= 0) + { + UpdateColorTextMeshProUGUI(playerPlate.playerNameScreen, 0); + UpdateColorImage(playerPlate.healthBarScreen, 0); + UpdateColorTextMeshProUGUI(playerPlate.healthNumberScreen, 0); + UpdateColorImage(playerPlate.healthBarBackgroundScreen, 0); + UpdateColorImage(playerPlate.healthNumberBackgroundScreen, 0); + UpdateColorImage(playerPlate.usecPlateScreen, 0); + UpdateColorImage(playerPlate.bearPlateScreen, 0); + return; + } - if (CameraClass.Instance.SSAA != null && CameraClass.Instance.SSAA.isActiveAndEnabled) + SSAA ssaa = cameraInstance.SSAA; + bool isSSAAEnabled = ssaa != null && ssaa.isActiveAndEnabled; + if (isSSAAEnabled) { - int outputWidth = CameraClass.Instance.SSAA.GetOutputWidth(); - float inputWidth = CameraClass.Instance.SSAA.GetInputWidth(); + int outputWidth = ssaa.GetOutputWidth(); + float inputWidth = ssaa.GetInputWidth(); screenScale = outputWidth / inputWidth; } - float distance = Vector3.Distance(CameraClass.Instance.Camera.transform.position, currentPlayer.Position) / 25; - distance = Mathf.Clamp(distance, 0.6f, 1f); - Vector3 position; + playerPlate.ScalarObjectScreen.transform.position = screenScale < 1 ? screenPoint : screenPoint * screenScale; - position = new(currentPlayer.PlayerBones.Neck.position.x, currentPlayer.PlayerBones.Neck.position.y + (1f * distance), currentPlayer.PlayerBones.Neck.position.z); + float distFromCenterMultiplier = 1f; + if (FikaPlugin.DecreaseOpacityNotLookingAt.Value) + { + float screenWidth = isSSAAEnabled ? ssaa.GetOutputWidth() : Screen.width; + float screenHeight = isSSAAEnabled ? ssaa.GetOutputHeight() : Screen.height; + Vector3 screenCenter = new(screenWidth / 2, screenHeight / 2, 0); + Vector3 playerPosition = playerPlate.ScalarObjectScreen.transform.position; + float sqrDistFromCenter = (screenCenter - playerPosition).sqrMagnitude; + float minScreenSizeHalf = Mathf.Min(screenWidth, screenHeight) / 2; + float maxSqrDistFromCenter = minScreenSizeHalf * minScreenSizeHalf; + distFromCenterMultiplier = Mathf.Clamp01(1 - (sqrDistFromCenter / maxSqrDistFromCenter)); + } - Vector3 screenPoint = camera.WorldToScreenPoint(position); + float alpha = 1f; + float halfMaxDistanceToShow = maxDistanceToShow / 2; + float lerpValue = Mathf.Clamp01((sqrDistance - halfMaxDistanceToShow) / (halfMaxDistanceToShow)); + alpha = Mathf.LerpUnclamped(alpha, 0, lerpValue); + float namePlateScaleMult = Mathf.LerpUnclamped(1f, 0.5f, lerpValue); + namePlateScaleMult = Mathf.Clamp(namePlateScaleMult * FikaPlugin.NamePlateScale.Value, FikaPlugin.MinimumNamePlateScale.Value * FikaPlugin.NamePlateScale.Value, FikaPlugin.NamePlateScale.Value); - if (screenPoint.z > 0) - { - playerPlate.ScalarObjectScreen.transform.position = screenScale < 1 ? screenPoint : screenPoint * screenScale; - playerPlate.ScalarObjectScreen.transform.localScale = (Vector3.one / distance) * FikaPlugin.NamePlateScale.Value; + playerPlate.ScalarObjectScreen.transform.localScale = (Vector3.one / processedDistance) * namePlateScaleMult; - float distanceToCenter = Vector3.Distance(screenPoint, new Vector3(Screen.width, Screen.height, 0) / 2); + alpha *= opacityMultiplier; + alpha *= distFromCenterMultiplier; + alpha = Mathf.Max(FikaPlugin.MinimumOpacity.Value, alpha); - #region Alpha Control for Health Bars. This code is ugly so its getting regioned so I can hide my shame. - if (distanceToCenter < 200) - { - playerPlate.playerNameScreen.color = new Color(playerPlate.playerNameScreen.color.r, playerPlate.playerNameScreen.color.g, playerPlate.playerNameScreen.color.b, Mathf.Max(0.1f, distanceToCenter / 200)); - playerPlate.healthBarScreen.color = new Color(playerPlate.healthBarScreen.color.r, playerPlate.healthBarScreen.color.g, playerPlate.healthBarScreen.color.b, Mathf.Max(0.1f, distanceToCenter / 200)); - playerPlate.healthBarBackgroundScreen.color = new Color(playerPlate.healthBarBackgroundScreen.color.r, playerPlate.healthBarBackgroundScreen.color.g, playerPlate.healthBarBackgroundScreen.color.b, Mathf.Clamp(Mathf.Max(0.1f, distanceToCenter / 200), 0f, 0.4392157f)); - playerPlate.healthNumberBackgroundScreen.color = new Color(playerPlate.healthNumberBackgroundScreen.color.r, playerPlate.healthNumberBackgroundScreen.color.g, playerPlate.healthNumberBackgroundScreen.color.b, Mathf.Clamp(Mathf.Max(0.1f, distanceToCenter / 200), 0f, 0.4392157f)); - playerPlate.healthNumberScreen.color = new Color(playerPlate.healthNumberScreen.color.r, playerPlate.healthNumberScreen.color.g, playerPlate.healthNumberScreen.color.b, Mathf.Max(0.1f, distanceToCenter / 200)); - playerPlate.usecPlateScreen.color = new Color(playerPlate.usecPlateScreen.color.r, playerPlate.usecPlateScreen.color.g, playerPlate.usecPlateScreen.color.b, Mathf.Max(0.1f, distanceToCenter / 200)); - playerPlate.bearPlateScreen.color = new Color(playerPlate.bearPlateScreen.color.r, playerPlate.bearPlateScreen.color.g, playerPlate.bearPlateScreen.color.b, Mathf.Max(0.1f, distanceToCenter / 200)); + float backgroundOpacity = Mathf.Clamp(alpha, 0f, 0.44f); + float healthAlphaMultiplier = FikaPlugin.HideHealthBar.Value ? 0 : 1f; - } - else - { - playerPlate.playerNameScreen.color = new Color(playerPlate.playerNameScreen.color.r, playerPlate.playerNameScreen.color.g, playerPlate.playerNameScreen.color.b, 1); - playerPlate.healthBarScreen.color = new Color(playerPlate.healthBarScreen.color.r, playerPlate.healthBarScreen.color.g, playerPlate.healthBarScreen.color.b, 1); - playerPlate.healthBarBackgroundScreen.color = new Color(playerPlate.healthBarBackgroundScreen.color.r, playerPlate.healthBarBackgroundScreen.color.g, playerPlate.healthBarBackgroundScreen.color.b, 1); - playerPlate.healthNumberBackgroundScreen.color = new Color(playerPlate.healthNumberBackgroundScreen.color.r, playerPlate.healthNumberBackgroundScreen.color.g, playerPlate.healthNumberBackgroundScreen.color.b, 1); - playerPlate.healthNumberScreen.color = new Color(playerPlate.healthNumberScreen.color.r, playerPlate.healthNumberScreen.color.g, playerPlate.healthNumberScreen.color.b, 1); - playerPlate.usecPlateScreen.color = new Color(playerPlate.usecPlateScreen.color.r, playerPlate.usecPlateScreen.color.g, playerPlate.usecPlateScreen.color.b, 1); - playerPlate.bearPlateScreen.color = new Color(playerPlate.bearPlateScreen.color.r, playerPlate.bearPlateScreen.color.g, playerPlate.bearPlateScreen.color.b, 1); - } - } - else - { - playerPlate.playerNameScreen.color = new Color(playerPlate.playerNameScreen.color.r, playerPlate.playerNameScreen.color.g, playerPlate.playerNameScreen.color.b, 0); - playerPlate.healthBarScreen.color = new Color(playerPlate.healthBarScreen.color.r, playerPlate.healthBarScreen.color.g, playerPlate.healthBarScreen.color.b, 0); - playerPlate.healthBarBackgroundScreen.color = new Color(playerPlate.healthBarBackgroundScreen.color.r, playerPlate.healthBarBackgroundScreen.color.g, playerPlate.healthBarBackgroundScreen.color.b, 0); - playerPlate.healthNumberBackgroundScreen.color = new Color(playerPlate.healthNumberBackgroundScreen.color.r, playerPlate.healthNumberBackgroundScreen.color.g, playerPlate.healthNumberBackgroundScreen.color.b, 0); - playerPlate.healthNumberScreen.color = new Color(playerPlate.healthNumberScreen.color.r, playerPlate.healthNumberScreen.color.g, playerPlate.healthNumberScreen.color.b, 0); - playerPlate.usecPlateScreen.color = new Color(playerPlate.usecPlateScreen.color.r, playerPlate.usecPlateScreen.color.g, playerPlate.usecPlateScreen.color.b, 0); - playerPlate.bearPlateScreen.color = new Color(playerPlate.bearPlateScreen.color.r, playerPlate.bearPlateScreen.color.g, playerPlate.bearPlateScreen.color.b, 0); - } - #endregion + UpdateColorTextMeshProUGUI(playerPlate.playerNameScreen, alpha); + UpdateColorImage(playerPlate.healthBarScreen, alpha * healthAlphaMultiplier); + UpdateColorTextMeshProUGUI(playerPlate.healthNumberScreen, alpha * healthAlphaMultiplier); + UpdateColorImage(playerPlate.healthBarBackgroundScreen, backgroundOpacity * healthAlphaMultiplier); + UpdateColorImage(playerPlate.healthNumberBackgroundScreen, backgroundOpacity * healthAlphaMultiplier); + UpdateColorImage(playerPlate.usecPlateScreen, alpha); + UpdateColorImage(playerPlate.bearPlateScreen, alpha); } private void CreateHealthBar() @@ -161,17 +197,6 @@ private void CreateHealthBar() GameObject uiGameObj = Instantiate(uiPrefab); playerPlate = uiGameObj.GetComponent(); playerPlate.SetNameText(currentPlayer.Profile.Info.MainProfileNickname); - if (FikaPlugin.UsePlateFactionSide.Value) - { - if (currentPlayer.Profile.Side == EPlayerSide.Usec) - { - playerPlate.usecPlateScreen.gameObject.SetActive(true); - } - else if (currentPlayer.Profile.Side == EPlayerSide.Bear) - { - playerPlate.bearPlateScreen.gameObject.SetActive(true); - } - } if (FikaPlugin.DevelopersList.ContainsKey(currentPlayer.Profile.Nickname.ToLower())) { playerPlate.playerNameScreen.color = new Color(0, 0.6091f, 1, 1); @@ -190,6 +215,9 @@ private void CreateHealthBar() playerPlate.usecPlateScreen.GetComponent().sprite = specialIcons.IconsSettings[2].IconSprite; playerPlate.usecPlateScreen.transform.localPosition = new Vector3(0f, 24.9f, 0); } + // Start the plates both disabled, the visibility will be set in the update loop + playerPlate.usecPlateScreen.gameObject.SetActive(false); + playerPlate.bearPlateScreen.gameObject.SetActive(false); } } @@ -200,6 +228,59 @@ private void UpdateHealthBarColor(float normalizedHealth) playerPlate.healthBarScreen.color = color; } + private void UpdateColorImage(Image screenObject, float alpha) + { + if (screenObject.gameObject.activeInHierarchy) + { + var color = screenObject.color; + color.a = alpha; + screenObject.color = color; + } + } + + private void UpdateColorTextMeshProUGUI(TMPro.TextMeshProUGUI screenObject, float alpha) + { + if (screenObject.gameObject.activeInHierarchy) + { + var color = screenObject.color; + color.a = alpha; + screenObject.color = color; + } + } + + private void SetPlayerPlateHealthVisibility(bool hidden) + { + playerPlate.healthNumberScreen.gameObject.SetActive(!hidden && FikaPlugin.UseHealthNumber.Value); + playerPlate.healthNumberBackgroundScreen.gameObject.SetActive(!hidden && FikaPlugin.UseHealthNumber.Value); + playerPlate.healthBarScreen.gameObject.SetActive(!hidden && !FikaPlugin.UseHealthNumber.Value); + playerPlate.healthBarBackgroundScreen.gameObject.SetActive(!hidden && !FikaPlugin.UseHealthNumber.Value); + } + + + private void SetPlayerPlateFactionVisibility(bool visible) + { + if (currentPlayer.Profile.Side == EPlayerSide.Usec) + { + playerPlate.usecPlateScreen.gameObject.SetActive(visible); + } + else if (currentPlayer.Profile.Side == EPlayerSide.Bear) + { + playerPlate.bearPlateScreen.gameObject.SetActive(visible); + } + } + + private bool IsThrottleUpdate() + { + // For throttling updates to various elements + frameCounter++; + bool throttleUpdate = frameCounter >= throttleInterval; + if (throttleUpdate) + { + frameCounter = 0; + } + return throttleUpdate; + } + private void OnDestroy() { playerPlate.gameObject.SetActive(false); diff --git a/Fika.Core/FikaPlugin.cs b/Fika.Core/FikaPlugin.cs index e733f8b7..3667d613 100644 --- a/Fika.Core/FikaPlugin.cs +++ b/Fika.Core/FikaPlugin.cs @@ -106,11 +106,20 @@ public class FikaPlugin : BaseUnityPlugin public static ConfigEntry FasterInventoryScrollSpeed { get; set; } public static ConfigEntry ExtractKey { get; set; } - // Coop | Custom + // Coop | Name Plates public static ConfigEntry UseNamePlates { get; set; } + public static ConfigEntry HideHealthBar { get; set; } public static ConfigEntry UseHealthNumber { get; set; } public static ConfigEntry UsePlateFactionSide { get; set; } + public static ConfigEntry HideNamePlateInOptic { get; set; } + public static ConfigEntry DecreaseOpacityNotLookingAt { get; set; } public static ConfigEntry NamePlateScale { get; set; } + public static ConfigEntry OpacityInADS { get; set; } + public static ConfigEntry MaxDistanceToShow { get; set; } + public static ConfigEntry MinimumOpacity { get; set; } + public static ConfigEntry MinimumNamePlateScale { get; set; } + + // Coop | Custom public static ConfigEntry UsePingSystem { get; set; } public static ConfigEntry PingButton { get; set; } public static ConfigEntry PingColor { get; set; } @@ -127,6 +136,7 @@ public class FikaPlugin : BaseUnityPlugin public static ConfigEntry DynamicAIRate { get; set; } public static ConfigEntry CullPlayers { get; set; } public static ConfigEntry CullingRange { get; set; } + // Performance | Bot Limits public static ConfigEntry EnforcedSpawnLimits { get; set; } public static ConfigEntry DespawnFurthest { get; set; } @@ -255,15 +265,31 @@ private void SetupConfig() ExtractKey = Config.Bind("Coop", "Extract Key", new KeyboardShortcut(KeyCode.F8), new ConfigDescription("The key used to extract from the raid.", tags: new ConfigurationManagerAttributes() { Order = 1 })); - // Coop | Custom + // 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 })); + + HideHealthBar = Config.Bind("Coop | Name Plates", "Hide Health Bar", false, new ConfigDescription("Completely hides the health bar.", tags: new ConfigurationManagerAttributes() { Order = 9 })); - UseNamePlates = Config.Bind("Coop | Custom", "Show Player Name Plates", false, new ConfigDescription("Toggle Health-Bars & Names.", 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 | Custom", "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 | Custom", "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 since it's kinda janky.", tags: new ConfigurationManagerAttributes() { Order = 6 })); - NamePlateScale = Config.Bind("Coop | Custom", "Name Plate Scale", 0.22f, new ConfigDescription("Size of the name plates", new AcceptableValueRange(0.05f, 1f), new ConfigurationManagerAttributes() { Order = 7 })); + 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 })); + + NamePlateScale = Config.Bind("Coop | Name Plates", "Name Plate Scale", 0.22f, new ConfigDescription("Size of the name plates", new AcceptableValueRange(0.05f, 1f), new ConfigurationManagerAttributes() { Order = 4 })); + + OpacityInADS = Config.Bind("Coop | Name Plates", "Opacity in ADS", 0.75f, new ConfigDescription("The opacity of the name plates when aiming down sights.", new AcceptableValueRange(0.1f, 1f), new ConfigurationManagerAttributes() { Order = 3 })); + + MaxDistanceToShow = Config.Bind("Coop | Name Plates", "Max Distance to Show", 500f, new ConfigDescription("The maximum distance at which name plates will become invisible, starts to fade at half the input value.", new AcceptableValueRange(10f, 1000f), new ConfigurationManagerAttributes() { Order = 2 })); + + MinimumOpacity = Config.Bind("Coop | Name Plates", "Minimum Opacity", 0.1f, new ConfigDescription("The minimum opacity of the name plates.", new AcceptableValueRange(0.0f, 1f), new ConfigurationManagerAttributes() { Order = 1 })); + + MinimumNamePlateScale = Config.Bind("Coop | Name Plates", "Minimum Name Plate Scale", 0.01f, new ConfigDescription("The minimum scale of the name plates.", new AcceptableValueRange(0.0f, 1f), new ConfigurationManagerAttributes() { Order = 0 })); + + // 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 = 6 }));