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