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; }
+}