using System.Collections.Generic; using Jovian.PopupSystem; using Nox.UI; using UnityEngine; using Object = UnityEngine.Object; namespace Nox.Game.UI { public class PartyGuiView { private const int PoolSize = 4; private readonly Transform portraitsContainer; private readonly PartyMemberSlot slotPrefab; private readonly PortraitsHolder portraitsHolder; private readonly IPopupSystem popupSystem; private readonly PartyMemberSlot[] slotPool = new PartyMemberSlot[PoolSize]; private PartyDefinition trackedParty; private int activeCount; public PartyGuiView(Transform portraitsContainer, PartyMemberSlot slotPrefab, PortraitsHolder portraitsHolder, IPopupSystem popupSystem = null) { this.portraitsContainer = portraitsContainer; this.slotPrefab = slotPrefab; this.portraitsHolder = portraitsHolder; this.popupSystem = popupSystem; } public void Initialize(PartyDefinition party) { trackedParty = party; activeCount = 0; // Pre-create all slots (deactivated) for(int i = 0; i < PoolSize; i++) { if(slotPool[i] == null) { slotPool[i] = Object.Instantiate(slotPrefab, portraitsContainer); } slotPool[i].gameObject.SetActive(false); } PopulateSlots(); } public void Tick() { if(trackedParty == null) { return; } // Rebuild if member count changed if(trackedParty.members.Count != activeCount) { PopulateSlots(); } // Update dynamic values var memberCount = Mathf.Min(activeCount, trackedParty.members.Count); for(int i = 0; i < memberCount; i++) { UpdateSlotStats(slotPool[i], trackedParty.members[i]); } } private void PopulateSlots() { var memberCount = trackedParty?.members != null ? Mathf.Min(trackedParty.members.Count, PoolSize) : 0; // Activate slots for current members, deactivate the rest for(int i = 0; i < PoolSize; i++) { var slot = slotPool[i]; if(i < memberCount) { var member = trackedParty.members[i]; slot.gameObject.SetActive(true); // Portrait if(portraitsHolder != null && portraitsHolder.portraits.Length > 0) { var idx = Mathf.Clamp(member.PortraitIndex, 0, portraitsHolder.portraits.Length - 1); slot.portrait.sprite = portraitsHolder.portraits[idx]; } UpdateSlotStats(slot, member); } else { slot.gameObject.SetActive(false); } } activeCount = memberCount; // Initialize popup triggers if(popupSystem != null) { popupSystem.InitializeTriggersInChildren(portraitsContainer, (trigger, view) => { var slot = trigger.GetComponentInParent(); if(slot == null) { return; } var slotIndex = System.Array.IndexOf(slotPool, slot); if(slotIndex < 0 || slotIndex >= activeCount) { return; } var member = trackedParty.members[slotIndex]; view.SetContent(builder => BuildCharacterPopup(builder, member)); }); } } private void BuildCharacterPopup(PopupContentBuilder builder, CharacterDefinition member) { // Header builder .AddText(member.Name, PopupElementType.Header) .AddText($"{member.Race} {member.Class}", "#CCCCCC", PopupElementType.Text) .AddText($"Role: {member.Role}", PopupElementType.Text) .AddSeparator(PopupElementType.Separator); // Stats if(member.Stats?.stats != null) { var level = member.Stats.GetValue(StatType.Level); var xp = member.Stats.GetValue(StatType.Experience); var health = member.Stats.GetValue(StatType.Health); var mana = member.Stats.GetValue(StatType.Mana); builder .AddNameValue("Level", level, PopupElementType.LabelValueText) .AddNameValue("XP", xp, PopupElementType.LabelValueText) .AddSeparator(PopupElementType.Separator) .AddNameValue("Health", health, PopupElementType.LabelValueText) .AddNameValue("Mana", mana, PopupElementType.LabelValueText) .AddSeparator(PopupElementType.Separator); } // Attributes if(member.Attributes?.attributes != null) { foreach(var attr in member.Attributes.attributes) { if(attr.attribute == AttributeType.None) { continue; } builder.AddNameValue(attr.attribute.ToString(), attr.value, PopupElementType.LabelValueText); } } builder.AddSeparator(PopupElementType.Separator); // Perks if(member.Perks?.perks is { Count: > 0 }) { builder.AddText("Perks", "#FFD700", PopupElementType.Text); foreach(var perk in member.Perks.perks) { builder.AddText($" {perk.Name}", PopupElementType.Text); } builder.AddSeparator(PopupElementType.Separator); } // Modifiers if(member.Modifiers?.modifiers is { Count: > 0 }) { builder.AddText("Modifiers", "#87CEEB", PopupElementType.Text); foreach(var mod in member.Modifiers.modifiers) { var target = mod.Target != null ? mod.Target.ToString() : ""; builder.AddText($" {mod.Name} ({mod.Operation} {mod.Value} {target})", PopupElementType.Text); } } } private static void UpdateSlotStats(PartyMemberSlot slot, CharacterDefinition member) { var health = member.Stats.GetValue(StatType.Health); var mana = member.Stats.GetValue(StatType.Mana); if(slot.healthBar != null) { slot.healthBar.fillAmount = Mathf.Clamp01(health / 100f); } if(slot.manaBar != null) { slot.manaBar.fillAmount = Mathf.Clamp01(mana / 100f); } } public void Dispose() { for(int i = 0; i < PoolSize; i++) { if(slotPool[i] != null) { Object.Destroy(slotPool[i].gameObject); slotPool[i] = null; } } activeCount = 0; } } }