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.Server.Utility;
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, IInteractUsing, 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 InteractUsing(InteractUsingEventArgs eventArgs)
{
Logger.DebugS("Storage", "Storage (UID {0}) attacked by user (UID {1}) with entity (UID {2}).", Owner.Uid, eventArgs.User.Uid, eventArgs.Using.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;
}
}
}