feat: Fancy Screenshake from Ephemeral Space (#5503)

* cherry-pick: Screenshake (EphemeralSpace/ephemeral-space#627)

* commit your shit at some point maybe

* y

* who knows

* rename?

* debugging  + shit api

* i hope no one fucks my ass

* erm

* p

* replace with shit that makes way more sense

* stuff

* pretty sure this is just fine

* actual fix noise shit

* uhhh hmaybe if i feel like it wrong then later

* explosion shake

* grav shake

* gun shake

* idk

* projectile melee

Co-authored-by: DisposableCrewmember42 <disposablecrewmember42@proton.me>

* tweak: tune recoil on weapons

* docs: add _ES license info to LEGAL.md

* fix: screenshake on spawn fix from ss14Starlight/space-station-14#3664

Co-authored-by: neomoth <69372252+neomoth@users.noreply.github.com>

* feat: add client cvar to disable screenshake

* feat: add screenshake toggle to accessibility settings

* fix: upmerge compatibility

* docs: properly document cvar

* docs: add missing import comments

---------

Co-authored-by: mirrorcult <mirrorcult@pm.me>
Co-authored-by: neomoth <69372252+neomoth@users.noreply.github.com>
This commit is contained in:
DisposableCrewmember42
2026-05-15 23:32:40 +02:00
committed by GitHub
co-authored by neomoth mirrorcult
parent 3a05ef885c
commit 9146c59ecf
27 changed files with 569 additions and 11 deletions
+36 -5
View File
@@ -36,6 +36,9 @@ public sealed class EyeLerpingSystem : EntitySystem
UpdatesAfter.Add(typeof(TransformSystem)); UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem)); UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem));
UpdatesBefore.Add(typeof(SharedEyeSystem)); UpdatesBefore.Add(typeof(SharedEyeSystem));
// ES START
UpdatesBefore.Add(typeof(EyeSystem));
// ES END
UpdatesOutsidePrediction = true; UpdatesOutsidePrediction = true;
} }
@@ -68,6 +71,11 @@ public sealed class EyeLerpingSystem : EntitySystem
{ {
_eye.SetRotation(uid, lerpInfo.TargetRotation, component); _eye.SetRotation(uid, lerpInfo.TargetRotation, component);
_eye.SetZoom(uid, lerpInfo.TargetZoom, component); _eye.SetZoom(uid, lerpInfo.TargetZoom, component);
// Starlight begin - fix ES Screenshake triggering on spawn
if (TryComp<ContentEyeComponent>(uid, out var contentEye))
contentEye.BaseRotation = lerpInfo.TargetRotation;
// Starlight end
} }
} }
@@ -175,9 +183,15 @@ public sealed class EyeLerpingSystem : EntitySystem
{ {
var tickFraction = (float) _gameTiming.TickFraction / ushort.MaxValue; var tickFraction = (float) _gameTiming.TickFraction / ushort.MaxValue;
const double lerpMinimum = 0.00001; const double lerpMinimum = 0.00001;
var query = AllEntityQuery<LerpingEyeComponent, EyeComponent, TransformComponent>(); // ES START
// add contenteye bc we only want to modify the fuckin base rotation
var query = AllEntityQuery<LerpingEyeComponent, EyeComponent, ContentEyeComponent, TransformComponent>();
// ES END
while (query.MoveNext(out var entity, out var lerpInfo, out var eye, out var xform)) // ES START
// contenteye
while (query.MoveNext(out var entity, out var lerpInfo, out var eye, out var contentEye, out var xform))
// ES END
{ {
// Handle zoom // Handle zoom
var zoomDiff = Vector2.Lerp(lerpInfo.LastZoom, lerpInfo.TargetZoom, tickFraction); var zoomDiff = Vector2.Lerp(lerpInfo.LastZoom, lerpInfo.TargetZoom, tickFraction);
@@ -191,6 +205,14 @@ public sealed class EyeLerpingSystem : EntitySystem
_eye.SetZoom(entity, zoomDiff, eye); _eye.SetZoom(entity, zoomDiff, eye);
} }
// ES START
// UHHHHHHHHHHHh
// shit is fucked idk
// we need to alter baserotation not regular eye rotation and also i added baserotation because uhh
// there was no good way to separate 'how the eye should be oriented because of the grid/turning/etc' and 'the eye rotating on top of that'
// had to add it
// ES END
// Handle Rotation // Handle Rotation
TryComp<InputMoverComponent>(entity, out var mover); TryComp<InputMoverComponent>(entity, out var mover);
@@ -199,7 +221,10 @@ public sealed class EyeLerpingSystem : EntitySystem
if (!NeedsLerp(mover)) if (!NeedsLerp(mover))
{ {
_eye.SetRotation(entity, lerpInfo.TargetRotation, eye); // ES START
// contenteye
contentEye.BaseRotation = lerpInfo.TargetRotation;
// ES END
continue; continue;
} }
@@ -207,11 +232,17 @@ public sealed class EyeLerpingSystem : EntitySystem
if (Math.Abs(shortest.Theta) < lerpMinimum) if (Math.Abs(shortest.Theta) < lerpMinimum)
{ {
_eye.SetRotation(entity, lerpInfo.TargetRotation, eye); // ES START
// contenteye
contentEye.BaseRotation = lerpInfo.TargetRotation;
// ES END
continue; continue;
} }
_eye.SetRotation(entity, shortest * tickFraction + lerpInfo.LastRotation, eye); // ES START
// contenteye
contentEye.BaseRotation = shortest * tickFraction + lerpInfo.LastRotation;
// ES END
} }
} }
} }
@@ -60,6 +60,9 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent)) while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent))
{ {
UpdateEyeOffset((entity, eyeComponent)); UpdateEyeOffset((entity, eyeComponent));
// ES START
UpdateEyeRotation((entity, eyeComponent));
// ES END
} }
} }
@@ -71,6 +74,9 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent)) while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent))
{ {
UpdateEyeOffset((entity, eyeComponent)); UpdateEyeOffset((entity, eyeComponent));
// ES START
UpdateEyeRotation((entity, eyeComponent));
// ES END
} }
} }
} }
@@ -7,6 +7,7 @@
<Label Text="{Loc 'ui-options-accessability-header-visuals'}" <Label Text="{Loc 'ui-options-accessability-header-visuals'}"
StyleClasses="LabelKeyText"/> StyleClasses="LabelKeyText"/>
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" /> <CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
<CheckBox Name="DisableScreenshakeCheckBox" Text="{Loc 'ui-options-es-screen-shake-toggle'}" /> <!-- DeltaV - ES Screenshake Port -->
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" /> <CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" /> <CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
<ui:OptionSlider Name="ScreenShakeIntensitySlider" Title="{Loc 'ui-options-screen-shake-intensity'}" /> <ui:OptionSlider Name="ScreenShakeIntensitySlider" Title="{Loc 'ui-options-screen-shake-intensity'}" />
@@ -1,3 +1,4 @@
using Content.Shared._DV.CCVars; // DeltaV - ES Screenshake Port
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
@@ -15,6 +16,7 @@ public sealed partial class AccessibilityTab : Control
Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox); Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox);
Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox); Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox);
Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox); Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox);
Control.AddOptionCheckBox(DCCVars.EsScreenshakeDisabled, DisableScreenshakeCheckBox); // DeltaV - ES Screenshake Port
Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider); Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider);
Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider); Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
Control.AddOptionPercentSlider(CCVars.SpeechBubbleTextOpacity, SpeechBubbleTextOpacitySlider); Control.AddOptionPercentSlider(CCVars.SpeechBubbleTextOpacity, SpeechBubbleTextOpacitySlider);
@@ -280,7 +280,10 @@ public sealed partial class GunSystem : SharedGunSystem
if (!Timing.IsFirstTimePredicted || user == null || recoil == Vector2.Zero || recoilScalar == 0) if (!Timing.IsFirstTimePredicted || user == null || recoil == Vector2.Zero || recoilScalar == 0)
return; return;
_recoil.KickCamera(user.Value, recoil.Normalized() * 0.5f * recoilScalar); // ES START
// reduce recoil even further
_recoil.KickCamera(user.Value, recoil.Normalized() * 0.13f * recoilScalar);
// ES END
} }
protected override void Popup(string message, EntityUid? uid, EntityUid? user) protected override void Popup(string message, EntityUid? uid, EntityUid? user)
+99
View File
@@ -0,0 +1,99 @@
using System.Numerics;
using Content.Shared._DV.CCVars;
using Content.Shared._ES.Camera;
using Content.Shared.Camera;
using Robust.Shared.Configuration;
using Robust.Shared.Noise;
using Robust.Shared.Timing;
namespace Content.Client._ES;
// DeltaV - Anything not marked DeltaV is Ephemeral Space code moved from SharedESScreenshakeSystem
public sealed class ESScreenshakeSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _config = default!; // DeltaV
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedESScreenshakeSystem _shared = default!;
private bool _disabled; // DeltaV
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ESScreenshakeComponent, GetEyeOffsetEvent>(OnGetEyeOffset);
SubscribeLocalEvent<ESScreenshakeComponent, ESGetEyeRotationEvent>(OnGetEyeRotation);
_config.OnValueChanged(DCCVars.EsScreenshakeDisabled, OnDisabledChanged, true); // DeltaV
}
private void OnDisabledChanged(bool obj)
{
_disabled = obj;
}
private void OnGetEyeOffset(Entity<ESScreenshakeComponent> ent, ref GetEyeOffsetEvent args)
{
if (!TryComp<EyeComponent>(ent, out var eye) || _disabled) // DeltaV - check if disabled
return;
var noise = new FastNoiseLite(67);
noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
var accumulatedOffset = Vector2.Zero;
var maxOffset = new Vector2(0.15f, 0.15f);
foreach (var command in ent.Comp.Commands)
{
if (command.Translational == null)
continue;
var trauma =
_shared.CalculateTraumaValueForCurrentTime(command.Translational, command.Start);
if (trauma <= 0)
continue;
noise.SetFrequency(command.Translational.Frequency);
// using the starst c ommand for y pos kinda doesnt work in the case where multiple shakes get sent at the same time
// and the shakes are identical otherwise. but like dont do that or something idk
var offsetX = (maxOffset.X * trauma) * noise.GetNoise((float)_timing.RealTime.TotalMilliseconds,
(float)command.Start.TotalMilliseconds);
noise.SetSeed(68);
var offsetY = (maxOffset.Y * trauma) * noise.GetNoise((float)_timing.RealTime.TotalMilliseconds,
(float)command.Start.TotalMilliseconds);
noise.SetSeed(67);
accumulatedOffset += new Vector2(offsetX, offsetY);
}
args.Offset += accumulatedOffset;
}
private void OnGetEyeRotation(Entity<ESScreenshakeComponent> ent, ref ESGetEyeRotationEvent args)
{
if (!TryComp<EyeComponent>(ent, out var eye) || _disabled) // DeltaV - check if disabled
return;
var noise = new FastNoiseLite(67 + 420); // Epic bacon
noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
// 20deg max
var accumulatedAngle = Angle.Zero;
var maxAngleDegrees = 20f;
foreach (var command in ent.Comp.Commands)
{
if (command.Rotational == null)
continue;
var trauma =
_shared.CalculateTraumaValueForCurrentTime(command.Rotational, command.Start);
if (trauma <= 0)
continue;
noise.SetFrequency(command.Rotational.Frequency);
var angle = (maxAngleDegrees * trauma) * noise.GetNoise((float)_timing.RealTime.TotalMilliseconds, (float)command.Start.TotalMilliseconds);
accumulatedAngle += Angle.FromDegrees(angle);
}
// TODO ughhh this shit breaks with something idk
args.Rotation += accumulatedAngle;
}
}
@@ -1,5 +1,6 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Weapons.Ranged.Systems; using Content.Server.Weapons.Ranged.Systems;
using Content.Shared._ES.Camera; // ES - Screenshake
using Content.Shared.Camera; using Content.Shared.Camera;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
@@ -15,6 +16,9 @@ namespace Content.Server.Damage.Systems;
public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem
{ {
// ES START
[Dependency] private readonly SharedESScreenshakeSystem _shake = default!;
// ES END
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly GunSystem _guns = default!; [Dependency] private readonly GunSystem _guns = default!;
[Dependency] private readonly Shared.Damage.Systems.DamageableSystem _damageable = default!; [Dependency] private readonly Shared.Damage.Systems.DamageableSystem _damageable = default!;
@@ -48,7 +52,12 @@ public sealed class DamageOtherOnHitSystem : SharedDamageOtherOnHitSystem
if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f) if (TryComp<PhysicsComponent>(uid, out var body) && body.LinearVelocity.LengthSquared() > 0f)
{ {
var direction = body.LinearVelocity.Normalized(); var direction = body.LinearVelocity.Normalized();
_sharedCameraRecoil.KickCamera(args.Target, direction); // ES START
// lower recoil + shake
_sharedCameraRecoil.KickCamera(args.Target, direction * 0.1f);
var otherHitShake = new ESScreenshakeParameters() { Trauma = 0.35f, DecayRate = 1.4f, Frequency = 0.014f };
_shake.Screenshake(args.Target, otherHitShake, null);
// ES END
} }
} }
} }
@@ -7,6 +7,7 @@ using Content.Server.Destructible;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding; using Content.Server.NPC.Pathfinding;
using Content.Server.Station.Systems; // DeltaV - Admin QOL using Content.Server.Station.Systems; // DeltaV - Admin QOL
using Content.Shared._ES.Camera; // ES - Screenshake
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Content.Shared.Camera; using Content.Shared.Camera;
using Content.Shared.CCVar; using Content.Shared.CCVar;
@@ -35,6 +36,10 @@ namespace Content.Server.Explosion.EntitySystems;
public sealed partial class ExplosionSystem : SharedExplosionSystem public sealed partial class ExplosionSystem : SharedExplosionSystem
{ {
// ES START
[Dependency] private readonly SharedESScreenshakeSystem _shake = default!;
// ES END
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
@@ -378,7 +383,9 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity); var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
// camera shake // camera shake
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity); // ES START
// CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
// ES END
//For whatever bloody reason, sound system requires ENTITY coordinates. //For whatever bloody reason, sound system requires ENTITY coordinates.
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos); var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
@@ -409,6 +416,13 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
? queued.Proto.SmallSoundFar ? queued.Proto.SmallSoundFar
: queued.Proto.SoundFar; : queued.Proto.SoundFar;
// ES START
var farTranslationShake = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
_shake.Screenshake(filter, farTranslationShake, null);
// ES END
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params); _audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
return new Explosion(this, return new Explosion(this,
+10 -1
View File
@@ -3,6 +3,7 @@ using Content.Server.Administration.Logs;
using Content.Server.Destructible; using Content.Server.Destructible;
using Content.Server.Effects; using Content.Server.Effects;
using Content.Server.Weapons.Ranged.Systems; using Content.Server.Weapons.Ranged.Systems;
using Content.Shared._ES.Camera; // ES - Screenshake
using Content.Shared.Camera; using Content.Shared.Camera;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
@@ -17,6 +18,9 @@ namespace Content.Server.Projectiles;
public sealed class ProjectileSystem : SharedProjectileSystem public sealed class ProjectileSystem : SharedProjectileSystem
{ {
// ES START
[Dependency] private readonly SharedESScreenshakeSystem _shake = default!;
// ES END
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly ColorFlashEffectSystem _color = default!; [Dependency] private readonly ColorFlashEffectSystem _color = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!;
@@ -81,8 +85,13 @@ public sealed class ProjectileSystem : SharedProjectileSystem
{ {
_guns.PlayImpactSound(target, damage, component.SoundHit, component.ForceSound); _guns.PlayImpactSound(target, damage, component.SoundHit, component.ForceSound);
// ES START
// shake + lower recoil
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
if (!args.OurBody.LinearVelocity.IsLengthZero()) if (!args.OurBody.LinearVelocity.IsLengthZero())
_sharedCameraRecoil.KickCamera(target, args.OurBody.LinearVelocity.Normalized()); _sharedCameraRecoil.KickCamera(target, args.OurBody.LinearVelocity.Normalized() * 0.08f);
_shake.Screenshake(target, otherShakeTranslation, null);
// ES END
} }
if (component.DeleteOnCollide && component.ProjectileSpent) if (component.DeleteOnCollide && component.ProjectileSpent)
@@ -1,7 +1,17 @@
using Content.Shared._ES.Camera; // ES
using Content.Shared.GameTicking; // ES
using Robust.Shared.Player; // ES
using Robust.Shared.Timing; // ES
namespace Content.Shared.Gravity; namespace Content.Shared.Gravity;
public abstract partial class SharedGravitySystem public abstract partial class SharedGravitySystem
{ {
// ES START
[Dependency] private readonly SharedGameTicker _ticker = default!;
[Dependency] private readonly SharedESScreenshakeSystem _shake = default!;
// ES END
protected const float GravityKick = 100.0f; protected const float GravityKick = 100.0f;
protected const float ShakeCooldown = 0.2f; protected const float ShakeCooldown = 0.2f;
@@ -37,6 +47,20 @@ public abstract partial class SharedGravitySystem
if (!Resolve(uid, ref gravity, false)) if (!Resolve(uid, ref gravity, false))
return; return;
// ES START
// do not shake grid if the round just started
// i did not want to have to think this logic through more. this is the simplest solution i could think of
if (Timing.CurTime - _ticker.RoundStartTimeSpan < TimeSpan.FromSeconds(30))
return;
// ES SCREENSHAKE LOGIC
// instead of poopass camera kick
var translation = new ESScreenshakeParameters() { Trauma = 0.8f, DecayRate = 0.04f, Frequency = 0.015f };
var filter = Filter.BroadcastGrid(uid);
_shake.Screenshake(filter, translation, null);
return;
// ES END
if (!TryComp<GravityShakeComponent>(uid, out var shake)) if (!TryComp<GravityShakeComponent>(uid, out var shake))
{ {
shake = AddComp<GravityShakeComponent>(uid); shake = AddComp<GravityShakeComponent>(uid);
@@ -7,7 +7,10 @@ namespace Content.Shared.Movement.Components;
/// <summary> /// <summary>
/// Holds SS14 eye data not relevant for engine, e.g. lerp targets. /// Holds SS14 eye data not relevant for engine, e.g. lerp targets.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedContentEyeSystem))] // ES START
// STOP adding ACCESS to shit for NO REASON
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
// ES END
public sealed partial class ContentEyeComponent : Component public sealed partial class ContentEyeComponent : Component
{ {
/// <summary> /// <summary>
@@ -21,4 +24,13 @@ public sealed partial class ContentEyeComponent : Component
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("maxZoom"), AutoNetworkedField] [ViewVariables(VVAccess.ReadWrite), DataField("maxZoom"), AutoNetworkedField]
public Vector2 MaxZoom = Vector2.One; public Vector2 MaxZoom = Vector2.One;
// ES START
// uhh fuckk
/// <summary>
/// Base rotation of this eye, because of grid movement/turning/etc. Modified by eye lerp.
/// </summary>
[DataField]
public Angle BaseRotation = Angle.Zero;
// ES END
} }
@@ -1,4 +1,5 @@
using System.Numerics; using System.Numerics;
using Content.Shared._ES.Camera; // ES - Screenshake
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Administration.Managers; using Content.Shared.Administration.Managers;
using Content.Shared.Camera; using Content.Shared.Camera;
@@ -160,6 +161,20 @@ public abstract class SharedContentEyeSystem : EntitySystem
_eye.SetOffset(eye, ev.Offset + evRelayed.Offset, eye); _eye.SetOffset(eye, ev.Offset + evRelayed.Offset, eye);
} }
// ES START
public void UpdateEyeRotation(Entity<EyeComponent> eye)
{
var baseAngle = Angle.Zero;
if (TryComp<ContentEyeComponent>(eye, out var contentEye))
baseAngle = contentEye.BaseRotation;
var ev = new ESGetEyeRotationEvent();
RaiseLocalEvent(eye, ref ev);
_eye.SetRotation(eye, baseAngle + ev.Rotation, eye);
}
// ES END
public void UpdatePvsScale(EntityUid uid, ContentEyeComponent? contentEye = null, EyeComponent? eye = null) public void UpdatePvsScale(EntityUid uid, ContentEyeComponent? contentEye = null, EyeComponent? eye = null)
{ {
if (!Resolve(uid, ref contentEye) || !Resolve(uid, ref eye)) if (!Resolve(uid, ref contentEye) || !Resolve(uid, ref eye))
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Shared._ES.Camera; // ES - Screenshake
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Actions.Events; using Content.Shared.Actions.Events;
using Content.Shared.Administration.Components; using Content.Shared.Administration.Components;
@@ -67,6 +68,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
[Dependency] private readonly SharedStaminaSystem _stamina = default!; [Dependency] private readonly SharedStaminaSystem _stamina = default!;
[Dependency] private readonly DamageExamineSystem _damageExamine = default!; [Dependency] private readonly DamageExamineSystem _damageExamine = default!;
// ES START
[Dependency] private readonly SharedESScreenshakeSystem _shake = default!;
// ES END
private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque); private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque);
@@ -594,6 +598,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
if (damageResult.GetTotal() > FixedPoint2.Zero) if (damageResult.GetTotal() > FixedPoint2.Zero)
{ {
DoDamageEffect(targets, user, targetXform); DoDamageEffect(targets, user, targetXform);
// ES START
// dog shit copy plaste but thats melee for you
var userShakeRotation = new ESScreenshakeParameters()
{ Trauma = 0.08f, DecayRate = 1.0f, Frequency = 0.009f };
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
_shake.Screenshake(user, null, userShakeRotation);
foreach (var shakeTarget in targets)
{
_shake.Screenshake(shakeTarget, otherShakeTranslation, null);
}
// ES END
} }
} }
@@ -759,6 +775,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
_meleeSound.PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component); _meleeSound.PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component);
} }
// ES START
// dog shit copy plaste but thats melee for you
var userShakeRotation = new ESScreenshakeParameters()
{ Trauma = 0.08f, DecayRate = 1.0f, Frequency = 0.009f };
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
_shake.Screenshake(user, null, userShakeRotation);
foreach (var shakeTarget in targets)
{
_shake.Screenshake(shakeTarget, otherShakeTranslation, null);
}
// ES END
if (appliedDamage.GetTotal() > FixedPoint2.Zero) if (appliedDamage.GetTotal() > FixedPoint2.Zero)
{ {
DoDamageEffect(targets, user, Transform(targets[0])); DoDamageEffect(targets, user, Transform(targets[0]));
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using Content.Shared._ES.Camera; // ES
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
@@ -41,6 +42,9 @@ namespace Content.Shared.Weapons.Ranged.Systems;
public abstract partial class SharedGunSystem : EntitySystem public abstract partial class SharedGunSystem : EntitySystem
{ {
// ES START
[Dependency] private SharedESScreenshakeSystem _shake = default!;
// ES END
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly INetManager _netManager = default!;
@@ -423,6 +427,12 @@ public abstract partial class SharedGunSystem : EntitySystem
var shotEv = new GunShotEvent(user, ev.Ammo); var shotEv = new GunShotEvent(user, ev.Ammo);
RaiseLocalEvent(gun, ref shotEv); RaiseLocalEvent(gun, ref shotEv);
// ES START
// this is a suspicious place to do this but whatever.
var gunShakeRotation = new ESScreenshakeParameters() { Trauma = 0.085f * gun.Comp.CameraRecoilScalarModified, DecayRate = 1.2f, Frequency = 0.008f};
_shake.Screenshake(user, null, gunShakeRotation);
// ES END
if (!userImpulse || !TryComp<PhysicsComponent>(user, out var userPhysics)) if (!userImpulse || !TryComp<PhysicsComponent>(user, out var userPhysics))
return true; return true;
+7
View File
@@ -301,4 +301,11 @@ public sealed partial class DCCVars
/// </summary> /// </summary>
public static readonly CVarDef<int> CosmicCultFinaleDelaySeconds = public static readonly CVarDef<int> CosmicCultFinaleDelaySeconds =
CVarDef.Create("cosmiccult.extra_entropy_for_finale", 1, CVar.SERVER); CVarDef.Create("cosmiccult.extra_entropy_for_finale", 1, CVar.SERVER);
/// <summary>
/// Whether the screenshake ported from ES should be disabled.
/// False by default, so enabled. Players can change this in accessiblity settings.
/// </summary>
public static readonly CVarDef<bool> EsScreenshakeDisabled =
CVarDef.Create("deltav.es_screenshake.disabled", false, CVar.CLIENTONLY | CVar.ARCHIVE);
} }
@@ -0,0 +1,15 @@
using Content.Shared.Camera;
using Content.Shared.Movement.Systems;
namespace Content.Shared._ES.Camera;
/// <summary>
/// Raised directed by-ref when <see cref="SharedContentEyeSystem.UpdateEyeRotation"/> is called.
/// Should be subscribed to by any systems that want to modify an entity's eye rotation,
/// so that they do not override each other.
/// </summary>
/// <remarks>
/// Counterpart of <see cref="GetEyeOffsetEvent"/>, but for rotation, to use for screenshake.
/// </remarks>
[ByRefEvent]
public record struct ESGetEyeRotationEvent(Angle Rotation);
@@ -0,0 +1,63 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
namespace Content.Shared._ES.Camera;
/// <summary>
/// Marks an entity which is actively screenshaking because of a screenshake command being given.
/// </summary>
/// <remarks>
/// This doesn't mark an entity which *can* screenshake--all entities can, by default, as long as a client is controlling them.
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ESScreenshakeComponent : Component
{
/// <summary>
/// A set of screenshake commands which this entity is currently processing.
/// Trauma is 0..1, with 0 being no shake and 1 being maximum shake.
/// </summary>
/// <remarks>
/// This is a set, because order doesn't matter, and we don't want to accidentally readd the same command twice.
/// </remarks>
[DataField, AutoNetworkedField]
public HashSet<ESScreenshakeCommand> Commands = new();
public override bool SendOnlyToOwner => true;
}
/// <summary>
/// Represents a single screenshake command. These are stored and networked on <see cref="ESScreenshakeComponent"/>,
/// and the client that controls that entity will use the trauma values in each command, and their start time,
/// to calculate multipliers on the current eye offset & rotation modifiers.
/// </summary>
/// <param name="Translational">Parameters of translational screenshake (offset-based)</param>
/// <param name="Rotational">Parameters of rotational screenshake (rotation-based)</param>
/// <param name="Start">Time this screenshake command was added.</param>
/// <param name="CalculatedEnd">The end time for this command, calculated from trauma, decay rate, and start time.</param>
[DataRecord, Serializable, NetSerializable]
public partial record ESScreenshakeCommand(ESScreenshakeParameters? Translational, ESScreenshakeParameters? Rotational, TimeSpan Start, TimeSpan CalculatedEnd);
/// <summary>
/// Represents the parameters of a single instance of screenshake, which may apply to translational or rotational shake.
/// </summary>
[DataDefinition, Serializable, NetSerializable]
public partial record ESScreenshakeParameters()
{
/// <summary>
/// Strength of the shake.
/// </summary>
[DataField(required: true)]
public float Trauma = 0f;
/// <summary>
/// How fast the shake decays.
/// </summary>
[DataField]
public float DecayRate = 1.2f;
/// <summary>
/// How frantically the shake oscillates.
/// </summary>
[DataField]
public float Frequency = 0.01f;
};
@@ -0,0 +1,138 @@
using System.Linq;
using System.Numerics;
using Content.Shared.Camera;
using Robust.Shared.Noise;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared._ES.Camera;
/// <summary>
/// Handles sending rotational or translational screenshake to an entity, managing the screenshake commands
/// of every entity currently screenshaking, and setting offset/rotation when updated
/// </summary>
// DeltaV - renamed from ESScreenshakeSystem to SharedESScreenshakeSystem
public sealed class SharedESScreenshakeSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
#region Internal
public override void Initialize()
{
base.Initialize();
// SubscribeLocalEvent<ESScreenshakeComponent, ESGetEyeRotationEvent>(OnGetEyeRotation); // DeltaV - Moved to Client system
// SubscribeLocalEvent<ESScreenshakeComponent, GetEyeOffsetEvent>(OnGetEyeOffset); // DeltaV - Moved to Client system
SubscribeLocalEvent<ESScreenshakeComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// TODO mirror might make sense to never remove individual commands and only remove the comp if theyre all > calculatedend instead.
var shakers = EntityQueryEnumerator<EyeComponent, ESScreenshakeComponent>();
while (shakers.MoveNext(out var ent, out var eye, out var shake))
{
if (shake.Commands.Count == 0)
{
RemCompDeferred<ESScreenshakeComponent>(ent);
continue;
}
foreach (var command in shake.Commands.ToList())
{
// handle removing old commands
if (_timing.CurTime < command.CalculatedEnd)
continue;
shake.Commands.Remove(command);
Dirty(ent, shake);
}
}
}
private void OnEntityUnpaused(Entity<ESScreenshakeComponent> ent, ref EntityUnpausedEvent args)
{
// rebuild screenshake commands but with offset times
var newSet = new HashSet<ESScreenshakeCommand>();
foreach (var command in ent.Comp.Commands)
{
var newCommand = command with
{
CalculatedEnd = command.CalculatedEnd + args.PausedTime,
Start = command.Start + args.PausedTime,
};
newSet.Add(newCommand);
}
ent.Comp.Commands = newSet;
Dirty(ent);
}
/// <summary>
/// Calculates when both traumas will be at least = 0 given the decay rate and start time.
/// </summary>
private TimeSpan CalculateEndTimeForCommand(Entity<ESScreenshakeComponent> ent, ESScreenshakeParameters? translation, ESScreenshakeParameters? rotation, TimeSpan start)
{
// https://www.desmos.com/calculator/optip8eucx
var secsUntilRotationalEnd = rotation != null ? MathF.Sqrt(rotation.Trauma / rotation.DecayRate) : 0f;
var secsUntilTranslationalEnd = translation != null ? MathF.Sqrt(translation.Trauma / translation.DecayRate) : 0f;
var larger = secsUntilTranslationalEnd >= secsUntilRotationalEnd
? secsUntilTranslationalEnd
: secsUntilRotationalEnd;
return start + TimeSpan.FromSeconds(larger);
}
/// <summary>
/// Gets the trauma value for the current time, given the decay rate and start time.
/// </summary>
// DeltaV - make public
public float CalculateTraumaValueForCurrentTime(ESScreenshakeParameters parameters, TimeSpan start)
{
var timeDiff = _timing.CurTime - start;
// erm
if (timeDiff < TimeSpan.Zero)
return 0f;
// trauma decays quadratically with seconds passed
// https://www.desmos.com/calculator/optip8eucx
var totalSecsSquared = (float) (timeDiff.TotalSeconds * timeDiff.TotalSeconds);
return -totalSecsSquared * parameters.DecayRate + parameters.Trauma;
}
#endregion
#region Public API
public void Screenshake(EntityUid uid, ESScreenshakeParameters? translation, ESScreenshakeParameters? rotation)
{
if (!HasComp<EyeComponent>(uid))
return;
var comp = EnsureComp<ESScreenshakeComponent>(uid);
var time = _timing.CurTime;
var end = CalculateEndTimeForCommand((uid, comp), translation, rotation, time);
var command = new ESScreenshakeCommand(translation, rotation, time, end);
comp.Commands.Add(command);
Dirty(uid, comp);
}
public void Screenshake(Filter filter, ESScreenshakeParameters? translation, ESScreenshakeParameters? rotation)
{
foreach (var player in filter.Recipients)
{
if (player.AttachedEntity is {} ent)
Screenshake(ent, translation, rotation);
}
}
#endregion
}
@@ -0,0 +1,17 @@
using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.GameStates;
namespace Content.Shared._ES.Camera.Trigger;
/// <summary>
/// Screenshakes the user on trigger.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ESScreenshakeUserOnTriggerComponent : BaseXOnTriggerComponent
{
[DataField]
public ESScreenshakeParameters? Translation;
[DataField]
public ESScreenshakeParameters? Rotation;
}
@@ -0,0 +1,17 @@
using Content.Shared.Trigger;
namespace Content.Shared._ES.Camera.Trigger;
public sealed class ESScreenshakeUserOnTriggerSystem : XOnTriggerSystem<ESScreenshakeUserOnTriggerComponent>
{
[Dependency] private readonly SharedESScreenshakeSystem _screenShake = default!;
protected override void OnTrigger(Entity<ESScreenshakeUserOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
{
if (args.User == null)
return;
_screenShake.Screenshake(args.User.Value, ent.Comp.Translation, ent.Comp.Rotation);
args.Handled = true;
}
}
+2
View File
@@ -27,6 +27,8 @@ For MIT this means you **must not** remove the copy of the MIT license from any
- Code taken from [the Starlight project](https://github.com/ss14Starlight/space-station-14) in the `_Starlight` namespace was taken under [their custom MIT-like license](/LICENSE-Starlight.txt) and sublicensed to AGPLv3. - Code taken from [the Starlight project](https://github.com/ss14Starlight/space-station-14) in the `_Starlight` namespace was taken under [their custom MIT-like license](/LICENSE-Starlight.txt) and sublicensed to AGPLv3.
- Code from [Ephemeral Space](https://github.com/EphemeralSpace/ephemeral-space) in the `_ES` namespace was taken under [the MIT license](/LICENSE-MIT.txt).
Assets have distinct licenses in `attributions.yml` and `meta.json` files which must be followed individually. Assets have distinct licenses in `attributions.yml` and `meta.json` files which must be followed individually.
## Warranty ## Warranty
@@ -18,3 +18,7 @@ ui-options-function-open-c-help = Open curator help
ui-options-delta-tips = Tips ui-options-delta-tips = Tips
ui-options-delta-disable-tips = Disable tips ui-options-delta-disable-tips = Disable tips
ui-options-delta-reset-tips = Reset all seen tips ui-options-delta-reset-tips = Reset all seen tips
## DeltaV Screenshake Accessiblity Options
ui-options-screen-shake-intensity = Camera kick intensity
ui-options-es-screen-shake-toggle = Disable Screenshake
@@ -363,7 +363,8 @@ ui-options-accessability-header-content = Content
ui-options-enable-color-name = Add colors to character names ui-options-enable-color-name = Add colors to character names
ui-options-colorblind-friendly = Colorblind friendly mode ui-options-colorblind-friendly = Colorblind friendly mode
ui-options-reduced-motion = Reduce motion of visual effects ui-options-reduced-motion = Reduce motion of visual effects
ui-options-screen-shake-intensity = Screen shake intensity # DeltaV - Moved to _DV file
# ui-options-screen-shake-intensity = Screen shake intensity
ui-options-chat-window-opacity = Chat window opacity ui-options-chat-window-opacity = Chat window opacity
ui-options-speech-bubble-text-opacity = Speech bubble text opacity ui-options-speech-bubble-text-opacity = Speech bubble text opacity
@@ -15,6 +15,7 @@
- type: AmmoCounter - type: AmmoCounter
- type: Gun - type: Gun
fireRate: 2 fireRate: 2
cameraRecoilScalar: 0.5 # DeltaV - most laser guns have lower screenshake
selectedMode: SemiAuto selectedMode: SemiAuto
availableModes: availableModes:
- SemiAuto - SemiAuto
@@ -260,6 +261,7 @@
shader: unshaded shader: unshaded
- type: Gun - type: Gun
projectileSpeed: 10 projectileSpeed: 10
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Effects/Lightning/lightningshock.ogg path: /Audio/Effects/Lightning/lightningshock.ogg
params: params:
@@ -471,6 +473,7 @@
sprite: Objects/Weapons/Guns/Battery/laser_cannon.rsi sprite: Objects/Weapons/Guns/Battery/laser_cannon.rsi
- type: Gun - type: Gun
fireRate: .7 # Delta V - was 1.5 fireRate: .7 # Delta V - was 1.5
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
@@ -507,6 +510,7 @@
map: ["enum.GunVisualLayers.Base"] map: ["enum.GunVisualLayers.Base"]
- type: Gun - type: Gun
fireRate: 0.5 fireRate: 0.5
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Weapons/emitter.ogg path: /Audio/Weapons/emitter.ogg
params: params:
@@ -539,6 +543,7 @@
- type: Clothing - type: Clothing
sprite: Objects/Weapons/Guns/Battery/xray.rsi sprite: Objects/Weapons/Guns/Battery/xray.rsi
- type: Gun - type: Gun
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/laser3.ogg path: /Audio/Weapons/Guns/Gunshots/laser3.ogg
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
@@ -579,6 +584,7 @@
- type: StaticPrice - type: StaticPrice
price: 100 price: 100
- type: Gun - type: Gun
cameraRecoilScalar: 0.25 # DeltaV
fireRate: 2 fireRate: 2
projectileSpeed: 35 # any higher and this causes issues in lag projectileSpeed: 35 # any higher and this causes issues in lag
soundGunshot: soundGunshot:
@@ -691,6 +697,7 @@
- type: Gun - type: Gun
fireRate: 0.2 # DeltaV - No taser spam, was 0.5 fireRate: 0.2 # DeltaV - No taser spam, was 0.5
resetOnHandSelected: false # DeltaV - Makes it so you dont have to wait 10 seconds to shoot a taser after pulling it out resetOnHandSelected: false # DeltaV - Makes it so you dont have to wait 10 seconds to shoot a taser after pulling it out
cameraRecoilScalar: 0.25 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/taser.ogg path: /Audio/Weapons/Guns/Gunshots/taser.ogg
projectileSpeed: 25 projectileSpeed: 25
@@ -927,6 +934,7 @@
sprite: Objects/Weapons/Guns/Battery/energy_shotgun.rsi sprite: Objects/Weapons/Guns/Battery/energy_shotgun.rsi
- type: Gun - type: Gun
fireRate: 2 fireRate: 2
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
@@ -983,6 +991,7 @@
- type: StealTarget - type: StealTarget
stealGroup: HoSSidearm # Delta V - to work along side the X-01 stealGroup: HoSSidearm # Delta V - to work along side the X-01
- type: Gun - type: Gun
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
@@ -23,6 +23,7 @@
selectedMode: SemiAuto selectedMode: SemiAuto
availableModes: availableModes:
- SemiAuto - SemiAuto
cameraRecoilScalar: 1.5 # DeltaV - higher screenshake on snipers
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/sniper.ogg path: /Audio/Weapons/Guns/Gunshots/sniper.ogg
- type: BallisticAmmoProvider - type: BallisticAmmoProvider
@@ -124,6 +124,7 @@
availableModes: availableModes:
- SemiAuto - SemiAuto
- FullAuto - FullAuto
cameraRecoilScalar: 1.0 # DeltaV
soundGunshot: soundGunshot:
path: /Audio/Effects/thunk.ogg path: /Audio/Effects/thunk.ogg
soundEmpty: soundEmpty:
@@ -0,0 +1,20 @@
- type: entity
name: screenshake debug doohickey
parent: BaseItem
id: ESDebugScreenshake
description: ohhh my head horts
suffix: ES, DEBUG
components:
- type: Sprite
sprite: Objects/Weapons/Melee/debug.rsi
state: icon
- type: ESScreenshakeUserOnTrigger
# VV to have an effect
translation:
trauma: 0.4
decayRate: 1.4
frequency: 0.04
- type: TriggerOnUse
- type: Item
size: Tiny
sprite: Objects/Weapons/Melee/debug.rsi