--- /dev/null
+using Content.Shared.Morgue;
+
+namespace Content.Client.Morgue;
+
+public sealed class CrematoriumSystem : SharedCrematoriumSystem;
--- /dev/null
+using Content.Shared.Morgue;
+
+namespace Content.Client.Morgue;
+
+public sealed class MorgueSystem : SharedMorgueSystem;
+++ /dev/null
-using Content.Shared.Storage.Components;
-using Robust.Shared.GameStates;
-
-namespace Content.Client.Storage.Components;
-
-[RegisterComponent]
-public sealed partial class EntityStorageComponent : SharedEntityStorageComponent
-{
-
-}
SubscribeLocalEvent<EntityStorageComponent, ComponentHandleState>(OnHandleState);
}
- public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component)
+ public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
{
if (component != null)
return true;
-using Content.Server.Storage.Components;
+using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Content.Server.Storage.Components;
using Content.Shared.Administration;
+using Content.Shared.Storage.Components;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
-using Content.Server.Storage.Components;
+using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
using Content.Server.Speech.Components;
-using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Tabletop;
using Content.Server.Tabletop.Components;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Slippery;
+using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tabletop.Components;
using Content.Shared.Tools.Systems;
-using Content.Server.Storage.Components;
using Content.Shared.Construction;
using Content.Shared.Examine;
+using Content.Shared.Storage.Components;
using Content.Shared.Tools.Systems;
using JetBrains.Annotations;
+++ /dev/null
-namespace Content.Server.Morgue.Components;
-
-/// <summary>
-/// used to track actively cooking crematoriums
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveCrematoriumComponent : Component
-{
- [ViewVariables(VVAccess.ReadWrite)]
- public float Accumulator = 0;
-}
+++ /dev/null
-using Robust.Shared.Audio;
-
-namespace Content.Server.Morgue.Components;
-
-[RegisterComponent]
-public sealed partial class CrematoriumComponent : Component
-{
- /// <summary>
- /// The time it takes to cook in second
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- public int CookTime = 5;
-
- [DataField("cremateStartSound")]
- public SoundSpecifier CremateStartSound = new SoundPathSpecifier("/Audio/Items/Lighters/lighter1.ogg");
-
- [DataField("crematingSound")]
- public SoundSpecifier CrematingSound = new SoundPathSpecifier("/Audio/Effects/burning.ogg");
-
- [DataField("cremateFinishSound")]
- public SoundSpecifier CremateFinishSound = new SoundPathSpecifier("/Audio/Machines/ding.ogg");
-}
using Content.Server.Ghost;
-using Content.Server.Morgue.Components;
-using Content.Server.Storage.Components;
-using Content.Server.Storage.EntitySystems;
-using Content.Shared.Database;
-using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
-using Content.Shared.Mind;
using Content.Shared.Morgue;
+using Content.Shared.Morgue.Components;
using Content.Shared.Popups;
-using Content.Shared.Standing;
-using Content.Shared.Storage;
-using Content.Shared.Storage.Components;
-using Content.Shared.Verbs;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Containers;
using Robust.Shared.Player;
namespace Content.Server.Morgue;
-
-public sealed class CrematoriumSystem : EntitySystem
+public sealed class CrematoriumSystem : SharedCrematoriumSystem
{
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly GhostSystem _ghostSystem = default!;
- [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly StandingStateSystem _standing = default!;
- [Dependency] private readonly SharedMindSystem _minds = default!;
- [Dependency] private readonly SharedContainerSystem _containers = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<CrematoriumComponent, ExaminedEvent>(OnExamine);
- SubscribeLocalEvent<CrematoriumComponent, GetVerbsEvent<AlternativeVerb>>(AddCremateVerb);
SubscribeLocalEvent<CrematoriumComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
- SubscribeLocalEvent<ActiveCrematoriumComponent, StorageOpenAttemptEvent>(OnAttemptOpen);
- }
-
- private void OnExamine(EntityUid uid, CrematoriumComponent component, ExaminedEvent args)
- {
- if (!TryComp<AppearanceComponent>(uid, out var appearance))
- return;
-
- using (args.PushGroup(nameof(CrematoriumComponent)))
- {
- if (_appearance.TryGetData<bool>(uid, CrematoriumVisuals.Burning, out var isBurning, appearance) &&
- isBurning)
- {
- args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-is-burning",
- ("owner", uid)));
- }
-
- if (_appearance.TryGetData<bool>(uid, StorageVisuals.HasContents, out var hasContents, appearance) &&
- hasContents)
- {
- args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-has-contents"));
- }
- else
- {
- args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-empty"));
- }
- }
- }
-
- private void OnAttemptOpen(EntityUid uid, ActiveCrematoriumComponent component, ref StorageOpenAttemptEvent args)
- {
- args.Cancelled = true;
- }
-
- private void AddCremateVerb(EntityUid uid, CrematoriumComponent component, GetVerbsEvent<AlternativeVerb> args)
- {
- if (!TryComp<EntityStorageComponent>(uid, out var storage))
- return;
-
- if (!args.CanAccess || !args.CanInteract || args.Hands == null || storage.Open)
- return;
-
- if (HasComp<ActiveCrematoriumComponent>(uid))
- return;
-
- AlternativeVerb verb = new()
- {
- Text = Loc.GetString("cremate-verb-get-data-text"),
- // TODO VERB ICON add flame/burn symbol?
- Act = () => TryCremate(uid, component, storage),
- Impact = LogImpact.High // could be a body? or evidence? I dunno.
- };
- args.Verbs.Add(verb);
- }
-
- public bool Cremate(EntityUid uid, CrematoriumComponent? component = null, EntityStorageComponent? storage = null)
- {
- if (!Resolve(uid, ref component, ref storage))
- return false;
-
- if (HasComp<ActiveCrematoriumComponent>(uid))
- return false;
-
- _audio.PlayPvs(component.CremateStartSound, uid);
- _appearance.SetData(uid, CrematoriumVisuals.Burning, true);
-
- _audio.PlayPvs(component.CrematingSound, uid);
-
- AddComp<ActiveCrematoriumComponent>(uid);
- return true;
}
- public bool TryCremate(EntityUid uid, CrematoriumComponent? component = null, EntityStorageComponent? storage = null)
- {
- if (!Resolve(uid, ref component, ref storage))
- return false;
-
- if (storage.Open || storage.Contents.ContainedEntities.Count < 1)
- return false;
-
- return Cremate(uid, component, storage);
- }
-
- private void FinishCooking(EntityUid uid, CrematoriumComponent component, EntityStorageComponent? storage = null)
- {
- if (!Resolve(uid, ref storage))
- return;
-
- _appearance.SetData(uid, CrematoriumVisuals.Burning, false);
- RemComp<ActiveCrematoriumComponent>(uid);
-
- if (storage.Contents.ContainedEntities.Count > 0)
- {
- for (var i = storage.Contents.ContainedEntities.Count - 1; i >= 0; i--)
- {
- var item = storage.Contents.ContainedEntities[i];
- _containers.Remove(item, storage.Contents);
- Del(item);
- }
- var ash = Spawn("Ash", Transform(uid).Coordinates);
- _containers.Insert(ash, storage.Contents);
- }
-
- _entityStorage.OpenStorage(uid, storage);
- _audio.PlayPvs(component.CremateFinishSound, uid);
- }
-
- private void OnSuicideByEnvironment(EntityUid uid, CrematoriumComponent component, SuicideByEnvironmentEvent args)
+ private void OnSuicideByEnvironment(Entity<CrematoriumComponent> ent, ref SuicideByEnvironmentEvent args)
{
if (args.Handled)
return;
var victim = args.Victim;
- if (TryComp(victim, out ActorComponent? actor) && _minds.TryGetMind(victim, out var mindId, out var mind))
+ if (HasComp<ActorComponent>(victim) && Mind.TryGetMind(victim, out var mindId, out var mind))
{
_ghostSystem.OnGhostAttempt(mindId, false, mind: mind);
if (mind.OwnedEntity is { Valid: true } entity)
{
- _popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message"), entity);
+ Popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message"), entity);
}
}
- _popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message-others",
+ Popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message-others",
("victim", Identity.Entity(victim, EntityManager))),
- victim, Filter.PvsExcept(victim), true, PopupType.LargeCaution);
+ victim,
+ Filter.PvsExcept(victim),
+ true,
+ PopupType.LargeCaution);
- if (_entityStorage.CanInsert(victim, uid))
+ if (EntityStorage.CanInsert(victim, ent.Owner))
{
- _entityStorage.CloseStorage(uid);
- _standing.Down(victim, false);
- _entityStorage.Insert(victim, uid);
+ EntityStorage.CloseStorage(ent.Owner);
+ Standing.Down(victim, false);
+ EntityStorage.Insert(victim, ent.Owner);
}
else
{
+ EntityStorage.CloseStorage(ent.Owner);
Del(victim);
}
- _entityStorage.CloseStorage(uid);
- Cremate(uid, component);
+ Cremate(ent.AsNullable());
args.Handled = true;
}
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var query = EntityQueryEnumerator<ActiveCrematoriumComponent, CrematoriumComponent>();
- while (query.MoveNext(out var uid, out var act, out var crem))
- {
- act.Accumulator += frameTime;
-
- if (act.Accumulator >= crem.CookTime)
- FinishCooking(uid, crem);
- }
- }
}
-using Content.Server.Storage.Components;
-using Content.Shared.Examine;
-using Content.Shared.Mobs.Components;
using Content.Shared.Morgue;
using Content.Shared.Morgue.Components;
+using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
-using Robust.Shared.Player;
+using Robust.Shared.Timing;
namespace Content.Server.Morgue;
-public sealed class MorgueSystem : EntitySystem
+public sealed class MorgueSystem : SharedMorgueSystem
{
- [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent<MorgueComponent, ExaminedEvent>(OnExamine);
+ SubscribeLocalEvent<MorgueComponent, MapInitEvent>(OnMapInit);
}
- /// <summary>
- /// Handles the examination text for looking at a morgue.
- /// </summary>
- private void OnExamine(Entity<MorgueComponent> ent, ref ExaminedEvent args)
+ private void OnMapInit(Entity<MorgueComponent> ent, ref MapInitEvent args)
{
- if (!args.IsInDetailsRange)
- return;
-
- _appearance.TryGetData<MorgueContents>(ent.Owner, MorgueVisuals.Contents, out var contents);
-
- var text = contents switch
- {
- MorgueContents.HasSoul => "morgue-entity-storage-component-on-examine-details-body-has-soul",
- MorgueContents.HasContents => "morgue-entity-storage-component-on-examine-details-has-contents",
- MorgueContents.HasMob => "morgue-entity-storage-component-on-examine-details-body-has-no-soul",
- _ => "morgue-entity-storage-component-on-examine-details-empty"
- };
-
- args.PushMarkup(Loc.GetString(text));
+ ent.Comp.NextBeep = _timing.CurTime + ent.Comp.NextBeep;
}
/// <summary>
- /// Updates data periodically in case something died/got deleted in the morgue.
- /// </summary>
- private void CheckContents(EntityUid uid, MorgueComponent? morgue = null, EntityStorageComponent? storage = null, AppearanceComponent? app = null)
- {
- if (!Resolve(uid, ref morgue, ref storage, ref app))
- return;
-
- if (storage.Contents.ContainedEntities.Count == 0)
- {
- _appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.Empty);
- return;
- }
-
- var hasMob = false;
-
- foreach (var ent in storage.Contents.ContainedEntities)
- {
- if (!hasMob && HasComp<MobStateComponent>(ent))
- hasMob = true;
-
- if (HasComp<ActorComponent>(ent))
- {
- _appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.HasSoul, app);
- return;
- }
- }
-
- _appearance.SetData(uid, MorgueVisuals.Contents, hasMob ? MorgueContents.HasMob : MorgueContents.HasContents, app);
- }
-
- /// <summary>
- /// Handles the periodic beeping that morgues do when a live body is inside.
+ /// Handles the periodic beeping that morgues do when a live body is inside.
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);
+ var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<MorgueComponent, EntityStorageComponent, AppearanceComponent>();
while (query.MoveNext(out var uid, out var comp, out var storage, out var appearance))
{
- comp.AccumulatedFrameTime += frameTime;
-
- CheckContents(uid, comp, storage);
-
- if (comp.AccumulatedFrameTime < comp.BeepTime)
+ if (curTime < comp.NextBeep)
continue;
- comp.AccumulatedFrameTime -= comp.BeepTime;
+ comp.NextBeep += comp.BeepTime;
+
+ CheckContents(uid, comp, storage);
if (comp.DoSoulBeep && _appearance.TryGetData<MorgueContents>(uid, MorgueVisuals.Contents, out var contents, appearance) && contents == MorgueContents.HasSoul)
{
using Content.Server.NPC.Queries.Queries;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
-using Content.Server.Storage.Components;
using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
+using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tools.Systems;
using Content.Shared.Turrets;
using Content.Server.Popups;
-using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Content.Shared.Resist;
+using Content.Shared.Storage.Components;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.Revenant;
using Robust.Shared.Random;
using Content.Shared.Tag;
-using Content.Server.Storage.Components;
+using Content.Shared.Storage.Components;
using Content.Server.Light.Components;
using Content.Server.Ghost;
using Robust.Shared.Physics;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Access.Components;
using Content.Shared.Station.Components;
+using Content.Shared.Storage.Components;
using Content.Shared.GameTicking.Components;
namespace Content.Server.StationEvents.Events;
-using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
-using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.GameTicking.Components;
+using Content.Shared.Storage.Components;
using Robust.Shared.Map;
using Robust.Shared.Random;
+++ /dev/null
-using Content.Server.Atmos;
-using Content.Shared.Atmos;
-using Content.Shared.Storage.Components;
-using Robust.Shared.GameStates;
-
-namespace Content.Server.Storage.Components;
-
-[RegisterComponent]
-public sealed partial class EntityStorageComponent : SharedEntityStorageComponent, IGasMixtureHolder
-{
- /// <summary>
- /// Gas currently contained in this entity storage.
- /// None while open. Grabs gas from the atmosphere when closed, and exposes any entities inside to it.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("air")]
- public GasMixture Air { get; set; } = new (200);
-}
}
}
- protected override void OnComponentInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
+ protected override void OnComponentInit(EntityUid uid, EntityStorageComponent component, ComponentInit args)
{
base.OnComponentInit(uid, component, args);
_construction.AddContainer(uid, ContainerName, construction);
}
- public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component)
+ public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
{
if (component != null)
return true;
args.Contents.AddRange(ent.Comp.Contents.ContainedEntities);
}
- protected override void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
+ protected override void TakeGas(EntityUid uid, EntityStorageComponent component)
{
if (!component.Airtight)
return;
}
}
- public override void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
+ public override void ReleaseGas(EntityUid uid, EntityStorageComponent component)
{
var serverComp = (EntityStorageComponent) component;
using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Stack;
-using Content.Server.Storage.Components;
using Content.Shared.Body.Components;
using Content.Shared.Damage;
using Content.Shared.Power;
+using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Content.Shared.Xenoarchaeology.Equipment;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Morgue.Components;
+
+/// <summary>
+/// Used to track actively cooking crematoriums.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveCrematoriumComponent : Component;
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Morgue.Components;
+
+/// <summary>
+/// Allows an entity storage to dispose bodies by turning them into ash.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class CrematoriumComponent : Component
+{
+ /// <summary>
+ /// The entity to spawn when something was burned.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntProtoId LeftOverProtoId = "Ash";
+
+ /// <summary>
+ /// The time it takes to cremate something.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan CookTime = TimeSpan.FromSeconds(5);
+
+ /// <summary>
+ /// The timestamp at which cremating is finished.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan ActiveUntil = TimeSpan.Zero;
+
+ [DataField]
+ public SoundSpecifier CremateStartSound = new SoundPathSpecifier("/Audio/Items/Lighters/lighter1.ogg");
+
+ [DataField]
+ public SoundSpecifier CrematingSound = new SoundPathSpecifier("/Audio/Effects/burning.ogg");
+
+ [DataField]
+ public SoundSpecifier CremateFinishSound = new SoundPathSpecifier("/Audio/Machines/ding.ogg");
+}
+using Robust.Shared.GameStates;
+
namespace Content.Shared.Morgue.Components;
-[RegisterComponent]
-public sealed partial class EntityStorageLayingDownOverrideComponent : Component
-{
-}
+/// <summary>
+/// Makes an entity storage only accept entities that are laying down.
+/// This is true for mobs that are crit, dead or crawling.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class EntityStorageLayingDownOverrideComponent : Component;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Morgue.Components;
+/// <summary>
+/// When added to an entity storage this component will keep track of the mind status of the player inside.
+/// </summary>
[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class MorgueComponent : Component
{
/// <summary>
- /// Whether or not the morgue beeps if a living player is inside.
+ /// Whether or not the morgue beeps if a living player is inside.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public bool DoSoulBeep = true;
- [DataField]
- public float AccumulatedFrameTime = 0f;
+ /// <summary>
+ /// The timestamp for the next beep.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoPausedField]
+ public TimeSpan NextBeep = TimeSpan.Zero;
/// <summary>
- /// The amount of time between each beep.
+ /// The amount of time between each beep.
/// </summary>
[DataField]
- public float BeepTime = 10f;
+ public TimeSpan BeepTime = TimeSpan.FromSeconds(10);
+ /// <summary>
+ /// The beep sound to play.
+ /// </summary>
[DataField]
public SoundSpecifier OccupantHasSoulAlarmSound = new SoundPathSpecifier("/Audio/Weapons/Guns/EmptyAlarm/smg_empty_alarm.ogg");
}
--- /dev/null
+using Content.Shared.Database;
+using Content.Shared.Examine;
+using Content.Shared.Mind;
+using Content.Shared.Morgue.Components;
+using Content.Shared.Popups;
+using Content.Shared.Standing;
+using Content.Shared.Storage;
+using Content.Shared.Storage.Components;
+using Content.Shared.Storage.EntitySystems;
+using Content.Shared.Verbs;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Morgue;
+
+public abstract class SharedCrematoriumSystem : EntitySystem
+{
+ [Dependency] protected readonly SharedEntityStorageSystem EntityStorage = default!;
+ [Dependency] protected readonly SharedPopupSystem Popup = default!;
+ [Dependency] protected readonly StandingStateSystem Standing = default!;
+ [Dependency] protected readonly SharedMindSystem Mind = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<CrematoriumComponent, ExaminedEvent>(OnExamine);
+ SubscribeLocalEvent<CrematoriumComponent, GetVerbsEvent<AlternativeVerb>>(AddCremateVerb);
+ SubscribeLocalEvent<ActiveCrematoriumComponent, StorageOpenAttemptEvent>(OnAttemptOpen);
+ }
+
+ private void OnExamine(Entity<CrematoriumComponent> ent, ref ExaminedEvent args)
+ {
+ if (!TryComp<AppearanceComponent>(ent, out var appearance))
+ return;
+
+ using (args.PushGroup(nameof(CrematoriumComponent)))
+ {
+ if (_appearance.TryGetData<bool>(ent.Owner, CrematoriumVisuals.Burning, out var isBurning, appearance) &&
+ isBurning)
+ {
+ args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-is-burning",
+ ("owner", ent.Owner)));
+ }
+
+ if (_appearance.TryGetData<bool>(ent.Owner, StorageVisuals.HasContents, out var hasContents, appearance) &&
+ hasContents)
+ {
+ args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-has-contents"));
+ }
+ else
+ {
+ args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-empty"));
+ }
+ }
+ }
+
+ private void OnAttemptOpen(Entity<ActiveCrematoriumComponent> ent, ref StorageOpenAttemptEvent args)
+ {
+ args.Cancelled = true;
+ }
+
+ private void AddCremateVerb(EntityUid uid, CrematoriumComponent component, GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!TryComp<EntityStorageComponent>(uid, out var storage))
+ return;
+
+ if (!args.CanAccess || !args.CanInteract || args.Hands == null || storage.Open)
+ return;
+
+ if (HasComp<ActiveCrematoriumComponent>(uid))
+ return;
+
+ AlternativeVerb verb = new()
+ {
+ Text = Loc.GetString("cremate-verb-get-data-text"),
+ // TODO VERB ICON add flame/burn symbol?
+ Act = () => TryCremate((uid, component, storage), args.User),
+ Impact = LogImpact.High // could be a body? or evidence? I dunno.
+ };
+ args.Verbs.Add(verb);
+ }
+
+ /// <summary>
+ /// Start the cremation.
+ /// </summary>
+ public bool Cremate(Entity<CrematoriumComponent?> ent, EntityUid? user = null)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (HasComp<ActiveCrematoriumComponent>(ent))
+ return false;
+
+ _audio.PlayPredicted(ent.Comp.CremateStartSound, ent.Owner, user);
+ _audio.PlayPredicted(ent.Comp.CrematingSound, ent.Owner, user);
+ _appearance.SetData(ent.Owner, CrematoriumVisuals.Burning, true);
+
+ AddComp<ActiveCrematoriumComponent>(ent);
+ ent.Comp.ActiveUntil = _timing.CurTime + ent.Comp.CookTime;
+ Dirty(ent);
+ return true;
+ }
+
+ /// <summary>
+ /// Try to start to start the cremation.
+ /// Only works when the crematorium is closed and there are entities inside.
+ /// </summary>
+ public bool TryCremate(Entity<CrematoriumComponent?, EntityStorageComponent?> ent, EntityUid? user = null)
+ {
+ if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2))
+ return false;
+
+ if (ent.Comp2.Open || ent.Comp2.Contents.ContainedEntities.Count < 1)
+ return false;
+
+ return Cremate((ent.Owner, ent.Comp1), user);
+ }
+
+ /// <summary>
+ /// Finish the cremation process.
+ /// This will delete the entities inside and spawn ash.
+ /// </summary>
+ private void FinishCooking(Entity<CrematoriumComponent?, EntityStorageComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2))
+ return;
+
+ _appearance.SetData(ent.Owner, CrematoriumVisuals.Burning, false);
+ RemComp<ActiveCrematoriumComponent>(ent);
+
+ if (ent.Comp2.Contents.ContainedEntities.Count > 0)
+ {
+ for (var i = ent.Comp2.Contents.ContainedEntities.Count - 1; i >= 0; i--)
+ {
+ var item = ent.Comp2.Contents.ContainedEntities[i];
+ _container.Remove(item, ent.Comp2.Contents);
+ PredictedDel(item);
+ }
+ PredictedTrySpawnInContainer(ent.Comp1.LeftOverProtoId, ent.Owner, ent.Comp2.Contents.ID, out _);
+ }
+
+ EntityStorage.OpenStorage(ent.Owner, ent.Comp2);
+
+ if (_net.IsServer) // can't predict without the user
+ _audio.PlayPvs(ent.Comp1.CremateFinishSound, ent.Owner);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var curTime = _timing.CurTime;
+ var query = EntityQueryEnumerator<ActiveCrematoriumComponent, CrematoriumComponent>();
+ while (query.MoveNext(out var uid, out _, out var crematorium))
+ {
+ if (curTime < crematorium.ActiveUntil)
+ continue;
+
+ FinishCooking((uid, crematorium, null));
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Mobs.Components;
+using Content.Shared.Storage.Components;
+using Content.Shared.Examine;
+using Content.Shared.Morgue.Components;
+using Robust.Shared.Player;
+
+namespace Content.Shared.Morgue;
+
+public abstract class SharedMorgueSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<MorgueComponent, ExaminedEvent>(OnExamine);
+ SubscribeLocalEvent<MorgueComponent, StorageAfterCloseEvent>(OnClosed);
+ SubscribeLocalEvent<MorgueComponent, StorageAfterOpenEvent>(OnOpened);
+ }
+
+ /// <summary>
+ /// Handles the examination text for looking at a morgue.
+ /// </summary>
+ private void OnExamine(Entity<MorgueComponent> ent, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+
+ _appearance.TryGetData<MorgueContents>(ent.Owner, MorgueVisuals.Contents, out var contents);
+
+ var text = contents switch
+ {
+ MorgueContents.HasSoul => "morgue-entity-storage-component-on-examine-details-body-has-soul",
+ MorgueContents.HasContents => "morgue-entity-storage-component-on-examine-details-has-contents",
+ MorgueContents.HasMob => "morgue-entity-storage-component-on-examine-details-body-has-no-soul",
+ _ => "morgue-entity-storage-component-on-examine-details-empty"
+ };
+
+ args.PushMarkup(Loc.GetString(text));
+ }
+
+ private void OnClosed(Entity<MorgueComponent> ent, ref StorageAfterCloseEvent args)
+ {
+ CheckContents(ent.Owner, ent.Comp);
+ }
+
+ private void OnOpened(Entity<MorgueComponent> ent, ref StorageAfterOpenEvent args)
+ {
+ CheckContents(ent.Owner, ent.Comp);
+ }
+
+ /// <summary>
+ /// Updates data in case something died/got deleted in the morgue.
+ /// </summary>
+ public void CheckContents(EntityUid uid, MorgueComponent? morgue = null, EntityStorageComponent? storage = null, AppearanceComponent? app = null)
+ {
+ if (!Resolve(uid, ref morgue, ref storage, ref app))
+ return;
+
+ if (storage.Contents.ContainedEntities.Count == 0)
+ {
+ _appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.Empty, app);
+ return;
+ }
+
+ var hasMob = false;
+
+ foreach (var ent in storage.Contents.ContainedEntities)
+ {
+ if (!hasMob && HasComp<MobStateComponent>(ent))
+ hasMob = true;
+
+ if (HasComp<ActorComponent>(ent))
+ {
+ _appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.HasSoul, app);
+ return;
+ }
+ }
+
+ _appearance.SetData(uid, MorgueVisuals.Contents, hasMob ? MorgueContents.HasMob : MorgueContents.HasContents, app);
+ }
+}
using System.Numerics;
+using Content.Shared.Atmos;
using Content.Shared.Physics;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
namespace Content.Shared.Storage.Components;
-[NetworkedComponent]
-public abstract partial class SharedEntityStorageComponent : Component
+[RegisterComponent, NetworkedComponent]
+public sealed partial class EntityStorageComponent : Component, IGasMixtureHolder
{
public readonly float MaxSize = 1.0f; // maximum width or height of an entity allowed inside the storage.
/// <summary>
/// Collision masks that get removed when the storage gets opened.
/// </summary>
- public readonly int MasksToRemove = (int) (
+ public readonly int MasksToRemove = (int)(
CollisionGroup.MidImpassable |
CollisionGroup.HighImpassable |
CollisionGroup.LowImpassable);
/// <summary>
/// The total amount of items that can fit in one entitystorage
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public int Capacity = 30;
/// <summary>
/// Whether or not the entity still has collision when open
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public bool IsCollidableWhenOpen;
/// <summary>
/// If false, it prevents the storage from opening when the entity inside of it moves.
/// This is for objects that you want the player to move while inside, like large cardboard boxes, without opening the storage.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public bool OpenOnMove = true;
//The offset for where items are emptied/vacuumed for the EntityStorage.
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public Vector2 EnteringOffset = new(0, 0);
//The collision groups checked, so that items are depositied or grabbed from inside walls.
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public CollisionGroup EnteringOffsetCollisionFlags = CollisionGroup.Impassable | CollisionGroup.MidImpassable;
/// <summary>
/// How close you have to be to the "entering" spot to be able to enter
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float EnteringRange = 0.18f;
/// <summary>
/// Whether or not to show the contents when the storage is closed
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public bool ShowContents;
/// <summary>
/// Whether or not light is occluded by the storage
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public bool OccludesLight = true;
/// <summary>
/// Whether or not all the contents stored should be deleted with the entitystorage
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public bool DeleteContentsOnDestruction;
/// <summary>
/// Whether or not the container is sealed and traps air inside of it
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public bool Airtight = true;
/// <summary>
/// </summary>
[ViewVariables]
public Container Contents = default!;
+
+ /// <summary>
+ /// Gas currently contained in this entity storage.
+ /// None while open. Grabs gas from the atmosphere when closed, and exposes any entities inside to it.
+ /// </summary>
+ [DataField]
+ public GasMixture Air { get; set; } = new(200);
}
[Serializable, NetSerializable]
public const string ContainerName = "entity_storage";
- protected void OnEntityUnpausedEvent(EntityUid uid, SharedEntityStorageComponent component, EntityUnpausedEvent args)
+ protected void OnEntityUnpausedEvent(EntityUid uid, EntityStorageComponent component, EntityUnpausedEvent args)
{
component.NextInternalOpenAttempt += args.PausedTime;
}
- protected void OnGetState(EntityUid uid, SharedEntityStorageComponent component, ref ComponentGetState args)
+ protected void OnGetState(EntityUid uid, EntityStorageComponent component, ref ComponentGetState args)
{
args.State = new EntityStorageComponentState(component.Open,
component.Capacity,
component.NextInternalOpenAttempt);
}
- protected void OnHandleState(EntityUid uid, SharedEntityStorageComponent component, ref ComponentHandleState args)
+ protected void OnHandleState(EntityUid uid, EntityStorageComponent component, ref ComponentHandleState args)
{
if (args.Current is not EntityStorageComponentState state)
return;
component.NextInternalOpenAttempt = state.NextInternalOpenAttempt;
}
- protected virtual void OnComponentInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
+ protected virtual void OnComponentInit(EntityUid uid, EntityStorageComponent component, ComponentInit args)
{
component.Contents = _container.EnsureContainer<Container>(uid, ContainerName);
component.Contents.ShowContents = component.ShowContents;
component.Contents.OccludesLight = component.OccludesLight;
}
- protected virtual void OnComponentStartup(EntityUid uid, SharedEntityStorageComponent component, ComponentStartup args)
+ protected virtual void OnComponentStartup(EntityUid uid, EntityStorageComponent component, ComponentStartup args)
{
_appearance.SetData(uid, StorageVisuals.Open, component.Open);
}
- protected void OnInteract(EntityUid uid, SharedEntityStorageComponent component, ActivateInWorldEvent args)
+ protected void OnInteract(EntityUid uid, EntityStorageComponent component, ActivateInWorldEvent args)
{
if (args.Handled || !args.Complex)
return;
ToggleOpen(args.User, uid, component);
}
- public abstract bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component);
+ public abstract bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component);
- protected void OnLockToggleAttempt(EntityUid uid, SharedEntityStorageComponent target, ref LockToggleAttemptEvent args)
+ protected void OnLockToggleAttempt(EntityUid uid, EntityStorageComponent target, ref LockToggleAttemptEvent args)
{
// Cannot (un)lock open lockers.
if (target.Open)
args.Cancelled = true;
}
- protected void OnDestruction(EntityUid uid, SharedEntityStorageComponent component, DestructionEventArgs args)
+ protected void OnDestruction(EntityUid uid, EntityStorageComponent component, DestructionEventArgs args)
{
component.Open = true;
Dirty(uid, component);
}
}
- protected void OnRelayMovement(EntityUid uid, SharedEntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
+ protected void OnRelayMovement(EntityUid uid, EntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
{
if (!HasComp<HandsComponent>(args.Entity))
return;
if (_timing.CurTime < component.NextInternalOpenAttempt)
return;
- component.NextInternalOpenAttempt = _timing.CurTime + SharedEntityStorageComponent.InternalOpenAttemptDelay;
+ component.NextInternalOpenAttempt = _timing.CurTime + EntityStorageComponent.InternalOpenAttemptDelay;
Dirty(uid, component);
if (component.OpenOnMove)
TryOpenStorage(args.Entity, uid);
}
- protected void OnFoldAttempt(EntityUid uid, SharedEntityStorageComponent component, ref FoldAttemptEvent args)
+ protected void OnFoldAttempt(EntityUid uid, EntityStorageComponent component, ref FoldAttemptEvent args)
{
if (args.Cancelled)
return;
args.Cancelled = component.Open || component.Contents.ContainedEntities.Count != 0;
}
- protected void AddToggleOpenVerb(EntityUid uid, SharedEntityStorageComponent component, GetVerbsEvent<InteractionVerb> args)
+ protected void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract)
return;
}
- public void ToggleOpen(EntityUid user, EntityUid target, SharedEntityStorageComponent? component = null)
+ public void ToggleOpen(EntityUid user, EntityUid target, EntityStorageComponent? component = null)
{
if (!ResolveStorage(target, ref component))
return;
}
}
- public void EmptyContents(EntityUid uid, SharedEntityStorageComponent? component = null)
+ public void EmptyContents(EntityUid uid, EntityStorageComponent? component = null)
{
if (!ResolveStorage(uid, ref component))
return;
}
}
- public void OpenStorage(EntityUid uid, SharedEntityStorageComponent? component = null)
+ public void OpenStorage(EntityUid uid, EntityStorageComponent? component = null)
{
if (!ResolveStorage(uid, ref component))
return;
RaiseLocalEvent(uid, ref afterev);
}
- public void CloseStorage(EntityUid uid, SharedEntityStorageComponent? component = null)
+ public void CloseStorage(EntityUid uid, EntityStorageComponent? component = null)
{
if (!ResolveStorage(uid, ref component))
return;
RaiseLocalEvent(uid, ref afterev);
}
- public bool Insert(EntityUid toInsert, EntityUid container, SharedEntityStorageComponent? component = null)
+ public bool Insert(EntityUid toInsert, EntityUid container, EntityStorageComponent? component = null)
{
if (!ResolveStorage(container, ref component))
return false;
return true;
}
- public bool Remove(EntityUid toRemove, EntityUid container, SharedEntityStorageComponent? component = null, TransformComponent? xform = null)
+ public bool Remove(EntityUid toRemove, EntityUid container, EntityStorageComponent? component = null, TransformComponent? xform = null)
{
if (!Resolve(container, ref xform, false))
return false;
return true;
}
- public bool CanInsert(EntityUid toInsert, EntityUid container, SharedEntityStorageComponent? component = null)
+ public bool CanInsert(EntityUid toInsert, EntityUid container, EntityStorageComponent? component = null)
{
if (!ResolveStorage(container, ref component))
return false;
return true;
}
- public bool IsOpen(EntityUid target, SharedEntityStorageComponent? component = null)
+ public bool IsOpen(EntityUid target, EntityStorageComponent? component = null)
{
if (!ResolveStorage(target, ref component))
return false;
return component.Open;
}
- public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, SharedEntityStorageComponent? component = null)
+ public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, EntityStorageComponent? component = null)
{
if (!ResolveStorage(target, ref component))
return false;
return !ev.Cancelled;
}
- public bool AddToContents(EntityUid toAdd, EntityUid container, SharedEntityStorageComponent? component = null)
+ public bool AddToContents(EntityUid toAdd, EntityUid container, EntityStorageComponent? component = null)
{
if (!ResolveStorage(container, ref component))
return false;
return Insert(toAdd, container, component);
}
- private void ModifyComponents(EntityUid uid, SharedEntityStorageComponent? component = null)
+ private void ModifyComponents(EntityUid uid, EntityStorageComponent? component = null)
{
if (!ResolveStorage(uid, ref component))
return;
_appearance.SetData(uid, StorageVisuals.HasContents, component.Contents.ContainedEntities.Count > 0);
}
- protected virtual void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
+ protected virtual void TakeGas(EntityUid uid, EntityStorageComponent component)
{
}
- public virtual void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
+ public virtual void ReleaseGas(EntityUid uid, EntityStorageComponent component)
{
}