using Content.Client.Movement.Systems; using Content.Shared.Actions; using Content.Shared.Ghost; using Robust.Client.Console; using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.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 PointLightSystem _pointLightSystem = default!; [Dependency] private readonly ContentEyeSystem _contentEye = default!; [Dependency] private readonly SpriteSystem _sprite = 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.SetVisible((uid, sprite), value || uid == _playerManager.LocalEntity); } } } public GhostComponent? Player => CompOrNull(_playerManager.LocalEntity); 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); 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.SetVisible((uid, sprite), GhostVisibility || uid == _playerManager.LocalEntity); } private void OnToggleLighting(EntityUid uid, EyeComponent component, ToggleLightingActionEvent args) { if (args.Handled) return; TryComp(uid, out var light); if (!component.DrawLight) { // normal lighting Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-normal"), args.Performer); _contentEye.RequestEye(component.DrawFov, true); } else if (!light?.Enabled ?? false) // skip this option if we have no PointLightComponent { // enable personal light Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-personal-light"), args.Performer); _pointLightSystem.SetEnabled(uid, true, light); } else { // fullbright mode Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-lighting-manager-popup-fullbright"), args.Performer); _contentEye.RequestEye(component.DrawFov, false); _pointLightSystem.SetEnabled(uid, false, light); } args.Handled = true; } private void OnToggleFoV(EntityUid uid, EyeComponent component, ToggleFoVActionEvent args) { if (args.Handled) return; Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-fov-popup"), args.Performer); _contentEye.RequestToggleFov(uid, component); args.Handled = true; } private void OnToggleGhosts(EntityUid uid, GhostComponent component, ToggleGhostsActionEvent args) { if (args.Handled) return; var locId = GhostVisibility ? "ghost-gui-toggle-ghost-visibility-popup-off" : "ghost-gui-toggle-ghost-visibility-popup-on"; Popup.PopupEntity(Loc.GetString(locId), args.Performer); if (uid == _playerManager.LocalEntity) 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.LocalEntity) return; GhostVisibility = false; PlayerRemoved?.Invoke(component); } private void OnGhostPlayerAttach(EntityUid uid, GhostComponent component, LocalPlayerAttachedEvent localPlayerAttachedEvent) { GhostVisibility = true; PlayerAttached?.Invoke(component); } private void OnGhostState(EntityUid uid, GhostComponent component, ref AfterAutoHandleStateEvent args) { if (TryComp(uid, out var sprite)) _sprite.LayerSetColor((uid, sprite), 0, component.Color); if (uid != _playerManager.LocalEntity) return; PlayerUpdated?.Invoke(component); } private void OnGhostPlayerDetach(EntityUid uid, GhostComponent component, LocalPlayerDetachedEvent args) { GhostVisibility = false; PlayerDetached?.Invoke(); } 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(bool? visibility = null) { GhostVisibility = visibility ?? !GhostVisibility; } } }