predict morgue and crematorium (#39293)

This commit is contained in:
slarticodefast
2025-08-22 01:49:50 +02:00
committed by GitHub
parent be62e08de4
commit d4f96fd1c6
29 changed files with 433 additions and 341 deletions

View File

@@ -0,0 +1,5 @@
using Content.Shared.Morgue;
namespace Content.Client.Morgue;
public sealed class CrematoriumSystem : SharedCrematoriumSystem;

View File

@@ -0,0 +1,5 @@
using Content.Shared.Morgue;
namespace Content.Client.Morgue;
public sealed class MorgueSystem : SharedMorgueSystem;

View File

@@ -1,10 +0,0 @@
using Content.Shared.Storage.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Storage.Components;
[RegisterComponent]
public sealed partial class EntityStorageComponent : SharedEntityStorageComponent
{
}

View File

@@ -31,7 +31,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
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;

View File

@@ -1,4 +1,4 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;

View File

@@ -1,5 +1,6 @@
using Content.Server.Storage.Components;
using Content.Shared.Administration;
using Content.Shared.Storage.Components;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;

View File

@@ -1,4 +1,4 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;

View File

@@ -14,7 +14,6 @@ using Content.Server.Pointing.Components;
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;
@@ -40,6 +39,7 @@ using Content.Shared.Movement.Systems;
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;

View File

@@ -1,6 +1,6 @@
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;

View File

@@ -1,11 +0,0 @@
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;
}

View File

@@ -1,22 +0,0 @@
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");
}

View File

@@ -1,195 +1,58 @@
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);
}
}
}

View File

@@ -1,75 +1,27 @@
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));
}
/// <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);
ent.Comp.NextBeep = _timing.CurTime + ent.Comp.NextBeep;
}
/// <summary>
@@ -79,17 +31,16 @@ public sealed class MorgueSystem : EntitySystem
{
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)
{

View File

@@ -7,7 +7,6 @@ using Content.Server.NPC.Queries.Curves;
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;
@@ -20,6 +19,7 @@ using Content.Shared.Mobs.Systems;
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;

View File

@@ -1,11 +1,11 @@
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;

View File

@@ -3,7 +3,7 @@ using Content.Shared.Damage;
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;

View File

@@ -4,6 +4,7 @@ using Content.Server.Storage.Components;
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;

View File

@@ -1,8 +1,7 @@
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;

View File

@@ -1,18 +0,0 @@
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);
}

View File

@@ -68,7 +68,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
}
}
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);
@@ -76,7 +76,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
_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;
@@ -107,7 +107,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
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;
@@ -121,7 +121,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
}
}
public override void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
public override void ReleaseGas(EntityUid uid, EntityStorageComponent component)
{
var serverComp = (EntityStorageComponent) component;

View File

@@ -2,10 +2,10 @@ using Content.Server.Body.Systems;
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;

View File

@@ -0,0 +1,9 @@
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;

View File

@@ -0,0 +1,42 @@
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");
}

View File

@@ -1,6 +1,10 @@
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;

View File

@@ -1,26 +1,38 @@
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.
/// </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.
/// </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");
}

View File

@@ -0,0 +1,170 @@
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));
}
}
}

View File

@@ -0,0 +1,83 @@
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);
}
}

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using Content.Shared.Atmos;
using Content.Shared.Physics;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
@@ -8,8 +9,8 @@ using Robust.Shared.Serialization;
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.
@@ -19,7 +20,7 @@ public abstract partial class SharedEntityStorageComponent : Component
/// <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);
@@ -33,13 +34,13 @@ public abstract partial class SharedEntityStorageComponent : Component
/// <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>
@@ -47,45 +48,45 @@ public abstract partial class SharedEntityStorageComponent : Component
/// 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>
@@ -118,6 +119,13 @@ public abstract partial class SharedEntityStorageComponent : Component
/// </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]

View File

@@ -48,12 +48,12 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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,
@@ -63,7 +63,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -75,19 +75,19 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -96,9 +96,9 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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)
@@ -109,7 +109,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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);
@@ -125,7 +125,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
}
}
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;
@@ -136,21 +136,21 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -175,7 +175,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
}
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;
@@ -190,7 +190,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
}
}
public void EmptyContents(EntityUid uid, SharedEntityStorageComponent? component = null)
public void EmptyContents(EntityUid uid, EntityStorageComponent? component = null)
{
if (!ResolveStorage(uid, ref component))
return;
@@ -203,7 +203,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
}
}
public void OpenStorage(EntityUid uid, SharedEntityStorageComponent? component = null)
public void OpenStorage(EntityUid uid, EntityStorageComponent? component = null)
{
if (!ResolveStorage(uid, ref component))
return;
@@ -224,7 +224,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -275,7 +275,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -295,7 +295,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -322,7 +322,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -379,7 +379,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -387,7 +387,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -429,7 +429,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -440,7 +440,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
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;
@@ -472,12 +472,12 @@ public abstract class SharedEntityStorageSystem : EntitySystem
_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)
{
}