Greatly reduced how much the stealth shimmer changes over time for ninja and stealth boxes (#35575)

* Added ShimmerFrequency to stealth component and shader

* Reduced stealth shimmer frequency for ninja and stealth boxes

* Undid pointless change.

* cleanup

---------

Co-authored-by: Pok245 <113675512+Pok27@users.noreply.github.com>
This commit is contained in:
Matt Idzik 2026-07-17 11:27:19 -05:00 • committed by GitHub
parent 8aa95fab84
commit c32ccf787b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 34 additions and 31 deletions

View file

@ -92,6 +92,7 @@ public sealed partial class StealthSystem : SharedStealthSystem
_shader.SetParameter("reference", reference);
_shader.SetParameter("visibility", visibility);
_shader.SetParameter("shimmer_frequency", component.ShimmerFrequency);
visibility = MathF.Max(0, visibility);
_sprite.SetColor((uid, args.Sprite), new Color(visibility, visibility, 1, 1));

View file

@ -3,6 +3,7 @@ using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Stealth.Components;
/// <summary>
/// Add this component to an entity that you want to be cloaked.
/// It overlays a shader on the entity to give them an invisibility cloaked effect.
@ -16,25 +17,25 @@ public sealed partial class StealthComponent : Component
/// <summary>
/// Whether or not the stealth effect should currently be applied.
/// </summary>
[DataField("enabled")]
[DataField]
public bool Enabled = true;
/// <summary>
/// The creature will continue invisible at death.
/// </summary>
[DataField("enabledOnDeath")]
[DataField]
public bool EnabledOnDeath = true;
/// <summary>
/// Whether or not the entity previously had an interaction outline prior to cloaking.
/// </summary>
[DataField("hadOutline")]
[DataField]
public bool HadOutline;
/// <summary>
/// Minimum visibility before the entity becomes unexaminable (and thus no longer appears on context menus).
/// </summary>
[DataField("examineThreshold")]
[DataField]
public float ExamineThreshold = 0.5f;
/// <summary>
@ -43,48 +44,47 @@ public sealed partial class StealthComponent : Component
/// get the actual current visibility, use <see cref="SharedStealthSystem.GetVisibility(EntityUid, StealthComponent?)"/>
/// If you don't have anything else updating the stealth, this will just stay at a constant value, which can be useful.
/// </summary>
[DataField("lastVisibility")]
[DataField]
[Access(typeof(SharedStealthSystem), Other = AccessPermissions.None)]
public float LastVisibility = 1;
/// <summary>
/// Time at which <see cref="LastVisibility"/> was set. Null implies the entity is currently paused and not
/// accumulating any visibility change.
/// </summary>
[DataField("lastUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))]
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan? LastUpdated;
/// <summary>
/// Minimum visibility. Note that the visual effect caps out at -1, but this value is allowed to be larger or smaller.
/// </summary>
[DataField("minVisibility")]
[DataField]
public float MinVisibility = -1f;
/// <summary>
/// Maximum visibility. Note that the visual effect caps out at +1, but this value is allowed to be larger or smaller.
/// </summary>
[DataField("maxVisibility")]
[DataField]
public float MaxVisibility = 1.5f;
/// <summary>
/// Localization string for how you'd like to describe this effect.
/// The frequency of the shimmer effect. 0 disables the shimmering, leaving only a static distortion.
/// </summary>
[DataField("examinedDesc")]
[DataField]
public float ShimmerFrequency = 1f;
/// <summary>
/// Localization string for how you'd like to describe this effect.
/// </summary>
[DataField]
public string ExaminedDesc = "stealth-visual-effect";
}
[Serializable, NetSerializable]
public sealed class StealthComponentState : ComponentState
public sealed class StealthComponentState(float stealthLevel, TimeSpan? lastUpdated, bool enabled, float shimmerFrequency) : ComponentState
{
public readonly float Visibility;
public readonly TimeSpan? LastUpdated;
public readonly bool Enabled;
public StealthComponentState(float stealthLevel, TimeSpan? lastUpdated, bool enabled)
{
Visibility = stealthLevel;
LastUpdated = lastUpdated;
Enabled = enabled;
}
public readonly float Visibility = stealthLevel;
public readonly TimeSpan? LastUpdated = lastUpdated;
public readonly bool Enabled = enabled;
public readonly float ShimmerFrequency = shimmerFrequency;
}

View file

@ -98,7 +98,7 @@ public abstract partial class SharedStealthSystem : EntitySystem
private void OnStealthGetState(EntityUid uid, StealthComponent component, ref ComponentGetState args)
{
args.State = new StealthComponentState(component.LastVisibility, component.LastUpdated, component.Enabled);
args.State = new StealthComponentState(component.LastVisibility, component.LastUpdated, component.Enabled, component.ShimmerFrequency);
}
private void OnStealthHandleState(EntityUid uid, StealthComponent component, ref ComponentHandleState args)
@ -109,6 +109,7 @@ public abstract partial class SharedStealthSystem : EntitySystem
SetEnabled(uid, cast.Enabled, component);
component.LastVisibility = cast.Visibility;
component.LastUpdated = cast.LastUpdated;
component.ShimmerFrequency = cast.ShimmerFrequency;
}
private void OnMove(EntityUid uid, StealthOnMoveComponent component, ref MoveEvent args)

View file

@ -179,6 +179,7 @@
- type: Stealth
minVisibility: 0.1
lastVisibility: 0.1
shimmerFrequency: 0.05
- type: PowerCellDraw
drawRate: 1.8 # 200 seconds on the default cell
- type: ToggleCellDraw

View file

@ -70,6 +70,7 @@
quiet: true
- type: Stealth
hadOutline: true
shimmerFrequency: 0.05
- type: StealthOnMove
passiveVisibilityRate: -1 # very useful for going around the station concealed, if you start jitterstrafing you get seen
movementVisibilityRate: 0.20

View file

@ -865,6 +865,7 @@
components:
- type: Stealth
hadOutline: true
shimmerFrequency: 0.05
- type: StealthOnMove
passiveVisibilityRate: -0.10
movementVisibilityRate: 0.10

View file

@ -2,6 +2,7 @@ light_mode unshaded;
uniform sampler2D SCREEN_TEXTURE;
uniform highp float visibility; // number between -1 and 1
uniform highp float shimmer_frequency;
uniform mediump vec2 reference;
const mediump float time_scale = 0.25;
@ -17,20 +18,17 @@ void fragment() {
// get distortion magnitude. hand crafted from a random jumble of trig functions
highp vec2 coords = (FRAGCOORD.xy + reference) * distance_scale;
highp float w = sin(TIME + (coords.x + coords.y + 2.0*sin(TIME*time_scale) * sin(TIME*time_scale + coords.x - coords.y)) );
highp float t = TIME * shimmer_frequency;
highp float w = sin(t + (coords.x + coords.y + 2.0 * sin(t * time_scale) * sin(t * time_scale + coords.x - coords.y)));
// visualize distortion via:
// COLOR = vec4(w,w,w,1.0);
// COLOR = vec4(w, w, w, 1.0);
w *= (3.0 + visibility * 2.0);
highp vec4 background = zTextureSpec(SCREEN_TEXTURE, ( FRAGCOORD.xy + vec2(w) ) * SCREEN_PIXEL_SIZE );
highp vec4 background = zTextureSpec(SCREEN_TEXTURE, (FRAGCOORD.xy + vec2(w)) * SCREEN_PIXEL_SIZE);
lowp float alpha;
if (visibility>0.0)
alpha = sprite.a * visibility;
else
alpha = 0.0;
lowp float alpha = sprite.a * max(visibility, 0.0);
COLOR.xyz = mix(background.xyz, sprite.xyz, alpha);
COLOR.a = 1.0;