]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Grave digging and decomposition (#23646)
authorthemias <89101928+themias@users.noreply.github.com>
Sun, 14 Jan 2024 16:47:31 +0000 (11:47 -0500)
committerGitHub <noreply@github.com>
Sun, 14 Jan 2024 16:47:31 +0000 (11:47 -0500)
* Grave digging and decomposition

* fix

* update based on review comments

* code review

* remove unused field

15 files changed:
Content.Server/Atmos/Rotting/RottingSystem.cs
Content.Server/Botany/Systems/PlantHolderSystem.cs
Content.Shared/Atmos/Rotting/ProRottingContainerComponent.cs [new file with mode: 0644]
Content.Shared/Burial/BurialSystem.cs [new file with mode: 0644]
Content.Shared/Burial/Components/GraveComponent.cs [new file with mode: 0644]
Content.Shared/Burial/Components/ShovelComponent.cs [new file with mode: 0644]
Content.Shared/Burial/GraveDiggingDoAfterEvent.cs [new file with mode: 0644]
Resources/Audio/Items/attributions.yml
Resources/Audio/Items/shovel_dig.ogg [new file with mode: 0644]
Resources/Locale/en-US/burial/burial.ftl [new file with mode: 0644]
Resources/Locale/en-US/tools/tool-qualities.ftl
Resources/Prototypes/Entities/Objects/Misc/utensils.yml
Resources/Prototypes/Entities/Objects/Specific/Hydroponics/tools.yml
Resources/Prototypes/Entities/Objects/Tools/tools.yml
Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml

index 8ddc1b0fb9054a526b7009d15d1357330fdc3c78..ff0ecaada41e38b09cc3ce4dcf4c3a136541641f 100644 (file)
@@ -185,6 +185,23 @@ public sealed class RottingSystem : EntitySystem
         args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f;
     }
 
+    /// <summary>
+    /// Is anything speeding up the decay?
+    /// e.g. buried in a grave
+    /// TODO: hot temperatures increase rot?
+    /// </summary>
+    /// <returns></returns>
+    private float GetRotRate(EntityUid uid)
+    {
+        if (_container.TryGetContainingContainer(uid, out var container) &&
+            TryComp<ProRottingContainerComponent>(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<RottingComponent>(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)
             {
index 0bef58a293ab99ebd0faa0ceb81e5e090eb47636..1484051c841ba88a59d8c4910be65a2c3c82812f 100644 (file)
@@ -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<ShovelComponent>(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 (file)
index 0000000..3f5c229
--- /dev/null
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Atmos.Rotting;
+
+/// <summary>
+/// Entities inside this container will rot at a faster pace, e.g. a grave
+/// </summary>
+[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 (file)
index 0000000..937784c
--- /dev/null
@@ -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<GraveComponent, InteractUsingEvent>(OnInteractUsing);
+        SubscribeLocalEvent<GraveComponent, ActivateInWorldEvent>(OnActivate);
+        SubscribeLocalEvent<GraveComponent, AfterInteractUsingEvent>(OnAfterInteractUsing, before: new[] { typeof(PlaceableSurfaceSystem) });
+        SubscribeLocalEvent<GraveComponent, GraveDiggingDoAfterEvent>(OnGraveDigging);
+
+        SubscribeLocalEvent<GraveComponent, StorageOpenAttemptEvent>(OnOpenAttempt);
+        SubscribeLocalEvent<GraveComponent, StorageCloseAttemptEvent>(OnCloseAttempt);
+        SubscribeLocalEvent<GraveComponent, StorageAfterOpenEvent>(OnAfterOpen);
+        SubscribeLocalEvent<GraveComponent, StorageAfterCloseEvent>(OnAfterClose);
+
+        SubscribeLocalEvent<GraveComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
+    }
+
+    private void OnInteractUsing(EntityUid uid, GraveComponent component, InteractUsingEvent args)
+    {
+        if (args.Handled || component.ActiveShovelDigging)
+            return;
+
+        if (TryComp<ShovelComponent>(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<ShovelComponent>(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 (file)
index 0000000..0910f98
--- /dev/null
@@ -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
+{
+    /// <summary>
+    /// How long it takes to dig this grave, without modifiers
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan DigDelay = TimeSpan.FromSeconds(15);
+
+    /// <summary>
+    /// Modifier if digging yourself out by hand if buried alive
+    /// TODO: Handle digging with bare hands in the tools system
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float DigOutByHandModifier = 0.1f;
+
+    /// <summary>
+    /// Sound to make when digging/filling this grave
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadOnly)]
+    public SoundPathSpecifier DigSound = new SoundPathSpecifier("/Audio/Items/shovel_dig.ogg")
+    {
+        Params = AudioParams.Default.WithLoop(true)
+    };
+
+    /// <summary>
+    /// Is this grave in the process of being dug/filled?
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadOnly)]
+    public bool DiggingComplete = false;
+
+    [DataField, ViewVariables(VVAccess.ReadOnly)]
+    public EntityUid? Stream;
+
+    /// <summary>
+    /// 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.)
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadOnly), AutoNetworkedField]
+    public bool ActiveShovelDigging;
+
+    /// <summary>
+    /// Tracks someone digging themself out of the grave
+    /// </summary>
+    [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 (file)
index 0000000..944e06d
--- /dev/null
@@ -0,0 +1,13 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Burial.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ShovelComponent : Component
+{
+    /// <summary>
+    /// The speed modifier for how fast this shovel will dig.
+    /// </summary>
+    [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 (file)
index 0000000..c1a85be
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Burial;
+
+[Serializable, NetSerializable]
+public sealed partial class GraveDiggingDoAfterEvent : SimpleDoAfterEvent
+{
+}
index 7e186cc076d0b6a37551cd59f7e07cce88e00c5a..13eff6e7025f88035f4ce15b48fc4a346ffb4186 100644 (file)
@@ -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 (file)
index 0000000..4997cdd
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 (file)
index 0000000..7c20e9d
--- /dev/null
@@ -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
index 331925b4b98115d6b176b110a8c09a504550b9e5..14e42390a766f17082bfb89c3f01e05886642090 100644 (file)
@@ -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
index 5f2ce47ba13d4215040665eddaf0f661c9890309..4ac05e1e4b11ee4eda600cf4ab4a97accc6217a0 100644 (file)
@@ -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
index d389a35fe07c53f9393f74c417123ae48bc052ed..bc03308d144bfc7ca6c6c2497aee968613eea7d1 100644 (file)
@@ -96,7 +96,6 @@
   components:
   - type: Tag
     tags:
-    - Shovel
     - BotanyShovel
   - type: Sprite
     sprite: Objects/Tools/Hydroponics/spade.rsi
         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
index 8d85a13429aa901979a347be8c0b062ee85b201c..216deefcd02d30e253c6953d190f0cc9a55f02b0 100644 (file)
   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
       Wood: 50
   - type: StaticPrice
     price: 25
+  - type: Shovel
 
 - type: entity
   parent: BaseItem
index 419bbce07d98c9b38a707523d74db9271e32f43f..cb6c64f264c15d5b5b26fd3bdc4390756560401c 100644 (file)
     thresholds:
     - trigger:
         !type:DamageTrigger
-        damage: 15
+        damage: 200 # discourage just beating the grave to break it open
       behaviors:
       - !type:PlaySoundBehavior
         sound:
         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