using Content.Client.Actions; using Content.Shared._White.Wizard; using Content.Shared._White.Wizard.Charging; using Content.Shared.Actions; using Content.Shared.Mobs.Systems; using Content.Shared.StatusEffect; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Timing; namespace Content.Client.UserInterface.Systems.Actions; public sealed class ChargeActionSystem : SharedChargingSystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly ActionsSystem _actionsSystem = default!; [Dependency] private readonly IUserInterfaceManager _uiManager = default!; [Dependency] private readonly InputSystem _inputSystem = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; private ActionUIController? _controller; public event Action? ChargingUpdated; private bool _charging; private bool _prevCharging; private float _chargeTime; private int _chargeLevel; private int _prevChargeLevel; private bool _isChargingPlaying; private bool _isChargedPlaying; private const float LevelChargeTime = 1.5f; public override void Initialize() { base.Initialize(); _controller = _uiManager.GetUIController(); } public override void Update(float frameTime) { base.Update(frameTime); if (!_timing.IsFirstTimePredicted) return; if (_playerManager.LocalEntity is not { } user) { Reset(); return; } if (!_mobState.IsAlive(user) || _statusEffects.HasStatusEffect(user, "Incorporeal")) { Reset(); return; } if (_controller == null || _controller.SelectingTargetFor is not { } actionId) { Reset(); return; } if (!_actionsSystem.TryGetActionData(actionId, out var baseAction) || baseAction is not BaseTargetActionComponent action || !action.IsChargeEnabled) { Reset(); return; } if (!action.Enabled || action is { Charges: 0, RenewCharges: false } || action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime) { Reset(); return; } var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary); switch (altDown) { case BoundKeyState.Down: _prevCharging = _charging; _charging = true; _chargeTime += frameTime; _chargeLevel = (int) (_chargeTime / LevelChargeTime) + 1; _chargeLevel = Math.Clamp(_chargeLevel, 1, action.MaxChargeLevel); break; case BoundKeyState.Up when _charging: _prevCharging = _charging; _charging = false; _chargeTime = 0f; _isChargingPlaying = false; _isChargedPlaying = false; HandleAction(actionId, action, user, _chargeLevel); _chargeLevel = 0; RaiseNetworkEvent(new RequestAudioSpellStop()); RaiseNetworkEvent(new RemoveWizardChargeEvent()); break; case BoundKeyState.Up: _prevCharging = _charging; _chargeLevel = 0; _charging = false; _chargeTime = 0f; _isChargingPlaying = false; _isChargedPlaying = false; RaiseNetworkEvent(new RequestAudioSpellStop()); RaiseNetworkEvent(new RemoveWizardChargeEvent()); break; } if (_chargeLevel != _prevChargeLevel) { if (_chargeLevel > 0 && _charging) { RaiseNetworkEvent(new AddWizardChargeEvent(GetNetEntity(actionId))); } _prevChargeLevel = _chargeLevel; } if (_prevCharging != _charging) { ChargingUpdated?.Invoke(_charging); } if (_charging && !_isChargingPlaying) { _isChargingPlaying = true; RaiseNetworkEvent(new RequestSpellChargingAudio(action.ChargingSound, action.LoopCharging)); } if (_chargeLevel >= action.MaxChargeLevel && !_isChargedPlaying && _charging) { _isChargedPlaying = true; RaiseNetworkEvent(new RequestSpellChargedAudio(action.MaxChargedSound, action.LoopMaxCharged)); } } private void Reset() { _charging = false; _prevCharging = false; _chargeTime = 0f; _chargeLevel = 0; _prevChargeLevel = 0; _isChargingPlaying = false; _isChargedPlaying = false; } private void HandleAction(EntityUid actionId, BaseTargetActionComponent action, EntityUid user, int chargeLevel) { var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition); if (mousePos.MapId == MapId.Nullspace) return; var coordinates = EntityCoordinates.FromMap(_mapManager.TryFindGridAt(mousePos, out var gridUid, out _) ? gridUid : _mapManager.GetMapEntityId(mousePos.MapId), mousePos, _transformSystem, EntityManager); if (!EntityManager.TryGetComponent(user, out ActionsComponent? comp)) return; switch (action) { case WorldTargetActionComponent mapTarget: _controller?.TryTargetWorld(coordinates, actionId, mapTarget, user, comp, ActionUseType.Charge, chargeLevel); break; } RaiseNetworkEvent(new RequestAudioSpellStop()); RaiseNetworkEvent(new RemoveWizardChargeEvent()); } public override void Shutdown() { base.Shutdown(); _controller = null; _charging = false; _prevCharging = false; _chargeTime = 0f; _chargeLevel = 0; _prevChargeLevel = 0; _isChargingPlaying = false; _isChargedPlaying = false; } }