using System.Numerics; using Content.Shared._RMC14.Attachable.Events; using Content.Shared.Actions; using Content.Shared.Camera; using Content.Shared.DoAfter; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Toggleable; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Wieldable; using Content.Shared.Wieldable.Components; using Robust.Shared.Containers; namespace Content.Shared._RMC14.Scoping; public abstract partial class SharedScopeSystem : EntitySystem { [Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContentEyeSystem _contentEye = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly PullingSystem _pulling = default!; public override void Initialize() { InitializeUser(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnScopeEntityTerminating); SubscribeLocalEvent(OnUnequip); SubscribeLocalEvent(OnDeselectHand); SubscribeLocalEvent(OnUnwielded); SubscribeLocalEvent(OnGetActions); SubscribeLocalEvent(OnToggleAction); SubscribeLocalEvent(OnCycleZoomLevel); SubscribeLocalEvent(OnActivateInWorld); SubscribeLocalEvent(OnGunShot); SubscribeLocalEvent(OnScopeDoAfter); SubscribeLocalEvent(OnGunUnequip); SubscribeLocalEvent(OnGunDeselectHand); SubscribeLocalEvent(OnGunUnwielded); SubscribeLocalEvent(OnGunGunShot); } private void OnMapInit(Entity ent, ref MapInitEvent args) { _actionContainer.EnsureAction(ent.Owner, ref ent.Comp.ScopingToggleActionEntity, ent.Comp.ScopingToggleAction); if (ent.Comp.ZoomLevels.Count > 1) _actionContainer.EnsureAction(ent.Owner, ref ent.Comp.CycleZoomLevelActionEntity, ent.Comp.CycleZoomLevelAction); Dirty(ent.Owner, ent.Comp); } private void OnShutdown(Entity ent, ref ComponentRemove args) { if (ent.Comp.User is not { } user) return; Unscope(ent); _actionsSystem.RemoveProvidedActions(user, ent.Owner); } private void OnScopeEntityTerminating(Entity ent, ref EntityTerminatingEvent args) { Unscope(ent); } private void OnUnequip(Entity ent, ref GotUnequippedHandEvent args) { Unscope(ent); } private void OnDeselectHand(Entity ent, ref HandDeselectedEvent args) { Unscope(ent); } private void OnUnwielded(Entity ent, ref ItemUnwieldedEvent args) { if (ent.Comp.RequireWielding) Unscope(ent); } private void OnGetActions(Entity ent, ref GetItemActionsEvent args) { args.AddAction(ref ent.Comp.ScopingToggleActionEntity, ent.Comp.ScopingToggleAction); if (ent.Comp.ZoomLevels.Count > 1) args.AddAction(ref ent.Comp.CycleZoomLevelActionEntity, ent.Comp.CycleZoomLevelAction); } private void OnToggleAction(Entity ent, ref ToggleActionEvent args) { if (args.Handled) return; args.Handled = true; ToggleScoping(ent, args.Performer); } private void OnCycleZoomLevel(Entity scope, ref ScopeCycleZoomLevelEvent args) { if (args.Handled) return; args.Handled = true; if (scope.Comp.CurrentZoomLevel >= scope.Comp.ZoomLevels.Count - 1) scope.Comp.CurrentZoomLevel = 0; else ++scope.Comp.CurrentZoomLevel; var zoomLevel = GetCurrentZoomLevel(scope); if (zoomLevel.Name != null) _popup.PopupClient(Loc.GetString("rcm-action-popup-scope-cycle-zoom", ("zoom", zoomLevel.Name)), args.Performer, args.Performer); Dirty(scope); } private void OnActivateInWorld(Entity ent, ref ActivateInWorldEvent args) { if (args.Handled || !args.Complex || !ent.Comp.UseInHand) return; args.Handled = true; ToggleScoping(ent, args.User); } private void OnGunShot(Entity ent, ref GunShotEvent args) { var dir = Transform(args.User).LocalRotation.GetCardinalDir(); if (ent.Comp.ScopingDirection != dir) Unscope(ent); } private void OnScopeDoAfter(Entity ent, ref ScopeDoAfterEvent args) { if (args.Handled) return; args.Handled = true; if (args.Cancelled) { DeleteRelay(ent, args.User); return; } var user = args.User; if (!CanScopePopup(ent, user)) { DeleteRelay(ent, args.User); return; } Scope(ent, user, args.Direction); } private void OnGunUnequip(Entity ent, ref GotUnequippedHandEvent args) { UnscopeGun(ent); } private void OnGunDeselectHand(Entity ent, ref HandDeselectedEvent args) { UnscopeGun(ent); } private void OnGunUnwielded(Entity ent, ref ItemUnwieldedEvent args) { UnscopeGun(ent); } private void OnGunGunShot(Entity ent, ref GunShotEvent args) { var dir = Transform(args.User).LocalRotation.GetCardinalDir(); if (TryComp(ent.Comp.Scope, out ScopeComponent? scope) && scope.ScopingDirection != dir) UnscopeGun(ent); } private bool CanScopePopup(Entity scope, EntityUid user) { var ent = scope.Owner; if (scope.Comp.Attachment && !TryGetActiveEntity(scope, out ent)) { var msgError = Loc.GetString("cm-action-popup-scoping-must-attach", ("scope", ent)); _popup.PopupClient(msgError, user, user); return false; } if (!_hands.TryGetActiveItem(user, out var heldItem) || !scope.Comp.Attachment && heldItem != scope.Owner) { var msgError = Loc.GetString("cm-action-popup-scoping-user-must-hold", ("scope", ent)); _popup.PopupClient(msgError, user, user); return false; } if (_pulling.IsPulled(user)) { var msgError = Loc.GetString("cm-action-popup-scoping-user-must-not-pulled", ("scope", ent)); _popup.PopupClient(msgError, user, user); return false; } if (_container.IsEntityInContainer(user)) { var msgError = Loc.GetString("cm-action-popup-scoping-user-must-not-contained", ("scope", ent)); _popup.PopupClient(msgError, user, user); return false; } if (scope.Comp.RequireWielding && TryComp(ent, out WieldableComponent? wieldable) && !wieldable.Wielded) { var msgError = Loc.GetString("cm-action-popup-scoping-user-must-wield", ("scope", ent)); _popup.PopupClient(msgError, user, user); return false; } return true; } protected virtual Direction? StartScoping(Entity scope, EntityUid user) { if (!CanScopePopup(scope, user)) return null; // TODO RMC14 make this work properly with rotations var xform = Transform(user); var cardinalDir = xform.LocalRotation.GetCardinalDir(); var ev = new ScopeDoAfterEvent(cardinalDir); var zoomLevel = GetCurrentZoomLevel(scope); var doAfter = new DoAfterArgs(EntityManager, user, zoomLevel.DoAfter, ev, scope, null, scope) { BreakOnMove = !zoomLevel.AllowMovement }; if (_doAfter.TryStartDoAfter(doAfter)) return cardinalDir; return null; } private void Scope(Entity scope, EntityUid user, Direction direction) { if (TryComp(user, out ScopingComponent? scoping)) UserStopScoping((user, scoping)); var zoomLevel = GetCurrentZoomLevel(scope); scope.Comp.User = user; scope.Comp.ScopingDirection = direction; Dirty(scope); scoping = EnsureComp(user); scoping.Scope = scope; scoping.AllowMovement = zoomLevel.AllowMovement; Dirty(user, scoping); if (scope.Comp.Attachment && TryGetActiveEntity(scope, out var active)) { var gunScoping = EnsureComp(active); gunScoping.Scope = scope; Dirty(active, gunScoping); } var targetOffset = GetScopeOffset(scope, direction); scoping.EyeOffset = targetOffset; var msgUser = Loc.GetString("cm-action-popup-scoping-user", ("scope", scope.Owner)); _popup.PopupClient(msgUser, user, user); _actionsSystem.SetToggled(scope.Comp.ScopingToggleActionEntity, true); _contentEye.SetZoom(user, Vector2.One * zoomLevel.Zoom, true); UpdateOffset(user); } protected virtual bool Unscope(Entity scope) { if (scope.Comp.User is not { } user) return false; RemCompDeferred(user); if (scope.Comp.Attachment && TryGetActiveEntity(scope, out var active)) RemCompDeferred(active); if (scope.Comp.Attachment && scope.Comp.User != null) { var interruptEvent = new AttachableToggleableInterruptEvent(scope.Comp.User.Value); RaiseLocalEvent(scope.Owner, ref interruptEvent); } scope.Comp.User = null; scope.Comp.ScopingDirection = null; Dirty(scope); var msgUser = Loc.GetString("cm-action-popup-scoping-stopping-user", ("scope", scope.Owner)); _popup.PopupClient(msgUser, user, user); _actionsSystem.SetToggled(scope.Comp.ScopingToggleActionEntity, false); _contentEye.ResetZoom(user); return true; } private void UnscopeGun(Entity gun) { if (TryComp(gun.Comp.Scope, out ScopeComponent? scope)) Unscope((gun.Comp.Scope.Value, scope)); } private void ToggleScoping(Entity scope, EntityUid user) { if (HasComp(user)) { Unscope(scope); if (TryComp(user, out ScopingComponent? scoping)) UserStopScoping((user, scoping)); return; } StartScoping(scope, user); } private bool TryGetActiveEntity(Entity scope, out EntityUid active) { if (!scope.Comp.Attachment) { active = scope; return true; } if (!_container.TryGetContainingContainer((scope, null), out var container) || !HasComp(container.Owner)) { active = default; return false; } active = container.Owner; return true; } protected Vector2 GetScopeOffset(Entity scope, Direction direction) { var zoomLevel = GetCurrentZoomLevel(scope); return direction.ToVec() * ((zoomLevel.Offset * zoomLevel.Zoom - 1) / 2); } protected virtual void DeleteRelay(Entity scope, EntityUid? user) { } private ScopeZoomLevel GetCurrentZoomLevel(Entity scope) { ValidateCurrentZoomLevel(scope); return scope.Comp.ZoomLevels[scope.Comp.CurrentZoomLevel]; } private void ValidateCurrentZoomLevel(Entity scope) { bool dirty = false; if (scope.Comp.ZoomLevels == null || scope.Comp.ZoomLevels.Count <= 0) { scope.Comp.ZoomLevels = new List(){ new ScopeZoomLevel(null, 1f, 15, false, TimeSpan.FromSeconds(1)) }; dirty = true; } if (scope.Comp.CurrentZoomLevel >= scope.Comp.ZoomLevels.Count) { scope.Comp.CurrentZoomLevel = 0; dirty = true; } if (dirty) Dirty(scope); } private void UpdateOffset(EntityUid user) { var ev = new GetEyeOffsetEvent(); RaiseLocalEvent(user, ref ev); _eye.SetOffset(user, ev.Offset); } }