* Decoupled from gravity, constant animation time, manual networking, cubic interpolation
* Reduced overshoot
* Implemented PointAttemptEvent, reacts with mobstate & sleeping
* Brains can no longer point, PBs must be inside a chassis
* Removed chassis check, made callback more obvious
-using System.Numerics;
using Content.Shared.Pointing.Components;
+using System.Numerics;
namespace Content.Client.Pointing.Components;
[RegisterComponent]
public sealed partial class PointingArrowComponent : SharedPointingArrowComponent
{
/// <summary>
- /// How long it takes to go from the bottom of the animation to the top.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("animationTime")]
- public float AnimationTime = 0.5f;
-
- /// <summary>
- /// How far it goes in any direction.
+ /// How far the arrow moves up and down during the floating phase.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("offset")]
--- /dev/null
+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<PointingArrowComponent, AnimationCompletedEvent>(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);
+ }
+}
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<GetVerbsEvent<Verb>>(AddPointingVerb);
SubscribeLocalEvent<PointingArrowComponent, ComponentStartup>(OnArrowStartup);
- SubscribeLocalEvent<PointingArrowComponent, AnimationCompletedEvent>(OnArrowAnimation);
SubscribeLocalEvent<RoguePointingArrowComponent, ComponentStartup>(OnRogueArrowStartup);
- }
+ SubscribeLocalEvent<PointingArrowComponent, ComponentHandleState>(HandleCompState);
- private void OnArrowAnimation(EntityUid uid, PointingArrowComponent component, AnimationCompletedEvent args)
- {
- _floatingSystem.FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime);
+ InitializeVisualizer();
}
private void AddPointingVerb(GetVerbsEvent<Verb> args)
// 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<PointingArrowComponent>(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,
private void OnArrowStartup(EntityUid uid, PointingArrowComponent component, ComponentStartup args)
{
if (TryComp<SpriteComponent>(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)
sprite.NoRotation = false;
}
}
+
+ private void HandleCompState(Entity<PointingArrowComponent> entity, ref ComponentHandleState args)
+ {
+ if (args.Current is not SharedPointingArrowComponentState state)
+ return;
+
+ entity.Comp.StartPosition = state.StartPosition;
+ entity.Comp.EndTime = state.EndTime;
+ }
}
using Content.Shared.Body.Events;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
+using Content.Shared.Pointing;
namespace Content.Server.Body.Systems
{
SubscribeLocalEvent<BrainComponent, AddedToPartInBodyEvent>((uid, _, args) => HandleMind(args.Body, uid));
SubscribeLocalEvent<BrainComponent, RemovedFromPartInBodyEvent>((uid, _, args) => HandleMind(uid, args.OldBody));
+ SubscribeLocalEvent<BrainComponent, PointAttemptEvent>(OnPointAttempt);
}
private void HandleMind(EntityUid newEntity, EntityUid oldEntity)
_mindSystem.TransferTo(mindId, newEntity, mind: mind);
}
+
+ private void OnPointAttempt(EntityUid uid, BrainComponent component, PointAttemptEvent args)
+ {
+ args.Cancel();
+ }
}
}
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;
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;
[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!;
private const float PointingRange = 15f;
+ private void GetCompState(Entity<PointingArrowComponent> 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)
}
}
- public bool TryPoint(ICommonSession? session, EntityCoordinates coords, EntityUid pointed)
+ public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, EntityUid pointed)
{
if (session?.AttachedEntity is not { } player)
{
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;
}
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))
- {
- return false;
- }
-
- if (HasComp<SleepingComponent>(player))
+ if (!CanPoint(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<PointingArrowComponent>(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<PointingArrowAngeringComponent>().FirstOrDefault() != null)
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];
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;
{
base.Initialize();
+ SubscribeLocalEvent<PointingArrowComponent, ComponentGetState>(GetCompState);
+
SubscribeNetworkEvent<PointingAttemptEvent>(OnPointAttempt);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
{
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}");
}
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;
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
+ SubscribeLocalEvent<BorgBrainComponent, PointAttemptEvent>(OnBrainPointAttempt);
InitializeModules();
InitializeMMI();
_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))
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;
SubscribeLocalEvent<SleepingComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
+ SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
SubscribeLocalEvent<SleepingComponent, EntityUnpausedEvent>(OnSleepUnpaused);
}
if (component.LifeStage <= ComponentLifeStage.Running)
args.Cancel();
}
+
+ private void OnPointAttempt(EntityUid uid, SleepingComponent component, PointAttemptEvent args)
+ {
+ args.Cancel();
+ }
}
}
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;
SubscribeLocalEvent<MobStateComponent, StartPullAttemptEvent>(CheckAct);
SubscribeLocalEvent<MobStateComponent, UpdateCanMoveEvent>(CheckAct);
SubscribeLocalEvent<MobStateComponent, StandAttemptEvent>(CheckAct);
+ SubscribeLocalEvent<MobStateComponent, PointAttemptEvent>(CheckAct);
SubscribeLocalEvent<MobStateComponent, TryingToSleepEvent>(OnSleepAttempt);
SubscribeLocalEvent<MobStateComponent, CombatModeShouldHandInteractEvent>(OnCombatModeShouldHandInteract);
SubscribeLocalEvent<MobStateComponent, AttemptPacifiedAttackEvent>(OnAttemptPacifiedAttack);
using Robust.Shared.GameStates;
+using System.Numerics;
namespace Content.Shared.Pointing.Components;
[NetworkedComponent]
public abstract partial class SharedPointingArrowComponent : Component
{
+ /// <summary>
+ /// The position of the sender when the point began.
+ /// </summary>
+ [DataField]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public Vector2 StartPosition;
+
/// <summary>
/// When the pointing arrow ends
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField("endTime")]
+ [DataField]
+ [ViewVariables(VVAccess.ReadWrite)]
public TimeSpan EndTime;
}
using Robust.Shared.Serialization;
+using System.Numerics;
namespace Content.Shared.Pointing;
public abstract class SharedPointingSystem : EntitySystem
{
+ protected readonly TimeSpan PointDuration = TimeSpan.FromSeconds(4);
+ protected readonly float PointKeyTimeMove = 0.1f;
+ protected readonly float PointKeyTimeHover = 0.5f;
+
[Serializable, NetSerializable]
- protected sealed class PointingArrowComponentState : ComponentState
+ public sealed class SharedPointingArrowComponentState : ComponentState
{
- public TimeSpan EndTime;
+ 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 PointingArrowComponentState(TimeSpan endTime)
- {
- EndTime = endTime;
- }
+public sealed class PointAttemptEvent : CancellableEntityEventArgs
+{
+ public PointAttemptEvent(EntityUid uid)
+ {
+ Uid = uid;
}
+
+ public EntityUid Uid { get; }
}