Files
tbd-station-14/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs
DrSmugleaf bda5ce655f Add the trash man (#1367)
* Add disposal.rsi

* Rename disposal resource to disposal.rsi and create basic components

* Add disposal nets

* Add pushing entities along the disposal network

* Add disposal unit

* Unregister disposable component

* Add flush and selfinsert verbs to disposal unit

* Add gradual disposals movement

* Fix being able to walk through space for a while after exiting disposals

* Multiply disposals speed by 10

And fix early returns when moving an entity

* Rename Disposable component to InDisposals

* Remove DisposalNet and add on anchor events

* Remove anchored events, moved to interfaces

* Code cleanup

* Fix adjacent tubes' connections when a tube connects

* Fix jittery movement in disposals

* Remove Logger.Debug call

* Move disposals updates to InDisposalsComponent

* Fix adjacent connection valid directions check

* Disposal tubes now throw you out where they are facing

* Add disposal unit exit cooldown

* Set different disposal pipe sprite state depending on anchored value

* Add recycler

* Add recycler animation

* Add bloody texture to the recycler when grinding a living being

* Add PowerDevice component to the disposal unit

* Made the Recycler center on the grid

* Add disposal junction

* Add picking a random direction if junction is entered from the output side

* Add disposal flush and clang sounds

Taken from VGStation

* Move disposal flush and clang sound file names to exposedata

* Add disposalsmap.yml to test with

* Add summaries to DisposalUnit fields

* Add sideDegrees yaml property to disposal junctions

* Fix outdated usings

* Add conveyor resources

* Update RobustToolbox

* More merge fixes

Add conveyor collision masks

* Add ConveyorComponent

* Fix crash when reentering a body

* Merge branch 'master' into disposals-1147

* Reduce recycler bounds, set hard to false, add summary and expose "safe" to yaml

* Move IAnchored and IUnAnchored to AnchorableComponent

* Update power components and remove old disposals map

* Remove redundant sprite layers

* Add tile pry command

* Fix tilepry command

* Fix DisposalJunctionComponent missing a component reference

* Add anchor by radius command

* Add Y-Junctions

* Add disposal bend

* Add unanchor command

* Change DisposalJunction prototypes to specify their angles

* Fix disposal units being hidden below the floor

* Removed IAnhored and IUnAnchored interfaces

* Replace CanBeNull annotation with nullable reference types

* Update showwires command

* Add recycler recycling items

* Added angle and speed properties to ConveyorComponent

* Fix conveyort textures

* Add animation to the disposal unit

* Fix anchor and unanchor commands sometimes not finding any entities

* Fix not reading flush_time from disposal unit prototype

* Fix merge conflict wrong using

* Fix disposal, recycling and conveyor texture paths

Delete diverters

* Update visualizer names

* Add DisposableComponent, change drag and drop to work with multiple components

Ignoreinsideblocker client side for drag and drops, like on the server
Add more comments

* Add conveyor belts properly moving entities on top

* Anchorr wires

* Change conveyor bounds to 0.49

* Anchor catwalks, airlocks, gravity generators, low walls, wires and windows

* Add starting/stopping conveyors

* Add reversed conveyors

* Add conveyor switches

* Move InDisposalsComponent code to DisposableComponent

* Add ExitVector method to tubes

* Fix not updating tube references when disconnecting one

* Replace IoCManager call with dependency

* Add tubes disconnecting if they move too far apart from one another

* Move disposals action blocking to shared

* Add rotating and flipping pipes

* Make conveyor intersection calculations approximate

* Fix 1% chance of the server crashing when initializing the map

Happens when emergency lockers remove themselves

* Add disposal unit interface

* Make disposal units refuse items if not powered

* Make disposal tubes hide only when anchored

* Make disposal junction arrows visible to mere mortals

* Add disposal tubes breaking

* Add tubeconnections command

* Add missing verb attribute

* Add flipped disposal junction

* Add ids and linking to conveyors and switches

* Add conveyor switch prying and placing

* Add anchoring conveyor switches and refactor placing them

* Add missing serializable attributes from DisposableComponentState

* Make conveyor speed VV ReadWrite

* Change drawdepth of conveyors to FloorObjects

* Make conveyor anchored check consistent

* Remove anchoring interaction from switches

* Add conveyor switch id syncing and move switches slightly when pried

* Make entities in containers not able to be moved by conveyors

* Add conveyor and switches loose textures

* Merge conflict fixes

* Add disposal unit test

* Add flushing test to disposal unit test

* Add disposal unit flush fail test

* Add disposals to the saltern map

* Fix saltern disposal junctions

* Add power checks to the recycler

* Fix disposal unit placement in maintenance closet

* Remove disposal junctions from saltern

* Readd junctions to saltern

* Add the chemmaster to saltern at the request of Ike

* Move the chemistry disposal unit

* Fix casing of disposal flush sound

* More merge conflict fixes

* Fix a compiler warning.

* Remove popup invocation from buckle

* Remove showPopup parameter from InteractionChecks

* Remove unnecessary physics components

Fixes the physics system dying

* Replace PhysicsComponent usages with CollidableComponent

* Update existing code for the new controller system

* Change conveyors to use a VirtualController instead of teleporting the entity

* Remove visualizer 2d suffix and update physics code

* Transition code to new controller system

* Fix shuttles not moving

* Fix throwing

* Fix guns

* Change hands to use physics.Stop() and remove item fumble method

* Add syncing conveyor switches states

* Fix the recycler wanting to be a conveyor too hard

* Fix showwires > showsubfloor rename in mapping command

* Fix wifi air conveyors

* Fix test error

* Add showsubfloorforever command

Changes drawdepth of the relevant entities

* Disable opening the disposal unit interface while inside

* Add closing the disposal unit interface when getting inside

* Add closing the interface when the disposal unit component is removed

* Add removing entities on disposal unit component removal

* Delay disposal unit flush and fix serialization

* Implement pressure in disposal units

* Fix chain engaging a disposal unit

* Implement states to the disposal unit

* Fix missing imports from merge conflict

* Update Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>

* Address some reviews

* Fix za buildo

* Use container helper to detach disposables

* Make conveyors use the construction system

* Make conveyor groups and syncing sane

* Make flip flip

brave

* Add activate interface to conveyor switches

* Fix not removing the switch from its group when it's deleted

* Fix not registering conveyors and switches on initialize

* Stop using 0 as null

* Disconnect conveyors and switches when disposing of a group

* Make disposal units not able to be exited when flushing

* Make disposal units flush after a configurable 30 seconds

* Add handle and light layers to the disposal unit

* Merge engaging and flushing

* Update saltern.yml

* I love using 0 as null

* Make disposal unit visual layers make sense

* Remove duplicate remove method in disposal units and update light

* Replace DisposableComponent with disposal holders

* Fix disposal holders deleting their contents on deletion

* Account for disposal unit pressure in tests and make a failed flush autoengage

* Rename disposable to holder

* Fix junction connections

* Disable self insert and flush verbs when inside a disposal unit

* Fix spamming the engage button making the animation reset

* Make the recycler take materials into account properly

Fix cablestack1 not existing

* Merge conflict fixes

* Fix pipes not being saved anchored

* Change conveyors and groups to not use an id

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2020-07-30 23:45:28 +02:00

545 lines
18 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Storage;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
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.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.Components.Items.Storage
{
/// <summary>
/// Storage component for containing entities within this one, matches a UI on the client which shows stored entities
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IStorageComponent))]
public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct,
IDragDrop
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning restore 649
private const string LoggerName = "Storage";
private Container? _storage;
private bool _occludesLight;
private bool _storageInitialCalculated;
private int _storageUsed;
private int _storageCapacityMax;
public readonly HashSet<IPlayerSession> SubscribedSessions = new HashSet<IPlayerSession>();
public IReadOnlyCollection<IEntity>? StoredEntities => _storage?.ContainedEntities;
public bool OccludesLight
{
get => _occludesLight;
set
{
_occludesLight = value;
if (_storage != null) _storage.OccludesLight = value;
}
}
private void EnsureInitialCalculated()
{
if (_storageInitialCalculated)
{
return;
}
RecalculateStorageUsed();
_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="entity">The entity to insert</param>
/// <returns>true if the entity was inserted, false otherwise</returns>
public bool Insert(IEntity entity)
{
return CanInsert(entity) && _storage?.Insert(entity) == true;
}
/// <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)
{
EnsureInitialCalculated();
return _storage?.Remove(entity) == true;
}
public void HandleEntityMaybeInserted(EntInsertedIntoContainerMessage message)
{
if (message.Container != _storage)
{
return;
}
EnsureInitialCalculated();
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>
/// Inserts an entity into storage from the player's active hand
/// </summary>
/// <param name="player">The player to insert an entity from</param>
/// <returns>true if inserted, false otherwise</returns>
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))
{
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>
/// Opens the storage UI for an entity
/// </summary>
/// <param name="entity">The entity to open the UI for</param>
public void OpenStorageUI(IEntity entity)
{
EnsureInitialCalculated();
var userSession = entity.GetComponent<BasicActorComponent>().playerSession;
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) \"used\" by player session (UID {userSession.AttachedEntityUid}).");
SubscribeSession(userSession);
SendNetworkMessage(new OpenStorageUIMessage(), userSession.ConnectedClient);
UpdateClientInventory(userSession);
}
/// <summary>
/// Updates the storage UI on all subscribed actors, informing them of the state of the container.
/// </summary>
private void UpdateClientInventories()
{
foreach (var session in SubscribedSessions)
{
UpdateClientInventory(session);
}
}
/// <summary>
/// Updates storage UI on a client, informing them of the state of the container.
/// </summary>
/// <param name="session">The client to be updated</param>
private void UpdateClientInventory(IPlayerSession session)
{
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(LoggerName, $"Storage (UID {Owner.Uid}) subscribed player session (UID {session.AttachedEntityUid}).");
session.PlayerStatusChanged += HandlePlayerSessionChangeEvent;
SubscribedSessions.Add(session);
UpdateDoorState();
}
}
/// <summary>
/// Removes a session from the update list.
/// </summary>
/// <param name="session">The session to remove</param>
public void UnsubscribeSession(IPlayerSession session)
{
if (SubscribedSessions.Contains(session))
{
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))
{
appearance.SetData(StorageVisuals.Open, SubscribedSessions.Count != 0);
}
}
public override void Initialize()
{
base.Initialize();
// ReSharper disable once StringLiteralTypo
_storage = ContainerManagerComponent.Ensure<Container>("storagebase", Owner);
_storage.OccludesLight = _occludesLight;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _storageCapacityMax, "capacity", 10000);
serializer.DataField(ref _occludesLight, "occludesLight", true);
//serializer.DataField(ref StorageUsed, "used", 0);
}
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 remove:
{
EnsureInitialCalculated();
var player = session.AttachedEntity;
if (player == null)
{
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) == false)
{
break;
}
var item = entity.GetComponent<ItemComponent>();
if (item == null ||
!player.TryGetComponent(out HandsComponent hands))
{
break;
}
if (!hands.CanPutInHand(item))
{
break;
}
hands.PutInHand(item);
break;
}
case InsertEntityMessage _:
{
EnsureInitialCalculated();
var player = session.AttachedEntity;
if (player == null)
{
break;
}
var storagePosition = Owner.Transform.MapPosition;
if (!InteractionChecks.InRangeUnobstructed(player, storagePosition))
{
break;
}
PlayerInsertEntity(player);
break;
}
case CloseStorageUIMessage _:
{
if (!(session is IPlayerSession playerSession))
{
break;
}
UnsubscribeSession(playerSession);
break;
}
}
}
/// <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 });
}
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
{
var storedEntities = StoredEntities?.ToList();
if (storedEntities == null)
{
return;
}
foreach (var entity in storedEntities)
{
Remove(entity);
}
}
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
{
if (eventArgs.Severity < ExplosionSeverity.Heavy)
{
return;
}
var storedEntities = StoredEntities?.ToList();
if (storedEntities == null)
{
return;
}
foreach (var entity in storedEntities)
{
var exActs = entity.GetAllComponents<IExAct>();
foreach (var exAct in exActs)
{
exAct.OnExplosion(eventArgs);
}
}
}
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent placeable) &&
placeable.IsPlaceable;
}
bool IDragDrop.DragDrop(DragDropEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
return false;
}
if (!eventArgs.Target.TryGetComponent<PlaceableSurfaceComponent>(out var placeableSurface) ||
!placeableSurface.IsPlaceable)
{
return false;
}
var storedEntities = StoredEntities?.ToList();
if (storedEntities == null)
{
return false;
}
// empty everything out
foreach (var storedEntity in StoredEntities.ToList())
{
if (Remove(storedEntity))
{
storedEntity.Transform.WorldPosition = eventArgs.DropLocation.Position;
}
}
return true;
}
}
}