diff --git a/Content.Client/Chasm/ChasmFallingVisualsSystem.cs b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs
new file mode 100644
index 0000000000..a22ea945ef
--- /dev/null
+++ b/Content.Client/Chasm/ChasmFallingVisualsSystem.cs
@@ -0,0 +1,74 @@
+using Content.Shared.Chasm;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.Animations;
+
+namespace Content.Client.Chasm;
+
+///
+/// Handles the falling animation for entities that fall into a chasm.
+///
+public sealed class ChasmFallingVisualsSystem : EntitySystem
+{
+ [Dependency] private readonly AnimationPlayerSystem _anim = default!;
+
+ private readonly string _chasmFallAnimationKey = "chasm_fall";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnComponentRemove);
+ }
+
+ private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ component.OriginalScale = sprite.Scale;
+
+ var player = EnsureComp(uid);
+ if (_anim.HasRunningAnimation(player, _chasmFallAnimationKey))
+ return;
+
+ _anim.Play(player, GetFallingAnimation(component), _chasmFallAnimationKey);
+ }
+
+ private void OnComponentRemove(EntityUid uid, ChasmFallingComponent component, ComponentRemove args)
+ {
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ var player = EnsureComp(uid);
+ if (_anim.HasRunningAnimation(player, _chasmFallAnimationKey))
+ _anim.Stop(player, _chasmFallAnimationKey);
+
+ sprite.Scale = component.OriginalScale;
+ }
+
+ private Animation GetFallingAnimation(ChasmFallingComponent component)
+ {
+ var length = component.AnimationTime;
+
+ return new Animation()
+ {
+ Length = length,
+ AnimationTracks =
+ {
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Scale),
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(component.OriginalScale, 0.0f),
+ new AnimationTrackProperty.KeyFrame(component.AnimationScale, length.Seconds),
+ },
+ InterpolationMode = AnimationInterpolationMode.Cubic
+ }
+ }
+ };
+ }
+}
diff --git a/Content.Shared/Chasm/ChasmComponent.cs b/Content.Shared/Chasm/ChasmComponent.cs
new file mode 100644
index 0000000000..6a36653129
--- /dev/null
+++ b/Content.Shared/Chasm/ChasmComponent.cs
@@ -0,0 +1,17 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Chasm;
+
+///
+/// Marks a component that will cause entities to fall into them on a step trigger activation
+///
+[NetworkedComponent, RegisterComponent, Access(typeof(ChasmSystem))]
+public sealed class ChasmComponent : Component
+{
+ ///
+ /// Sound that should be played when an entity falls into the chasm
+ ///
+ [DataField("fallingSound")]
+ public SoundSpecifier FallingSound = new SoundPathSpecifier("/Audio/Effects/falling.ogg");
+}
diff --git a/Content.Shared/Chasm/ChasmFallingComponent.cs b/Content.Shared/Chasm/ChasmFallingComponent.cs
new file mode 100644
index 0000000000..726c561ab9
--- /dev/null
+++ b/Content.Shared/Chasm/ChasmFallingComponent.cs
@@ -0,0 +1,37 @@
+using System.Numerics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Chasm;
+
+///
+/// Added to entities which have started falling into a chasm.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed class ChasmFallingComponent : Component
+{
+ ///
+ /// Time it should take for the falling animation (scaling down) to complete.
+ ///
+ [DataField("animationTime")]
+ public TimeSpan AnimationTime = TimeSpan.FromSeconds(1.5f);
+
+ ///
+ /// Time it should take in seconds for the entity to actually delete
+ ///
+ [DataField("deletionTime")]
+ public TimeSpan DeletionTime = TimeSpan.FromSeconds(1.8f);
+
+ [DataField("nextDeletionTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
+ public TimeSpan NextDeletionTime = TimeSpan.Zero;
+
+ ///
+ /// Original scale of the object so it can be restored if the component is removed in the middle of the animation
+ ///
+ public Vector2 OriginalScale = Vector2.Zero;
+
+ ///
+ /// Scale that the animation should bring entities to.
+ ///
+ public Vector2 AnimationScale = new Vector2(0.01f, 0.01f);
+}
diff --git a/Content.Shared/Chasm/ChasmSystem.cs b/Content.Shared/Chasm/ChasmSystem.cs
new file mode 100644
index 0000000000..123fa5b998
--- /dev/null
+++ b/Content.Shared/Chasm/ChasmSystem.cs
@@ -0,0 +1,78 @@
+using Content.Shared.ActionBlocker;
+using Content.Shared.Movement.Events;
+using Content.Shared.StepTrigger.Systems;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Chasm;
+
+///
+/// Handles making entities fall into chasms when stepped on.
+///
+public sealed class ChasmSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ActionBlockerSystem _blocker = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStepTriggered);
+ SubscribeLocalEvent(OnStepTriggerAttempt);
+ SubscribeLocalEvent(OnUnpaused);
+ SubscribeLocalEvent(OnUpdateCanMove);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ // don't predict queuedels on client
+ if (_net.IsClient)
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var chasm))
+ {
+ if (_timing.CurTime < chasm.NextDeletionTime)
+ continue;
+
+ QueueDel(uid);
+ }
+ }
+
+ private void OnStepTriggered(EntityUid uid, ChasmComponent component, ref StepTriggeredEvent args)
+ {
+ // already doomed
+ if (HasComp(args.Tripper))
+ return;
+
+ var falling = AddComp(args.Tripper);
+
+ falling.NextDeletionTime = _timing.CurTime + falling.DeletionTime;
+ _blocker.UpdateCanMove(args.Tripper);
+ _audio.PlayPredicted(component.FallingSound, uid, args.Tripper);
+ }
+
+ private void OnStepTriggerAttempt(EntityUid uid, ChasmComponent component, ref StepTriggerAttemptEvent args)
+ {
+ if (TryComp(args.Tripper, out var physics) && physics.BodyStatus == BodyStatus.InAir)
+ return;
+
+ args.Continue = true;
+ }
+
+ private void OnUnpaused(EntityUid uid, ChasmFallingComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextDeletionTime += args.PausedTime;
+ }
+
+ private void OnUpdateCanMove(EntityUid uid, ChasmFallingComponent component, UpdateCanMoveEvent args)
+ {
+ args.Cancel();
+ }
+}
diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs
index 0fb87ff375..d0abac8cbd 100644
--- a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs
+++ b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs
@@ -78,14 +78,15 @@ public sealed class StepTriggerSystem : EntitySystem
return false;
}
- private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerTransform, EntityUid otherUid, EntityQuery query)
+ private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerXform, EntityUid otherUid, EntityQuery query)
{
if (!query.TryGetComponent(otherUid, out var otherPhysics))
return;
+ var otherXform = Transform(otherUid);
// TODO: This shouldn't be calculating based on world AABBs.
- var ourAabb = _entityLookup.GetWorldAABB(uid, ownerTransform);
- var otherAabb = _entityLookup.GetWorldAABB(otherUid);
+ var ourAabb = _entityLookup.GetAABBNoContainer(uid, ownerXform.LocalPosition, ownerXform.LocalRotation);
+ var otherAabb = _entityLookup.GetAABBNoContainer(otherUid, otherXform.LocalPosition, otherXform.LocalRotation);
if (!ourAabb.Intersects(otherAabb))
{
@@ -96,9 +97,13 @@ public sealed class StepTriggerSystem : EntitySystem
return;
}
+ // max 'area of enclosure' between the two aabbs
+ // this is hard to explain
+ var intersect = Box2.Area(otherAabb.Intersect(ourAabb));
+ var ratio = Math.Max(intersect / Box2.Area(otherAabb), intersect / Box2.Area(ourAabb));
if (otherPhysics.LinearVelocity.Length() < component.RequiredTriggerSpeed
|| component.CurrentlySteppedOn.Contains(otherUid)
- || otherAabb.IntersectPercentage(ourAabb) < component.IntersectRatio
+ || ratio < component.IntersectRatio
|| !CanTrigger(uid, otherUid, component))
{
return;
diff --git a/Resources/Audio/Effects/attributions.yml b/Resources/Audio/Effects/attributions.yml
index 85e898997d..77a3e0cd0d 100644
--- a/Resources/Audio/Effects/attributions.yml
+++ b/Resources/Audio/Effects/attributions.yml
@@ -46,4 +46,9 @@
- files: ["fence_rattle1.ogg", "fence_rattle2.ogg", "fence_rattle3.ogg"]
license: "CC0-1.0"
copyright: "Taken from MWsfx via freesound.org and cropped + mixed from stereo to mono."
- source: "https://freesound.org/people/MWsfx/sounds/575388/"
\ No newline at end of file
+ source: "https://freesound.org/people/MWsfx/sounds/575388/"
+
+- files: ["falling.ogg"]
+ license: "CC0-1.0"
+ copyright: "Taken from MATRIXXX_ via freesound.org and mixed from stereo to mono."
+ source: "https://freesound.org/people/MATRIXXX_/sounds/415990/"
\ No newline at end of file
diff --git a/Resources/Audio/Effects/falling.ogg b/Resources/Audio/Effects/falling.ogg
new file mode 100644
index 0000000000..5894f41467
Binary files /dev/null and b/Resources/Audio/Effects/falling.ogg differ
diff --git a/Resources/Prototypes/Entities/Tiles/chasm.yml b/Resources/Prototypes/Entities/Tiles/chasm.yml
new file mode 100644
index 0000000000..2347532587
--- /dev/null
+++ b/Resources/Prototypes/Entities/Tiles/chasm.yml
@@ -0,0 +1,47 @@
+- type: entity
+ id: FloorChasmEntity
+ name: chasm
+ description: You can't even see the bottom.
+ placement:
+ mode: SnapgridCenter
+ snap:
+ - Wall
+ components:
+ - type: Chasm
+ - type: StepTrigger
+ requiredTriggeredSpeed: 0
+ intersectRatio: 0.4
+ blacklist:
+ tags:
+ - Catwalk
+ - type: Transform
+ anchored: true
+ - type: Clickable
+ - type: Sprite
+ sprite: Tiles/Planet/chasm.rsi
+ drawdepth: BelowFloor
+ layers:
+ - state: chasm
+ - type: Icon
+ sprite: Tiles/Planet/chasm.rsi
+ state: full
+ - type: IconSmooth
+ key: chasm
+ base: chasm
+ - type: Physics
+ bodyType: Static
+ - type: Fixtures
+ fixtures:
+ fix1:
+ shape:
+ !type:PhysShapeAabb
+ bounds: "-0.5,-0.5,0.5,0.5"
+ layer:
+ - WallLayer
+ mask:
+ - ItemMask
+ density: 1000
+ hard: false
+ - type: Tag
+ tags:
+ - HideContextMenu
diff --git a/Resources/Prototypes/Procedural/biome_templates.yml b/Resources/Prototypes/Procedural/biome_templates.yml
index 0a2b98cee2..9ce5f31b5c 100644
--- a/Resources/Prototypes/Procedural/biome_templates.yml
+++ b/Resources/Prototypes/Procedural/biome_templates.yml
@@ -310,6 +310,20 @@
cellularReturnType: Distance2
entities:
- WallRockBasalt
+ # chasm time
+ - !type:BiomeEntityLayer
+ allowedTiles:
+ - FloorBasalt
+ threshold: 0.6
+ noise:
+ seed: 3
+ frequency: 0.02
+ fractalType: FBm
+ octaves: 5
+ lacunarity: 2
+ gain: 0.4
+ entities:
+ - FloorChasmEntity
- !type:BiomeDummyLayer
id: Loot
# Fill basalt
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm.png
new file mode 100644
index 0000000000..b73869b5f9
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm0.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm0.png
new file mode 100644
index 0000000000..42935e7398
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm0.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm1.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm1.png
new file mode 100644
index 0000000000..bc916d5198
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm1.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm2.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm2.png
new file mode 100644
index 0000000000..42935e7398
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm2.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm3.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm3.png
new file mode 100644
index 0000000000..bc916d5198
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm3.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm4.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm4.png
new file mode 100644
index 0000000000..e1c77dc40c
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm4.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm5.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm5.png
new file mode 100644
index 0000000000..72a234e4ed
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm5.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm6.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm6.png
new file mode 100644
index 0000000000..e1c77dc40c
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm6.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/chasm7.png b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm7.png
new file mode 100644
index 0000000000..0858c19f05
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/chasm7.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/full.png b/Resources/Textures/Tiles/Planet/chasm.rsi/full.png
new file mode 100644
index 0000000000..6d83d9e0b0
Binary files /dev/null and b/Resources/Textures/Tiles/Planet/chasm.rsi/full.png differ
diff --git a/Resources/Textures/Tiles/Planet/chasm.rsi/meta.json b/Resources/Textures/Tiles/Planet/chasm.rsi/meta.json
new file mode 100644
index 0000000000..185ed3fad9
--- /dev/null
+++ b/Resources/Textures/Tiles/Planet/chasm.rsi/meta.json
@@ -0,0 +1,49 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "Taken from https://github.com/tgstation/tgstation/tree/f116442e34fe3e941a1df474bb57bb410dd177a3/icons/turf and modified",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "full"
+ },
+ {
+ "name": "chasm0",
+ "directions": 4
+ },
+ {
+ "name": "chasm1",
+ "directions": 4
+ },
+ {
+ "name": "chasm2",
+ "directions": 4
+ },
+ {
+ "name": "chasm3",
+ "directions": 4
+ },
+ {
+ "name": "chasm4",
+ "directions": 4
+ },
+ {
+ "name": "chasm5",
+ "directions": 4
+ },
+ {
+ "name": "chasm6",
+ "directions": 4
+ },
+ {
+ "name": "chasm7",
+ "directions": 4
+ },
+ {
+ "name": "chasm"
+ }
+ ]
+}
\ No newline at end of file