Lavaland chasms (#19154)
74
Content.Client/Chasm/ChasmFallingVisualsSystem.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using Content.Shared.Chasm;
|
||||||
|
using Robust.Client.Animations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Animations;
|
||||||
|
|
||||||
|
namespace Content.Client.Chasm;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles the falling animation for entities that fall into a chasm.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ChasmFallingVisualsSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly AnimationPlayerSystem _anim = default!;
|
||||||
|
|
||||||
|
private readonly string _chasmFallAnimationKey = "chasm_fall";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<ChasmFallingComponent, ComponentInit>(OnComponentInit);
|
||||||
|
SubscribeLocalEvent<ChasmFallingComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args)
|
||||||
|
{
|
||||||
|
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.OriginalScale = sprite.Scale;
|
||||||
|
|
||||||
|
var player = EnsureComp<AnimationPlayerComponent>(uid);
|
||||||
|
if (_anim.HasRunningAnimation(player, _chasmFallAnimationKey))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_anim.Play(player, GetFallingAnimation(component), _chasmFallAnimationKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnComponentRemove(EntityUid uid, ChasmFallingComponent component, ComponentRemove args)
|
||||||
|
{
|
||||||
|
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var player = EnsureComp<AnimationPlayerComponent>(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Content.Shared/Chasm/ChasmComponent.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chasm;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a component that will cause entities to fall into them on a step trigger activation
|
||||||
|
/// </summary>
|
||||||
|
[NetworkedComponent, RegisterComponent, Access(typeof(ChasmSystem))]
|
||||||
|
public sealed class ChasmComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sound that should be played when an entity falls into the chasm
|
||||||
|
/// </summary>
|
||||||
|
[DataField("fallingSound")]
|
||||||
|
public SoundSpecifier FallingSound = new SoundPathSpecifier("/Audio/Effects/falling.ogg");
|
||||||
|
}
|
||||||
37
Content.Shared/Chasm/ChasmFallingComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||||
|
|
||||||
|
namespace Content.Shared.Chasm;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Added to entities which have started falling into a chasm.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
public sealed class ChasmFallingComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Time it should take for the falling animation (scaling down) to complete.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("animationTime")]
|
||||||
|
public TimeSpan AnimationTime = TimeSpan.FromSeconds(1.5f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time it should take in seconds for the entity to actually delete
|
||||||
|
/// </summary>
|
||||||
|
[DataField("deletionTime")]
|
||||||
|
public TimeSpan DeletionTime = TimeSpan.FromSeconds(1.8f);
|
||||||
|
|
||||||
|
[DataField("nextDeletionTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||||
|
public TimeSpan NextDeletionTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Original scale of the object so it can be restored if the component is removed in the middle of the animation
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 OriginalScale = Vector2.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale that the animation should bring entities to.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 AnimationScale = new Vector2(0.01f, 0.01f);
|
||||||
|
}
|
||||||
78
Content.Shared/Chasm/ChasmSystem.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles making entities fall into chasms when stepped on.
|
||||||
|
/// </summary>
|
||||||
|
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<ChasmComponent, StepTriggeredEvent>(OnStepTriggered);
|
||||||
|
SubscribeLocalEvent<ChasmComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
|
||||||
|
SubscribeLocalEvent<ChasmFallingComponent, EntityUnpausedEvent>(OnUnpaused);
|
||||||
|
SubscribeLocalEvent<ChasmFallingComponent, UpdateCanMoveEvent>(OnUpdateCanMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
// don't predict queuedels on client
|
||||||
|
if (_net.IsClient)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<ChasmFallingComponent>();
|
||||||
|
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<ChasmFallingComponent>(args.Tripper))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var falling = AddComp<ChasmFallingComponent>(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<PhysicsComponent>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,14 +78,15 @@ public sealed class StepTriggerSystem : EntitySystem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerTransform, EntityUid otherUid, EntityQuery<PhysicsComponent> query)
|
private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerXform, EntityUid otherUid, EntityQuery<PhysicsComponent> query)
|
||||||
{
|
{
|
||||||
if (!query.TryGetComponent(otherUid, out var otherPhysics))
|
if (!query.TryGetComponent(otherUid, out var otherPhysics))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var otherXform = Transform(otherUid);
|
||||||
// TODO: This shouldn't be calculating based on world AABBs.
|
// TODO: This shouldn't be calculating based on world AABBs.
|
||||||
var ourAabb = _entityLookup.GetWorldAABB(uid, ownerTransform);
|
var ourAabb = _entityLookup.GetAABBNoContainer(uid, ownerXform.LocalPosition, ownerXform.LocalRotation);
|
||||||
var otherAabb = _entityLookup.GetWorldAABB(otherUid);
|
var otherAabb = _entityLookup.GetAABBNoContainer(otherUid, otherXform.LocalPosition, otherXform.LocalRotation);
|
||||||
|
|
||||||
if (!ourAabb.Intersects(otherAabb))
|
if (!ourAabb.Intersects(otherAabb))
|
||||||
{
|
{
|
||||||
@@ -96,9 +97,13 @@ public sealed class StepTriggerSystem : EntitySystem
|
|||||||
return;
|
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
|
if (otherPhysics.LinearVelocity.Length() < component.RequiredTriggerSpeed
|
||||||
|| component.CurrentlySteppedOn.Contains(otherUid)
|
|| component.CurrentlySteppedOn.Contains(otherUid)
|
||||||
|| otherAabb.IntersectPercentage(ourAabb) < component.IntersectRatio
|
|| ratio < component.IntersectRatio
|
||||||
|| !CanTrigger(uid, otherUid, component))
|
|| !CanTrigger(uid, otherUid, component))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -46,4 +46,9 @@
|
|||||||
- files: ["fence_rattle1.ogg", "fence_rattle2.ogg", "fence_rattle3.ogg"]
|
- files: ["fence_rattle1.ogg", "fence_rattle2.ogg", "fence_rattle3.ogg"]
|
||||||
license: "CC0-1.0"
|
license: "CC0-1.0"
|
||||||
copyright: "Taken from MWsfx via freesound.org and cropped + mixed from stereo to mono."
|
copyright: "Taken from MWsfx via freesound.org and cropped + mixed from stereo to mono."
|
||||||
source: "https://freesound.org/people/MWsfx/sounds/575388/"
|
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/"
|
||||||
BIN
Resources/Audio/Effects/falling.ogg
Normal file
47
Resources/Prototypes/Entities/Tiles/chasm.yml
Normal file
@@ -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
|
||||||
@@ -310,6 +310,20 @@
|
|||||||
cellularReturnType: Distance2
|
cellularReturnType: Distance2
|
||||||
entities:
|
entities:
|
||||||
- WallRockBasalt
|
- 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
|
- !type:BiomeDummyLayer
|
||||||
id: Loot
|
id: Loot
|
||||||
# Fill basalt
|
# Fill basalt
|
||||||
|
|||||||
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm0.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm1.png
Normal file
|
After Width: | Height: | Size: 923 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm3.png
Normal file
|
After Width: | Height: | Size: 923 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm4.png
Normal file
|
After Width: | Height: | Size: 914 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm5.png
Normal file
|
After Width: | Height: | Size: 422 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm6.png
Normal file
|
After Width: | Height: | Size: 914 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/chasm7.png
Normal file
|
After Width: | Height: | Size: 96 B |
BIN
Resources/Textures/Tiles/Planet/chasm.rsi/full.png
Normal file
|
After Width: | Height: | Size: 443 B |
49
Resources/Textures/Tiles/Planet/chasm.rsi/meta.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||