Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Content.Client/_Stalker/Anomaly/STAnomalySystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Content.Client._Stalker.Anomaly;

public sealed class STAnomalySystem : EntitySystem
{
}
83 changes: 83 additions & 0 deletions Content.Client/_Stalker/Anomaly/STAnomalyTipsOverlay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System.Numerics;
using Content.Shared._Stalker.Anomaly;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.Physics;

namespace Content.Client._Stalker.Anomaly;

public sealed class STAnomalyTipsOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entity = default!;

public override OverlaySpace Space => OverlaySpace.WorldSpace;

private readonly SpriteSystem _sprite;
private readonly TransformSystem _transform;
private readonly EntityLookupSystem _lookup;

public STAnomalyTipsOverlay()
{
IoCManager.InjectDependencies(this);

_sprite = _entity.System<SpriteSystem>();
_transform = _entity.System<TransformSystem>();
_lookup = _entity.System<EntityLookupSystem>();
}

protected override void Draw(in OverlayDrawArgs args)
{
var eye = args.Viewport.Eye;
var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero;
var viewport = args.WorldAABB;

var handle = args.WorldHandle;

const float scale = 1f;

var scaleMatrix = Matrix3x2.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3x2.CreateRotation(-(float)rotation.Theta);

var entities = _entity.EntityQueryEnumerator<STAnomalyTipsComponent, TransformComponent, FixturesComponent>();
while (entities.MoveNext(out _, out var tips, out var xform, out var fixtures))
{
if (xform.MapID != eye?.Position.MapId)
continue;

var worldPosition = _transform.GetWorldPosition(xform);
var worldMatrix = Matrix3x2.CreateTranslation(worldPosition);

var scaledWorld = Matrix3x2.Multiply(scaleMatrix, worldMatrix);
var matty = Matrix3x2.Multiply(rotationMatrix, scaledWorld);

handle.SetTransform(matty);

var size = fixtures.Fixtures.TryGetValue("fix1", out var fixture)
? (int) MathF.Round(MathF.Max(0, fixture.Shape.Radius - 0.5f))
: 0;

var texture = _sprite.GetFrame(tips.Icon, TimeSpan.Zero);
var color = Color.White.WithAlpha(tips.Visibility);

if (size == 0)
{
handle.DrawTexture(texture, tips.Offset, color);
continue;
}

for (var x = -size; x <= size; x++)
{
for (var y = -size; y <= size; y++)
{
if (!args.WorldAABB.Contains(worldPosition + new Vector2(x, y) + tips.Offset))
continue;

handle.DrawTexture(texture, new Vector2(x, y) + tips.Offset, color);
}
}
}

handle.SetTransform(Matrix3x2.Identity);
}
}
80 changes: 80 additions & 0 deletions Content.Client/_Stalker/Anomaly/STAnomalyTipsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Content.Shared._Stalker.Anomaly;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client._Stalker.Anomaly;

public sealed class STAnomalyTipsSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayMan = default!;
[Dependency] private readonly IPlayerManager _player = default!;

private STAnomalyTipsOverlay? _overlay;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<STAnomalyTipsViewingComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<STAnomalyTipsViewingComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<STAnomalyTipsViewingComponent, LocalPlayerAttachedEvent>(OnAttached);
SubscribeLocalEvent<STAnomalyTipsViewingComponent, LocalPlayerDetachedEvent>(OnDetached);
}

private void OnInit(Entity<STAnomalyTipsViewingComponent> entity, ref ComponentInit args)
{
if (_player.LocalEntity is null)
return;

if (_player.LocalEntity != entity)
return;

AddOverlay();
}

private void OnShutdown(Entity<STAnomalyTipsViewingComponent> entity, ref ComponentShutdown args)
{
if (_player.LocalEntity is null)
return;

if (_player.LocalEntity != entity)
return;

RemoveOverlay();
}

private void OnAttached(Entity<STAnomalyTipsViewingComponent> entity, ref LocalPlayerAttachedEvent args)
{
AddOverlay();
}

private void OnDetached(Entity<STAnomalyTipsViewingComponent> entity, ref LocalPlayerDetachedEvent args)
{
RemoveOverlay();
}

private void AddOverlay()
{
if (_overlay != null)
return;

_overlay = new STAnomalyTipsOverlay();
_overlayMan.AddOverlay(_overlay);
}

private void RemoveOverlay()
{
if (_overlay == null)
return;

_overlayMan.RemoveOverlay(_overlay);
_overlay = null;
}

public override void Shutdown()
{
base.Shutdown();
_overlayMan.RemoveOverlay<STAnomalyTipsOverlay>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using Content.Shared._Stalker.ZoneAnomaly.Audio;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Timing;

namespace Content.Client._Stalker.ZoneAnomaly.Audio;

/// <summary>
/// Handles distance-based volume scaling for zone anomaly proximity sounds.
/// Uses cooldown-based updates for performance efficiency.
/// </summary>
public sealed class ZoneAnomalyProximitySoundSystem : EntitySystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IGameTiming _timing = default!;

private static readonly AudioParams BaseParams = AudioParams.Default
.WithLoop(true)
.WithVolume(0f); // Start at 0, we'll control volume ourselves

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ZoneAnomalyProximitySoundComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ZoneAnomalyProximitySoundComponent, ComponentShutdown>(OnShutdown);
}

private void OnStartup(EntityUid uid, ZoneAnomalyProximitySoundComponent component, ComponentStartup args)
{
// Start playing the sound immediately (at initial calculated volume)
var audioParams = BaseParams.WithMaxDistance(component.MaxRange);

var stream = _audio.PlayEntity(component.Sound, Filter.Local(), uid, false, audioParams);
if (stream != null)
{
component.PlayingStream = stream.Value.Entity;
component.CurrentVolume = component.MinVolume;

// Set initial volume based on distance
UpdateVolumeForEntity(uid, component);
}
}

private void OnShutdown(EntityUid uid, ZoneAnomalyProximitySoundComponent component, ComponentShutdown args)
{
if (component.PlayingStream != null)
{
_audio.Stop(component.PlayingStream);
component.PlayingStream = null;
}
}

public override void Update(float frameTime)
{
base.Update(frameTime);

if (!_timing.IsFirstTimePredicted)
return;

var curTime = _timing.CurTime;

// Get player position once per update
var playerEntity = _player.LocalEntity;
if (playerEntity == null || !TryComp<TransformComponent>(playerEntity, out var playerXform))
return;

var playerPos = _transform.GetWorldPosition(playerXform);

var query = EntityQueryEnumerator<ZoneAnomalyProximitySoundComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var xform))
{
// Skip if not time to update yet
if (curTime < comp.NextUpdate)
continue;

comp.NextUpdate = curTime + TimeSpan.FromSeconds(comp.UpdateCooldown);

// Skip if no stream playing
if (comp.PlayingStream == null)
continue;

// Skip if on different map
if (xform.MapID != playerXform.MapID)
{
// Mute if on different map
if (comp.CurrentVolume > 0)
{
comp.CurrentVolume = 0;
_audio.SetVolume(comp.PlayingStream, SharedAudioSystem.GainToVolume(0f));
}
continue;
}

// Calculate distance
var entityPos = _transform.GetWorldPosition(xform);
var distance = (playerPos - entityPos).Length();

// Calculate target volume based on distance
float targetVolume;
if (distance >= comp.MaxRange)
{
targetVolume = 0f; // Out of range, silent
}
else
{
// Linear interpolation: MaxVolume at center, MinVolume at MaxRange
var t = 1f - (distance / comp.MaxRange);
targetVolume = comp.MinVolume + (comp.MaxVolume - comp.MinVolume) * t;
}

// Apply volume if changed significantly (avoid unnecessary audio calls)
if (MathF.Abs(targetVolume - comp.CurrentVolume) > 0.01f)
{
comp.CurrentVolume = targetVolume;
// Convert our 0-1 volume to the audio system's gain/volume
_audio.SetVolume(comp.PlayingStream, SharedAudioSystem.GainToVolume(targetVolume));
}
}
}

private void UpdateVolumeForEntity(EntityUid uid, ZoneAnomalyProximitySoundComponent component)
{
if (component.PlayingStream == null)
return;

var playerEntity = _player.LocalEntity;
if (playerEntity == null || !TryComp<TransformComponent>(playerEntity, out var playerXform))
return;

if (!TryComp<TransformComponent>(uid, out var xform))
return;

if (xform.MapID != playerXform.MapID)
{
_audio.SetVolume(component.PlayingStream, SharedAudioSystem.GainToVolume(0f));
return;
}

var playerPos = _transform.GetWorldPosition(playerXform);
var entityPos = _transform.GetWorldPosition(xform);
var distance = (playerPos - entityPos).Length();

float targetVolume;
if (distance >= component.MaxRange)
{
targetVolume = 0f;
}
else
{
var t = 1f - (distance / component.MaxRange);
targetVolume = component.MinVolume + (component.MaxVolume - component.MinVolume) * t;
}

component.CurrentVolume = targetVolume;
_audio.SetVolume(component.PlayingStream, SharedAudioSystem.GainToVolume(targetVolume));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Content.Client.Stealth;
using Content.Shared._Stalker.ZoneAnomaly.Effects.Components;
using Content.Shared.Stealth;
using Content.Shared.Stealth.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;

namespace Content.Client._Stalker.ZoneAnomaly.Effects.Systems;

public sealed class ZoneAnomalyStealthVisualsSystem : EntitySystem
{
[Dependency] private readonly SharedStealthSystem _stealth = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ZoneAnomalyEffectStealthComponent, BeforePostShaderRenderEvent>(OnShaderRender, after: [typeof(StealthSystem)]);
}

private void OnShaderRender(EntityUid uid, ZoneAnomalyEffectStealthComponent component, BeforePostShaderRenderEvent args)
{
// Override the blue tint from the standard stealth system
// Use grayscale instead so anomalies fade without blue hue
if (!TryComp<StealthComponent>(uid, out var stealth))
return;

var visibility = _stealth.GetVisibility(uid, stealth);
visibility = Math.Clamp(visibility, -1f, 1f);
visibility = MathF.Max(0, visibility);

// Set all channels to visibility (grayscale) instead of blue tint
args.Sprite.Color = new Color(visibility, visibility, visibility, 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Content.Shared.Damage;

namespace Content.Server._Stalker.Anomaly.Effects.Components;

[RegisterComponent]
public sealed partial class STAnomalyEffectDamageComponent : Component
{
[DataField]
public Dictionary<string, STAnomalyDamageEffectOptions> Options = new();
}

[Serializable, DataDefinition]
public partial struct STAnomalyDamageEffectOptions
{
[DataField]
public DamageSpecifier Damage;

[DataField]
public float Range = 1f;
}
Loading
Loading