diff --git a/Content.Client/Pointing/Components/PointingArrowComponent.cs b/Content.Client/Pointing/Components/PointingArrowComponent.cs index 0d3bc4a5cc..4156f84aac 100644 --- a/Content.Client/Pointing/Components/PointingArrowComponent.cs +++ b/Content.Client/Pointing/Components/PointingArrowComponent.cs @@ -1,19 +1,12 @@ -using System.Numerics; using Content.Shared.Pointing.Components; +using System.Numerics; namespace Content.Client.Pointing.Components; [RegisterComponent] public sealed partial class PointingArrowComponent : SharedPointingArrowComponent { /// - /// How long it takes to go from the bottom of the animation to the top. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("animationTime")] - public float AnimationTime = 0.5f; - - /// - /// How far it goes in any direction. + /// How far the arrow moves up and down during the floating phase. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("offset")] diff --git a/Content.Client/Pointing/PointingSystem.Visualizer.cs b/Content.Client/Pointing/PointingSystem.Visualizer.cs new file mode 100644 index 0000000000..63ce882e06 --- /dev/null +++ b/Content.Client/Pointing/PointingSystem.Visualizer.cs @@ -0,0 +1,62 @@ +using Content.Client.Pointing.Components; +using Content.Shared.Pointing; +using Robust.Client.GameObjects; +using Robust.Client.Animations; +using Robust.Shared.Animations; +using System.Numerics; + +namespace Content.Client.Pointing; + +public sealed partial class PointingSystem : SharedPointingSystem +{ + [Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!; + + public void InitializeVisualizer() + { + SubscribeLocalEvent(OnAnimationCompleted); + } + + private void OnAnimationCompleted(EntityUid uid, PointingArrowComponent component, AnimationCompletedEvent args) + { + if (args.Key == component.AnimationKey) + _animationPlayer.Stop(uid, component.AnimationKey); + } + + private void BeginPointAnimation(EntityUid uid, Vector2 startPosition, Vector2 offset, string animationKey) + { + if (_animationPlayer.HasRunningAnimation(uid, animationKey)) + return; + + var animation = new Animation + { + Length = PointDuration, + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Cubic, + KeyFrames = + { + // We pad here to prevent improper looping and tighten the overshoot, just a touch + new AnimationTrackProperty.KeyFrame(startPosition, 0f), + new AnimationTrackProperty.KeyFrame(Vector2.Lerp(startPosition, offset, 0.9f), PointKeyTimeMove), + new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeMove), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeMove), + new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(offset, PointKeyTimeHover), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, PointKeyTimeHover), + } + } + } + }; + + _animationPlayer.Play(uid, animation, animationKey); + } +} diff --git a/Content.Client/Pointing/PointingSystem.cs b/Content.Client/Pointing/PointingSystem.cs index 82b12fbf36..a86b6e21b5 100644 --- a/Content.Client/Pointing/PointingSystem.cs +++ b/Content.Client/Pointing/PointingSystem.cs @@ -1,32 +1,25 @@ using Content.Client.Pointing.Components; -using Content.Client.Gravity; -using Content.Shared.Mobs.Systems; using Content.Shared.Pointing; using Content.Shared.Verbs; using Robust.Client.GameObjects; +using Robust.Shared.GameStates; using Robust.Shared.Utility; using DrawDepth = Content.Shared.DrawDepth.DrawDepth; namespace Content.Client.Pointing; -public sealed class PointingSystem : SharedPointingSystem +public sealed partial class PointingSystem : SharedPointingSystem { - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly FloatingVisualizerSystem _floatingSystem = default!; - public override void Initialize() { base.Initialize(); SubscribeLocalEvent>(AddPointingVerb); SubscribeLocalEvent(OnArrowStartup); - SubscribeLocalEvent(OnArrowAnimation); SubscribeLocalEvent(OnRogueArrowStartup); - } + SubscribeLocalEvent(HandleCompState); - private void OnArrowAnimation(EntityUid uid, PointingArrowComponent component, AnimationCompletedEvent args) - { - _floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); + InitializeVisualizer(); } private void AddPointingVerb(GetVerbsEvent args) @@ -38,15 +31,11 @@ public sealed class PointingSystem : SharedPointingSystem // I'm just adding this verb exclusively to clients so that the verb-loading pop-in on the verb menu isn't // as bad. Important for this verb seeing as its usually an option on just about any entity. + // this is a pointing arrow. no pointing here... if (HasComp(args.Target)) - { - // this is a pointing arrow. no pointing here... return; - } - // Can the user point? Checking mob state directly instead of some action blocker, as many action blockers are blocked for - // ghosts and there is no obvious choice for pointing (unless ghosts CanEmote?). - if (_mobState.IsIncapacitated(args.User)) + if (!CanPoint(args.User)) return; // We won't check in range or visibility, as this verb is currently only executable via the context menu, @@ -66,11 +55,9 @@ public sealed class PointingSystem : SharedPointingSystem private void OnArrowStartup(EntityUid uid, PointingArrowComponent component, ComponentStartup args) { if (TryComp(uid, out var sprite)) - { sprite.DrawDepth = (int) DrawDepth.Overlays; - } - _floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); + BeginPointAnimation(uid, component.StartPosition, component.Offset, component.AnimationKey); } private void OnRogueArrowStartup(EntityUid uid, RoguePointingArrowComponent arrow, ComponentStartup args) @@ -81,4 +68,13 @@ public sealed class PointingSystem : SharedPointingSystem sprite.NoRotation = false; } } + + private void HandleCompState(Entity entity, ref ComponentHandleState args) + { + if (args.Current is not SharedPointingArrowComponentState state) + return; + + entity.Comp.StartPosition = state.StartPosition; + entity.Comp.EndTime = state.EndTime; + } } diff --git a/Content.Server/Body/Systems/BrainSystem.cs b/Content.Server/Body/Systems/BrainSystem.cs index b55274808d..abb5497120 100644 --- a/Content.Server/Body/Systems/BrainSystem.cs +++ b/Content.Server/Body/Systems/BrainSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Body.Components; using Content.Shared.Body.Events; using Content.Shared.Mind; using Content.Shared.Mind.Components; +using Content.Shared.Pointing; namespace Content.Server.Body.Systems { @@ -17,6 +18,7 @@ namespace Content.Server.Body.Systems SubscribeLocalEvent((uid, _, args) => HandleMind(args.Body, uid)); SubscribeLocalEvent((uid, _, args) => HandleMind(uid, args.OldBody)); + SubscribeLocalEvent(OnPointAttempt); } private void HandleMind(EntityUid newEntity, EntityUid oldEntity) @@ -36,5 +38,10 @@ namespace Content.Server.Body.Systems _mindSystem.TransferTo(mindId, newEntity, mind: mind); } + + private void OnPointAttempt(EntityUid uid, BrainComponent component, PointAttemptEvent args) + { + args.Cancel(); + } } } diff --git a/Content.Server/Pointing/EntitySystems/PointingSystem.cs b/Content.Server/Pointing/EntitySystems/PointingSystem.cs index 6fcdfcf994..a7c455e6a5 100644 --- a/Content.Server/Pointing/EntitySystems/PointingSystem.cs +++ b/Content.Server/Pointing/EntitySystems/PointingSystem.cs @@ -1,7 +1,6 @@ using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Pointing.Components; -using Content.Shared.Bed.Sleep; using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Eye; @@ -10,13 +9,13 @@ using Content.Shared.IdentityManagement; using Content.Shared.Input; using Content.Shared.Interaction; using Content.Shared.Mind; -using Content.Shared.Mobs.Systems; using Content.Shared.Pointing; using Content.Shared.Popups; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Enums; +using Robust.Shared.GameStates; using Robust.Shared.Input.Binding; using Robust.Shared.Map; using Robust.Shared.Player; @@ -34,7 +33,6 @@ namespace Content.Server.Pointing.EntitySystems [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly VisibilitySystem _visibilitySystem = default!; [Dependency] private readonly SharedMindSystem _minds = default!; @@ -50,6 +48,15 @@ namespace Content.Server.Pointing.EntitySystems private const float PointingRange = 15f; + private void GetCompState(Entity entity, ref ComponentGetState args) + { + args.State = new SharedPointingArrowComponentState + { + StartPosition = entity.Comp.StartPosition, + EndTime = entity.Comp.EndTime + }; + } + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { if (e.NewStatus != SessionStatus.Disconnected) @@ -97,7 +104,7 @@ namespace Content.Server.Pointing.EntitySystems } } - public bool TryPoint(ICommonSession? session, EntityCoordinates coords, EntityUid pointed) + public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, EntityUid pointed) { if (session?.AttachedEntity is not { } player) { @@ -105,9 +112,9 @@ namespace Content.Server.Pointing.EntitySystems return false; } - if (!coords.IsValid(EntityManager)) + if (!coordsPointed.IsValid(EntityManager)) { - Log.Warning($"Player {ToPrettyString(player)} attempted to point at invalid coordinates: {coords}"); + Log.Warning($"Player {ToPrettyString(player)} attempted to point at invalid coordinates: {coordsPointed}"); return false; } @@ -123,33 +130,30 @@ namespace Content.Server.Pointing.EntitySystems return false; } - // Checking mob state directly instead of some action blocker, as many action blockers are blocked for - // ghosts and there is no obvious choice for pointing. - if (_mobState.IsIncapacitated(player)) + if (!CanPoint(player)) { return false; } - if (HasComp(player)) - { - return false; - } - - if (!InRange(player, coords)) + if (!InRange(player, coordsPointed)) { _popup.PopupEntity(Loc.GetString("pointing-system-try-point-cannot-reach"), player, player); return false; } + var mapCoordsPointed = coordsPointed.ToMap(EntityManager); + _rotateToFaceSystem.TryFaceCoordinates(player, mapCoordsPointed.Position); - var mapCoords = coords.ToMap(EntityManager); - _rotateToFaceSystem.TryFaceCoordinates(player, mapCoords.Position); - - var arrow = EntityManager.SpawnEntity("PointingArrow", coords); + var arrow = EntityManager.SpawnEntity("PointingArrow", coordsPointed); if (TryComp(arrow, out var pointing)) { - pointing.EndTime = _gameTiming.CurTime + TimeSpan.FromSeconds(4); + if (TryComp(player, out TransformComponent? xformPlayer)) + pointing.StartPosition = EntityCoordinates.FromMap(arrow, xformPlayer.Coordinates.ToMap(EntityManager)).Position; + + pointing.EndTime = _gameTiming.CurTime + PointDuration; + + Dirty(arrow, pointing); } if (EntityQuery().FirstOrDefault() != null) @@ -215,10 +219,10 @@ namespace Content.Server.Pointing.EntitySystems TileRef? tileRef = null; string? position = null; - if (_mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid)) + if (_mapManager.TryFindGridAt(mapCoordsPointed, out var gridUid, out var grid)) { - position = $"EntId={gridUid} {grid.WorldToTile(mapCoords.Position)}"; - tileRef = grid.GetTileRef(grid.WorldToTile(mapCoords.Position)); + position = $"EntId={gridUid} {grid.WorldToTile(mapCoordsPointed.Position)}"; + tileRef = grid.GetTileRef(grid.WorldToTile(mapCoordsPointed.Position)); } var tileDef = _tileDefinitionManager[tileRef?.Tile.TypeId ?? 0]; @@ -228,7 +232,7 @@ namespace Content.Server.Pointing.EntitySystems viewerMessage = Loc.GetString("pointing-system-other-point-at-tile", ("otherName", playerName), ("tileName", name)); - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):user} pointed at {name} {(position == null ? mapCoords : position)}"); + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):user} pointed at {name} {(position == null ? mapCoordsPointed : position)}"); } _pointers[session] = _gameTiming.CurTime; @@ -242,6 +246,8 @@ namespace Content.Server.Pointing.EntitySystems { base.Initialize(); + SubscribeLocalEvent(GetCompState); + SubscribeNetworkEvent(OnPointAttempt); _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; @@ -255,8 +261,8 @@ namespace Content.Server.Pointing.EntitySystems { var target = GetEntity(ev.Target); - if (TryComp(target, out TransformComponent? xform)) - TryPoint(args.SenderSession, xform.Coordinates, target); + if (TryComp(target, out TransformComponent? xformTarget)) + TryPoint(args.SenderSession, xformTarget.Coordinates, target); else Log.Warning($"User {args.SenderSession} attempted to point at a non-existent entity uid: {ev.Target}"); } diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 0efbe1a40a..869c279704 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -14,6 +14,7 @@ using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Systems; +using Content.Shared.Pointing; using Content.Shared.PowerCell; using Content.Shared.PowerCell.Components; using Content.Shared.Roles; @@ -67,6 +68,7 @@ public sealed partial class BorgSystem : SharedBorgSystem SubscribeLocalEvent(OnGetDeadIC); SubscribeLocalEvent(OnBrainMindAdded); + SubscribeLocalEvent(OnBrainPointAttempt); InitializeModules(); InitializeMMI(); @@ -242,6 +244,11 @@ public sealed partial class BorgSystem : SharedBorgSystem _mind.TransferTo(mindId, containerEnt, mind: mind); } + private void OnBrainPointAttempt(EntityUid uid, BorgBrainComponent component, PointAttemptEvent args) + { + args.Cancel(); + } + private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null) { if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent)) diff --git a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs b/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs index 2ac1c372ca..505b2a18e7 100644 --- a/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SharedSleepingSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Actions; using Content.Shared.Bed.Sleep; using Content.Shared.Damage.ForceSay; using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Pointing; using Content.Shared.Speech; using Robust.Shared.Network; using Robust.Shared.Prototypes; @@ -25,6 +26,7 @@ namespace Content.Server.Bed.Sleep SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnSpeakAttempt); SubscribeLocalEvent(OnSeeAttempt); + SubscribeLocalEvent(OnPointAttempt); SubscribeLocalEvent(OnSleepUnpaused); } @@ -70,6 +72,11 @@ namespace Content.Server.Bed.Sleep if (component.LifeStage <= ComponentLifeStage.Running) args.Cancel(); } + + private void OnPointAttempt(EntityUid uid, SleepingComponent component, PointAttemptEvent args) + { + args.Cancel(); + } } } diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index 8ce12db518..5199133253 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -9,6 +9,7 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Mobs.Components; using Content.Shared.Movement.Events; +using Content.Shared.Pointing; using Content.Shared.Pulling.Events; using Content.Shared.Speech; using Content.Shared.Standing; @@ -38,6 +39,7 @@ public partial class MobStateSystem SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(CheckAct); + SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnSleepAttempt); SubscribeLocalEvent(OnCombatModeShouldHandInteract); SubscribeLocalEvent(OnAttemptPacifiedAttack); diff --git a/Content.Shared/Pointing/Components/SharedPointingArrowComponent.cs b/Content.Shared/Pointing/Components/SharedPointingArrowComponent.cs index 07613c7746..66cca50c2e 100644 --- a/Content.Shared/Pointing/Components/SharedPointingArrowComponent.cs +++ b/Content.Shared/Pointing/Components/SharedPointingArrowComponent.cs @@ -1,13 +1,22 @@ using Robust.Shared.GameStates; +using System.Numerics; namespace Content.Shared.Pointing.Components; [NetworkedComponent] public abstract partial class SharedPointingArrowComponent : Component { + /// + /// The position of the sender when the point began. + /// + [DataField] + [ViewVariables(VVAccess.ReadWrite)] + public Vector2 StartPosition; + /// /// When the pointing arrow ends /// - [ViewVariables(VVAccess.ReadWrite), DataField("endTime")] + [DataField] + [ViewVariables(VVAccess.ReadWrite)] public TimeSpan EndTime; } diff --git a/Content.Shared/Pointing/SharedPointingSystem.cs b/Content.Shared/Pointing/SharedPointingSystem.cs index 05e4b4aa27..bb3436a14b 100644 --- a/Content.Shared/Pointing/SharedPointingSystem.cs +++ b/Content.Shared/Pointing/SharedPointingSystem.cs @@ -1,17 +1,36 @@ using Robust.Shared.Serialization; +using System.Numerics; namespace Content.Shared.Pointing; public abstract class SharedPointingSystem : EntitySystem { - [Serializable, NetSerializable] - protected sealed class PointingArrowComponentState : ComponentState - { - public TimeSpan EndTime; + protected readonly TimeSpan PointDuration = TimeSpan.FromSeconds(4); + protected readonly float PointKeyTimeMove = 0.1f; + protected readonly float PointKeyTimeHover = 0.5f; - public PointingArrowComponentState(TimeSpan endTime) - { - EndTime = endTime; - } + [Serializable, NetSerializable] + public sealed class SharedPointingArrowComponentState : ComponentState + { + public Vector2 StartPosition { get; init; } + public TimeSpan EndTime { get; init; } + } + + public bool CanPoint(EntityUid uid) + { + var ev = new PointAttemptEvent(uid); + RaiseLocalEvent(uid, ev, true); + + return !ev.Cancelled; } } + +public sealed class PointAttemptEvent : CancellableEntityEventArgs +{ + public PointAttemptEvent(EntityUid uid) + { + Uid = uid; + } + + public EntityUid Uid { get; } +}