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; namespace Content.Client.Ghost { public sealed class GhostSystem : SharedGhostSystem { [Dependency] private readonly IClientConsoleHost _console = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly ILightManager _lightManager = default!; [Dependency] private readonly ContentEyeSystem _contentEye = default!; public int AvailableGhostRoleCount { get; private set; } private bool _ghostVisibility = true; private bool GhostVisibility { get => _ghostVisibility; set { if (_ghostVisibility == value) { return; } _ghostVisibility = value; var query = AllEntityQuery(); while (query.MoveNext(out var uid, out _, out var sprite)) { sprite.Visible = value || uid == _playerManager.LocalPlayer?.ControlledEntity; } } } public GhostComponent? Player => CompOrNull(_playerManager.LocalPlayer?.ControlledEntity); public bool IsGhost => Player != null; public event Action? PlayerRemoved; public event Action? PlayerUpdated; public event Action? PlayerAttached; public event Action? PlayerDetached; public event Action? GhostWarpsResponse; public event Action? GhostRoleCountUpdated; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnGhostRemove); SubscribeLocalEvent(OnGhostState); SubscribeLocalEvent(OnGhostPlayerAttach); SubscribeLocalEvent(OnGhostPlayerDetach); SubscribeLocalEvent(OnPlayerAttach); SubscribeNetworkEvent(OnGhostWarpsResponse); SubscribeNetworkEvent(OnUpdateGhostRoleCount); SubscribeLocalEvent(OnToggleLighting); SubscribeLocalEvent(OnToggleFoV); SubscribeLocalEvent(OnToggleGhosts); } private void OnStartup(EntityUid uid, GhostComponent component, ComponentStartup args) { if (TryComp(uid, out SpriteComponent? sprite)) sprite.Visible = GhostVisibility; } private void OnToggleLighting(EntityUid uid, GhostComponent component, ToggleLightingActionEvent args) { if (args.Handled) return; Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup"), args.Performer); _lightManager.Enabled = !_lightManager.Enabled; args.Handled = true; } private void OnToggleFoV(EntityUid uid, GhostComponent component, ToggleFoVActionEvent args) { if (args.Handled) return; Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-fov-popup"), args.Performer); _contentEye.RequestToggleFov(uid); args.Handled = true; } private void OnToggleGhosts(EntityUid uid, GhostComponent component, ToggleGhostsActionEvent args) { if (args.Handled) return; Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-ghost-visibility-popup"), args.Performer); if (uid == _playerManager.LocalPlayer?.ControlledEntity) ToggleGhostVisibility(); args.Handled = true; } private void OnGhostRemove(EntityUid uid, GhostComponent component, ComponentRemove args) { _actions.RemoveAction(uid, component.ToggleLightingActionEntity); _actions.RemoveAction(uid, component.ToggleFoVActionEntity); _actions.RemoveAction(uid, component.ToggleGhostsActionEntity); _actions.RemoveAction(uid, component.ToggleGhostHearingActionEntity); if (uid != _playerManager.LocalPlayer?.ControlledEntity) return; _lightManager.Enabled = true; if (component.IsAttached) { GhostVisibility = false; } PlayerRemoved?.Invoke(component); } private void OnGhostPlayerAttach(EntityUid uid, GhostComponent component, LocalPlayerAttachedEvent localPlayerAttachedEvent) { if (uid != _playerManager.LocalPlayer?.ControlledEntity) return; GhostVisibility = true; component.IsAttached = true; PlayerAttached?.Invoke(component); } private void OnGhostState(EntityUid uid, GhostComponent component, ref AfterAutoHandleStateEvent args) { if (TryComp(uid, out var sprite)) sprite.LayerSetColor(0, component.color); if (uid != _playerManager.LocalPlayer?.ControlledEntity) return; PlayerUpdated?.Invoke(component); } private bool PlayerDetach(EntityUid uid) { if (uid != _playerManager.LocalPlayer?.ControlledEntity) return false; GhostVisibility = false; PlayerDetached?.Invoke(); return true; } private void OnGhostPlayerDetach(EntityUid uid, GhostComponent component, LocalPlayerDetachedEvent args) { if (PlayerDetach(uid)) component.IsAttached = false; } private void OnPlayerAttach(LocalPlayerAttachedEvent ev) { if (!HasComp(ev.Entity)) PlayerDetach(ev.Entity); } private void OnGhostWarpsResponse(GhostWarpsResponseEvent msg) { if (!IsGhost) { return; } GhostWarpsResponse?.Invoke(msg); } private void OnUpdateGhostRoleCount(GhostUpdateGhostRoleCountEvent msg) { AvailableGhostRoleCount = msg.AvailableGhostRoles; GhostRoleCountUpdated?.Invoke(msg); } public void RequestWarps() { RaiseNetworkEvent(new GhostWarpsRequestEvent()); } public void ReturnToBody() { var msg = new GhostReturnToBodyRequest(); RaiseNetworkEvent(msg); } public void OpenGhostRoles() { _console.RemoteExecuteCommand(null, "ghostroles"); } public void ToggleGhostVisibility() { GhostVisibility = !GhostVisibility; } } }