using System; using System.Collections.Generic; using Unity.BossRoom.Gameplay.UserInput; using Unity.BossRoom.Gameplay.GameplayObjects; using Unity.BossRoom.Gameplay.GameplayObjects.Character; using TMPro; using Unity.BossRoom.Utils; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.UI; namespace Unity.BossRoom.Gameplay.UI { /// /// Provides logic for the Party HUD with information on the player and allies /// Party HUD shows hero portrait and class info for all ally characters /// Party HUD also shows healthbars for each player allows clicks to select an ally /// public class PartyHUD : MonoBehaviour { [SerializeField] ClientPlayerAvatarRuntimeCollection m_PlayerAvatars; [SerializeField] private Image m_HeroPortrait; [SerializeField] private GameObject[] m_AllyPanel; [SerializeField] private TextMeshProUGUI[] m_PartyNames; [SerializeField] private Image[] m_PartyClassSymbols; [SerializeField] private Slider[] m_PartyHealthSliders; [SerializeField] private Image[] m_PartyHealthGodModeImages; // track a list of hero (slot 0) + allies private ulong[] m_PartyIds; // track Hero's target to show when it is the Hero or an ally private ulong m_CurrentTarget; ServerCharacter m_OwnedServerCharacter; ClientPlayerAvatar m_OwnedPlayerAvatar; private Dictionary m_TrackedAllies = new Dictionary(); private ClientInputSender m_ClientSender; void Awake() { // Make sure arrays are initialized InitPartyArrays(); m_PlayerAvatars.ItemAdded += PlayerAvatarAdded; m_PlayerAvatars.ItemRemoved += PlayerAvatarRemoved; } void PlayerAvatarAdded(ClientPlayerAvatar clientPlayerAvatar) { if (clientPlayerAvatar.IsOwner) { SetHeroData(clientPlayerAvatar); } else { SetAllyData(clientPlayerAvatar); } } void PlayerAvatarRemoved(ClientPlayerAvatar clientPlayerAvatar) { if (m_OwnedPlayerAvatar == clientPlayerAvatar) { RemoveHero(); } else if (m_TrackedAllies.ContainsKey(clientPlayerAvatar.NetworkObjectId)) { RemoveAlly(clientPlayerAvatar.NetworkObjectId); m_TrackedAllies.Remove(clientPlayerAvatar.NetworkObjectId); } } void SetHeroData(ClientPlayerAvatar clientPlayerAvatar) { m_OwnedServerCharacter = clientPlayerAvatar.GetComponent(); Assert.IsTrue(m_OwnedServerCharacter, "ServerCharacter component not found on ClientPlayerAvatar"); m_OwnedPlayerAvatar = clientPlayerAvatar; // Hero is always our slot 0 m_PartyIds[0] = m_OwnedServerCharacter.NetworkObject.NetworkObjectId; // set hero portrait if (m_OwnedServerCharacter.TryGetComponent(out NetworkAvatarGuidState avatarGuidState)) { m_HeroPortrait.sprite = avatarGuidState.RegisteredAvatar.Portrait; } SetUIFromSlotData(0, m_OwnedServerCharacter); m_OwnedServerCharacter.NetHealthState.HitPoints.OnValueChanged += SetHeroHealth; #if UNITY_EDITOR || DEVELOPMENT_BUILD m_OwnedServerCharacter.NetLifeState.IsGodMode.OnValueChanged += SetHeroGodModeStatus; #endif // plus we track their target m_OwnedServerCharacter.TargetId.OnValueChanged += OnHeroSelectionChanged; m_ClientSender = m_OwnedServerCharacter.GetComponent(); } void SetHeroHealth(int previousValue, int newValue) { m_PartyHealthSliders[0].value = newValue; } #if UNITY_EDITOR || DEVELOPMENT_BUILD void SetHeroGodModeStatus(bool previousValue, bool newValue) { m_PartyHealthGodModeImages[0].gameObject.SetActive(newValue); } #endif /// /// Gets Player Name from the NetworkObjectId of his controlled Character. /// string GetPlayerName(Component component) { var networkName = component.GetComponent(); return networkName.Name.Value; } // set the class type for an ally - allies are tracked by appearance so you must also provide appearance id void SetAllyData(ClientPlayerAvatar clientPlayerAvatar) { var networkCharacterStateExists = clientPlayerAvatar.TryGetComponent(out ServerCharacter serverCharacter); Assert.IsTrue(networkCharacterStateExists, "NetworkCharacterState component not found on ClientPlayerAvatar"); ulong id = serverCharacter.NetworkObjectId; int slot = FindOrAddAlly(id); // do nothing if not in a slot if (slot == -1) { return; } SetUIFromSlotData(slot, serverCharacter); serverCharacter.NetHealthState.HitPoints.OnValueChanged += (int previousValue, int newValue) => { SetAllyHealth(id, newValue); }; #if UNITY_EDITOR || DEVELOPMENT_BUILD serverCharacter.NetLifeState.IsGodMode.OnValueChanged += (value, newValue) => { SetAllyGodModeStatus(id, newValue); }; #endif m_TrackedAllies.Add(serverCharacter.NetworkObjectId, serverCharacter); } void SetUIFromSlotData(int slot, ServerCharacter serverCharacter) { m_PartyHealthSliders[slot].maxValue = serverCharacter.CharacterClass.BaseHP.Value; m_PartyHealthSliders[slot].value = serverCharacter.HitPoints; m_PartyNames[slot].text = GetPlayerName(serverCharacter); #if UNITY_EDITOR || DEVELOPMENT_BUILD m_PartyHealthGodModeImages[slot].gameObject.SetActive(serverCharacter.NetLifeState.IsGodMode.Value); #endif m_PartyClassSymbols[slot].sprite = serverCharacter.CharacterClass.ClassBannerLit; } #if UNITY_EDITOR || DEVELOPMENT_BUILD void SetAllyGodModeStatus(ulong id, bool newValue) { int slot = FindOrAddAlly(id); // do nothing if not in a slot if (slot == -1) { return; } m_PartyHealthGodModeImages[slot].gameObject.SetActive(newValue); } #endif void SetAllyHealth(ulong id, int hp) { int slot = FindOrAddAlly(id); // do nothing if not in a slot if (slot == -1) { return; } m_PartyHealthSliders[slot].value = hp; } private void OnHeroSelectionChanged(ulong prevTarget, ulong newTarget) { SetHeroSelectFX(m_CurrentTarget, false); SetHeroSelectFX(newTarget, true); } // Helper to change name appearance for selected or unselected party members // also updates m_CurrentTarget private void SetHeroSelectFX(ulong target, bool selected) { // check id against all party slots int slot = FindOrAddAlly(target, true); if (slot >= 0) { m_PartyNames[slot].color = selected ? Color.green : Color.white; if (selected) { m_CurrentTarget = target; } else { m_CurrentTarget = 0; } } } public void SelectPartyMember(int slot) { m_ClientSender.RequestAction(GameDataSource.Instance.GeneralTargetActionPrototype.ActionID, ClientInputSender.SkillTriggerStyle.UI, m_PartyIds[slot]); } // helper to initialize the Allies array - safe to call multiple times private void InitPartyArrays() { if (m_PartyIds == null) { // clear party ID array m_PartyIds = new ulong[m_PartyHealthSliders.Length]; for (int i = 0; i < m_PartyHealthSliders.Length; i++) { // initialize all IDs positions to 0 and HP to 1000 on sliders m_PartyIds[i] = 0; m_PartyHealthSliders[i].maxValue = 1000; } } } // Helper to find ally slots, returns -1 if no slot is found for the id // If a slot is available one will be added for this id unless dontAdd=true private int FindOrAddAlly(ulong id, bool dontAdd = false) { // make sure allies array is ready InitPartyArrays(); int openslot = -1; for (int i = 0; i < m_PartyIds.Length; i++) { // if this ID is in the list, return the slot index if (m_PartyIds[i] == id) { return i; } // otherwise, record the first open slot (not slot 0 thats for the Hero) if (openslot == -1 && i > 0 && m_PartyIds[i] == 0) { openslot = i; } } // if we don't add, we are done nw and didnt fint the ID if (dontAdd) { return -1; } // Party slot was not found for this ID - add one in the open slot if (openslot > 0) { // activeate the correct ally panel m_AllyPanel[openslot - 1].SetActive(true); // and save ally ID to party array m_PartyIds[openslot] = id; return openslot; } // this should not happen unless there are too many players - we didn't find the ally or a slot return -1; } void RemoveHero() { if (m_OwnedServerCharacter && m_OwnedServerCharacter.NetHealthState) { m_OwnedServerCharacter.NetHealthState.HitPoints.OnValueChanged -= SetHeroHealth; #if UNITY_EDITOR || DEVELOPMENT_BUILD m_OwnedServerCharacter.NetLifeState.IsGodMode.OnValueChanged -= SetHeroGodModeStatus; #endif } m_OwnedServerCharacter = null; } /// /// Remove an ally from the PartyHUD UI. /// /// NetworkObjectID of the ally. void RemoveAlly(ulong id) { for (int i = 0; i < m_PartyIds.Length; i++) { // if this ID is in the list, return the slot index if (m_PartyIds[i] == id) { m_AllyPanel[i - 1].SetActive(false); // and save ally ID to party array m_PartyIds[i] = 0; return; } } if (m_TrackedAllies.TryGetValue(id, out ServerCharacter serverCharacter)) { serverCharacter.NetHealthState.HitPoints.OnValueChanged -= (int previousValue, int newValue) => { SetAllyHealth(id, newValue); }; #if UNITY_EDITOR || DEVELOPMENT_BUILD serverCharacter.NetLifeState.IsGodMode.OnValueChanged -= (value, newValue) => { SetAllyGodModeStatus(id, value); }; #endif } } void OnDestroy() { m_PlayerAvatars.ItemAdded -= PlayerAvatarAdded; m_PlayerAvatars.ItemRemoved -= PlayerAvatarRemoved; RemoveHero(); foreach (var kvp in m_TrackedAllies) { RemoveAlly(kvp.Key); } } } }