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 0000000000..4997cdd82c
Binary files /dev/null and b/Resources/Audio/Items/shovel_dig.ogg differ
diff --git a/Resources/Locale/en-US/burial/burial.ftl b/Resources/Locale/en-US/burial/burial.ftl
new file mode 100644
index 0000000000..7c20e9de7e
--- /dev/null
+++ b/Resources/Locale/en-US/burial/burial.ftl
@@ -0,0 +1,5 @@
+grave-start-digging-others = {CAPITALIZE($user)} starts digging {THE($grave)} with {THE($tool)}.
+grave-start-digging-user = You start digging {THE($grave)} with {THE($tool)}.
+grave-start-digging-user-trapped = You start clawing your way out of {THE($grave)}!
+
+grave-digging-requires-tool = You need a tool to dig this {$grave}!
\ No newline at end of file
diff --git a/Resources/Locale/en-US/tools/tool-qualities.ftl b/Resources/Locale/en-US/tools/tool-qualities.ftl
index 331925b4b9..14e42390a7 100644
--- a/Resources/Locale/en-US/tools/tool-qualities.ftl
+++ b/Resources/Locale/en-US/tools/tool-qualities.ftl
@@ -30,3 +30,6 @@ tool-quality-woodcutting-tool-name = Hatchet
tool-quality-rolling-name = Rolling
tool-quality-rolling-tool-name = Rolling Pin
+
+tool-quality-digging-name = Digging
+tool-quality-digging-tool-name = Shovel
\ No newline at end of file
diff --git a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml
index 5f2ce47ba1..4ac05e1e4b 100644
--- a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml
+++ b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml
@@ -79,6 +79,8 @@
damage:
types:
Blunt: 1
+ - type: Shovel
+ speedModifier: 0.1 # you can try
- type: entity
parent: UtensilBasePlastic
@@ -93,6 +95,8 @@
- type: Utensil
types:
- Spoon
+ - type: Shovel
+ speedModifier: 0.1 # you can try
- type: entity
parent: UtensilBase
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml
index d389a35fe0..bc03308d14 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml
@@ -96,7 +96,6 @@
components:
- type: Tag
tags:
- - Shovel
- BotanyShovel
- type: Sprite
sprite: Objects/Tools/Hydroponics/spade.rsi
@@ -109,6 +108,8 @@
Piercing: 5 # I guess you can stab it into them?
- type: Item
sprite: Objects/Tools/Hydroponics/spade.rsi
+ - type: Shovel
+ speedModifier: 0.75 # slower at digging than a full-sized shovel
- type: entity
name: plant bag
diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml
index 8d85a13429..216deefcd0 100644
--- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml
+++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml
@@ -489,9 +489,6 @@
id: Shovel
description: A large tool for digging and moving dirt.
components:
- - type: Tag
- tags:
- - Shovel
- type: Sprite
sprite: Objects/Tools/shovel.rsi
state: icon
@@ -509,6 +506,7 @@
Wood: 50
- type: StaticPrice
price: 25
+ - type: Shovel
- type: entity
parent: BaseItem
diff --git a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml
index 419bbce07d..cb6c64f264 100644
--- a/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml
+++ b/Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml
@@ -496,7 +496,7 @@
thresholds:
- trigger:
!type:DamageTrigger
- damage: 15
+ damage: 200 # discourage just beating the grave to break it open
behaviors:
- !type:PlaySoundBehavior
sound:
@@ -510,6 +510,15 @@
acts: [ "Destruction" ]
- type: Physics
bodyType: Static
+ - type: Grave
+ - type: ProRottingContainer
+ - type: EntityStorage
+ airtight: true
+ isCollidableWhenOpen: false
+ closeSound:
+ path: /Audio/Items/shovel_dig.ogg
+ openSound:
+ path: /Audio/Items/shovel_dig.ogg
- type: entity
parent: CrateWoodenGrave