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