* Enable lighting of Cigars with IsHotEvent Subscribed to IsHotEvent in ThrusterSystem, added AfterInteractEvent to SmokingSystem.Cigar which will set SmokableState to Lit if IsHotEvent returns true This should mean that cigs/Cigars should light on anything hot * Revert "Enable lighting of Cigars with IsHotEvent" This reverts commit db896e1f80940892a87d34aa7182b9c605fa53bc. * Add the ability to kick your way out of welded or locked closets Added a ResistLocker component/system that hooks into OnRelayMove to begin resisting out of lockers so that players aren't stuck in lockers forever Also added check to EntityStorageComponent to not print out welded shut message if the entity attempting to open the locker is inside of it. * Changes based on review minor formatting changes make components passed into AttemptResist nullable and resolve them inside of the function Remove incorrectly named .ftl file * Changes based on review Ditched async DoAfter Used PopupSystem over User.PopupMessage Added Cancel token to ResistLockerComponent Subscribed to EntRemovedFromContainer event to cancel DoAfter if the player is removed from a container * I am in formatting hell, apparently * Changes based on review Added TryComp for the EntityStorageComponent instead of the event passed one * Changes based on review * Apply suggestions from code review Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> * Formatting changes based on review * Shuffle functions around in LockSystem to properly trigger visualizer Moved all the unlocking logic to a Lock method inside of LockSystem and TryUnlock calls this method if the user passes all of the access checks * Formatting, replacing Resolve with TryComp and making AttemptResist arguments optional Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> * Check if the player is inside THE container not A container me no read good * ok ok I give my code is bad Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
501 lines
16 KiB
C#
501 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Construction;
|
|
using Content.Server.Construction.Components;
|
|
using Content.Server.Ghost.Components;
|
|
using Content.Server.Tools;
|
|
using Content.Shared.Acts;
|
|
using Content.Shared.Body.Components;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Item;
|
|
using Content.Shared.Physics;
|
|
using Content.Shared.Placeable;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Sound;
|
|
using Content.Shared.Storage;
|
|
using Content.Shared.Tools;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Localization;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Serialization.Manager.Attributes;
|
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
using Robust.Shared.ViewVariables;
|
|
|
|
namespace Content.Server.Storage.Components
|
|
{
|
|
[RegisterComponent]
|
|
[ComponentReference(typeof(IActivate))]
|
|
[ComponentReference(typeof(IStorageComponent))]
|
|
public class EntityStorageComponent : Component, IActivate, IStorageComponent, IInteractUsing, IDestroyAct, IExAct
|
|
{
|
|
[Dependency] private readonly IEntityManager _entMan = default!;
|
|
|
|
public override string Name => "EntityStorage";
|
|
|
|
private const float MaxSize = 1.0f; // maximum width or height of an entity allowed inside the storage.
|
|
|
|
public static readonly TimeSpan InternalOpenAttemptDelay = TimeSpan.FromSeconds(0.5);
|
|
public TimeSpan LastInternalOpenAttempt;
|
|
|
|
private const int OpenMask = (int) (
|
|
CollisionGroup.MobImpassable |
|
|
CollisionGroup.VaultImpassable |
|
|
CollisionGroup.SmallImpassable);
|
|
|
|
[ViewVariables]
|
|
[DataField("Capacity")]
|
|
private int _storageCapacityMax = 30;
|
|
|
|
[ViewVariables]
|
|
[DataField("IsCollidableWhenOpen")]
|
|
private bool _isCollidableWhenOpen;
|
|
|
|
[ViewVariables]
|
|
[DataField("EnteringRange")]
|
|
private float _enteringRange = -0.4f;
|
|
|
|
[DataField("showContents")]
|
|
private bool _showContents;
|
|
|
|
[DataField("occludesLight")]
|
|
private bool _occludesLight = true;
|
|
|
|
[DataField("open")]
|
|
private bool _open;
|
|
|
|
[DataField("weldingQuality", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
|
private string _weldingQuality = "Welding";
|
|
|
|
[DataField("CanWeldShut")]
|
|
private bool _canWeldShut = true;
|
|
|
|
[DataField("IsWeldedShut")]
|
|
private bool _isWeldedShut;
|
|
|
|
[DataField("closeSound")]
|
|
private SoundSpecifier _closeSound = new SoundPathSpecifier("/Audio/Effects/closetclose.ogg");
|
|
|
|
[DataField("openSound")]
|
|
private SoundSpecifier _openSound = new SoundPathSpecifier("/Audio/Effects/closetopen.ogg");
|
|
|
|
[ViewVariables]
|
|
public Container Contents = default!;
|
|
|
|
/// <summary>
|
|
/// Determines if the container contents should be drawn when the container is closed.
|
|
/// </summary>
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool ShowContents
|
|
{
|
|
get => _showContents;
|
|
set
|
|
{
|
|
_showContents = value;
|
|
Contents.ShowContents = _showContents;
|
|
}
|
|
}
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool OccludesLight
|
|
{
|
|
get => _occludesLight;
|
|
set
|
|
{
|
|
_occludesLight = value;
|
|
Contents.OccludesLight = _occludesLight;
|
|
}
|
|
}
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool Open
|
|
{
|
|
get => _open;
|
|
private set => _open = value;
|
|
}
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool IsWeldedShut
|
|
{
|
|
get => _isWeldedShut;
|
|
set
|
|
{
|
|
if (_isWeldedShut == value) return;
|
|
|
|
_isWeldedShut = value;
|
|
UpdateAppearance();
|
|
}
|
|
}
|
|
|
|
private bool _beingWelded;
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public bool CanWeldShut
|
|
{
|
|
get => _canWeldShut;
|
|
set
|
|
{
|
|
if (_canWeldShut == value) return;
|
|
|
|
_canWeldShut = value;
|
|
UpdateAppearance();
|
|
}
|
|
}
|
|
|
|
[ViewVariables(VVAccess.ReadWrite)]
|
|
public float EnteringRange
|
|
{
|
|
get => _enteringRange;
|
|
set => _enteringRange = value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
Contents = Owner.EnsureContainer<Container>(nameof(EntityStorageComponent));
|
|
Contents.ShowContents = _showContents;
|
|
Contents.OccludesLight = _occludesLight;
|
|
|
|
if(_entMan.TryGetComponent(Owner, out ConstructionComponent? construction))
|
|
EntitySystem.Get<ConstructionSystem>().AddContainer(Owner, Contents.ID, construction);
|
|
|
|
if (_entMan.TryGetComponent<PlaceableSurfaceComponent?>(Owner, out var surface))
|
|
{
|
|
EntitySystem.Get<PlaceableSurfaceSystem>().SetPlaceable(Owner, Open, surface);
|
|
}
|
|
|
|
UpdateAppearance();
|
|
}
|
|
|
|
public virtual void Activate(ActivateEventArgs eventArgs)
|
|
{
|
|
ToggleOpen(eventArgs.User);
|
|
}
|
|
|
|
public virtual bool CanOpen(EntityUid user, bool silent = false)
|
|
{
|
|
if (IsWeldedShut)
|
|
{
|
|
if (!silent && !Contents.Contains(user))
|
|
Owner.PopupMessage(user, Loc.GetString("entity-storage-component-welded-shut-message"));
|
|
|
|
return false;
|
|
}
|
|
|
|
if (_entMan.TryGetComponent<LockComponent?>(Owner, out var @lock) && @lock.Locked)
|
|
{
|
|
if (!silent) Owner.PopupMessage(user, Loc.GetString("entity-storage-component-locked-message"));
|
|
return false;
|
|
}
|
|
|
|
var @event = new StorageOpenAttemptEvent();
|
|
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, @event);
|
|
|
|
return !@event.Cancelled;
|
|
}
|
|
|
|
public virtual bool CanClose(EntityUid user, bool silent = false)
|
|
{
|
|
var @event = new StorageCloseAttemptEvent();
|
|
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, @event);
|
|
|
|
return !@event.Cancelled;
|
|
}
|
|
|
|
public void ToggleOpen(EntityUid user)
|
|
{
|
|
if (Open)
|
|
{
|
|
TryCloseStorage(user);
|
|
}
|
|
else
|
|
{
|
|
TryOpenStorage(user);
|
|
}
|
|
}
|
|
|
|
protected virtual void CloseStorage()
|
|
{
|
|
Open = false;
|
|
|
|
var count = 0;
|
|
foreach (var entity in DetermineCollidingEntities())
|
|
{
|
|
// prevents taking items out of inventories, out of containers, and orphaning child entities
|
|
if (entity.IsInContainer())
|
|
continue;
|
|
|
|
// conditions are complicated because of pizzabox-related issues, so follow this guide
|
|
// 1. AddToContents can block anything
|
|
// 2. maximum item count can block anything
|
|
// 3. ghosts can NEVER be eaten
|
|
// 4. items can always be eaten unless a previous law prevents it
|
|
// 5. if this is NOT AN ITEM, then mobs can always be eaten unless unless a previous law prevents it
|
|
|
|
// Let's not insert admin ghosts, yeah? This is really a a hack and should be replaced by attempt events
|
|
if (_entMan.HasComponent<GhostComponent>(entity))
|
|
continue;
|
|
|
|
// checks
|
|
|
|
var targetIsItem = _entMan.HasComponent<SharedItemComponent>(entity);
|
|
var targetIsMob = _entMan.HasComponent<SharedBodyComponent>(entity);
|
|
var storageIsItem = _entMan.HasComponent<SharedItemComponent>(Owner);
|
|
|
|
var allowedToEat = false;
|
|
|
|
if (targetIsItem)
|
|
allowedToEat = true;
|
|
|
|
// BEFORE REPLACING THIS WITH, I.E. A PROPERTY:
|
|
// Make absolutely 100% sure you have worked out how to stop people ending up in backpacks.
|
|
// Seriously, it is insanely hacky and weird to get someone out of a backpack once they end up in there.
|
|
// And to be clear, they should NOT be in there.
|
|
// For the record, what you need to do is empty the backpack onto a PlacableSurface (table, rack)
|
|
if (targetIsMob && !storageIsItem)
|
|
allowedToEat = true;
|
|
|
|
if (!allowedToEat)
|
|
continue;
|
|
|
|
// finally, AddToContents
|
|
|
|
if (!AddToContents(entity))
|
|
continue;
|
|
|
|
count++;
|
|
if (count >= _storageCapacityMax)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
ModifyComponents();
|
|
SoundSystem.Play(Filter.Pvs(Owner), _closeSound.GetSound(), Owner);
|
|
LastInternalOpenAttempt = default;
|
|
}
|
|
|
|
protected virtual void OpenStorage()
|
|
{
|
|
Open = true;
|
|
EmptyContents();
|
|
ModifyComponents();
|
|
SoundSystem.Play(Filter.Pvs(Owner), _openSound.GetSound(), Owner);
|
|
}
|
|
|
|
private void UpdateAppearance()
|
|
{
|
|
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
|
|
{
|
|
appearance.SetData(StorageVisuals.CanWeld, _canWeldShut);
|
|
appearance.SetData(StorageVisuals.Welded, _isWeldedShut);
|
|
}
|
|
}
|
|
|
|
private void ModifyComponents()
|
|
{
|
|
if (!_isCollidableWhenOpen && _entMan.TryGetComponent<FixturesComponent?>(Owner, out var manager))
|
|
{
|
|
if (Open)
|
|
{
|
|
foreach (var (_, fixture) in manager.Fixtures)
|
|
{
|
|
fixture.CollisionLayer &= ~OpenMask;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var (_, fixture) in manager.Fixtures)
|
|
{
|
|
fixture.CollisionLayer |= OpenMask;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_entMan.TryGetComponent<PlaceableSurfaceComponent?>(Owner, out var surface))
|
|
{
|
|
EntitySystem.Get<PlaceableSurfaceSystem>().SetPlaceable(Owner, Open, surface);
|
|
}
|
|
|
|
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
|
|
{
|
|
appearance.SetData(StorageVisuals.Open, Open);
|
|
}
|
|
}
|
|
|
|
protected virtual bool AddToContents(EntityUid entity)
|
|
{
|
|
if (entity == Owner) return false;
|
|
if (_entMan.TryGetComponent(entity, out IPhysBody? entityPhysicsComponent))
|
|
{
|
|
if (MaxSize < entityPhysicsComponent.GetWorldAABB().Size.X
|
|
|| MaxSize < entityPhysicsComponent.GetWorldAABB().Size.Y)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return Contents.CanInsert(entity) && Insert(entity);
|
|
}
|
|
|
|
public virtual Vector2 ContentsDumpPosition()
|
|
{
|
|
return _entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
|
|
}
|
|
|
|
private void EmptyContents()
|
|
{
|
|
foreach (var contained in Contents.ContainedEntities.ToArray())
|
|
{
|
|
if (Contents.Remove(contained))
|
|
{
|
|
_entMan.GetComponent<TransformComponent>(contained).WorldPosition = ContentsDumpPosition();
|
|
if (_entMan.TryGetComponent<IPhysBody?>(contained, out var physics))
|
|
{
|
|
physics.CanCollide = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual bool TryOpenStorage(EntityUid user)
|
|
{
|
|
if (!CanOpen(user)) return false;
|
|
OpenStorage();
|
|
return true;
|
|
}
|
|
|
|
public virtual bool TryCloseStorage(EntityUid user)
|
|
{
|
|
if (!CanClose(user)) return false;
|
|
CloseStorage();
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool Remove(EntityUid entity)
|
|
{
|
|
return Contents.CanRemove(entity);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool Insert(EntityUid entity)
|
|
{
|
|
// Trying to add while open just dumps it on the ground below us.
|
|
if (Open)
|
|
{
|
|
var entMan = _entMan;
|
|
entMan.GetComponent<TransformComponent>(entity).WorldPosition = entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
|
|
return true;
|
|
}
|
|
|
|
return Contents.Insert(entity);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool CanInsert(EntityUid entity)
|
|
{
|
|
if (Open)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Contents.ContainedEntities.Count >= _storageCapacityMax)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Contents.CanInsert(entity);
|
|
}
|
|
|
|
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
|
{
|
|
if (_beingWelded)
|
|
return false;
|
|
|
|
if (Open)
|
|
{
|
|
_beingWelded = false;
|
|
return false;
|
|
}
|
|
|
|
if (!CanWeldShut)
|
|
{
|
|
_beingWelded = false;
|
|
return false;
|
|
}
|
|
|
|
if (Contents.Contains(eventArgs.User))
|
|
{
|
|
_beingWelded = false;
|
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("entity-storage-component-already-contains-user-message"));
|
|
return false;
|
|
}
|
|
|
|
if (_beingWelded)
|
|
return false;
|
|
|
|
_beingWelded = true;
|
|
|
|
var toolSystem = EntitySystem.Get<ToolSystem>();
|
|
|
|
if (!await toolSystem.UseTool(eventArgs.Using, eventArgs.User, Owner, 1f, 1f, _weldingQuality))
|
|
{
|
|
_beingWelded = false;
|
|
return false;
|
|
}
|
|
|
|
_beingWelded = false;
|
|
IsWeldedShut ^= true;
|
|
return true;
|
|
}
|
|
|
|
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
|
{
|
|
Open = true;
|
|
EmptyContents();
|
|
}
|
|
|
|
protected virtual IEnumerable<EntityUid> DetermineCollidingEntities()
|
|
{
|
|
var entityLookup = IoCManager.Resolve<IEntityLookup>();
|
|
return entityLookup.GetEntitiesIntersecting(Owner, _enteringRange, LookupFlags.Approximate);
|
|
}
|
|
|
|
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
|
{
|
|
if (eventArgs.Severity < ExplosionSeverity.Heavy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var containedEntities = Contents.ContainedEntities.ToList();
|
|
foreach (var entity in containedEntities)
|
|
{
|
|
var exActs = _entMan.GetComponents<IExAct>(entity).ToArray();
|
|
foreach (var exAct in exActs)
|
|
{
|
|
exAct.OnExplosion(eventArgs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class StorageOpenAttemptEvent : CancellableEntityEventArgs
|
|
{
|
|
|
|
}
|
|
|
|
public sealed class StorageCloseAttemptEvent : CancellableEntityEventArgs
|
|
{
|
|
|
|
}
|
|
}
|