using System; using System.Collections.Generic; using System.Linq; using GameCult.Aetheria.State.Verse; using UnityEngine; public sealed class AetheriaUnityActionBarPresentation { private IReadOnlyList _slots = Array.Empty(); private AetheriaRuntimeCatalogSnapshot _catalog; private GameSettings _settings; private Func _resolveEntity = () => null; private Func _resolveControl = () => null; private IReadOnlyList _localBindings = Array.Empty(); public void Bind( IReadOnlyList slots, AetheriaRuntimeCatalogSnapshot catalog, GameSettings settings, Func resolveEntity, Func resolveControl) { _slots = slots ?? Array.Empty(); _catalog = catalog; _settings = settings; _resolveEntity = resolveEntity ?? (() => null); _resolveControl = resolveControl ?? (() => null); } public int SlotCount => _slots?.Count ?? 0; public string SlotLabel(int slotIndex) { var slot = ResolveSlot(slotIndex); if (slot == null) return ""; var controlPath = slot.ControlPath ?? ""; if (string.IsNullOrWhiteSpace(controlPath)) return "Action Bar"; var slashIndex = controlPath.LastIndexOf('/'); return slashIndex >= 0 && slashIndex < controlPath.Length - 1 ? controlPath.Substring(slashIndex + 1) : controlPath; } public string BindingLabel(int slotIndex) { var slot = ResolveSlot(slotIndex); return slot?.Binding switch { ActionBarWeaponGroupBinding weaponGroup => $"G{weaponGroup.Group + 1}", ActionBarConsumableBinding consumable => consumable.Target?.Name ?? "Consumable", ActionBarGearBinding gear => _catalog?.FindItem(gear.Item?.EquippableItem, x => x.ItemKey)?.Name ?? "Gear", _ => "Empty" }; } public bool TryResolveControlPath(int slotIndex, out string controlPath) { controlPath = ResolveSlot(slotIndex)?.ControlPath ?? ""; return !string.IsNullOrWhiteSpace(controlPath); } public void RequestBinding(ActionBarSlot slot, DragObject dragAction) { var entity = _resolveEntity(); if (slot == null || dragAction == null || entity == null) return; var binding = CreateBindingCommit(slot, entity, dragAction); if (binding == null || string.IsNullOrWhiteSpace(binding.ControlPath) || string.IsNullOrWhiteSpace(binding.Kind)) { return; } SetLocalBinding(binding); ApplyLocalBindings(); } public bool RequestWeaponGroupBinding(int slotIndex, int groupIndex) { if (groupIndex < 0 || !TryResolveControlPath(slotIndex, out var controlPath)) return false; SetLocalBinding(new AetheriaUnityActionBarBinding { ControlPath = controlPath, Kind = "weapon_group", ItemKey = "", EquipmentIndex = -1, BehaviorIndex = -1, WeaponGroup = groupIndex }); ApplyLocalBindings(); return true; } public bool ClearBinding(int slotIndex) { if (!TryResolveControlPath(slotIndex, out var controlPath)) return false; _localBindings = (_localBindings ?? Array.Empty()) .Where(binding => !string.Equals(binding?.ControlPath ?? "", controlPath, StringComparison.Ordinal)) .ToArray(); ApplyLocalBindings(); return true; } public void ApplyLocalBindings() { ApplyBindings(_resolveEntity(), _localBindings); } public void ApplyBindings( Entity entity, IReadOnlyList bindings) { if (_slots == null || _slots.Count == 0) return; foreach (var slot in _slots) slot.Binding = null; if (entity == null || bindings == null || bindings.Count == 0) return; var slotsByControlPath = _slots .Where(slot => !string.IsNullOrWhiteSpace(slot?.ControlPath)) .GroupBy(slot => slot.ControlPath, StringComparer.Ordinal) .ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal); foreach (var binding in bindings) { if (binding == null || string.IsNullOrWhiteSpace(binding.ControlPath) || !slotsByControlPath.TryGetValue(binding.ControlPath, out var slot)) { continue; } var slotBinding = CreateBinding(slot, entity, binding); if (slotBinding != null) slot.Binding = slotBinding; } } public ActionBarBinding CreateBinding(ActionBarSlot slot, Entity entity, DragObject dragAction) { var binding = CreateBindingCommit(slot, entity, dragAction); return binding == null ? null : CreateBinding(slot, entity, binding); } private AetheriaUnityActionBarBinding CreateBindingCommit( ActionBarSlot slot, Entity entity, DragObject dragAction) { if (slot == null || entity == null || dragAction == null) return null; switch (dragAction) { case EquippedItemDragObject equippedItemDragAction: var equippedItem = equippedItemDragAction.EquippedItem; var trigger = equippedItem?.GetBehavior(); var equipmentIndex = equippedItem == null ? -1 : entity.Equipment.IndexOf(equippedItem); var behaviorIndex = equippedItem?.Behaviors == null ? -1 : Array.IndexOf(equippedItem.Behaviors, trigger); return trigger == null || equipmentIndex < 0 || behaviorIndex < 0 ? null : new AetheriaUnityActionBarBinding { ControlPath = slot.ControlPath ?? "", Kind = "gear", ItemKey = equippedItem.EquippableItem?.ItemKey ?? "", EquipmentIndex = equipmentIndex, BehaviorIndex = behaviorIndex }; case ItemInstanceDragObject itemInstanceDragAction: var consumable = FindConsumable(itemInstanceDragAction.Item); return consumable == null ? null : new AetheriaUnityActionBarBinding { ControlPath = slot.ControlPath ?? "", Kind = "consumable", ItemKey = consumable.ItemKey ?? "" }; default: return null; } } private void SetLocalBinding(AetheriaUnityActionBarBinding binding) { if (binding == null || string.IsNullOrWhiteSpace(binding.ControlPath)) return; _localBindings = (_localBindings ?? Array.Empty()) .Where(existing => !string.Equals(existing?.ControlPath ?? "", binding.ControlPath, StringComparison.Ordinal)) .Concat(new[] { binding }) .ToArray(); } private ActionBarBinding CreateBinding( ActionBarSlot slot, Entity entity, AetheriaUnityActionBarBinding binding) { if (slot == null || entity == null || binding == null) return null; switch (binding.Kind) { case "consumable": var consumable = _catalog?.FindItem(binding.ItemKey ?? ""); return consumable != null && string.Equals(consumable.Category, AetheriaRuntimeItemCategories.Consumable, StringComparison.Ordinal) ? new ActionBarConsumableBinding(entity, slot, _resolveControl, _settings, consumable) : null; case "gear": if (binding.EquipmentIndex < 0 || binding.EquipmentIndex >= entity.Equipment.Count) return null; var equippedItem = entity.Equipment[binding.EquipmentIndex]; var behaviors = equippedItem?.Behaviors; if (equippedItem == null || !string.Equals(equippedItem.EquippableItem?.ItemKey ?? "", binding.ItemKey ?? "", StringComparison.Ordinal) || behaviors == null || binding.BehaviorIndex < 0 || binding.BehaviorIndex >= behaviors.Length) { return null; } if (!(behaviors[binding.BehaviorIndex] is IActivatedBehavior activatedBehavior)) return null; return new ActionBarGearBinding(entity, slot, _resolveControl, _settings, equippedItem, activatedBehavior); case "weapon_group": return entity.WeaponGroups != null && binding.WeaponGroup >= 0 && binding.WeaponGroup < entity.WeaponGroups.Length ? new ActionBarWeaponGroupBinding(entity, slot, _resolveControl, _settings, binding.WeaponGroup) : null; default: return null; } } private AetheriaRuntimeCatalogItem FindConsumable(ItemInstance item) { var typedItem = _catalog?.FindItem(item, x => x.ItemKey); return typedItem != null && string.Equals(typedItem.Category, AetheriaRuntimeItemCategories.Consumable, StringComparison.Ordinal) ? typedItem : null; } private ActionBarSlot ResolveSlot(int slotIndex) { return _slots != null && slotIndex >= 0 && slotIndex < _slots.Count ? _slots[slotIndex] : null; } }