diff --git a/QSB/AssetBundles/qsb_conversation b/QSB/AssetBundles/qsb_conversation index b69a644a..98d160ff 100644 Binary files a/QSB/AssetBundles/qsb_conversation and b/QSB/AssetBundles/qsb_conversation differ diff --git a/QSB/AssetBundles/qsb_debug b/QSB/AssetBundles/qsb_debug index 2aa9f8be..5bfb7e36 100644 Binary files a/QSB/AssetBundles/qsb_debug and b/QSB/AssetBundles/qsb_debug differ diff --git a/QSB/AssetBundles/qsb_empty b/QSB/AssetBundles/qsb_empty index 719c709d..d3ffe180 100644 Binary files a/QSB/AssetBundles/qsb_empty and b/QSB/AssetBundles/qsb_empty differ diff --git a/QSB/AssetBundles/qsb_hud b/QSB/AssetBundles/qsb_hud new file mode 100644 index 00000000..3233cc3e Binary files /dev/null and b/QSB/AssetBundles/qsb_hud differ diff --git a/QSB/AssetBundles/qsb_network b/QSB/AssetBundles/qsb_network index cbf886f5..9b03b4b4 100644 Binary files a/QSB/AssetBundles/qsb_network and b/QSB/AssetBundles/qsb_network differ diff --git a/QSB/AssetBundles/qsb_network_big b/QSB/AssetBundles/qsb_network_big index 22247a5b..ab53bfb6 100644 Binary files a/QSB/AssetBundles/qsb_network_big and b/QSB/AssetBundles/qsb_network_big differ diff --git a/QSB/HUD/HUDIcon.cs b/QSB/HUD/HUDIcon.cs new file mode 100644 index 00000000..20407db0 --- /dev/null +++ b/QSB/HUD/HUDIcon.cs @@ -0,0 +1,19 @@ +namespace QSB.HUD; + +public enum HUDIcon +{ + UNKNOWN, + SPACE, + DEAD, + SHIP, + CAVE_TWIN, + TOWER_TWIN, + TIMBER_HEARTH, + ATTLEROCK, + BRITTLE_HOLLOW, + HOLLOWS_LANTERN, + GIANTS_DEEP, + DARK_BRAMBLE, + INTERLOPER, + WHITE_HOLE +} diff --git a/QSB/HUD/Messages/PlanetMessage.cs b/QSB/HUD/Messages/PlanetMessage.cs new file mode 100644 index 00000000..fceb2bcb --- /dev/null +++ b/QSB/HUD/Messages/PlanetMessage.cs @@ -0,0 +1,24 @@ +using QSB.Messaging; +using QSB.Player; +using QSB.Utility; + +namespace QSB.HUD.Messages; + +internal class PlanetMessage : QSBMessage +{ + public PlanetMessage(HUDIcon icon) : base(icon) { } + + public override void OnReceiveLocal() => OnReceiveRemote(); + + public override void OnReceiveRemote() + { + var from = QSBPlayerManager.GetPlayer(From); + + if (from == default) + { + return; + } + + from.HUDBox?.UpdateIcon(Data); + } +} diff --git a/QSB/HUD/MultiplayerHUDManager.cs b/QSB/HUD/MultiplayerHUDManager.cs new file mode 100644 index 00000000..2a16ba01 --- /dev/null +++ b/QSB/HUD/MultiplayerHUDManager.cs @@ -0,0 +1,272 @@ +using QSB.HUD.Messages; +using QSB.Messaging; +using QSB.Player; +using QSB.ServerSettings; +using QSB.Utility; +using QSB.WorldSync; +using System.Linq; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace QSB.HUD; + +internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart +{ + public static MultiplayerHUDManager Instance; + + private Transform _playerList; + private Material _markerMaterial; + + public static Sprite UnknownSprite; + public static Sprite DeadSprite; + public static Sprite SpaceSprite; + public static Sprite ShipSprite; + public static Sprite TimberHearth; + public static Sprite Attlerock; + public static Sprite CaveTwin; + public static Sprite TowerTwin; + public static Sprite BrittleHollow; + public static Sprite HollowsLantern; + public static Sprite GiantsDeep; + public static Sprite DarkBramble; + public static Sprite Interloper; + public static Sprite WhiteHole; + + public static ListStack HUDIconStack = new(); + + private void Start() + { + Instance = this; + + GlobalMessenger.AddListener(OWEvents.WakeUp, OnWakeUp); + + QSBPlayerManager.OnAddPlayer += OnAddPlayer; + QSBPlayerManager.OnRemovePlayer += OnRemovePlayer; + + UnknownSprite = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_unknown.png"); + DeadSprite = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_dead.png"); + ShipSprite = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_ship.png"); + CaveTwin = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_cavetwin.png"); + TowerTwin = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_towertwin.png"); + TimberHearth = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_timberhearth.png"); + Attlerock = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_attlerock.png"); + BrittleHollow = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_brittlehollow.png"); + HollowsLantern = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_hollowslantern.png"); + GiantsDeep = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_giantsdeep.png"); + DarkBramble = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_darkbramble.png"); + Interloper = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_interloper.png"); + WhiteHole = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_whitehole.png"); + SpaceSprite = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_space.png"); + } + + private void Update() + { + if (!QSBWorldSync.AllObjectsReady || _playerList == null) + { + return; + } + + _playerList.gameObject.SetActive(ServerSettingsManager.ShowExtraHUD); + } + + private void OnWakeUp() + { + var hudController = Locator.GetPlayerCamera().transform.Find("Helmet").Find("HUDController").GetComponent(); + var hudCamera = hudController._hudCamera; + var hudCanvas = hudCamera.transform.parent.Find("UICanvas"); + + var multiplayerGroup = Instantiate(QSBCore.HUDAssetBundle.LoadAsset("assets/Prefabs/multiplayergroup.prefab")); + + Delay.RunNextFrame(() => + { + // no idea why this has to be next frame, but it does + multiplayerGroup.transform.parent = hudCanvas; + multiplayerGroup.transform.localPosition = Vector3.zero; + var rect = multiplayerGroup.GetComponent(); + rect.anchorMin = new Vector2(1, 0.5f); + rect.anchorMax = new Vector2(1, 0.5f); + rect.sizeDelta = new Vector2(100, 100); + rect.anchoredPosition3D = new Vector3(-267, 0, 0); + rect.localRotation = Quaternion.Euler(0, 55, 0); + rect.localScale = Vector3.one; + }); + + _playerList = multiplayerGroup.transform.Find("PlayerList"); + + foreach (var player in QSBPlayerManager.PlayerList) + { + AddBox(player); + + foreach (var item in QSBWorldSync.GetUnityObjects()) + { + AddMinimapMarker(player, item); + } + } + + CreateTrigger("TowerTwin_Body/Sector_TowerTwin", HUDIcon.TOWER_TWIN); + CreateTrigger("CaveTwin_Body/Sector_CaveTwin", HUDIcon.CAVE_TWIN); + CreateTrigger("TimberHearth_Body/Sector_TH", HUDIcon.TIMBER_HEARTH); + CreateTrigger("Moon_Body/Sector_THM", HUDIcon.ATTLEROCK); + CreateTrigger("BrittleHollow_Body/Sector_BH", HUDIcon.BRITTLE_HOLLOW); + CreateTrigger("VolcanicMoon_Body/Sector_VM", HUDIcon.HOLLOWS_LANTERN); + CreateTrigger("GiantsDeep_Body/Sector_GD", HUDIcon.GIANTS_DEEP); + CreateTrigger("DarkBramble_Body/Sector_DB", HUDIcon.DARK_BRAMBLE); + CreateTrigger("Comet_Body/Sector_CO", HUDIcon.INTERLOPER); + CreateTrigger("WhiteHole_Body/Sector_WhiteHole", HUDIcon.WHITE_HOLE); + + HUDIconStack.Clear(); + HUDIconStack.Push(HUDIcon.SPACE); + HUDIconStack.Push(HUDIcon.TIMBER_HEARTH); + + new PlanetMessage(HUDIcon.TIMBER_HEARTH).Send(); + + var playerMinimap = QSBWorldSync.GetUnityObjects().First(x => x.name == "Minimap_Root"); + _markerMaterial = Instantiate(playerMinimap._probeMarkerTransform.GetComponent().material); + _markerMaterial.color = Color.gray; + } + + public void UpdateMinimapMarkers(Minimap minimap) + { + var localRuleset = Locator.GetPlayerRulesetDetector().GetPlanetoidRuleset(); + + foreach (var player in QSBPlayerManager.PlayerList) + { + if (player.IsDead || player.IsLocalPlayer || !player.IsReady) + { + continue; + } + + if (player.RulesetDetector == null) + { + if (player.Body != null) + { + DebugLog.ToConsole($"Error - {player.PlayerId}'s RulesetDetector is null.", OWML.Common.MessageType.Error); + } + + continue; + } + + if (player.RulesetDetector.GetPlanetoidRuleset() == null + || player.RulesetDetector.GetPlanetoidRuleset() != localRuleset) + { + continue; + } + + if (player.MinimapPlayerMarker == null) + { + continue; + } + + if (ServerSettingsManager.ShowExtraHUD) + { + player.MinimapPlayerMarker.localPosition = GetLocalMapPosition(player, minimap); + player.MinimapPlayerMarker.LookAt(minimap._globeMeshTransform, minimap._globeMeshTransform.up); + } + else + { + player.MinimapPlayerMarker.localPosition = Vector3.zero; + player.MinimapPlayerMarker.localRotation = Quaternion.identity; + } + } + } + + private void AddMinimapMarker(PlayerInfo player, Minimap minimap) + { + player.MinimapPlayerMarker = Instantiate(minimap._probeMarkerTransform); + player.MinimapPlayerMarker.parent = minimap._probeMarkerTransform.parent; + player.MinimapPlayerMarker.localScale = new Vector3(0.05f, 0.05f, 0.05f); + player.MinimapPlayerMarker.localPosition = Vector3.zero; + player.MinimapPlayerMarker.localRotation = Quaternion.identity; + player.MinimapPlayerMarker.GetComponent().material = _markerMaterial; + } + + private void AddBox(PlayerInfo player) + { + var box = Instantiate(QSBCore.HUDAssetBundle.LoadAsset("assets/Prefabs/playerbox.prefab")); + box.transform.parent = _playerList; + box.transform.localScale = new Vector3(1, 1, 1); + box.transform.localPosition = Vector3.zero; + box.transform.localRotation = Quaternion.identity; + + var boxScript = box.GetComponent(); + boxScript.AssignPlayer(player); + } + + private Vector3 GetLocalMapPosition(PlayerInfo player, Minimap minimap) + { + return Vector3.Scale( + player.RulesetDetector.GetPlanetoidRuleset().transform.InverseTransformPoint(player.Body.transform.position).normalized * 0.51f, + minimap._globeMeshTransform.localScale); + } + + private void OnAddPlayer(PlayerInfo player) + { + if (!QSBWorldSync.AllObjectsReady) + { + return; + } + + AddBox(player); + + foreach (var item in QSBWorldSync.GetUnityObjects()) + { + AddMinimapMarker(player, item); + } + } + + private void OnRemovePlayer(PlayerInfo player) + { + Destroy(player.HUDBox.gameObject); + } + + private PlanetTrigger CreateTrigger(string parentPath, HUDIcon icon) + => CreateTrigger(Find(parentPath), icon); + + private PlanetTrigger CreateTrigger(GameObject parent, HUDIcon icon) + { + if (parent == null) + { + return null; + } + + var triggerGO = parent.FindChild("HUD_PLANET_TRIGGER"); + if (triggerGO != null) + { + var trigger = triggerGO.GetAddComponent(); + trigger.Icon = icon; + return trigger; + } + else + { + triggerGO = new GameObject("HUD_PLANET_TRIGGER"); + triggerGO.transform.SetParent(parent.transform, false); + triggerGO.SetActive(false); + var trigger = triggerGO.AddComponent(); + trigger.Icon = icon; + triggerGO.SetActive(true); + return trigger; + } + } + + public static GameObject Find(string path) + { + var go = GameObject.Find(path); + + if (go == null) + { + // find inactive use root + transform.find + var names = path.Split('/'); + var rootName = names[0]; + var root = SceneManager.GetActiveScene().GetRootGameObjects().FirstOrDefault(x => x.name == rootName); + if (root == null) + { + return null; + } + + var childPath = string.Join("/", names.Skip(1)); + go = root.FindChild(childPath); + } + + return go; + } +} \ No newline at end of file diff --git a/QSB/HUD/Patches/MinimapPatches.cs b/QSB/HUD/Patches/MinimapPatches.cs new file mode 100644 index 00000000..07189831 --- /dev/null +++ b/QSB/HUD/Patches/MinimapPatches.cs @@ -0,0 +1,17 @@ +using HarmonyLib; +using QSB.Patches; + +namespace QSB.HUD.Patches; + +[HarmonyPatch(typeof(Minimap))] +internal class MinimapPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPostfix] + [HarmonyPatch(nameof(Minimap.UpdateMarkers))] + public static void UpdateMarkers(Minimap __instance) + { + MultiplayerHUDManager.Instance.UpdateMinimapMarkers(__instance); + } +} diff --git a/QSB/HUD/Patches/RulesetVolumePatches.cs b/QSB/HUD/Patches/RulesetVolumePatches.cs new file mode 100644 index 00000000..5aba29ac --- /dev/null +++ b/QSB/HUD/Patches/RulesetVolumePatches.cs @@ -0,0 +1,54 @@ +using HarmonyLib; +using QSB.Patches; +using QSB.Player; +using QSB.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace QSB.HUD.Patches; + +[HarmonyPatch(typeof(RulesetVolume))] +internal class RulesetVolumePatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnClientConnect; + + [HarmonyPrefix] + [HarmonyPatch(nameof(RulesetVolume.OnEffectVolumeEnter))] + public static bool OnEffectVolumeEnter(RulesetVolume __instance, GameObject hitObj) + { + var baseDetector = hitObj.GetComponent(); + var customDetector = hitObj.GetComponent(); + if (baseDetector != null) + { + baseDetector.AddVolume(__instance); + } + else if (customDetector != null) + { + customDetector.AddVolume(__instance); + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(RulesetVolume.OnEffectVolumeExit))] + public static bool OnEffectVolumeExit(RulesetVolume __instance, GameObject hitObj) + { + var baseDetector = hitObj.GetComponent(); + var customDetector = hitObj.GetComponent(); + if (baseDetector != null) + { + baseDetector.RemoveVolume(__instance); + } + else if (customDetector != null) + { + customDetector.RemoveVolume(__instance); + } + + return false; + } +} diff --git a/QSB/HUD/PlanetTrigger.cs b/QSB/HUD/PlanetTrigger.cs new file mode 100644 index 00000000..339e32a1 --- /dev/null +++ b/QSB/HUD/PlanetTrigger.cs @@ -0,0 +1,34 @@ +using QSB.HUD.Messages; +using QSB.Messaging; +using QSB.Utility; + +namespace QSB.HUD; + +public class PlanetTrigger : SectoredMonoBehaviour +{ + public HUDIcon Icon; + + public override void OnSectorOccupantAdded(SectorDetector detector) + { + if (detector.GetOccupantType() != DynamicOccupant.Player) + { + return; + } + + MultiplayerHUDManager.HUDIconStack.Push(Icon); + var top = MultiplayerHUDManager.HUDIconStack.Peek(); + new PlanetMessage(top).Send(); + } + + public override void OnSectorOccupantRemoved(SectorDetector detector) + { + if (detector.GetOccupantType() != DynamicOccupant.Player) + { + return; + } + + MultiplayerHUDManager.HUDIconStack.Remove(Icon); + var top = MultiplayerHUDManager.HUDIconStack.Peek(); + new PlanetMessage(top).Send(); + } +} diff --git a/QSB/HUD/PlayerBox.cs b/QSB/HUD/PlayerBox.cs new file mode 100644 index 00000000..f685a0e3 --- /dev/null +++ b/QSB/HUD/PlayerBox.cs @@ -0,0 +1,113 @@ +using QSB.Player; +using QSB.Player.Messages; +using QSB.Utility; +using System; +using System.Linq; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.UI; + +namespace QSB.HUD; + +public class PlayerBox : MonoBehaviour +{ + public Text PlayerName; + public Image InfoImage; + + private PlayerInfo _player; + private bool _planetIconOverride; + private bool _inUnknown; + + public HUDIcon PlanetIcon { get; private set; } + + public void AssignPlayer(PlayerInfo player) + { + _player = player; + _player.HUDBox = this; + + if (player.Name != null) + { + PlayerName.text = player.Name.ToUpper(); + } + + InfoImage.sprite = MultiplayerHUDManager.UnknownSprite; + + new RequestStateResyncMessage().Send(); + } + + public void OnDeath() + { + InfoImage.sprite = MultiplayerHUDManager.DeadSprite; + _planetIconOverride = true; + } + + public void OnRespawn() + { + InfoImage.sprite = MultiplayerHUDManager.ShipSprite; + _planetIconOverride = true; // still in ship + } + + public void OnEnterShip() + { + if (_inUnknown) + { + return; + } + + InfoImage.sprite = MultiplayerHUDManager.ShipSprite; + _planetIconOverride = true; + } + + public void OnExitShip() + { + if (_inUnknown) + { + return; + } + + _planetIconOverride = false; + InfoImage.sprite = SpriteFromEnum(PlanetIcon); + } + + public void OnEnterUnknown() + { + _inUnknown = true; + _planetIconOverride = true; + InfoImage.sprite = MultiplayerHUDManager.UnknownSprite; + } + + public void OnExitUnknown() + { + _inUnknown = false; + _planetIconOverride = false; + InfoImage.sprite = SpriteFromEnum(PlanetIcon); + } + + public void UpdateIcon(HUDIcon icon) + { + PlanetIcon = icon; + + if (!_planetIconOverride) + { + InfoImage.sprite = SpriteFromEnum(PlanetIcon); + } + } + + public Sprite SpriteFromEnum(HUDIcon icon) => icon switch + { + HUDIcon.SHIP => MultiplayerHUDManager.ShipSprite, + HUDIcon.DEAD => MultiplayerHUDManager.DeadSprite, + HUDIcon.SPACE => MultiplayerHUDManager.SpaceSprite, + HUDIcon.CAVE_TWIN => MultiplayerHUDManager.CaveTwin, + HUDIcon.TOWER_TWIN => MultiplayerHUDManager.TowerTwin, + HUDIcon.TIMBER_HEARTH => MultiplayerHUDManager.TimberHearth, + HUDIcon.ATTLEROCK => MultiplayerHUDManager.Attlerock, + HUDIcon.BRITTLE_HOLLOW => MultiplayerHUDManager.BrittleHollow, + HUDIcon.HOLLOWS_LANTERN => MultiplayerHUDManager.HollowsLantern, + HUDIcon.GIANTS_DEEP => MultiplayerHUDManager.GiantsDeep, + HUDIcon.DARK_BRAMBLE => MultiplayerHUDManager.DarkBramble, + HUDIcon.INTERLOPER => MultiplayerHUDManager.Interloper, + HUDIcon.WHITE_HOLE => MultiplayerHUDManager.WhiteHole, + _ => MultiplayerHUDManager.UnknownSprite, + }; +} diff --git a/QSB/Player/Messages/EnterLeaveMessage.cs b/QSB/Player/Messages/EnterLeaveMessage.cs index 8fa22dd3..2d0708b7 100644 --- a/QSB/Player/Messages/EnterLeaveMessage.cs +++ b/QSB/Player/Messages/EnterLeaveMessage.cs @@ -60,15 +60,19 @@ internal class EnterLeaveMessage : QSBMessage { case EnterLeaveType.EnterMoon: player.IsInMoon = true; + player.HUDBox.OnEnterUnknown(); break; case EnterLeaveType.ExitMoon: player.IsInMoon = false; + player.HUDBox.OnExitUnknown(); break; case EnterLeaveType.EnterCloak: player.IsInCloak = true; + player.HUDBox.OnEnterUnknown(); break; case EnterLeaveType.ExitCloak: player.IsInCloak = false; + player.HUDBox.OnExitUnknown(); break; case EnterLeaveType.EnterPlatform: CustomNomaiRemoteCameraPlatform.CustomPlatformList[ObjectId] diff --git a/QSB/Player/Messages/PlayerInformationMessage.cs b/QSB/Player/Messages/PlayerInformationMessage.cs index 61775a96..fd8508dc 100644 --- a/QSB/Player/Messages/PlayerInformationMessage.cs +++ b/QSB/Player/Messages/PlayerInformationMessage.cs @@ -1,6 +1,7 @@ using Mirror; using OWML.Common; using QSB.ClientServerStateSync; +using QSB.HUD; using QSB.Messaging; using QSB.Utility; @@ -19,6 +20,7 @@ public class PlayerInformationMessage : QSBMessage private ClientState ClientState; private float FieldOfView; private bool IsInShip; + private HUDIcon HUDIcon; public PlayerInformationMessage() { @@ -34,6 +36,7 @@ public class PlayerInformationMessage : QSBMessage ClientState = player.State; FieldOfView = PlayerData.GetGraphicSettings().fieldOfView; IsInShip = player.IsInShip; + HUDIcon = player.HUDBox == null ? HUDIcon.UNKNOWN : player.HUDBox.PlanetIcon; } public override void Serialize(NetworkWriter writer) @@ -50,6 +53,7 @@ public class PlayerInformationMessage : QSBMessage writer.Write(ClientState); writer.Write(FieldOfView); writer.Write(IsInShip); + writer.Write(HUDIcon); } public override void Deserialize(NetworkReader reader) @@ -66,6 +70,7 @@ public class PlayerInformationMessage : QSBMessage ClientState = reader.Read(); FieldOfView = reader.ReadFloat(); IsInShip = reader.ReadBool(); + HUDIcon = reader.Read(); } public override void OnReceiveRemote() @@ -93,6 +98,12 @@ public class PlayerInformationMessage : QSBMessage () => player.Camera.fieldOfView = FieldOfView); player.State = ClientState; + + Delay.RunWhen(() => player.HUDBox != null, () => + { + player.HUDBox.PlayerName.text = PlayerName.ToUpper(); + player.HUDBox.UpdateIcon(HUDIcon); + }); } else { diff --git a/QSB/Player/PlayerInfo.cs b/QSB/Player/PlayerInfo.cs index b0a16927..5774880d 100644 --- a/QSB/Player/PlayerInfo.cs +++ b/QSB/Player/PlayerInfo.cs @@ -2,6 +2,7 @@ using QSB.Animation.Player; using QSB.Audio; using QSB.ClientServerStateSync; +using QSB.HUD; using QSB.Messaging; using QSB.ModelShip; using QSB.Player.Messages; @@ -11,6 +12,7 @@ using QSB.ShipSync; using QSB.Tools; using QSB.Utility; using System.Linq; +using UnityEngine; namespace QSB.Player; @@ -23,6 +25,8 @@ public partial class PlayerInfo public string Name { get; set; } public PlayerHUDMarker HudMarker { get; set; } public PlayerMapMarker MapMarker { get; set; } + public PlayerBox HUDBox { get; set; } + public Transform MinimapPlayerMarker { get; set; } public PlayerTransformSync TransformSync { get; } public ClientState State { get; set; } public EyeState EyeState { get; set; } @@ -39,6 +43,7 @@ public partial class PlayerInfo public ThrusterLightTracker ThrusterLightTracker; public bool FlyingShip => ShipManager.Instance.CurrentFlyer == PlayerId; public bool FlyingModelShip => ModelShipManager.Instance.CurrentFlyer == PlayerId; + public RemotePlayerRulesetDetector RulesetDetector { get; set; } public PlayerInfo(PlayerTransformSync transformSync) { @@ -159,5 +164,19 @@ public partial class PlayerInfo } } + public void Die() + { + IsDead = true; + SetVisible(false, 1); + HUDBox.OnDeath(); + } + + public void Revive() + { + IsDead = false; + SetVisible(true, 1); + HUDBox.OnRespawn(); + } + public override string ToString() => $"{PlayerId}:{GetType().Name} ({Name})"; } diff --git a/QSB/Player/PlayerInfoParts/Body.cs b/QSB/Player/PlayerInfoParts/Body.cs index 40e3d806..f2f3702e 100644 --- a/QSB/Player/PlayerInfoParts/Body.cs +++ b/QSB/Player/PlayerInfoParts/Body.cs @@ -8,15 +8,7 @@ public partial class PlayerInfo { public OWCamera Camera { - get - { - if (_camera == null && IsReady) - { - DebugLog.ToConsole($"Warning - {PlayerId}.Camera is null!", MessageType.Warning); - } - - return _camera; - } + get => _camera; set { if (value == null) diff --git a/QSB/Player/RemotePlayerRulesetDetector.cs b/QSB/Player/RemotePlayerRulesetDetector.cs new file mode 100644 index 00000000..8b51ed4e --- /dev/null +++ b/QSB/Player/RemotePlayerRulesetDetector.cs @@ -0,0 +1,73 @@ +using QSB.Utility; +using System.Collections.Generic; +using UnityEngine; + +namespace QSB.Player; + +[UsedInUnityProject] +public class RemotePlayerRulesetDetector : Detector +{ + private PlanetoidRuleset _closestPlanetoidRuleset; + private List _planetoidRulesets; + + public override void Awake() + { + base.Awake(); + _planetoidRulesets = new List(8); + } + + public override void AddVolume(EffectVolume volume) + { + if ((volume as RulesetVolume) == null) + { + return; + } + + base.AddVolume(volume); + if (volume.GetType() == typeof(PlanetoidRuleset)) + { + _planetoidRulesets.Add((PlanetoidRuleset)volume); + UpdateClosestPlanetoidRuleset(); + } + } + + public override void RemoveVolume(EffectVolume volume) + { + if ((volume as RulesetVolume) == null) + { + return; + } + + base.RemoveVolume(volume); + if (volume.GetType() == typeof(PlanetoidRuleset)) + { + _planetoidRulesets.Remove((PlanetoidRuleset)volume); + UpdateClosestPlanetoidRuleset(); + } + } + + public PlanetoidRuleset GetPlanetoidRuleset() => _closestPlanetoidRuleset; + + private void Update() + { + if (_planetoidRulesets.Count > 1) + { + UpdateClosestPlanetoidRuleset(); + } + } + + private void UpdateClosestPlanetoidRuleset() + { + _closestPlanetoidRuleset = null; + var num = float.PositiveInfinity; + for (var i = 0; i < _planetoidRulesets.Count; i++) + { + var num2 = Vector3.SqrMagnitude(_planetoidRulesets[i].transform.position - transform.position); + if (num2 < num) + { + _closestPlanetoidRuleset = _planetoidRulesets[i]; + num = num2; + } + } + } +} diff --git a/QSB/PlayerBodySetup/Remote/FontReplacer.cs b/QSB/PlayerBodySetup/Remote/FontReplacer.cs index bf974239..d721e1ca 100644 --- a/QSB/PlayerBodySetup/Remote/FontReplacer.cs +++ b/QSB/PlayerBodySetup/Remote/FontReplacer.cs @@ -13,6 +13,12 @@ public static class FontReplacer foreach (var monoBehaviour in prefab.GetComponentsInChildren(true)) { + if (monoBehaviour == null) + { + DebugLog.ToConsole($"Null monobehaviour found on {prefab.name}!", OWML.Common.MessageType.Warning); + continue; + } + var publicFields = monoBehaviour .GetType() .GetFields(BindingFlags.Public | BindingFlags.Instance); diff --git a/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs b/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs index 2fe62c0f..76233c86 100644 --- a/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs +++ b/QSB/PlayerBodySetup/Remote/RemotePlayerCreation.cs @@ -53,6 +53,7 @@ public static class RemotePlayerCreation player.Body = REMOTE_Player_Body; player.ThrusterLightTracker = player.Body.GetComponentInChildren(); player.FluidDetector = REMOTE_PlayerDetector.GetComponent(); + player.RulesetDetector = REMOTE_PlayerDetector.GetComponent(); player.AnimationSync.InitRemote(REMOTE_Traveller_HEA_Player_v2.transform); diff --git a/QSB/QSBCore.cs b/QSB/QSBCore.cs index eb17db01..c1442b2f 100644 --- a/QSB/QSBCore.cs +++ b/QSB/QSBCore.cs @@ -50,6 +50,7 @@ public class QSBCore : ModBehaviour public static AssetBundle NetworkAssetBundle { get; private set; } public static AssetBundle ConversationAssetBundle { get; private set; } public static AssetBundle DebugAssetBundle { get; private set; } + public static AssetBundle HUDAssetBundle { get; private set; } public static bool IsHost => NetworkServer.active; public static bool IsInMultiplayer; public static string QSBVersion => Helper.Manifest.Version; @@ -60,6 +61,7 @@ public class QSBCore : ModBehaviour public static bool IncompatibleModsAllowed { get; private set; } public static bool ShowPlayerNames { get; private set; } public static bool ShipDamage { get; private set; } + public static bool ShowExtraHUDElements { get ; private set; } public static GameVendor GameVendor { get; private set; } = GameVendor.None; public static bool IsStandalone => GameVendor is GameVendor.Epic or GameVendor.Steam; public static IProfileManager ProfileManager => IsStandalone @@ -174,6 +176,7 @@ public class QSBCore : ModBehaviour NetworkAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_network"); ConversationAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_conversation"); DebugAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_debug"); + HUDAssetBundle = Helper.Assets.LoadBundle("AssetBundles/qsb_hud"); if (NetworkAssetBundle == null || ConversationAssetBundle == null || DebugAssetBundle == null) { @@ -255,6 +258,7 @@ public class QSBCore : ModBehaviour IncompatibleModsAllowed = config.GetSettingsValue("incompatibleModsAllowed"); ShowPlayerNames = config.GetSettingsValue("showPlayerNames"); ShipDamage = config.GetSettingsValue("shipDamage"); + ShowExtraHUDElements = config.GetSettingsValue("showExtraHud"); if (IsHost) { diff --git a/QSB/RespawnSync/RespawnManager.cs b/QSB/RespawnSync/RespawnManager.cs index d7256e3d..ef9112f7 100644 --- a/QSB/RespawnSync/RespawnManager.cs +++ b/QSB/RespawnSync/RespawnManager.cs @@ -142,7 +142,7 @@ internal class RespawnManager : MonoBehaviour, IAddComponentOnStart public void OnPlayerDeath(PlayerInfo player) { - player.IsDead = true; + player.Die(); if (_playersPendingRespawn.Contains(player)) { @@ -160,13 +160,11 @@ internal class RespawnManager : MonoBehaviour, IAddComponentOnStart new EndLoopMessage().Send(); return; } - - player.SetVisible(false, 1); } public void OnPlayerRespawn(PlayerInfo player) { - player.IsDead = false; + player.Revive(); if (!_playersPendingRespawn.Contains(player)) { @@ -176,8 +174,6 @@ internal class RespawnManager : MonoBehaviour, IAddComponentOnStart { _playersPendingRespawn.Remove(player); } - - player.SetVisible(true, 1); } public void RespawnSomePlayer() diff --git a/QSB/ServerSettings/ServerSettingsManager.cs b/QSB/ServerSettings/ServerSettingsManager.cs index 505f0ba2..76274dd4 100644 --- a/QSB/ServerSettings/ServerSettingsManager.cs +++ b/QSB/ServerSettings/ServerSettingsManager.cs @@ -7,4 +7,5 @@ internal class ServerSettingsManager : MonoBehaviour, IAddComponentOnStart { public static bool ServerShowPlayerNames; public static bool ShowPlayerNames => (ServerShowPlayerNames || QSBCore.IsHost) && QSBCore.ShowPlayerNames; + public static bool ShowExtraHUD => ShowPlayerNames && QSBCore.ShowExtraHUDElements; } diff --git a/QSB/ShipSync/ShipManager.cs b/QSB/ShipSync/ShipManager.cs index 8b9b3669..381edcd5 100644 --- a/QSB/ShipSync/ShipManager.cs +++ b/QSB/ShipSync/ShipManager.cs @@ -166,6 +166,7 @@ internal class ShipManager : WorldObjectManager public void AddPlayerToShip(PlayerInfo player) { player.IsInShip = true; + player.HUDBox.OnEnterShip(); _playersInShip.Add(player); UpdateElectricalComponent(); } @@ -173,6 +174,7 @@ internal class ShipManager : WorldObjectManager public void RemovePlayerFromShip(PlayerInfo player) { player.IsInShip = false; + player.HUDBox.OnExitShip(); _playersInShip.Remove(player); UpdateElectricalComponent(); } diff --git a/QSB/Utility/DebugGUI.cs b/QSB/Utility/DebugGUI.cs index aa034073..54139539 100644 --- a/QSB/Utility/DebugGUI.cs +++ b/QSB/Utility/DebugGUI.cs @@ -1,5 +1,6 @@ using QSB.ClientServerStateSync; using QSB.EchoesOfTheEye.Ghosts.WorldObjects; +using QSB.HUD; using QSB.Player; using QSB.Player.TransformSync; using QSB.ShipSync; @@ -152,6 +153,12 @@ internal class DebugGUI : MonoBehaviour, IAddComponentOnStart { WriteLine(1, $" - {item}"); } + + WriteLine(1, $"HUD Icon Stack :"); + foreach (var item in MultiplayerHUDManager.HUDIconStack) + { + WriteLine(1, $" - {item}"); + } } #endregion @@ -172,10 +179,10 @@ internal class DebugGUI : MonoBehaviour, IAddComponentOnStart if (player.IsReady && QSBWorldSync.AllObjectsReady) { - WriteLine(2, $"Illuminated : {player.LightSensor.IsIlluminated()}"); + WriteLine(2, $"Illuminated : {player.LightSensor?.IsIlluminated()}"); var singleLightSensor = (SingleLightSensor)player.LightSensor; // will be null for remote player light sensors - if (singleLightSensor._lightSources != null) + if (singleLightSensor?._lightSources != null) { foreach (var item in singleLightSensor._lightSources) { diff --git a/QSB/Utility/Extensions.cs b/QSB/Utility/Extensions.cs index eede0565..0b1488c5 100644 --- a/QSB/Utility/Extensions.cs +++ b/QSB/Utility/Extensions.cs @@ -31,6 +31,9 @@ public static class Extensions return copy; } + public static GameObject FindChild(this GameObject g, string childPath) => + g.transform.Find(childPath)?.gameObject; + #endregion #region MIRROR diff --git a/QSB/Utility/ListStack.cs b/QSB/Utility/ListStack.cs new file mode 100644 index 00000000..bfc7b81d --- /dev/null +++ b/QSB/Utility/ListStack.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace QSB.Utility; + +public class ListStack : IEnumerable +{ + private readonly List _items = new(); + + public void Clear() + => _items.Clear(); + + public void Push(T item) + { + if (_items.Contains(item)) + { + RemoveAll(x => EqualityComparer.Default.Equals(x, item)); + } + + _items.Add(item); + } + + public T Pop() + { + if (_items.Count > 0) + { + var temp = _items[_items.Count - 1]; + _items.RemoveAt(_items.Count - 1); + return temp; + } + + return default; + } + + public T Peek() => _items.Count > 0 + ? _items[_items.Count - 1] + : default; + + public void RemoveAt(int index) + => _items.RemoveAt(index); + + public bool Remove(T item) + => _items.Remove(item); + + public int RemoveAll(Predicate match) + => _items.RemoveAll(match); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_items).GetEnumerator(); + public IEnumerator GetEnumerator() => ((IEnumerable)_items).GetEnumerator(); +} diff --git a/QSB/default-config.json b/QSB/default-config.json index 852343c8..7672cee1 100644 --- a/QSB/default-config.json +++ b/QSB/default-config.json @@ -24,6 +24,12 @@ "type": "toggle", "value": true, "tooltip": "Take impact damage when inside the ship." + }, + "showExtraHud": { + "title": "Show Extra HUD Elements", + "type": "toggle", + "value": true, + "tooltip" : "Show extra HUD elements, like player status and minimap icons." } } } \ No newline at end of file