Cryogenic Sleep Units (#24096)

* Cryogenic sleep units

* pause map support

* no more body deletion

* Cryogenic Storage Units

* boowomp

* no more emag, no more dropping present people
This commit is contained in:
Nemanja
2024-01-15 01:35:28 -05:00
committed by GitHub
parent 1fc3c411ca
commit 736b9dd7df
38 changed files with 1376 additions and 8 deletions

View File

@@ -0,0 +1,110 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared.Bed.Cryostorage;
/// <summary>
/// This is used for a container which, when a player logs out while inside of,
/// will delete their body and redistribute their items.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class CryostorageComponent : Component
{
[DataField, ViewVariables(VVAccess.ReadWrite)]
public string ContainerId = "storage";
/// <summary>
/// How long a player can remain inside Cryostorage before automatically being taken care of, given that they have no mind.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan NoMindGracePeriod = TimeSpan.FromSeconds(30f);
/// <summary>
/// How long a player can remain inside Cryostorage before automatically being taken care of.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan GracePeriod = TimeSpan.FromMinutes(5f);
/// <summary>
/// A list of players who have actively entered cryostorage.
/// </summary>
[DataField]
public List<EntityUid> StoredPlayers = new();
/// <summary>
/// Sound that is played when a player is removed by a cryostorage.
/// </summary>
[DataField]
public SoundSpecifier? RemoveSound = new SoundPathSpecifier("/Audio/Effects/teleport_departure.ogg");
}
[Serializable, NetSerializable]
public enum CryostorageVisuals : byte
{
Full
}
[Serializable, NetSerializable]
public record struct CryostorageContainedPlayerData()
{
/// <summary>
/// The player's IC name
/// </summary>
public string PlayerName = string.Empty;
/// <summary>
/// The player's entity
/// </summary>
public NetEntity PlayerEnt = NetEntity.Invalid;
/// <summary>
/// A dictionary relating a slot definition name to the name of the item inside of it.
/// </summary>
public Dictionary<string, string> ItemSlots = new();
/// <summary>
/// A dictionary relating a hand ID to the hand name and the name of the item being held.
/// </summary>
public Dictionary<string, string> HeldItems = new();
}
[Serializable, NetSerializable]
public sealed class CryostorageBuiState : BoundUserInterfaceState
{
public List<CryostorageContainedPlayerData> PlayerData;
public CryostorageBuiState(List<CryostorageContainedPlayerData> playerData)
{
PlayerData = playerData;
}
}
[Serializable, NetSerializable]
public sealed class CryostorageRemoveItemBuiMessage : BoundUserInterfaceMessage
{
public NetEntity Entity;
public string Key;
public RemovalType Type;
public enum RemovalType : byte
{
Hand,
Inventory
}
public CryostorageRemoveItemBuiMessage(NetEntity entity, string key, RemovalType type)
{
Entity = entity;
Key = key;
Type = type;
}
}
[Serializable, NetSerializable]
public enum CryostorageUIKey : byte
{
Key
}

View File

@@ -0,0 +1,34 @@
using Robust.Shared.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Bed.Cryostorage;
/// <summary>
/// This is used to track an entity that is currently being held in Cryostorage.
/// </summary>
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
public sealed partial class CryostorageContainedComponent : Component
{
/// <summary>
/// Whether or not this entity is being stored on another map or is just chilling in a container
/// </summary>
[DataField, AutoNetworkedField]
public bool StoredWhileDisconnected;
/// <summary>
/// The time at which the cryostorage grace period ends.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan? GracePeriodEndTime;
/// <summary>
/// The cryostorage this entity is 'stored' in.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? Cryostorage;
[DataField]
public NetUserId? UserId;
}

View File

@@ -0,0 +1,179 @@
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.DragDrop;
using Content.Shared.GameTicking;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Shared.Bed.Cryostorage;
/// <summary>
/// This handles <see cref="CryostorageComponent"/>
/// </summary>
public abstract class SharedCryostorageSystem : EntitySystem
{
[Dependency] protected readonly ISharedAdminLogManager AdminLog = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] protected readonly SharedMindSystem Mind = default!;
protected EntityUid? PausedMap { get; private set; }
protected bool CryoSleepRejoiningEnabled;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<CryostorageComponent, EntInsertedIntoContainerMessage>(OnInsertedContainer);
SubscribeLocalEvent<CryostorageComponent, EntRemovedFromContainerMessage>(OnRemovedContainer);
SubscribeLocalEvent<CryostorageComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
SubscribeLocalEvent<CryostorageComponent, ComponentShutdown>(OnShutdownContainer);
SubscribeLocalEvent<CryostorageComponent, CanDropTargetEvent>(OnCanDropTarget);
SubscribeLocalEvent<CryostorageContainedComponent, EntGotRemovedFromContainerMessage>(OnRemovedContained);
SubscribeLocalEvent<CryostorageContainedComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<CryostorageContainedComponent, ComponentShutdown>(OnShutdownContained);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
_configuration.OnValueChanged(CCVars.GameCryoSleepRejoining, OnCvarChanged);
}
public override void Shutdown()
{
base.Shutdown();
_configuration.UnsubValueChanged(CCVars.GameCryoSleepRejoining, OnCvarChanged);
}
private void OnCvarChanged(bool value)
{
CryoSleepRejoiningEnabled = value;
}
protected virtual void OnInsertedContainer(Entity<CryostorageComponent> ent, ref EntInsertedIntoContainerMessage args)
{
var (_, comp) = ent;
if (args.Container.ID != comp.ContainerId)
return;
_appearance.SetData(ent, CryostorageVisuals.Full, true);
if (!Timing.IsFirstTimePredicted)
return;
var containedComp = EnsureComp<CryostorageContainedComponent>(args.Entity);
var delay = Mind.TryGetMind(args.Entity, out _, out _) ? comp.GracePeriod : comp.NoMindGracePeriod;
containedComp.GracePeriodEndTime = Timing.CurTime + delay;
containedComp.Cryostorage = ent;
Dirty(args.Entity, containedComp);
}
private void OnRemovedContainer(Entity<CryostorageComponent> ent, ref EntRemovedFromContainerMessage args)
{
var (_, comp) = ent;
if (args.Container.ID != comp.ContainerId)
return;
_appearance.SetData(ent, CryostorageVisuals.Full, args.Container.ContainedEntities.Count > 0);
}
private void OnInsertAttempt(Entity<CryostorageComponent> ent, ref ContainerIsInsertingAttemptEvent args)
{
var (_, comp) = ent;
if (args.Container.ID != comp.ContainerId)
return;
if (!TryComp<MindContainerComponent>(args.EntityUid, out var mindContainer))
{
args.Cancel();
return;
}
if (Mind.TryGetMind(args.EntityUid, out _, out var mindComp, mindContainer) &&
(mindComp.PreventSuicide || mindComp.PreventGhosting))
{
args.Cancel();
}
}
private void OnShutdownContainer(Entity<CryostorageComponent> ent, ref ComponentShutdown args)
{
var comp = ent.Comp;
foreach (var stored in comp.StoredPlayers)
{
if (TryComp<CryostorageContainedComponent>(stored, out var containedComponent))
{
containedComponent.Cryostorage = null;
Dirty(stored, containedComponent);
}
}
comp.StoredPlayers.Clear();
Dirty(ent, comp);
}
private void OnCanDropTarget(Entity<CryostorageComponent> ent, ref CanDropTargetEvent args)
{
if (args.Dragged == args.User)
return;
if (!Mind.TryGetMind(args.Dragged, out _, out var mindComp) || mindComp.Session?.AttachedEntity != args.Dragged)
return;
args.CanDrop = false;
args.Handled = true;
}
private void OnRemovedContained(Entity<CryostorageContainedComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
var (_, comp) = ent;
if (!comp.StoredWhileDisconnected)
RemCompDeferred(ent, comp);
}
private void OnUnpaused(Entity<CryostorageContainedComponent> ent, ref EntityUnpausedEvent args)
{
var comp = ent.Comp;
if (comp.GracePeriodEndTime != null)
comp.GracePeriodEndTime = comp.GracePeriodEndTime.Value + args.PausedTime;
}
private void OnShutdownContained(Entity<CryostorageContainedComponent> ent, ref ComponentShutdown args)
{
var comp = ent.Comp;
CompOrNull<CryostorageComponent>(comp.Cryostorage)?.StoredPlayers.Remove(ent);
ent.Comp.Cryostorage = null;
Dirty(ent, comp);
}
private void OnRoundRestart(RoundRestartCleanupEvent _)
{
DeletePausedMap();
}
private void DeletePausedMap()
{
if (PausedMap == null || !Exists(PausedMap))
return;
EntityManager.DeleteEntity(PausedMap.Value);
PausedMap = null;
}
protected void EnsurePausedMap()
{
if (PausedMap != null && Exists(PausedMap))
return;
var map = _mapManager.CreateMap();
_mapManager.SetMapPaused(map, true);
PausedMap = _mapManager.GetMapEntityId(map);
}
}