using System; using System.Collections.Generic; using System.Linq; using Content.Client.Animations; using Content.Client.GameObjects.Components.Items; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Storage; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.Shared.Players; namespace Content.Client.GameObjects.Components.Storage { /// /// Client version of item storage containers, contains a UI which displays stored entities and their size /// [RegisterComponent] public class ClientStorageComponent : SharedStorageComponent, IDraggable { private List _storedEntities = new(); private int StorageSizeUsed; private int StorageCapacityMax; private StorageWindow Window; private SharedBagState _bagState; public override IReadOnlyList StoredEntities => _storedEntities; public override void Initialize() { base.Initialize(); // Hide stackVisualizer on start _bagState = SharedBagState.Close; } public override void OnAdd() { base.OnAdd(); Window = new StorageWindow() { StorageEntity = this, Title = Owner.Name }; } public override void OnRemove() { Window.Dispose(); base.OnRemove(); } public override void HandleComponentState(ComponentState curState, ComponentState nextState) { base.HandleComponentState(curState, nextState); if (curState is not StorageComponentState state) { return; } _storedEntities = state.StoredEntities .Select(id => Owner.EntityManager.GetEntity(id)) .ToList(); } public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null) { base.HandleNetworkMessage(message, channel, session); switch (message) { //Updates what we are storing for the UI case StorageHeldItemsMessage msg: HandleStorageMessage(msg); ChangeStorageVisualization(_bagState); break; //Opens the UI case OpenStorageUIMessage _: ChangeStorageVisualization(SharedBagState.Open); ToggleUI(); break; case CloseStorageUIMessage _: ChangeStorageVisualization(SharedBagState.Close); CloseUI(); break; case AnimateInsertingEntitiesMessage msg: HandleAnimatingInsertingEntities(msg); break; } } /// /// Copies received values from server about contents of storage container /// /// private void HandleStorageMessage(StorageHeldItemsMessage storageState) { _storedEntities = storageState.StoredEntities.Select(id => Owner.EntityManager.GetEntity(id)).ToList(); StorageSizeUsed = storageState.StorageSizeUsed; StorageCapacityMax = storageState.StorageSizeMax; Window.BuildEntityList(); } /// /// Animate the newly stored entities in flying towards this storage's position /// /// private void HandleAnimatingInsertingEntities(AnimateInsertingEntitiesMessage msg) { for (var i = 0; msg.StoredEntities.Count > i; i++) { var entityId = msg.StoredEntities[i]; var initialPosition = msg.EntityPositions[i]; if (Owner.EntityManager.TryGetEntity(entityId, out var entity)) { ReusableAnimations.AnimateEntityPickup(entity, initialPosition, Owner.Transform.WorldPosition); } } } /// /// Opens the storage UI if closed. Closes it if opened. /// private void ToggleUI() { if (Window.IsOpen) Window.Close(); else Window.Open(); } private void CloseUI() { Window.Close(); } private void ChangeStorageVisualization(SharedBagState state) { _bagState = state; if (Owner.TryGetComponent(out var appearanceComponent)) { appearanceComponent.SetData(SharedBagOpenVisuals.BagState, state); appearanceComponent.SetData(StackVisuals.Hide, state == SharedBagState.Close); } } /// /// Function for clicking one of the stored entity buttons in the UI, tells server to remove that entity /// /// private void Interact(EntityUid entityUid) { SendNetworkMessage(new RemoveEntityMessage(entityUid)); } public override bool Remove(IEntity entity) { if (_storedEntities.Remove(entity)) { Dirty(); return true; } return false; } /// /// GUI class for client storage component /// private class StorageWindow : SS14Window { private Control VSplitContainer; private readonly VBoxContainer _entityList; private readonly Label _information; public ClientStorageComponent StorageEntity; private readonly StyleBoxFlat _hoveredBox = new() { BackgroundColor = Color.Black.WithAlpha(0.35f) }; private readonly StyleBoxFlat _unHoveredBox = new() { BackgroundColor = Color.Black.WithAlpha(0.0f) }; public StorageWindow() { MinSize = SetSize = (180, 320); Title = "Storage Item"; RectClipContent = true; var containerButton = new ContainerButton { MouseFilter = MouseFilterMode.Pass, }; var innerContainerButton = new PanelContainer { PanelOverride = _unHoveredBox, }; containerButton.AddChild(innerContainerButton); containerButton.OnPressed += args => { var controlledEntity = IoCManager.Resolve().LocalPlayer.ControlledEntity; if (controlledEntity.TryGetComponent(out HandsComponent hands)) { StorageEntity.SendNetworkMessage(new InsertEntityMessage()); } }; VSplitContainer = new VBoxContainer() { MouseFilter = MouseFilterMode.Ignore, }; containerButton.AddChild(VSplitContainer); _information = new Label { Text = "Items: 0 Volume: 0/0 Stuff", VerticalAlignment = VAlignment.Center }; VSplitContainer.AddChild(_information); var listScrollContainer = new ScrollContainer { VerticalExpand = true, HorizontalExpand = true, HScrollEnabled = true, VScrollEnabled = true, }; _entityList = new VBoxContainer { HorizontalExpand = true }; listScrollContainer.AddChild(_entityList); VSplitContainer.AddChild(listScrollContainer); Contents.AddChild(containerButton); listScrollContainer.OnMouseEntered += args => { innerContainerButton.PanelOverride = _hoveredBox; }; listScrollContainer.OnMouseExited += args => { innerContainerButton.PanelOverride = _unHoveredBox; }; } public override void Close() { StorageEntity.SendNetworkMessage(new CloseStorageUIMessage()); base.Close(); } /// /// Loops through stored entities creating buttons for each, updates information labels /// public void BuildEntityList() { _entityList.DisposeAllChildren(); var storageList = StorageEntity.StoredEntities; var storedGrouped = storageList.GroupBy(e => e).Select(e => new { Entity = e.Key, Amount = e.Count() }); foreach (var group in storedGrouped) { var entity = group.Entity; var button = new EntityButton() { EntityUid = entity.Uid, MouseFilter = MouseFilterMode.Stop, }; button.ActualButton.OnToggled += OnItemButtonToggled; //Name and Size labels set button.EntityName.Text = entity.Name; button.EntitySize.Text = group.Amount.ToString(); //Gets entity sprite and assigns it to button texture if (entity.TryGetComponent(out ISpriteComponent sprite)) { button.EntitySpriteView.Sprite = sprite; } _entityList.AddChild(button); } //Sets information about entire storage container current capacity if (StorageEntity.StorageCapacityMax != 0) { _information.Text = String.Format("Items: {0}, Stored: {1}/{2}", storageList.Count, StorageEntity.StorageSizeUsed, StorageEntity.StorageCapacityMax); } else { _information.Text = String.Format("Items: {0}", storageList.Count); } } /// /// Function assigned to button toggle which removes the entity from storage /// /// private void OnItemButtonToggled(BaseButton.ButtonToggledEventArgs args) { var control = (EntityButton) args.Button.Parent; args.Button.Pressed = false; StorageEntity.Interact(control.EntityUid); } } /// /// Button created for each entity that represents that item in the storage UI, with a texture, and name and size label /// private class EntityButton : Control { public EntityUid EntityUid { get; set; } public Button ActualButton { get; } public SpriteView EntitySpriteView { get; } public Control EntityControl { get; } public Label EntityName { get; } public Label EntitySize { get; } public EntityButton() { ActualButton = new Button { HorizontalExpand = true, VerticalExpand = true, ToggleMode = true, MouseFilter = MouseFilterMode.Stop }; AddChild(ActualButton); var hBoxContainer = new HBoxContainer(); EntitySpriteView = new SpriteView { MinSize = new Vector2(32.0f, 32.0f), OverrideDirection = Direction.South }; EntityName = new Label { VerticalAlignment = VAlignment.Center, Text = "Backpack", }; hBoxContainer.AddChild(EntitySpriteView); hBoxContainer.AddChild(EntityName); EntityControl = new Control { HorizontalExpand = true }; EntitySize = new Label { VerticalAlignment = VAlignment.Center, Text = "Size 6", Align = Label.AlignMode.Right, /*AnchorLeft = 1.0f, AnchorRight = 1.0f, AnchorBottom = 0.5f, AnchorTop = 0.5f, MarginLeft = -38.0f, MarginTop = -7.0f, MarginRight = -5.0f, MarginBottom = 7.0f*/ }; EntityControl.AddChild(EntitySize); hBoxContainer.AddChild(EntityControl); AddChild(hBoxContainer); } } } }