2022-05-27 02:41:18 -05:00
using System.Linq ;
2022-12-08 19:18:13 -06:00
using Content.Server.Administration.Logs ;
2021-06-09 22:19:39 +02:00
using Content.Server.Pointing.Components ;
using Content.Server.Visible ;
2022-08-16 05:22:16 +01:00
using Content.Shared.Bed.Sleep ;
2022-12-08 19:18:13 -06:00
using Content.Shared.Database ;
2023-08-25 18:50:46 +10:00
using Content.Shared.Ghost ;
2022-07-10 18:36:53 -07:00
using Content.Shared.IdentityManagement ;
2020-07-24 14:51:18 +02:00
using Content.Shared.Input ;
2021-11-02 18:38:47 +00:00
using Content.Shared.Interaction ;
2021-06-09 22:19:39 +02:00
using Content.Shared.Interaction.Helpers ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Mind ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Systems ;
2022-02-10 16:27:34 +13:00
using Content.Shared.Pointing ;
2021-12-06 00:52:58 +01:00
using Content.Shared.Popups ;
2020-07-24 14:51:18 +02:00
using JetBrains.Annotations ;
2021-02-11 01:13:03 -08:00
using Robust.Server.GameObjects ;
2020-07-24 14:51:18 +02:00
using Robust.Server.Player ;
using Robust.Shared.Enums ;
using Robust.Shared.Input.Binding ;
using Robust.Shared.Map ;
2021-11-22 23:11:48 -08:00
using Robust.Shared.Player ;
2020-07-24 14:51:18 +02:00
using Robust.Shared.Players ;
2022-11-22 13:49:48 +13:00
using Robust.Shared.Replays ;
2021-02-11 01:13:03 -08:00
using Robust.Shared.Timing ;
2020-07-24 14:51:18 +02:00
2021-06-09 22:19:39 +02:00
namespace Content.Server.Pointing.EntitySystems
2020-07-24 14:51:18 +02:00
{
[UsedImplicitly]
2022-09-17 00:37:15 +10:00
internal sealed class PointingSystem : SharedPointingSystem
2020-07-24 14:51:18 +02:00
{
2022-11-22 13:49:48 +13:00
[Dependency] private readonly IReplayRecordingManager _replay = default ! ;
2020-07-24 14:51:18 +02:00
[Dependency] private readonly IMapManager _mapManager = default ! ;
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default ! ;
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
2021-11-02 18:38:47 +00:00
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default ! ;
2023-01-13 16:57:10 -08:00
[Dependency] private readonly MobStateSystem _mobState = default ! ;
2022-08-25 23:56:56 +10:00
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
2021-12-29 05:12:28 +11:00
[Dependency] private readonly VisibilitySystem _visibilitySystem = default ! ;
2023-08-30 21:46:11 -07:00
[Dependency] private readonly SharedMindSystem _minds = default ! ;
2022-12-08 19:18:13 -06:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2020-07-24 14:51:18 +02:00
private static readonly TimeSpan PointDelay = TimeSpan . FromSeconds ( 0.5f ) ;
/// <summary>
/// A dictionary of players to the last time that they
/// pointed at something.
/// </summary>
2020-11-27 11:00:49 +01:00
private readonly Dictionary < ICommonSession , TimeSpan > _pointers = new ( ) ;
2020-07-24 14:51:18 +02:00
2020-08-21 13:32:04 +02:00
private const float PointingRange = 15f ;
2020-07-24 14:51:18 +02:00
private void OnPlayerStatusChanged ( object? sender , SessionStatusEventArgs e )
{
if ( e . NewStatus ! = SessionStatus . Disconnected )
{
return ;
}
_pointers . Remove ( e . Session ) ;
}
// TODO: FOV
2021-12-05 18:09:01 +01:00
private void SendMessage ( EntityUid source , IEnumerable < ICommonSession > viewers , EntityUid pointed , string selfMessage ,
2020-07-24 14:51:18 +02:00
string viewerMessage , string? viewerPointedAtMessage = null )
{
foreach ( var viewer in viewers )
{
2021-12-06 00:52:58 +01:00
if ( viewer . AttachedEntity is not { Valid : true } viewerEntity )
2020-07-24 14:51:18 +02:00
{
continue ;
}
var message = viewerEntity = = source
? selfMessage
: viewerEntity = = pointed & & viewerPointedAtMessage ! = null
? viewerPointedAtMessage
: viewerMessage ;
2022-11-22 13:49:48 +13:00
RaiseNetworkEvent ( new PopupEntityEvent ( message , PopupType . Small , source ) , viewerEntity ) ;
2020-07-24 14:51:18 +02:00
}
2022-11-22 13:49:48 +13:00
2023-06-19 05:23:31 +12:00
_replay . RecordServerMessage ( new PopupEntityEvent ( viewerMessage , PopupType . Small , source ) ) ;
2020-07-24 14:51:18 +02:00
}
2021-12-05 18:09:01 +01:00
public bool InRange ( EntityUid pointer , EntityCoordinates coordinates )
2020-07-24 14:51:18 +02:00
{
2021-12-14 15:21:54 +13:00
if ( HasComp < GhostComponent > ( pointer ) )
2021-06-21 02:13:54 +02:00
{
2021-12-14 15:21:54 +13:00
return Transform ( pointer ) . Coordinates . InRange ( EntityManager , coordinates , 15 ) ;
2020-12-24 10:33:07 -03:00
}
else
{
return pointer . InRangeUnOccluded ( coordinates , 15 , e = > e = = pointer ) ;
2021-02-04 00:20:48 +11:00
}
2020-07-24 14:51:18 +02:00
}
2021-12-06 00:52:58 +01:00
public bool TryPoint ( ICommonSession ? session , EntityCoordinates coords , EntityUid pointed )
2020-07-24 14:51:18 +02:00
{
2022-12-21 11:29:38 +13:00
if ( session ? . AttachedEntity is not { } player )
{
Logger . Warning ( $"Player {session} attempted to point without any attached entity" ) ;
return false ;
}
if ( ! coords . IsValid ( EntityManager ) )
2020-07-24 14:51:18 +02:00
{
2022-12-21 11:29:38 +13:00
Logger . Warning ( $"Player {ToPrettyString(player)} attempted to point at invalid coordinates: {coords}" ) ;
2020-07-24 14:51:18 +02:00
return false ;
}
2023-01-19 03:56:45 +01:00
if ( _pointers . TryGetValue ( session , out var lastTime ) & &
2020-07-24 14:51:18 +02:00
_gameTiming . CurTime < lastTime + PointDelay )
{
return false ;
}
2021-12-14 15:21:54 +13:00
if ( HasComp < PointingArrowComponent > ( pointed ) )
2020-07-29 12:36:31 +02:00
{
// this is a pointing arrow. no pointing here...
return false ;
}
2021-12-14 15:21:54 +13:00
// 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.
2022-08-25 23:56:56 +10:00
if ( _mobState . IsIncapacitated ( player ) )
2021-12-14 15:21:54 +13:00
{
return false ;
}
2022-08-16 05:22:16 +01:00
if ( HasComp < SleepingComponent > ( player ) )
{
return false ;
}
2020-11-03 11:25:31 +01:00
if ( ! InRange ( player , coords ) )
2020-07-24 14:51:18 +02:00
{
2022-12-19 10:41:47 +13:00
_popup . PopupEntity ( Loc . GetString ( "pointing-system-try-point-cannot-reach" ) , player , player ) ;
2020-07-24 14:51:18 +02:00
return false ;
}
2022-12-21 11:29:38 +13:00
var mapCoords = coords . ToMap ( EntityManager ) ;
2021-11-02 18:38:47 +00:00
_rotateToFaceSystem . TryFaceCoordinates ( player , mapCoords . Position ) ;
2020-07-24 14:51:18 +02:00
2022-12-24 19:47:56 -05:00
var arrow = EntityManager . SpawnEntity ( "PointingArrow" , coords ) ;
2022-09-17 00:37:15 +10:00
if ( TryComp < PointingArrowComponent > ( arrow , out var pointing ) )
{
pointing . EndTime = _gameTiming . CurTime + TimeSpan . FromSeconds ( 4 ) ;
}
2020-08-16 16:26:07 +02:00
2022-05-27 02:41:18 -05:00
if ( EntityQuery < PointingArrowAngeringComponent > ( ) . FirstOrDefault ( ) ! = null )
{
if ( TryComp < PointingArrowComponent > ( arrow , out var pointingArrowComponent ) )
{
pointingArrowComponent . Rogue = true ;
}
}
2021-06-21 02:13:54 +02:00
var layer = ( int ) VisibilityFlags . Normal ;
2021-12-14 15:21:54 +13:00
if ( TryComp ( player , out VisibilityComponent ? playerVisibility ) )
2020-08-16 16:26:07 +02:00
{
2021-12-29 05:12:28 +11:00
var arrowVisibility = EntityManager . EnsureComponent < VisibilityComponent > ( arrow ) ;
layer = playerVisibility . Layer ;
_visibilitySystem . SetLayer ( arrowVisibility , layer ) ;
2020-08-16 16:26:07 +02:00
}
2020-07-24 14:51:18 +02:00
2020-08-21 13:32:04 +02:00
// Get players that are in range and whose visibility layer matches the arrow's.
2021-11-22 23:11:48 -08:00
bool ViewerPredicate ( IPlayerSession playerSession )
2020-08-21 13:32:04 +02:00
{
2023-08-28 16:53:24 -07:00
if ( ! _minds . TryGetMind ( playerSession , out _ , out var mind ) | |
mind . CurrentEntity is not { Valid : true } ent | |
2021-12-14 15:21:54 +13:00
! TryComp ( ent , out EyeComponent ? eyeComp ) | |
2021-12-06 00:52:58 +01:00
( eyeComp . VisibilityMask & layer ) = = 0 )
return false ;
2020-08-21 13:32:04 +02:00
2021-12-14 15:21:54 +13:00
return Transform ( ent ) . MapPosition . InRange ( Transform ( player ) . MapPosition , PointingRange ) ;
2021-11-22 23:11:48 -08:00
}
var viewers = Filter . Empty ( )
. AddWhere ( session1 = > ViewerPredicate ( ( IPlayerSession ) session1 ) )
. Recipients ;
2020-08-21 13:32:04 +02:00
2020-07-24 14:51:18 +02:00
string selfMessage ;
string viewerMessage ;
string? viewerPointedAtMessage = null ;
2022-07-10 18:36:53 -07:00
var playerName = Identity . Entity ( player , EntityManager ) ;
2020-07-24 14:51:18 +02:00
2021-12-14 15:21:54 +13:00
if ( Exists ( pointed ) )
2020-07-24 14:51:18 +02:00
{
2022-07-10 18:36:53 -07:00
var pointedName = Identity . Entity ( pointed , EntityManager ) ;
2021-12-14 15:21:54 +13:00
2020-07-24 14:51:18 +02:00
selfMessage = player = = pointed
2021-06-21 02:13:54 +02:00
? Loc . GetString ( "pointing-system-point-at-self" )
2021-12-14 15:21:54 +13:00
: Loc . GetString ( "pointing-system-point-at-other" , ( "other" , pointedName ) ) ;
2020-07-24 14:51:18 +02:00
viewerMessage = player = = pointed
2021-12-14 15:21:54 +13:00
? Loc . GetString ( "pointing-system-point-at-self-others" , ( "otherName" , playerName ) , ( "other" , playerName ) )
: Loc . GetString ( "pointing-system-point-at-other-others" , ( "otherName" , playerName ) , ( "other" , pointedName ) ) ;
2020-07-24 14:51:18 +02:00
2021-12-14 15:21:54 +13:00
viewerPointedAtMessage = Loc . GetString ( "pointing-system-point-at-you-other" , ( "otherName" , playerName ) ) ;
2022-12-08 19:18:13 -06:00
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(player):user} pointed at {ToPrettyString(pointed):target} {Transform(pointed).Coordinates}" ) ;
2020-07-24 14:51:18 +02:00
}
else
{
2021-08-06 18:27:37 +02:00
TileRef ? tileRef = null ;
2022-12-08 19:18:13 -06:00
string? position = null ;
2021-08-06 18:27:37 +02:00
2023-05-28 23:22:44 +10:00
if ( _mapManager . TryFindGridAt ( mapCoords , out var gridUid , out var grid ) )
2021-08-06 18:27:37 +02:00
{
2023-05-28 23:22:44 +10:00
position = $"EntId={gridUid} {grid.WorldToTile(mapCoords.Position)}" ;
2021-08-06 18:27:37 +02:00
tileRef = grid . GetTileRef ( grid . WorldToTile ( mapCoords . Position ) ) ;
}
var tileDef = _tileDefinitionManager [ tileRef ? . Tile . TypeId ? ? 0 ] ;
2020-07-24 14:51:18 +02:00
2022-12-20 23:25:34 +01:00
var name = Loc . GetString ( tileDef . Name ) ;
selfMessage = Loc . GetString ( "pointing-system-point-at-tile" , ( "tileName" , name ) ) ;
2020-07-24 14:51:18 +02:00
2023-02-04 21:02:24 -05:00
viewerMessage = Loc . GetString ( "pointing-system-other-point-at-tile" , ( "otherName" , playerName ) , ( "tileName" , name ) ) ;
2022-12-08 19:18:13 -06:00
2022-12-20 23:25:34 +01:00
_adminLogger . Add ( LogType . Action , LogImpact . Low , $"{ToPrettyString(player):user} pointed at {name} {(position == null ? mapCoords : position)}" ) ;
2020-07-24 14:51:18 +02:00
}
2021-12-06 00:52:58 +01:00
_pointers [ session ] = _gameTiming . CurTime ;
2020-07-24 14:51:18 +02:00
SendMessage ( player , viewers , pointed , selfMessage , viewerMessage , viewerPointedAtMessage ) ;
return true ;
}
public override void Initialize ( )
{
base . Initialize ( ) ;
2022-02-10 16:27:34 +13:00
SubscribeNetworkEvent < PointingAttemptEvent > ( OnPointAttempt ) ;
2021-10-05 14:29:03 +11:00
2020-07-24 14:51:18 +02:00
_playerManager . PlayerStatusChanged + = OnPlayerStatusChanged ;
CommandBinds . Builder
. Bind ( ContentKeyFunctions . Point , new PointerInputCmdHandler ( TryPoint ) )
. Register < PointingSystem > ( ) ;
}
2022-02-10 16:27:34 +13:00
private void OnPointAttempt ( PointingAttemptEvent ev , EntitySessionEventArgs args )
2021-10-05 14:29:03 +11:00
{
2022-12-21 11:29:38 +13:00
if ( TryComp ( ev . Target , out TransformComponent ? xform ) )
TryPoint ( args . SenderSession , xform . Coordinates , ev . Target ) ;
else
Logger . Warning ( $"User {args.SenderSession} attempted to point at a non-existent entity uid: {ev.Target}" ) ;
2021-10-05 14:29:03 +11:00
}
2020-07-24 14:51:18 +02:00
public override void Shutdown ( )
{
base . Shutdown ( ) ;
_playerManager . PlayerStatusChanged - = OnPlayerStatusChanged ;
_pointers . Clear ( ) ;
}
public override void Update ( float frameTime )
{
2022-09-17 00:37:15 +10:00
var currentTime = _gameTiming . CurTime ;
foreach ( var component in EntityQuery < PointingArrowComponent > ( true ) )
2020-07-24 14:51:18 +02:00
{
2022-09-17 00:37:15 +10:00
Update ( component , currentTime ) ;
2020-07-24 14:51:18 +02:00
}
}
2022-09-17 00:37:15 +10:00
private void Update ( PointingArrowComponent component , TimeSpan currentTime )
{
// TODO: That pause PR
if ( component . EndTime > currentTime )
return ;
if ( component . Rogue )
{
RemComp < PointingArrowComponent > ( component . Owner ) ;
EnsureComp < RoguePointingArrowComponent > ( component . Owner ) ;
return ;
}
Del ( component . Owner ) ;
}
2020-07-24 14:51:18 +02:00
}
}