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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