@@ -1,11 +1,9 @@
using System ;
#nullable enable
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.Click ;
using Content.Server.Interfaces.GameObjects.Components.Interaction ;
using Content.Server.Interfaces.GameObjects ;
using Content.Server.Interfaces.GameObjects.Components.Interaction ;
using Content.Server.Utility ;
using Content.Shared.GameObjects.Components.Storage ;
using Content.Shared.Interfaces ;
@@ -17,16 +15,14 @@ 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.Maths ;
using Robust.Shared.Players ;
using Robust.Shared.Serialization ;
namespace Content.Server.GameObjects
namespace Content.Server.GameObjects.Components.Items.Storage
{
/// <summary>
/// Storage component for containing entities within this one, matches a UI on the client which shows stored entities
@@ -38,146 +34,182 @@ namespace Content.Server.GameObjects
IDragDrop
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager ;
[Dependency] private readonly IEntityManager _entityManager ;
[Dependency] private readonly IMapManager _mapManager = default ! ;
[Dependency] private readonly IEntityManager _entityManager = default ! ;
#pragma warning restore 649
private Container s torage;
private const string LoggerName = "S torage" ;
private bool _storageInitialCalculated = fals e;
private int StorageUsed = 0 ;
private int StorageCapacityMax = 10000 ;
public HashSet < IPlayerSession > SubscribedSessions = new HashSet < IPlayerSession > ( ) ;
private Container ? _storag e;
public IReadOnlyCollection < IEntity > StoredEntities = > storage. ContainedEntities ;
private bool _ storageInitialCalculated ;
private int _storageUsed ;
private int _storageCapacityMax = 10000 ;
public readonly HashSet < IPlayerSession > SubscribedSessions = new HashSet < IPlayerSession > ( ) ;
public override void Initializ e( )
public IReadOnlyCollection < IEntity > ? StoredEntities = > _storag e? . ContainedEntities ;
private void EnsureInitialCalculated ( )
{
base . Initialize ( ) ;
storage = ContainerManagerComponent . Ensure < Container > ( "storagebase" , Owner ) ;
}
public override void ExposeData ( ObjectSerializer serializer )
{
base . ExposeData ( serializer ) ;
serializer . DataField ( ref StorageCapacityMax , "Capacity" , 10000 ) ;
//serializer.DataField(ref StorageUsed, "used", 0);
}
/// <summary>
/// Removes from the storage container and updates the stored value
/// </summary>
/// <param name="toremove"></param>
/// <returns></returns>
public bool Remove ( IEntity toremove )
{
_ensureInitialCalculated ( ) ;
return storage . Remove ( toremove ) ;
}
internal void HandleEntityMaybeRemoved ( EntRemovedFromContainerMessage message )
{
if ( message . Container ! = storage )
if ( _storageInitialCalculated )
{
return ;
}
_ensureInitialCalculat ed( ) ;
Logger . DebugS ( "Storage" , "Storage (UID {0}) had entity (UID {1}) removed from it." , Owner . Uid ,
message . Entity . Uid ) ;
StorageUsed - = message . Entity . GetComponent < StoreableComponent > ( ) . ObjectSize ;
UpdateClientInventories ( ) ;
RecalculateStorageUs ed( ) ;
_storageInitialCalculated = true ;
}
private void RecalculateStorageUsed ( )
{
_storageUsed = 0 ;
if ( _storage = = null )
{
return ;
}
foreach ( var entity in _storage . ContainedEntities )
{
var item = entity . GetComponent < StorableComponent > ( ) ;
_storageUsed + = item . ObjectSize ;
}
}
/// <summary>
/// Verifies if an entity can be stored and if it fits
/// </summary>
/// <param name="entity">The entity to check</param>
/// <returns>true if it can be inserted, false otherwise</returns>
public bool CanInsert ( IEntity entity )
{
EnsureInitialCalculated ( ) ;
if ( entity . TryGetComponent ( out ServerStorageComponent storage ) & &
storage . _storageCapacityMax > = _storageCapacityMax )
{
return false ;
}
if ( entity . TryGetComponent ( out StorableComponent store ) & &
store . ObjectSize > _storageCapacityMax - _storageUsed )
{
return false ;
}
return true ;
}
/// <summary>
/// Inserts into the storage container
/// </summary>
/// <param name="toinsert"> </param>
/// <returns></returns>
public bool Insert ( IEntity toinsert )
/// <param name="entity">The entity to insert</param>
/// <returns>true if the entity was inserted, false otherwise </returns>
public bool Insert ( IEntity entity )
{
return CanInsert ( toinsert ) & & storage . Insert ( toinsert ) ;
return CanInsert ( entity ) & & _ storage? .Insert ( entity ) = = true ;
}
internal void HandleEntityMaybeInserted ( EntInsertedIntoContainerMessage message )
/// <summary>
/// Removes from the storage container and updates the stored value
/// </summary>
/// <param name="entity">The entity to remove</param>
/// <returns>true if no longer in storage, false otherwise</returns>
public bool Remove ( IEntity entity )
{
if ( message . Container ! = storage )
EnsureInitialCalculated ( ) ;
return _storage ? . Remove ( entity ) = = true ;
}
public void HandleEntityMaybeInserted ( EntInsertedIntoContainerMessage message )
{
if ( message . Container ! = _storage )
{
return ;
}
_e nsureInitialCalculated( ) ;
Logger . DebugS ( "Storage" , "Storage (UID {0}) had entity (UID {1}) inserted into it." , Owner . Uid ,
message. Entity. Uid ) ;
StorageUsed + = message . Entity . GetComponent < StoreableComponent > ( ) . ObjectSize ;
E nsureInitialCalculated( ) ;
Logger . DebugS ( LoggerName , $"Storage (UID {Owner.Uid}) had entity (UID { message. Entity.Uid}) inserted into it." ) ;
_storageUsed + = message . Entity . GetComponent < StorableComponent > ( ) . ObjectSize ;
UpdateClientInventories ( ) ;
}
public void HandleEntityMaybeRemoved ( EntRemovedFromContainerMessage message )
{
if ( message . Container ! = _storage )
{
return ;
}
EnsureInitialCalculated ( ) ;
Logger . DebugS ( LoggerName , $"Storage (UID {Owner.Uid}) had entity (UID {message.Entity.Uid}) removed from it." ) ;
if ( ! message . Entity . TryGetComponent ( out StorableComponent storable ) )
{
Logger . WarningS ( LoggerName , $"Removed entity {message.Entity.Uid} without a StorableComponent from storage {Owner.Uid} at {Owner.Transform.MapPosition}" ) ;
RecalculateStorageUsed ( ) ;
return ;
}
_storageUsed - = storable . ObjectSize ;
UpdateClientInventories ( ) ;
}
/// <summary>
/// Verifies the object can be inserted by checking if it is storeable and if it keeps under the capacity limit
/// Inserts an ent ity into storage from the player's active hand
/// </summary>
/// <param name="toinsert"> </param>
/// <returns></returns>
public bool CanInsert ( IEntity toins ert )
/// <param name="player">The player to insert an entity from </param>
/// <returns>true if inserted, false otherwise </returns>
public bool PlayerInsertEntity ( IEntity play er)
{
_e nsureInitialCalculated( ) ;
E nsureInitialCalculated( ) ;
if ( toins ert . TryGetComponent ( out ServerStorageComponent storage ) )
if ( ! play er. TryGetComponent ( out IHandsComponent hands ) | |
hands . GetActiveHand = = null )
{
if ( storage . StorageCapacityMax > = StorageCapacityMax )
return false ;
}
if ( toi nsert . TryGetComponent ( out StoreableComponent store ) )
var toI nsert = hands . GetActiveHand ;
if ( ! hands . Drop ( toInsert . Owner ) )
{
if ( store . ObjectSize > ( StorageCapacityMax - StorageUsed ) )
Owner . PopupMessage ( player , "Can't insert." ) ;
return false ;
}
if ( ! Insert ( toInsert . Owner ) )
{
hands . PutInHand ( toInsert ) ;
Owner . PopupMessage ( player , "Can't insert." ) ;
return false ;
}
return true ;
}
/// <summary>
/// Inserts storeable entities into this storage container if possible, otherwise return to the hand of the user
/// Opens the storage UI for an entity
/// </summary>
/// <param name="user"> </param>
/// <param name="attackwith"></param>
/// <returns></returns>
public bool InteractUsing ( InteractUsingEventArgs eventArgs )
/// <param name="entity">The entity to open the UI for </param>
public void OpenStorageUI ( IEntity entity )
{
Logger . DebugS ( "Storage" , "Storage (UID {0}) attacked by user (UID {1}) with entity (UID {2})." , Owner . Uid , eventArgs . User . Uid , eventArgs . Using . Uid ) ;
EnsureInitialCalculated ( ) ;
if ( Owner . Try GetComponent< PlaceableSurfaceComponent > ( out var placeableSurfaceComponent ) )
{
return false ;
}
var userSession = entity . GetComponent < BasicActorComponent > ( ) . playerSession ;
Logger . DebugS ( LoggerName , $"Storage (UID {Owner.Uid}) \" used \ " by player session (UID {userSession.AttachedEntityUid})." ) ;
return PlayerInsertEntity ( eventArgs . User ) ;
}
/// <summary>
/// Sends a message to open the storage UI
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
bool IUse . UseEntity ( UseEntityEventArgs eventArgs )
{
_ensureInitialCalculated ( ) ;
OpenStorageUI ( eventArgs . User ) ;
return false ;
}
public void OpenStorageUI ( IEntity Character )
{
_ensureInitialCalculated ( ) ;
var user_session = Character . GetComponent < BasicActorComponent > ( ) . 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 ) ;
SubscribeSession ( userSession ) ;
SendNetworkMessage ( new OpenStorageUIMessage ( ) , userSession . ConnectedClient ) ;
UpdateClientInventory ( userSession ) ;
}
/// <summary>
@@ -185,43 +217,89 @@ namespace Content.Server.GameObjects
/// </summary>
private void UpdateClientInventories ( )
{
foreach ( IPlayerSession session in SubscribedSessions )
foreach ( var session in SubscribedSessions )
{
UpdateClientInventory ( session ) ;
}
}
/// <summary>
/// Adds actor to the upd ate list .
/// Updates storage UI on a client, informing them of the st ate of the container .
/// </summary>
/// <param name="actor"> </param>
public void SubscribeSession ( IPlayerSession session )
/// <param name="session">The client to be updated </param>
private void UpdateClientInventory ( IPlayerSession session )
{
_ensureInitialCalculated ( ) ;
if ( session . AttachedEntity = = null )
{
Logger . DebugS ( LoggerName , $"Storage (UID {Owner.Uid}) detected no attached entity in player session (UID {session.AttachedEntityUid})." ) ;
UnsubscribeSession ( session ) ;
return ;
}
if ( _storage = = null )
{
Logger . WarningS ( LoggerName , $"{nameof(UpdateClientInventory)} called with null {nameof(_storage)}" ) ;
return ;
}
var storedEntities = new Dictionary < EntityUid , int > ( ) ;
foreach ( var entities in _storage . ContainedEntities )
{
storedEntities . Add ( entities . Uid , entities . GetComponent < StorableComponent > ( ) . ObjectSize ) ;
}
SendNetworkMessage ( new StorageHeldItemsMessage ( storedEntities , _storageUsed , _storageCapacityMax ) , session . ConnectedClient ) ;
}
/// <summary>
/// Adds a session to the update list.
/// </summary>
/// <param name="session">The session to add</param>
private void SubscribeSession ( IPlayerSession session )
{
EnsureInitialCalculated ( ) ;
if ( ! SubscribedSessions . Contains ( session ) )
{
Logger . DebugS ( "Storage" , "Storage (UID {0 }) subscribed player session (UID {1})." , Owner . Uid , session. AttachedEntityUid ) ;
Logger . DebugS ( LoggerName , $ "Storage (UID {Owner.Uid }) subscribed player session (UID {session. AttachedEntityUid})." ) ;
session . PlayerStatusChanged + = HandlePlayerSessionChangeEvent ;
SubscribedSessions . Add ( session ) ;
UpdateDoorState ( ) ;
}
}
/// <summary>
/// Removes actor from the update list.
/// Removes a session from the update list.
/// </summary>
/// <param name="channel"> </param>
/// <param name="session">The session to remove </param>
public void UnsubscribeSession ( IPlayerSession session )
{
if ( SubscribedSessions . Contains ( session ) )
{
Logger . DebugS ( "Storage" , "Storage (UID {0 }) unsubscribed player session (UID {1})." , Owner . Uid , session. AttachedEntityUid ) ;
Logger . DebugS ( LoggerName , $ "Storage (UID {Owner.Uid }) unsubscribed player session (UID {session. AttachedEntityUid})." ) ;
SubscribedSessions . Remove ( session ) ;
SendNetworkMessage ( new CloseStorageUIMessage ( ) , session . ConnectedClient ) ;
UpdateDoorState ( ) ;
}
}
private void HandlePlayerSessionChangeEvent ( object? obj , SessionStatusEventArgs sessionStatus )
{
Logger . DebugS ( LoggerName , $"Storage (UID {Owner.Uid}) handled a status change in player session (UID {sessionStatus.Session.AttachedEntityUid})." ) ;
if ( sessionStatus . NewStatus ! = SessionStatus . InGame )
{
UnsubscribeSession ( sessionStatus . Session ) ;
}
}
private void UpdateDoorState ( )
{
if ( Owner . TryGetComponent ( out AppearanceComponent appearance ) )
@@ -230,42 +308,23 @@ namespace Content.Server.GameObjects
}
}
public void HandlePlayerSessionChangeEvent ( object obj , SessionStatusEventArgs SSEA )
public override void Initialize ( )
{
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 ) ;
}
base . Initialize ( ) ;
// ReSharper disable once StringLiteralTypo
_storage = ContainerManagerComponent . Ensure < Container > ( "storagebase" , Owner ) ;
}
/// <summary>
/// Updates storage UI on a client, informing them of the state of the container.
/// </summary>
private void UpdateClientInventory ( IPlayerSession session )
public override void ExposeData ( ObjectSerializer serializer )
{
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 < EntityUid , int > storedentities = new Dictionary < EntityUid , int > ( ) ;
foreach ( var entities in storage . ContainedEntities )
{
storedentities . Add ( entities . Uid , entities . GetComponent < StoreableComponent > ( ) . ObjectSize ) ;
}
SendNetworkMessage ( new StorageHeldItemsMessage ( storedentities , StorageUsed , StorageCapacityMax ) , session . ConnectedClient ) ;
base . ExposeData ( serializer ) ;
serializer . DataField ( ref _storageCapacityMax , "Capacity" , 10000 ) ;
//serializer.DataField(ref StorageUsed, "used", 0) ;
}
/// <summary>
/// Receives messages to remove entities from storage, verifies the player can do them,
/// and puts the removed entity in hand or on the ground
/// </summary>
/// <param name="message"></param>
/// <param name="channel"></param>
/// <param name="session"></param>
public override void HandleNetworkMessage ( ComponentMessage message , INetChannel channel , ICommonSession session = null )
public override void HandleNetworkMessage ( ComponentMessage message , INetChannel channel , ICommonSession ? session = null )
{
base . HandleNetworkMessage ( message , channel , session ) ;
@@ -276,91 +335,128 @@ namespace Content.Server.GameObjects
switch ( message )
{
case RemoveEntityMessage _ :
case RemoveEntityMessage remove :
{
_e nsureInitialCalculated( ) ;
var playerentity = session . AttachedEntity ;
E nsureInitialCalculated( ) ;
var ourtransform = Owner . GetComponent < ITransformComponent > ( ) ;
var playertransform = playerentity . GetComponent < ITransformComponent > ( ) ;
var player = session . AttachedEntity ;
if ( playertransform . GridPosition . InRange ( _mapManager , ourtransform . GridPosition , 2 )
& & ( ourtransform . IsMapTransform | | playertransform . ContainsEntity ( ourtransform ) ) )
if ( player = = null )
{
var remove = ( RemoveEntityMessage ) message ;
break ;
}
var ownerTransform = Owner . Transform ;
var playerTransform = player . Transform ;
if ( ! playerTransform . GridPosition . InRange ( _mapManager , ownerTransform . GridPosition , 2 ) | |
! ownerTransform . IsMapTransform & &
! playerTransform . ContainsEntity ( ownerTransform ) )
{
break ;
}
var entity = _entityManager . GetEntity ( remove . EntityUid ) ;
if ( entity ! = null & & storage . Contains ( entity ) )
if ( entity = = null | | _storage ? . Contains ( entity ) = = false )
{
break ;
}
var item = entity . GetComponent < ItemComponent > ( ) ;
if ( item ! = null & & playerentity . TryGetComponent ( out HandsComponent hands ) )
if ( item = = null | |
! player . TryGetComponent ( out HandsComponent hands ) )
{
if ( hands . CanPutInHand ( item ) & & hands . PutInHand ( item ) )
{
return ;
}
break ;
}
if ( hands . CanPutInHand ( item ) )
{
break ;
}
}
hands . PutInHand ( item ) ;
break ;
}
case InsertEntityMessage _ :
{
_e nsureInitialCalculated( ) ;
var playerEntity = session . AttachedEntity ;
var storageTransform = Owner . GetComponent < ITransformComponent > ( ) ;
var playerTransform = playerEntity . GetComponent < ITransformComponent > ( ) ;
// TODO: Replace by proper entity range check once it is implemented.
if ( playerTransform . GridPosition . InRange ( _mapManager ,
storageTransform . GridPosition ,
InteractionSystem . InteractionRange ) )
E nsureInitialCalculated( ) ;
var player = session . AttachedEntity ;
if ( player = = null )
{
PlayerInsertEntity ( playerEntity ) ;
break ;
}
var storagePosition = Owner . Transform . MapPosition ;
if ( ! InteractionChecks . InRangeUnobstructed ( player , storagePosition ) )
{
break ;
}
PlayerInsertEntity ( player ) ;
break ;
}
case CloseStorageUIMessage _ :
{
UnsubscribeSession ( session a s IPlayerSession ) ;
if ( ! ( session i s IPlayerSession playerSession ) )
{
break ;
}
UnsubscribeSession ( playerSession ) ;
break ;
}
}
}
/// <inheritdoc / >
/// <summary >
/// Inserts storable entities into this storage container if possible, otherwise return to the hand of the user
/// </summary>
/// <param name="eventArgs"></param>
/// <returns>true if inserted, false otherwise</returns>
bool IInteractUsing . InteractUsing ( InteractUsingEventArgs eventArgs )
{
Logger . DebugS ( LoggerName , $"Storage (UID {Owner.Uid}) attacked by user (UID {eventArgs.User.Uid}) with entity (UID {eventArgs.Using.Uid})." ) ;
if ( Owner . HasComponent < PlaceableSurfaceComponent > ( ) )
{
return false ;
}
return PlayerInsertEntity ( eventArgs . User ) ;
}
/// <summary>
/// Sends a message to open the storage UI
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
bool IUse . UseEntity ( UseEntityEventArgs eventArgs )
{
EnsureInitialCalculated ( ) ;
OpenStorageUI ( eventArgs . User ) ;
return false ;
}
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 < StoreableComponent > ( ) ;
StorageUsed + = item . ObjectSize ;
}
_storageInitialCalculated = true ;
}
void IDestroyAct . OnDestroy ( DestructionEventArgs eventArgs )
{
var storedEntities = s torage . Contain edEntities. ToList ( ) ;
var storedEntities = S toredEntities? .ToList ( ) ;
if ( storedEntities = = null )
{
return ;
}
foreach ( var entity in storedEntities )
{
Remove ( entity ) ;
@@ -374,7 +470,13 @@ namespace Content.Server.GameObjects
return ;
}
var storedEntities = s torage . Contain edEntities. ToList ( ) ;
var storedEntities = S toredEntities? .ToList ( ) ;
if ( storedEntities = = null )
{
return ;
}
foreach ( var entity in storedEntities )
{
var exActs = entity . GetAllComponents < IExAct > ( ) ;
@@ -385,42 +487,22 @@ namespace Content.Server.GameObjects
}
}
/// <summary>
/// Inserts an entity into the storage component from the players active hand.
/// </summary>
public bool PlayerInsertEntity ( IEntity player )
bool IDragDrop . DragDrop ( DragDropEventArgs eventArgs )
{
_ensureInitialCalculated ( ) ;
if ( ! player . TryGetComponent ( out IHandsComponent hands ) | | hands . GetActiveHand = = null )
return false ;
var toInsert = hands . GetActiveHand ;
if ( hands . Drop ( toInsert . Owner ) )
if ( ! eventArgs . Target . TryGetComponent < PlaceableSurfaceComponent > ( out var placeableSurface ) | |
! placeableSurface . IsPlaceable )
{
if ( Insert ( toInsert . Owner ) )
{
return true ;
}
else
{
hands . PutInHand ( toInsert ) ;
}
}
Owner . PopupMessage ( player , "Can't insert." ) ;
return false ;
}
public bool DragDrop ( DragDropEventArgs eventArgs )
{
if ( eventArgs . Target . TryGetComponent < PlaceableSurfaceComponent > ( out var placeableSurface ) )
{
if ( ! placeableSurface . IsPlaceable ) return false ;
var storedEntities = StoredEntities ? . ToList ( ) ;
// empty everything out
foreach ( var storedEntity in StoredEntities . ToList ( ) )
if ( storedEntities = = null )
{
return false ;
}
foreach ( var storedEntity in storedEntities )
{
if ( Remove ( storedEntity ) )
{
@@ -430,8 +512,5 @@ namespace Content.Server.GameObjects
return true ;
}
return false ;
}
}
}