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:
110
Content.Shared/Bed/Cryostorage/CryostorageComponent.cs
Normal file
110
Content.Shared/Bed/Cryostorage/CryostorageComponent.cs
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
179
Content.Shared/Bed/Cryostorage/SharedCryostorageSystem.cs
Normal file
179
Content.Shared/Bed/Cryostorage/SharedCryostorageSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user