Grave digging and decomposition (#23646)

* Grave digging and decomposition

* fix

* update based on review comments

* code review

* remove unused field
This commit is contained in:
themias
2024-01-14 11:47:31 -05:00
committed by GitHub
parent 83f96a6766
commit fd7ff690b1
15 changed files with 315 additions and 9 deletions

View File

@@ -185,6 +185,23 @@ public sealed class RottingSystem : EntitySystem
args.Handled = component.CurrentTemperature < Atmospherics.T0C + 0.85f; 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) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -199,7 +216,7 @@ public sealed class RottingSystem : EntitySystem
if (IsRotten(uid) || !IsRotProgressing(uid, perishable)) if (IsRotten(uid) || !IsRotProgressing(uid, perishable))
continue; continue;
perishable.RotAccumulator += perishable.PerishUpdateRate; perishable.RotAccumulator += perishable.PerishUpdateRate * GetRotRate(uid);
if (perishable.RotAccumulator >= perishable.RotAfter) if (perishable.RotAccumulator >= perishable.RotAfter)
{ {
var rot = AddComp<RottingComponent>(uid); var rot = AddComp<RottingComponent>(uid);
@@ -216,7 +233,7 @@ public sealed class RottingSystem : EntitySystem
if (!IsRotProgressing(uid, perishable)) if (!IsRotProgressing(uid, perishable))
continue; continue;
rotting.TotalRotTime += rotting.RotUpdateRate; rotting.TotalRotTime += rotting.RotUpdateRate * GetRotRate(uid);
if (rotting.DealDamage) if (rotting.DealDamage)
{ {

View File

@@ -7,6 +7,7 @@ using Content.Server.Ghost.Roles.Components;
using Content.Server.Kitchen.Components; using Content.Server.Kitchen.Components;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Shared.Botany; using Content.Shared.Botany;
using Content.Shared.Burial.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Coordinates.Helpers; using Content.Shared.Coordinates.Helpers;
using Content.Shared.Examine; using Content.Shared.Examine;
@@ -191,7 +192,7 @@ public sealed class PlantHolderSystem : EntitySystem
return; return;
} }
if (_tagSystem.HasTag(args.Used, "Shovel")) if (HasComp<ShovelComponent>(args.Used))
{ {
if (component.Seed != null) if (component.Seed != null)
{ {

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared.Burial;
[Serializable, NetSerializable]
public sealed partial class GraveDiggingDoAfterEvent : SimpleDoAfterEvent
{
}

View File

@@ -87,3 +87,8 @@
license: "CC0-1.0" license: "CC0-1.0"
copyright: "User Hanbaal on freesound.org. Converted to ogg by TheShuEd" copyright: "User Hanbaal on freesound.org. Converted to ogg by TheShuEd"
source: "https://freesound.org/people/Hanbaal/sounds/178669/" 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"

Binary file not shown.

View File

@@ -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}!

View File

@@ -30,3 +30,6 @@ tool-quality-woodcutting-tool-name = Hatchet
tool-quality-rolling-name = Rolling tool-quality-rolling-name = Rolling
tool-quality-rolling-tool-name = Rolling Pin tool-quality-rolling-tool-name = Rolling Pin
tool-quality-digging-name = Digging
tool-quality-digging-tool-name = Shovel

View File

@@ -79,6 +79,8 @@
damage: damage:
types: types:
Blunt: 1 Blunt: 1
- type: Shovel
speedModifier: 0.1 # you can try
- type: entity - type: entity
parent: UtensilBasePlastic parent: UtensilBasePlastic
@@ -93,6 +95,8 @@
- type: Utensil - type: Utensil
types: types:
- Spoon - Spoon
- type: Shovel
speedModifier: 0.1 # you can try
- type: entity - type: entity
parent: UtensilBase parent: UtensilBase

View File

@@ -96,7 +96,6 @@
components: components:
- type: Tag - type: Tag
tags: tags:
- Shovel
- BotanyShovel - BotanyShovel
- type: Sprite - type: Sprite
sprite: Objects/Tools/Hydroponics/spade.rsi sprite: Objects/Tools/Hydroponics/spade.rsi
@@ -109,6 +108,8 @@
Piercing: 5 # I guess you can stab it into them? Piercing: 5 # I guess you can stab it into them?
- type: Item - type: Item
sprite: Objects/Tools/Hydroponics/spade.rsi sprite: Objects/Tools/Hydroponics/spade.rsi
- type: Shovel
speedModifier: 0.75 # slower at digging than a full-sized shovel
- type: entity - type: entity
name: plant bag name: plant bag

View File

@@ -489,9 +489,6 @@
id: Shovel id: Shovel
description: A large tool for digging and moving dirt. description: A large tool for digging and moving dirt.
components: components:
- type: Tag
tags:
- Shovel
- type: Sprite - type: Sprite
sprite: Objects/Tools/shovel.rsi sprite: Objects/Tools/shovel.rsi
state: icon state: icon
@@ -509,6 +506,7 @@
Wood: 50 Wood: 50
- type: StaticPrice - type: StaticPrice
price: 25 price: 25
- type: Shovel
- type: entity - type: entity
parent: BaseItem parent: BaseItem

View File

@@ -496,7 +496,7 @@
thresholds: thresholds:
- trigger: - trigger:
!type:DamageTrigger !type:DamageTrigger
damage: 15 damage: 200 # discourage just beating the grave to break it open
behaviors: behaviors:
- !type:PlaySoundBehavior - !type:PlaySoundBehavior
sound: sound:
@@ -510,6 +510,15 @@
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: Physics - type: Physics
bodyType: Static 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 - type: entity
parent: CrateWoodenGrave parent: CrateWoodenGrave