using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Content.Client.Clothing; using Content.Client.HUD; using Content.Shared.Input; using Content.Client.Items.Managers; using Content.Client.Items.UI; using Content.Shared.CCVar; using Content.Shared.Hands.Components; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Item; using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Configuration; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Content.Shared.Interaction.Events; namespace Content.Client.Inventory { [UsedImplicitly] public sealed class ClientInventorySystem : InventorySystem { [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [Dependency] private readonly ClothingSystem _clothingSystem = default!; public const int ButtonSize = 64; private const int ButtonSeparation = 4; private const int RightSeparation = 2; /// /// Stores delegates used to create controls for a given . /// private readonly Dictionary>, (DefaultWindow window, Control bottomLeft, Control bottomRight, Control topQuick)>> _uiGenerateDelegates = new(); public override void Initialize() { base.Initialize(); CommandBinds.Builder .Bind(ContentKeyFunctions.OpenInventoryMenu, InputCmdHandler.FromDelegate(_ => HandleOpenInventoryMenu())) .Register(); SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnDidEquip); SubscribeLocalEvent(OnDidUnequip); SubscribeLocalEvent(OnUseInHand); _config.OnValueChanged(CCVars.HudTheme, UpdateHudTheme); } private void OnUseInHand(EntityUid uid, ClothingComponent component, UseInHandEvent args) { if (args.Handled || !component.QuickEquip) return; QuickEquip(uid, component, args); } private void OnDidUnequip(EntityUid uid, ClientInventoryComponent component, DidUnequipEvent args) { UpdateComponentUISlot(uid, args.Slot, null, component); } private void OnDidEquip(EntityUid uid, ClientInventoryComponent component, DidEquipEvent args) { UpdateComponentUISlot(uid, args.Slot, args.Equipment, component); } private void UpdateComponentUISlot(EntityUid uid, string slot, EntityUid? item, ClientInventoryComponent? component = null) { if (!Resolve(uid, ref component)) return; if (!component.SlotButtons.TryGetValue(slot, out var buttons)) return; UpdateUISlot(buttons, item); } private void UpdateUISlot(List buttons, EntityUid? entity) { foreach (var button in buttons) { _itemSlotManager.SetItemSlot(button, entity); } } private void OnPlayerDetached(EntityUid uid, ClientInventoryComponent component, PlayerDetachedEvent? args = null) { if(!component.AttachedToGameHud) return; _gameHud.InventoryButtonVisible = false; _gameHud.BottomLeftInventoryQuickButtonContainer.RemoveChild(component.BottomLeftButtons); _gameHud.BottomRightInventoryQuickButtonContainer.RemoveChild(component.BottomRightButtons); _gameHud.TopInventoryQuickButtonContainer.RemoveChild(component.TopQuickButtons); component.AttachedToGameHud = false; } private void OnShutdown(EntityUid uid, ClientInventoryComponent component, ComponentShutdown args) { OnPlayerDetached(uid, component); } private void OnPlayerAttached(EntityUid uid, ClientInventoryComponent component, PlayerAttachedEvent args) { if(component.AttachedToGameHud) return; _gameHud.InventoryButtonVisible = true; _gameHud.BottomLeftInventoryQuickButtonContainer.AddChild(component.BottomLeftButtons); _gameHud.BottomRightInventoryQuickButtonContainer.AddChild(component.BottomRightButtons); _gameHud.TopInventoryQuickButtonContainer.AddChild(component.TopQuickButtons); component.AttachedToGameHud = true; } private void UpdateHudTheme(int obj) { if (!_gameHud.ValidateHudTheme(obj)) { return; } foreach (var inventoryComponent in EntityManager.EntityQuery(true)) { foreach (var slotButton in inventoryComponent.SlotButtons) { foreach (var btn in slotButton.Value) { btn.RefreshTextures(_gameHud); } } } } public override void Shutdown() { CommandBinds.Unregister(); _config.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme); base.Shutdown(); } private void OnInit(EntityUid uid, ClientInventoryComponent component, ComponentInit args) { _clothingSystem.InitClothing(uid, component); if (!TryGetUIElements(uid, out var window, out var bottomLeft, out var bottomRight, out var topQuick, component)) return; if (TryComp(uid, out var containerManager)) { foreach (var (slot, buttons) in component.SlotButtons) { if (!TryGetSlotEntity(uid, slot, out var entity, component, containerManager)) continue; UpdateUISlot(buttons, entity); } } component.InventoryWindow = window; component.BottomLeftButtons = bottomLeft; component.BottomRightButtons = bottomRight; component.TopQuickButtons = topQuick; } private void HoverInSlotButton(EntityUid uid, string slot, ItemSlotButton button, InventoryComponent? inventoryComponent = null, SharedHandsComponent? hands = null) { if (!Resolve(uid, ref inventoryComponent)) return; if (!Resolve(uid, ref hands, false)) return; if (!hands.TryGetActiveHeldEntity(out var heldEntity)) return; if(!TryGetSlotContainer(uid, slot, out var containerSlot, out var slotDef, inventoryComponent)) return; _itemSlotManager.HoverInSlot(button, heldEntity.Value, CanEquip(uid, heldEntity.Value, slot, out _, slotDef, inventoryComponent) && containerSlot.CanInsert(heldEntity.Value, EntityManager)); } private void HandleSlotButtonPressed(EntityUid uid, string slot, ItemSlotButton button, GUIBoundKeyEventArgs args) { if (TryGetSlotEntity(uid, slot, out var itemUid) && _itemSlotManager.OnButtonPressed(args, itemUid.Value)) return; if (args.Function != EngineKeyFunctions.UIClick) return; // only raise event if either itemUid is not null, or the user is holding something if (itemUid != null || TryComp(uid, out SharedHandsComponent? hands) && hands.TryGetActiveHeldEntity(out _)) EntityManager.RaisePredictiveEvent(new UseSlotNetworkMessage(slot)); } private bool TryGetUIElements(EntityUid uid, [NotNullWhen(true)] out DefaultWindow? invWindow, [NotNullWhen(true)] out Control? invBottomLeft, [NotNullWhen(true)] out Control? invBottomRight, [NotNullWhen(true)] out Control? invTopQuick, ClientInventoryComponent? component = null) { invWindow = null; invBottomLeft = null; invBottomRight = null; invTopQuick = null; if (!Resolve(uid, ref component)) return false; if(!_prototypeManager.TryIndex(component.TemplateId, out var template)) return false; if (!_uiGenerateDelegates.TryGetValue(component.TemplateId, out var genfunc)) { _uiGenerateDelegates[component.TemplateId] = genfunc = (entityUid, list) => { var window = new DefaultWindow() { Title = Loc.GetString("human-inventory-window-title"), Resizable = false }; window.OnClose += () => { _gameHud.InventoryButtonDown = false; _gameHud.TopInventoryQuickButtonContainer.Visible = false; }; var windowContents = new LayoutContainer { MinSize = (ButtonSize * 4 + ButtonSeparation * 3 + RightSeparation, ButtonSize * 4 + ButtonSeparation * 3) }; window.Contents.AddChild(windowContents); ItemSlotButton GetButton(SlotDefinition definition, string textureBack) { var btn = new ItemSlotButton(ButtonSize, $"{definition.TextureName}.png", textureBack, _gameHud) { OnStoragePressed = (e) => { if (e.Function != EngineKeyFunctions.UIClick && e.Function != ContentKeyFunctions.ActivateItemInWorld) return; RaiseNetworkEvent(new OpenSlotStorageNetworkMessage(definition.Name)); } }; btn.OnHover = (_) => { HoverInSlotButton(entityUid, definition.Name, btn); }; btn.OnPressed = (e) => { HandleSlotButtonPressed(entityUid, definition.Name, btn, e); }; return btn; } void AddButton(SlotDefinition definition, Vector2i position) { var button = GetButton(definition, "back.png"); LayoutContainer.SetPosition(button, position); windowContents.AddChild(button); if (!list.ContainsKey(definition.Name)) list[definition.Name] = new(); list[definition.Name].Add(button); } void AddHUDButton(BoxContainer container, SlotDefinition definition) { var button = GetButton(definition, "back.png"); container.AddChild(button); if (!list.ContainsKey(definition.Name)) list[definition.Name] = new(); list[definition.Name].Add(button); } var topQuick = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, SeparationOverride = 5 }; var bottomRight = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, SeparationOverride = 5 }; var bottomLeft = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal, SeparationOverride = 5 }; const int sizep = (ButtonSize + ButtonSeparation); foreach (var slotDefinition in template.Slots) { switch (slotDefinition.UIContainer) { case SlotUIContainer.BottomLeft: AddHUDButton(bottomLeft, slotDefinition); break; case SlotUIContainer.BottomRight: AddHUDButton(bottomRight, slotDefinition); break; case SlotUIContainer.Top: AddHUDButton(topQuick, slotDefinition); break; } AddButton(slotDefinition, slotDefinition.UIWindowPosition * sizep); } return (window, bottomLeft, bottomRight, topQuick); }; } var res = genfunc(uid, component.SlotButtons); invWindow = res.window; invBottomLeft = res.bottomLeft; invBottomRight = res.bottomRight; invTopQuick = res.topQuick; return true; } private void HandleOpenInventoryMenu() { _gameHud.InventoryButtonDown = !_gameHud.InventoryButtonDown; _gameHud.TopInventoryQuickButtonContainer.Visible = _gameHud.InventoryButtonDown; } } }