From fd7ff690b1ad7b1cf6cd62deba3076684e3e9282 Mon Sep 17 00:00:00 2001
From: themias <89101928+themias@users.noreply.github.com>
Date: Sun, 14 Jan 2024 11:47:31 -0500
Subject: [PATCH] Grave digging and decomposition (#23646)
* Grave digging and decomposition
* fix
* update based on review comments
* code review
* remove unused field
---
Content.Server/Atmos/Rotting/RottingSystem.cs | 21 ++-
.../Botany/Systems/PlantHolderSystem.cs | 3 +-
.../Rotting/ProRottingContainerComponent.cs | 14 ++
Content.Shared/Burial/BurialSystem.cs | 173 ++++++++++++++++++
.../Burial/Components/GraveComponent.cs | 54 ++++++
.../Burial/Components/ShovelComponent.cs | 13 ++
.../Burial/GraveDiggingDoAfterEvent.cs | 9 +
Resources/Audio/Items/attributions.yml | 7 +-
Resources/Audio/Items/shovel_dig.ogg | Bin 0 -> 11328 bytes
Resources/Locale/en-US/burial/burial.ftl | 5 +
.../Locale/en-US/tools/tool-qualities.ftl | 3 +
.../Entities/Objects/Misc/utensils.yml | 4 +
.../Objects/Specific/Hydroponics/tools.yml | 3 +-
.../Entities/Objects/Tools/tools.yml | 4 +-
.../Structures/Storage/Crates/crates.yml | 11 +-
15 files changed, 315 insertions(+), 9 deletions(-)
create mode 100644 Content.Shared/Atmos/Rotting/ProRottingContainerComponent.cs
create mode 100644 Content.Shared/Burial/BurialSystem.cs
create mode 100644 Content.Shared/Burial/Components/GraveComponent.cs
create mode 100644 Content.Shared/Burial/Components/ShovelComponent.cs
create mode 100644 Content.Shared/Burial/GraveDiggingDoAfterEvent.cs
create mode 100644 Resources/Audio/Items/shovel_dig.ogg
create mode 100644 Resources/Locale/en-US/burial/burial.ftl
diff --git a/Content.Server/Atmos/Rotting/RottingSystem.cs b/Content.Server/Atmos/Rotting/RottingSystem.cs
index 8ddc1b0fb9..ff0ecaada4 100644
--- a/Content.Server/Atmos/Rotting/RottingSystem.cs
+++ b/Content.Server/Atmos/Rotting/RottingSystem.cs
@@ -185,6 +185,23 @@ public sealed class RottingSystem : EntitySystem
args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f;
}
+ ///
+ /// Is anything speeding up the decay?
+ /// e.g. buried in a grave
+ /// TODO: hot temperatures increase rot?
+ ///
+ ///
+ private float GetRotRate(EntityUid uid)
+ {
+ if (_container.TryGetContainingContainer(uid, out var container) &&
+ TryComp(container.Owner, out var rotContainer))
+ {
+ return rotContainer.DecayModifier;
+ }
+
+ return 1f;
+ }
+
public override void Update(float frameTime)
{
base.Update(frameTime);
@@ -199,7 +216,7 @@ public sealed class RottingSystem : EntitySystem
if (IsRotten(uid) || !IsRotProgressing(uid, perishable))
continue;
- perishable.RotAccumulator += perishable.PerishUpdateRate;
+ perishable.RotAccumulator += perishable.PerishUpdateRate * GetRotRate(uid);
if (perishable.RotAccumulator >= perishable.RotAfter)
{
var rot = AddComp(uid);
@@ -216,7 +233,7 @@ public sealed class RottingSystem : EntitySystem
if (!IsRotProgressing(uid, perishable))
continue;
- rotting.TotalRotTime += rotting.RotUpdateRate;
+ rotting.TotalRotTime += rotting.RotUpdateRate * GetRotRate(uid);
if (rotting.DealDamage)
{
diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs
index 0bef58a293..1484051c84 100644
--- a/Content.Server/Botany/Systems/PlantHolderSystem.cs
+++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs
@@ -7,6 +7,7 @@ using Content.Server.Ghost.Roles.Components;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Botany;
+using Content.Shared.Burial.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.Examine;
@@ -191,7 +192,7 @@ public sealed class PlantHolderSystem : EntitySystem
return;
}
- if (_tagSystem.HasTag(args.Used, "Shovel"))
+ if (HasComp(args.Used))
{
if (component.Seed != null)
{
diff --git a/Content.Shared/Atmos/Rotting/ProRottingContainerComponent.cs b/Content.Shared/Atmos/Rotting/ProRottingContainerComponent.cs
new file mode 100644
index 0000000000..3f5c229c04
--- /dev/null
+++ b/Content.Shared/Atmos/Rotting/ProRottingContainerComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Atmos.Rotting;
+
+///
+/// Entities inside this container will rot at a faster pace, e.g. a grave
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ProRottingContainerComponent : Component
+{
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float DecayModifier = 3f;
+}
+
diff --git a/Content.Shared/Burial/BurialSystem.cs b/Content.Shared/Burial/BurialSystem.cs
new file mode 100644
index 0000000000..937784ca2a
--- /dev/null
+++ b/Content.Shared/Burial/BurialSystem.cs
@@ -0,0 +1,173 @@
+using Content.Shared.Burial;
+using Content.Shared.Burial.Components;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Content.Shared.Movement.Events;
+using Content.Shared.Placeable;
+using Content.Shared.Popups;
+using Content.Shared.Storage.Components;
+using Content.Shared.Storage.EntitySystems;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Player;
+
+namespace Content.Server.Burial.Systems;
+
+public sealed class BurialSystem : EntitySystem
+{
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly SharedEntityStorageSystem _storageSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInteractUsing);
+ SubscribeLocalEvent(OnActivate);
+ SubscribeLocalEvent(OnAfterInteractUsing, before: new[] { typeof(PlaceableSurfaceSystem) });
+ SubscribeLocalEvent(OnGraveDigging);
+
+ SubscribeLocalEvent(OnOpenAttempt);
+ SubscribeLocalEvent(OnCloseAttempt);
+ SubscribeLocalEvent(OnAfterOpen);
+ SubscribeLocalEvent(OnAfterClose);
+
+ SubscribeLocalEvent(OnRelayMovement);
+ }
+
+ private void OnInteractUsing(EntityUid uid, GraveComponent component, InteractUsingEvent args)
+ {
+ if (args.Handled || component.ActiveShovelDigging)
+ return;
+
+ if (TryComp(args.Used, out var shovel))
+ {
+ var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, component.DigDelay / shovel.SpeedModifier, new GraveDiggingDoAfterEvent(), uid, target: args.Target, used: uid)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ NeedHand = true,
+ BreakOnHandChange = true
+ };
+
+ if (!_doAfterSystem.TryStartDoAfter(doAfterEventArgs))
+ return;
+
+ StartDigging(uid, args.User, args.Used, component);
+ }
+ else
+ {
+ _popupSystem.PopupClient(Loc.GetString("grave-digging-requires-tool", ("grave", args.Target)), uid, args.User);
+ }
+
+ args.Handled = true;
+ }
+
+ private void OnAfterInteractUsing(EntityUid uid, GraveComponent component, AfterInteractUsingEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ // don't place shovels on the grave, only dig
+ if (HasComp(args.Used))
+ args.Handled = true;
+ }
+
+ private void OnActivate(EntityUid uid, GraveComponent component, ActivateInWorldEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ _popupSystem.PopupClient(Loc.GetString("grave-digging-requires-tool", ("grave", args.Target)), uid, args.User);
+ }
+
+ private void OnGraveDigging(EntityUid uid, GraveComponent component, GraveDiggingDoAfterEvent args)
+ {
+ if (args.Used != null)
+ {
+ component.ActiveShovelDigging = false;
+ component.Stream = _audioSystem.Stop(component.Stream);
+ }
+ else
+ {
+ component.HandDiggingDoAfter = null;
+ }
+
+ if (args.Cancelled || args.Handled)
+ return;
+
+ component.DiggingComplete = true;
+
+ if (args.Used != null)
+ _storageSystem.ToggleOpen(args.User, uid);
+ else
+ _storageSystem.TryOpenStorage(args.User, uid); //can only claw out
+ }
+
+ private void StartDigging(EntityUid uid, EntityUid user, EntityUid? used, GraveComponent component)
+ {
+ if (used != null)
+ {
+ _popupSystem.PopupClient(Loc.GetString("grave-start-digging-user", ("grave", uid), ("tool", used)), user, user);
+ _popupSystem.PopupEntity(Loc.GetString("grave-start-digging-others", ("user", user), ("grave", uid), ("tool", used)), user, Filter.PvsExcept(user), true);
+ if (component.Stream == null)
+ component.Stream = _audioSystem.PlayPredicted(component.DigSound, uid, user)?.Entity;
+ component.ActiveShovelDigging = true;
+ Dirty(uid, component);
+ }
+ else
+ {
+ _popupSystem.PopupClient(Loc.GetString("grave-start-digging-user-trapped", ("grave", uid)), user, user, PopupType.Medium);
+ }
+ }
+
+ private void OnOpenAttempt(EntityUid uid, GraveComponent component, ref StorageOpenAttemptEvent args)
+ {
+ if (component.DiggingComplete)
+ return;
+
+ args.Cancelled = true;
+ }
+
+ private void OnCloseAttempt(EntityUid uid, GraveComponent component, ref StorageCloseAttemptEvent args)
+ {
+ if (component.DiggingComplete)
+ return;
+
+ args.Cancelled = true;
+ }
+
+ private void OnAfterOpen(EntityUid uid, GraveComponent component, ref StorageAfterOpenEvent args)
+ {
+ component.DiggingComplete = false;
+ }
+
+ private void OnAfterClose(EntityUid uid, GraveComponent component, ref StorageAfterCloseEvent args)
+ {
+ component.DiggingComplete = false;
+ }
+
+ private void OnRelayMovement(EntityUid uid, GraveComponent component, ref ContainerRelayMovementEntityEvent args)
+ {
+ // We track a separate doAfter here, as we want someone with a shovel to
+ // be able to come along and help someone trying to claw their way out
+ if (component.HandDiggingDoAfter != null)
+ return;
+
+ var doAfterEventArgs = new DoAfterArgs(EntityManager, args.Entity, component.DigDelay / component.DigOutByHandModifier, new GraveDiggingDoAfterEvent(), uid, target: uid)
+ {
+ NeedHand = false,
+ BreakOnUserMove = true,
+ BreakOnTargetMove = false,
+ BreakOnHandChange = false,
+ BreakOnDamage = false
+ };
+
+ if (!_doAfterSystem.TryStartDoAfter(doAfterEventArgs, out component.HandDiggingDoAfter))
+ return;
+
+ StartDigging(uid, args.Entity, null, component);
+ }
+}
diff --git a/Content.Shared/Burial/Components/GraveComponent.cs b/Content.Shared/Burial/Components/GraveComponent.cs
new file mode 100644
index 0000000000..0910f98312
--- /dev/null
+++ b/Content.Shared/Burial/Components/GraveComponent.cs
@@ -0,0 +1,54 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Burial.Components;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GraveComponent : Component
+{
+ ///
+ /// How long it takes to dig this grave, without modifiers
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan DigDelay = TimeSpan.FromSeconds(15);
+
+ ///
+ /// Modifier if digging yourself out by hand if buried alive
+ /// TODO: Handle digging with bare hands in the tools system
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float DigOutByHandModifier = 0.1f;
+
+ ///
+ /// Sound to make when digging/filling this grave
+ ///
+ [DataField, ViewVariables(VVAccess.ReadOnly)]
+ public SoundPathSpecifier DigSound = new SoundPathSpecifier("/Audio/Items/shovel_dig.ogg")
+ {
+ Params = AudioParams.Default.WithLoop(true)
+ };
+
+ ///
+ /// Is this grave in the process of being dug/filled?
+ ///
+ [DataField, ViewVariables(VVAccess.ReadOnly)]
+ public bool DiggingComplete = false;
+
+ [DataField, ViewVariables(VVAccess.ReadOnly)]
+ public EntityUid? Stream;
+
+ ///
+ /// Auto-networked field to track shovel digging.
+ /// This makes sure a looping audio Stream isn't opened
+ /// on the client-side. (DoAfterId/EntityUid isn't serializable.)
+ ///
+ [DataField, ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
+ public bool ActiveShovelDigging;
+
+ ///
+ /// Tracks someone digging themself out of the grave
+ ///
+ [DataField, ViewVariables(VVAccess.ReadOnly)]
+ public DoAfterId? HandDiggingDoAfter;
+}
diff --git a/Content.Shared/Burial/Components/ShovelComponent.cs b/Content.Shared/Burial/Components/ShovelComponent.cs
new file mode 100644
index 0000000000..944e06d4cd
--- /dev/null
+++ b/Content.Shared/Burial/Components/ShovelComponent.cs
@@ -0,0 +1,13 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Burial.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShovelComponent : Component
+{
+ ///
+ /// The speed modifier for how fast this shovel will dig.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public float SpeedModifier = 1f;
+}
diff --git a/Content.Shared/Burial/GraveDiggingDoAfterEvent.cs b/Content.Shared/Burial/GraveDiggingDoAfterEvent.cs
new file mode 100644
index 0000000000..c1a85bef50
--- /dev/null
+++ b/Content.Shared/Burial/GraveDiggingDoAfterEvent.cs
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Burial;
+
+[Serializable, NetSerializable]
+public sealed partial class GraveDiggingDoAfterEvent : SimpleDoAfterEvent
+{
+}
diff --git a/Resources/Audio/Items/attributions.yml b/Resources/Audio/Items/attributions.yml
index 7e186cc076..13eff6e702 100644
--- a/Resources/Audio/Items/attributions.yml
+++ b/Resources/Audio/Items/attributions.yml
@@ -86,4 +86,9 @@
- files: ["scissors.ogg"]
license: "CC0-1.0"
copyright: "User Hanbaal on freesound.org. Converted to ogg by TheShuEd"
- source: "https://freesound.org/people/Hanbaal/sounds/178669/"
\ No newline at end of file
+ source: "https://freesound.org/people/Hanbaal/sounds/178669/"
+
+- files: ["shovel_dig.ogg"]
+ license: "CC-BY-SA-3.0"
+ copyright: "Taken from tgstation, modified by themias (github) for ss14"
+ source: "https://github.com/tgstation/tgstation/tree/85a0925051bb00e7a950ee66cb7f87982cd22439/sound/effects/shovel_dig.ogg"
\ No newline at end of file
diff --git a/Resources/Audio/Items/shovel_dig.ogg b/Resources/Audio/Items/shovel_dig.ogg
new file mode 100644
index 0000000000000000000000000000000000000000..4997cdd82ce035e6be274ba6786546946e3e02bf
GIT binary patch
literal 11328
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