Skip to content
Open
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
61 changes: 60 additions & 1 deletion Content.Client/Ghost/GhostSystem.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
using System;
using Content.Client.Movement.Systems;
using Content.Shared.Actions;
using Content.Shared.Ghost;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;

namespace Content.Client.Ghost
{
public sealed class GhostSystem : SharedGhostSystem
{
private const string VisualObserverPrototypePrefix = "MobObserverVisual";
private const string CompositeGhostShaderId = "GhostCompositeTint";
private const float VisualObserverAlphaMultiplier = 0.50f;

[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly PointLightSystem _pointLightSystem = default!;
[Dependency] private readonly ContentEyeSystem _contentEye = default!;

private readonly Dictionary<EntityUid, ShaderInstance> _compositeGhostShaders = new();

public int AvailableGhostRoleCount { get; private set; }

private bool _ghostVisibility = true;
Expand Down Expand Up @@ -72,7 +82,10 @@ public override void Initialize()
private void OnStartup(EntityUid uid, GhostComponent component, ComponentStartup args)
{
if (TryComp(uid, out SpriteComponent? sprite))
{
sprite.Visible = GhostVisibility || uid == _playerManager.LocalEntity;
ApplyGhostVisuals(uid, component, sprite);
}
}

private void OnToggleLighting(EntityUid uid, EyeComponent component, ToggleLightingActionEvent args)
Expand Down Expand Up @@ -129,6 +142,8 @@ private void OnToggleGhosts(EntityUid uid, GhostComponent component, ToggleGhost

private void OnGhostRemove(EntityUid uid, GhostComponent component, ComponentRemove args)
{
RemoveGhostCompositeShader(uid);

_actions.RemoveAction(uid, component.ToggleLightingActionEntity);
_actions.RemoveAction(uid, component.ToggleFoVActionEntity);
_actions.RemoveAction(uid, component.ToggleGhostsActionEntity);
Expand All @@ -150,7 +165,7 @@ private void OnGhostPlayerAttach(EntityUid uid, GhostComponent component, LocalP
private void OnGhostState(EntityUid uid, GhostComponent component, ref AfterAutoHandleStateEvent args)
{
if (TryComp<SpriteComponent>(uid, out var sprite))
sprite.LayerSetColor(0, component.color);
ApplyGhostVisuals(uid, component, sprite);

if (uid != _playerManager.LocalEntity)
return;
Expand Down Expand Up @@ -211,5 +226,49 @@ public void ReturnToRound()
var msg = new GhostReturnToRoundRequest();
RaiseNetworkEvent(msg);
}

private void ApplyGhostVisuals(EntityUid uid, GhostComponent component, SpriteComponent sprite)
{
if (TryComp(uid, out MetaDataComponent? metaData) &&
metaData.EntityPrototype?.ID.StartsWith(VisualObserverPrototypePrefix, StringComparison.Ordinal) == true)
{
var shader = EnsureGhostCompositeShader(uid, sprite);
shader.SetParameter("ghost_tint", new Robust.Shared.Maths.Vector3(component.color.R, component.color.G, component.color.B));
shader.SetParameter("ghost_alpha", Math.Clamp(component.color.A * VisualObserverAlphaMultiplier, 0f, 1f));
sprite.Color = Color.White;
return;
}

RemoveGhostCompositeShader(uid, sprite);
sprite.Color = component.color;
}

private ShaderInstance EnsureGhostCompositeShader(EntityUid uid, SpriteComponent sprite)
{
if (!_compositeGhostShaders.TryGetValue(uid, out var shader))
{
shader = _prototype.Index<ShaderPrototype>(CompositeGhostShaderId).InstanceUnique();
_compositeGhostShaders[uid] = shader;
}

if (sprite.PostShader != shader)
sprite.PostShader = shader;

return shader;
}

private void RemoveGhostCompositeShader(EntityUid uid, SpriteComponent? sprite = null)
{
if (!_compositeGhostShaders.Remove(uid, out var shader))
return;

if (sprite == null)
TryComp(uid, out sprite);

if (sprite != null && sprite.PostShader == shader)
sprite.PostShader = null;

shader.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Prototypes;
using Content.Shared.Ghost;

namespace Content.Client.Interactable.Components
{
Expand All @@ -11,6 +12,8 @@ public sealed partial class InteractionOutlineComponent : Component
[Dependency] private readonly IEntityManager _entMan = default!;

private const float DefaultWidth = 1;
private const float DefaultAlphaCutoff = 0f;
private const float GhostAlphaCutoff = 0.02f;

[ValidatePrototypeId<ShaderPrototype>]
private const string ShaderInRange = "SelectionOutlineInrange";
Expand All @@ -20,30 +23,37 @@ public sealed partial class InteractionOutlineComponent : Component

private bool _inRange;
private ShaderInstance? _shader;
private ShaderInstance? _previousPostShader;
private int _lastRenderScale;

public void OnMouseEnter(EntityUid uid, bool inInteractionRange, int renderScale)
{
_lastRenderScale = renderScale;
_inRange = inInteractionRange;
if (_entMan.TryGetComponent(uid, out SpriteComponent? sprite) && sprite.PostShader == null)
{
// TODO why is this creating a new instance of the outline shader every time the mouse enters???
_shader = MakeNewShader(inInteractionRange, renderScale);
sprite.PostShader = _shader;
}

if (!_entMan.TryGetComponent(uid, out SpriteComponent? sprite))
return;

if (_shader != null && sprite.PostShader == _shader)
return;

_previousPostShader = sprite.PostShader;
_shader?.Dispose();
_shader = MakeNewShader(inInteractionRange, renderScale);
sprite.PostShader = _shader;
}

public void OnMouseLeave(EntityUid uid)
{
if (_entMan.TryGetComponent(uid, out SpriteComponent? sprite))
{
if (sprite.PostShader == _shader)
sprite.PostShader = null;
sprite.PostShader = _previousPostShader;
}

_shader?.Dispose();
_shader = null;
_previousPostShader = null;
}

public void UpdateInRange(EntityUid uid, bool inInteractionRange, int renderScale)
Expand All @@ -55,6 +65,7 @@ public void UpdateInRange(EntityUid uid, bool inInteractionRange, int renderScal
_inRange = inInteractionRange;
_lastRenderScale = renderScale;

_shader?.Dispose();
_shader = MakeNewShader(_inRange, _lastRenderScale);
sprite.PostShader = _shader;
}
Expand All @@ -66,6 +77,7 @@ private ShaderInstance MakeNewShader(bool inRange, int renderScale)

var instance = _prototypeManager.Index<ShaderPrototype>(shaderName).InstanceUnique();
instance.SetParameter("outline_width", DefaultWidth * renderScale);
instance.SetParameter("alpha_cutoff", _entMan.HasComponent<GhostComponent>(Owner) ? GhostAlphaCutoff : DefaultAlphaCutoff);
return instance;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public void ScarierMindbreak(EntityUid uid, PsionicComponent component)
if (!_mind.TryGetMind(session, out var mindId, out var mind))
return;

_ghost.SpawnGhost((mindId, mind), Transform(uid).Coordinates, false);
_ghost.SpawnGhost((mindId, mind), Transform(uid).Coordinates, false, uid);
_npcFaction.AddFaction(uid, "SimpleNeutral");
var htn = EnsureComp<HTNComponent>(uid);
htn.RootTask = new HTNCompoundTask() { Task = "IdleCompound" };
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/GameTicking/GameTicker.Spawning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ public void SpawnObserver(ICommonSession player)
_roles.MindAddRole(mind.Value, "MindRoleObserver");
}

var ghost = _ghost.SpawnGhost(mind.Value);
var ghost = _ghost.SpawnLobbyObserverGhost(mind.Value);
_adminLogger.Add(LogType.LateJoin,
LogImpact.Low,
$"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}.");
Expand Down
Loading
Loading