using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces.GameObjects; using Content.Shared.GameObjects.Components.Storage; using Content.Shared.Interfaces; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystemMessages; using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects.Components; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Players; using Robust.Shared.Serialization; namespace Content.Server.GameObjects { /// /// Storage component for containing entities within this one, matches a UI on the client which shows stored entities /// [RegisterComponent] [ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IStorageComponent))] public class ServerStorageComponent : SharedStorageComponent, IAttackBy, IUse, IActivate, IStorageComponent, IDestroyAct { #pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager; [Dependency] private readonly IEntityManager _entityManager; #pragma warning restore 649 private Container storage; private bool _storageInitialCalculated = false; private int StorageUsed = 0; private int StorageCapacityMax = 10000; public HashSet SubscribedSessions = new HashSet(); public IReadOnlyCollection StoredEntities => storage.ContainedEntities; public override void Initialize() { base.Initialize(); storage = ContainerManagerComponent.Ensure("storagebase", Owner); } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataField(ref StorageCapacityMax, "Capacity", 10000); serializer.DataField(ref StorageUsed, "used", 0); } /// /// Removes from the storage container and updates the stored value /// /// /// public bool Remove(IEntity toremove) { _ensureInitialCalculated(); return storage.Remove(toremove); } internal void HandleEntityMaybeRemoved(EntRemovedFromContainerMessage message) { if (message.Container != storage) { return; } _ensureInitialCalculated(); Logger.DebugS("Storage", "Storage (UID {0}) had entity (UID {1}) removed from it.", Owner.Uid, message.Entity.Uid); StorageUsed -= message.Entity.GetComponent().ObjectSize; UpdateClientInventories(); } /// /// Inserts into the storage container /// /// /// public bool Insert(IEntity toinsert) { return CanInsert(toinsert) && storage.Insert(toinsert); } internal void HandleEntityMaybeInserted(EntInsertedIntoContainerMessage message) { if (message.Container != storage) { return; } _ensureInitialCalculated(); Logger.DebugS("Storage", "Storage (UID {0}) had entity (UID {1}) inserted into it.", Owner.Uid, message.Entity.Uid); StorageUsed += message.Entity.GetComponent().ObjectSize; UpdateClientInventories(); } /// /// Verifies the object can be inserted by checking if it is storeable and if it keeps under the capacity limit /// /// /// public bool CanInsert(IEntity toinsert) { _ensureInitialCalculated(); if (toinsert.TryGetComponent(out ServerStorageComponent storage)) { if (storage.StorageCapacityMax >= StorageCapacityMax) return false; } if (toinsert.TryGetComponent(out StoreableComponent store)) { if (store.ObjectSize > (StorageCapacityMax - StorageUsed)) return false; } return true; } /// /// Inserts storeable entities into this storage container if possible, otherwise return to the hand of the user /// /// /// /// public bool AttackBy(AttackByEventArgs eventArgs) { Logger.DebugS("Storage", "Storage (UID {0}) attacked by user (UID {1}) with entity (UID {2}).", Owner.Uid, eventArgs.User.Uid, eventArgs.AttackWith.Uid); if(Owner.TryGetComponent(out var placeableSurfaceComponent)) { return false; } return PlayerInsertEntity(eventArgs.User); } /// /// Sends a message to open the storage UI /// /// /// bool IUse.UseEntity(UseEntityEventArgs eventArgs) { _ensureInitialCalculated(); OpenStorageUI(eventArgs.User); return false; } public void OpenStorageUI(IEntity Character) { _ensureInitialCalculated(); var user_session = Character.GetComponent().playerSession; Logger.DebugS("Storage", "Storage (UID {0}) \"used\" by player session (UID {1}).", Owner.Uid, user_session.AttachedEntityUid); SubscribeSession(user_session); SendNetworkMessage(new OpenStorageUIMessage(), user_session.ConnectedClient); UpdateClientInventory(user_session); } /// /// Updates the storage UI on all subscribed actors, informing them of the state of the container. /// private void UpdateClientInventories() { foreach (IPlayerSession session in SubscribedSessions) { UpdateClientInventory(session); } } /// /// Adds actor to the update list. /// /// public void SubscribeSession(IPlayerSession session) { _ensureInitialCalculated(); if (!SubscribedSessions.Contains(session)) { Logger.DebugS("Storage", "Storage (UID {0}) subscribed player session (UID {1}).", Owner.Uid, session.AttachedEntityUid); session.PlayerStatusChanged += HandlePlayerSessionChangeEvent; SubscribedSessions.Add(session); UpdateDoorState(); } } /// /// Removes actor from the update list. /// /// public void UnsubscribeSession(IPlayerSession session) { if(SubscribedSessions.Contains(session)) { Logger.DebugS("Storage", "Storage (UID {0}) unsubscribed player session (UID {1}).", Owner.Uid, session.AttachedEntityUid); SubscribedSessions.Remove(session); SendNetworkMessage(new CloseStorageUIMessage(), session.ConnectedClient); UpdateDoorState(); } } private void UpdateDoorState() { if (Owner.TryGetComponent(out AppearanceComponent appearance)) { appearance.SetData(StorageVisuals.Open, SubscribedSessions.Count != 0); } } public void HandlePlayerSessionChangeEvent(object obj, SessionStatusEventArgs SSEA) { Logger.DebugS("Storage", "Storage (UID {0}) handled a status change in player session (UID {1}).", Owner.Uid, SSEA.Session.AttachedEntityUid); if (SSEA.NewStatus != SessionStatus.InGame) { UnsubscribeSession(SSEA.Session); } } /// /// Updates storage UI on a client, informing them of the state of the container. /// private void UpdateClientInventory(IPlayerSession session) { if (session.AttachedEntity == null) { Logger.DebugS("Storage", "Storage (UID {0}) detected no attached entity in player session (UID {1}).", Owner.Uid, session.AttachedEntityUid); UnsubscribeSession(session); return; } Dictionary storedentities = new Dictionary(); foreach (var entities in storage.ContainedEntities) { storedentities.Add(entities.Uid, entities.GetComponent().ObjectSize); } SendNetworkMessage(new StorageHeldItemsMessage(storedentities, StorageUsed, StorageCapacityMax), session.ConnectedClient); } /// /// Receives messages to remove entities from storage, verifies the player can do them, /// and puts the removed entity in hand or on the ground /// /// /// /// public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null) { base.HandleNetworkMessage(message, channel, session); if (session == null) { throw new ArgumentException(nameof(session)); } switch (message) { case RemoveEntityMessage _: { _ensureInitialCalculated(); var playerentity = session.AttachedEntity; var ourtransform = Owner.GetComponent(); var playertransform = playerentity.GetComponent(); if (playertransform.GridPosition.InRange(_mapManager, ourtransform.GridPosition, 2) && (ourtransform.IsMapTransform || playertransform.ContainsEntity(ourtransform))) { var remove = (RemoveEntityMessage)message; var entity = _entityManager.GetEntity(remove.EntityUid); if (entity != null && storage.Contains(entity)) { Remove(entity); var item = entity.GetComponent(); if (item != null && playerentity.TryGetComponent(out HandsComponent hands)) { if (hands.PutInHand(item)) return; } entity.GetComponent().WorldPosition = ourtransform.WorldPosition; } } break; } case InsertEntityMessage _: { _ensureInitialCalculated(); var playerEntity = session.AttachedEntity; var storageTransform = Owner.GetComponent(); var playerTransform = playerEntity.GetComponent(); // TODO: Replace by proper entity range check once it is implemented. if (playerTransform.GridPosition.InRange(_mapManager, storageTransform.GridPosition, InteractionSystem.InteractionRange)) { PlayerInsertEntity(playerEntity); } break; } case CloseStorageUIMessage _: { UnsubscribeSession(session as IPlayerSession); break; } } } /// void IActivate.Activate(ActivateEventArgs eventArgs) { ((IUse) this).UseEntity(new UseEntityEventArgs { User = eventArgs.User }); } private void _ensureInitialCalculated() { if (_storageInitialCalculated) { return; } StorageUsed = 0; if (storage == null) { return; } foreach (var entity in storage.ContainedEntities) { var item = entity.GetComponent(); StorageUsed += item.ObjectSize; } _storageInitialCalculated = true; } void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) { var storedEntities = storage.ContainedEntities.ToList(); foreach (var entity in storedEntities) { Remove(entity); } } /// /// Inserts an entity into the storage component from the players active hand. /// public bool PlayerInsertEntity(IEntity player) { _ensureInitialCalculated(); if (!player.TryGetComponent(out IHandsComponent hands) || hands.GetActiveHand == null) return false; var toInsert = hands.GetActiveHand; if (hands.Drop(toInsert.Owner)) { if (Insert(toInsert.Owner)) { return true; } else { hands.PutInHand(toInsert); } } Owner.PopupMessage(player, "Can't insert."); return false; } } }