mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
* Axes the easy IMapManager references * Axes the references to unported (obsolete) IMapManager methods * Converts salvageruler command to a LocalizedEntityCommand so it can use SharedMapSystem * Removes IMapManager from GameTicker This required axing the map nuke during restart, but that should be fine? Maps are entities so EntityManager.FlushEntities should delete them all and that's the preceding line * submodule update(?) --------- Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
372 lines
15 KiB
C#
372 lines
15 KiB
C#
using System.Linq;
|
|
using Content.Server.Administration.Logs;
|
|
using Content.Server.Pointing.Components;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.Eye;
|
|
using Content.Shared.Ghost;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.Input;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Mind;
|
|
using Content.Shared.Pointing;
|
|
using Content.Shared.Popups;
|
|
using JetBrains.Annotations;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Server.Player;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Input.Binding;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Replays;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Server.Pointing.EntitySystems
|
|
{
|
|
[UsedImplicitly]
|
|
internal sealed partial class PointingSystem : SharedPointingSystem
|
|
{
|
|
[Dependency] private IConfigurationManager _config = default!;
|
|
[Dependency] private IReplayRecordingManager _replay = default!;
|
|
[Dependency] private IPlayerManager _playerManager = default!;
|
|
[Dependency] private ITileDefinitionManager _tileDefinitionManager = default!;
|
|
[Dependency] private IGameTiming _gameTiming = default!;
|
|
[Dependency] private RotateToFaceSystem _rotateToFaceSystem = default!;
|
|
[Dependency] private SharedContainerSystem _container = default!;
|
|
[Dependency] private SharedPopupSystem _popup = default!;
|
|
[Dependency] private VisibilitySystem _visibilitySystem = default!;
|
|
[Dependency] private SharedMindSystem _minds = default!;
|
|
[Dependency] private SharedTransformSystem _transform = default!;
|
|
[Dependency] private SharedMapSystem _map = default!;
|
|
[Dependency] private IAdminLogManager _adminLogger = default!;
|
|
[Dependency] private ExamineSystemShared _examine = default!;
|
|
[Dependency] private EntityQuery<InventoryComponent> _inventoryQuery = default!;
|
|
|
|
private TimeSpan _pointDelay = TimeSpan.FromSeconds(0.5f);
|
|
|
|
/// <summary>
|
|
/// A dictionary of players to the last time that they
|
|
/// pointed at something.
|
|
/// </summary>
|
|
private readonly Dictionary<ICommonSession, TimeSpan> _pointers = new();
|
|
|
|
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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_pointers.Remove(e.Session);
|
|
}
|
|
|
|
// TODO: FOV
|
|
private void SendMessage(
|
|
EntityUid source,
|
|
IEnumerable<ICommonSession> viewers,
|
|
EntityUid pointed,
|
|
string selfMessage,
|
|
string viewerMessage,
|
|
string? viewerPointedAtMessage = null)
|
|
{
|
|
var netSource = GetNetEntity(source);
|
|
|
|
foreach (var viewer in viewers)
|
|
{
|
|
if (viewer.AttachedEntity is not {Valid: true} viewerEntity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var message = viewerEntity == source
|
|
? selfMessage
|
|
: viewerEntity == pointed && viewerPointedAtMessage != null
|
|
? viewerPointedAtMessage
|
|
: viewerMessage;
|
|
|
|
// Someone pointing at YOU is slightly more important
|
|
var popupType = viewerEntity == pointed ? PopupType.Medium : PopupType.Small;
|
|
|
|
RaiseNetworkEvent(new PopupEntityEvent(message, popupType, netSource), viewerEntity);
|
|
}
|
|
|
|
_replay.RecordServerMessage(new PopupEntityEvent(viewerMessage, PopupType.Small, netSource));
|
|
}
|
|
|
|
public bool InRange(EntityUid pointer, EntityCoordinates coordinates)
|
|
{
|
|
if (HasComp<GhostComponent>(pointer))
|
|
{
|
|
return _transform.InRange(Transform(pointer).Coordinates, coordinates, 15);
|
|
}
|
|
else
|
|
{
|
|
return _examine.InRangeUnOccluded(pointer, coordinates, 15, predicate: e => e == pointer);
|
|
}
|
|
}
|
|
|
|
public bool TryPoint(ICommonSession? session, EntityCoordinates coordsPointed, EntityUid pointed)
|
|
{
|
|
if (session?.AttachedEntity is not { } player)
|
|
{
|
|
Log.Warning($"Player {session} attempted to point without any attached entity");
|
|
return false;
|
|
}
|
|
|
|
if (!coordsPointed.IsValid(EntityManager))
|
|
{
|
|
Log.Warning($"Player {ToPrettyString(player)} attempted to point at invalid coordinates: {coordsPointed}");
|
|
return false;
|
|
}
|
|
|
|
if (_pointers.TryGetValue(session, out var lastTime) &&
|
|
_gameTiming.CurTime < lastTime + _pointDelay)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (HasComp<PointingArrowComponent>(pointed))
|
|
{
|
|
// this is a pointing arrow. no pointing here...
|
|
return false;
|
|
}
|
|
|
|
if (!CanPoint(player))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!InRange(player, coordsPointed))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("pointing-system-try-point-cannot-reach"), player, player);
|
|
return false;
|
|
}
|
|
var mapCoordsPointed = _transform.ToMapCoordinates(coordsPointed);
|
|
_rotateToFaceSystem.TryFaceCoordinates(player, mapCoordsPointed.Position);
|
|
|
|
var arrow = Spawn("PointingArrow", coordsPointed);
|
|
|
|
if (TryComp<PointingArrowComponent>(arrow, out var pointing))
|
|
{
|
|
pointing.StartPosition = _transform.ToCoordinates((arrow, Transform(arrow)), _transform.ToMapCoordinates(Transform(player).Coordinates)).Position;
|
|
pointing.EndTime = _gameTiming.CurTime + PointDuration;
|
|
|
|
Dirty(arrow, pointing);
|
|
}
|
|
|
|
if (EntityQuery<PointingArrowAngeringComponent>().FirstOrDefault() != null)
|
|
{
|
|
if (TryComp<PointingArrowComponent>(arrow, out var pointingArrowComponent))
|
|
{
|
|
pointingArrowComponent.Rogue = true;
|
|
}
|
|
}
|
|
|
|
var layer = (int) VisibilityFlags.Normal;
|
|
if (TryComp(player, out VisibilityComponent? playerVisibility))
|
|
{
|
|
var arrowVisibility = EnsureComp<VisibilityComponent>(arrow);
|
|
layer = playerVisibility.Layer;
|
|
_visibilitySystem.SetLayer((arrow, arrowVisibility), (ushort) layer);
|
|
}
|
|
|
|
// Get players that are in range and whose visibility layer matches the arrow's.
|
|
bool ViewerPredicate(ICommonSession playerSession)
|
|
{
|
|
if (!_minds.TryGetMind(playerSession, out _, out var mind) ||
|
|
mind.CurrentEntity is not { Valid: true } ent ||
|
|
!TryComp(ent, out EyeComponent? eyeComp) ||
|
|
(eyeComp.VisibilityMask & layer) == 0)
|
|
return false;
|
|
|
|
return _transform.GetMapCoordinates(ent).InRange(_transform.GetMapCoordinates(player), PointingRange);
|
|
}
|
|
|
|
var viewers = Filter.Empty()
|
|
.AddWhere(session1 => ViewerPredicate(session1))
|
|
.Recipients;
|
|
|
|
string selfMessage;
|
|
string viewerMessage;
|
|
string? viewerPointedAtMessage = null;
|
|
var playerName = Identity.Entity(player, EntityManager);
|
|
|
|
if (Exists(pointed))
|
|
{
|
|
var pointedName = Identity.Entity(pointed, EntityManager);
|
|
|
|
EntityUid? containingInventory = null;
|
|
// Search up through the target's containing containers until we find an inventory
|
|
foreach (var container in _container.GetContainingContainers(pointed))
|
|
{
|
|
if (_inventoryQuery.HasComp(container.Owner))
|
|
{
|
|
// We want the innermost inventory, since that's the "owner" of the item
|
|
containingInventory = container.Owner;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var pointingAtSelf = player == pointed;
|
|
|
|
// Are we in a mob's inventory?
|
|
if (containingInventory != null)
|
|
{
|
|
var item = pointed;
|
|
var itemName = Identity.Entity(item, EntityManager);
|
|
|
|
// Target the pointing at the item's holder
|
|
pointed = containingInventory.Value;
|
|
pointedName = Identity.Entity(pointed, EntityManager);
|
|
var pointingAtOwnItem = player == pointed;
|
|
|
|
if (pointingAtOwnItem)
|
|
{
|
|
// You point at your item
|
|
selfMessage = Loc.GetString("pointing-system-point-in-own-inventory-self", ("item", itemName));
|
|
// Urist McPointer points at his item
|
|
viewerMessage = Loc.GetString("pointing-system-point-in-own-inventory-others", ("item", itemName), ("pointer", playerName));
|
|
}
|
|
else
|
|
{
|
|
// You point at Urist McHands' item
|
|
selfMessage = Loc.GetString("pointing-system-point-in-other-inventory-self", ("item", itemName), ("wearer", pointedName));
|
|
// Urist McPointer points at Urist McWearer's item
|
|
viewerMessage = Loc.GetString("pointing-system-point-in-other-inventory-others", ("item", itemName), ("pointer", playerName), ("wearer", pointedName));
|
|
// Urist McPointer points at your item
|
|
viewerPointedAtMessage = Loc.GetString("pointing-system-point-in-other-inventory-target", ("item", itemName), ("pointer", playerName));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
selfMessage = pointingAtSelf
|
|
// You point at yourself
|
|
? Loc.GetString("pointing-system-point-at-self")
|
|
// You point at Urist McTarget
|
|
: Loc.GetString("pointing-system-point-at-other", ("other", pointedName));
|
|
|
|
viewerMessage = pointingAtSelf
|
|
// Urist McPointer points at himself
|
|
? Loc.GetString("pointing-system-point-at-self-others", ("otherName", playerName), ("other", playerName))
|
|
// Urist McPointer points at Urist McTarget
|
|
: Loc.GetString("pointing-system-point-at-other-others", ("otherName", playerName), ("other", pointedName));
|
|
|
|
// Urist McPointer points at you
|
|
viewerPointedAtMessage = Loc.GetString("pointing-system-point-at-you-other", ("otherName", playerName));
|
|
}
|
|
|
|
var ev = new AfterPointedAtEvent(pointed);
|
|
RaiseLocalEvent(player, ref ev);
|
|
var gotev = new AfterGotPointedAtEvent(player);
|
|
RaiseLocalEvent(pointed, ref gotev);
|
|
|
|
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(player):user} pointed at {ToPrettyString(pointed):target} {Transform(pointed).Coordinates}");
|
|
}
|
|
else
|
|
{
|
|
TileRef? tileRef = null;
|
|
string? position = null;
|
|
|
|
if (_map.TryFindGridAt(mapCoordsPointed, out var gridUid, out var grid))
|
|
{
|
|
position = $"EntId={gridUid} {_map.WorldToTile(gridUid, grid, mapCoordsPointed.Position)}";
|
|
tileRef = _map.GetTileRef(gridUid, grid, _map.WorldToTile(gridUid, grid, mapCoordsPointed.Position));
|
|
}
|
|
|
|
var tileDef = _tileDefinitionManager[tileRef?.Tile.TypeId ?? 0];
|
|
|
|
var name = Loc.GetString(tileDef.Name);
|
|
selfMessage = Loc.GetString("pointing-system-point-at-tile", ("tileName", name));
|
|
|
|
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 ? mapCoordsPointed : position)}");
|
|
}
|
|
|
|
_pointers[session] = _gameTiming.CurTime;
|
|
|
|
SendMessage(player, viewers, pointed, selfMessage, viewerMessage, viewerPointedAtMessage);
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<PointingArrowComponent, ComponentGetState>(GetCompState);
|
|
|
|
SubscribeNetworkEvent<PointingAttemptEvent>(OnPointAttempt);
|
|
|
|
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
|
|
|
CommandBinds.Builder
|
|
.Bind(ContentKeyFunctions.Point, new PointerInputCmdHandler(TryPoint))
|
|
.Register<PointingSystem>();
|
|
|
|
Subs.CVar(_config, CCVars.PointingCooldownSeconds, v => _pointDelay = TimeSpan.FromSeconds(v), true);
|
|
}
|
|
|
|
private void OnPointAttempt(PointingAttemptEvent ev, EntitySessionEventArgs args)
|
|
{
|
|
var target = GetEntity(ev.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}");
|
|
}
|
|
|
|
public override void Shutdown()
|
|
{
|
|
base.Shutdown();
|
|
|
|
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
|
_pointers.Clear();
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
var currentTime = _gameTiming.CurTime;
|
|
|
|
var query = AllEntityQuery<PointingArrowComponent>();
|
|
while (query.MoveNext(out var uid, out var component))
|
|
{
|
|
Update((uid, component), currentTime);
|
|
}
|
|
}
|
|
|
|
private void Update(Entity<PointingArrowComponent> pointing, TimeSpan currentTime)
|
|
{
|
|
// TODO: That pause PR
|
|
var component = pointing.Comp;
|
|
if (component.EndTime > currentTime)
|
|
return;
|
|
|
|
if (component.Rogue)
|
|
{
|
|
RemComp<PointingArrowComponent>(pointing);
|
|
EnsureComp<RoguePointingArrowComponent>(pointing);
|
|
return;
|
|
}
|
|
|
|
Del(pointing);
|
|
}
|
|
}
|
|
}
|