2023-12-28 19:02:21 -05:00
using Content.Server.Administration.Logs ;
2024-04-17 12:59:31 -05:00
using Content.Server.Atmos.Components ;
using Content.Server.Atmos.EntitySystems ;
2023-04-13 21:13:24 +10:00
using Content.Server.Station.Systems ;
2023-09-16 18:11:47 +10:00
using Content.Server.Warps ;
2023-12-28 19:02:21 -05:00
using Content.Shared.Database ;
using Content.Shared.Examine ;
2024-03-28 01:53:18 -04:00
using Content.Shared.Localizations ;
2024-04-17 12:59:31 -05:00
using Content.Shared.Maps ;
2023-04-13 16:21:24 +10:00
using Content.Shared.Pinpointer ;
2024-03-28 01:53:18 -04:00
using JetBrains.Annotations ;
using Robust.Shared.Map ;
2023-04-13 16:21:24 +10:00
using Robust.Shared.Map.Components ;
2024-04-17 12:59:31 -05:00
using Robust.Shared.Timing ;
using System.Diagnostics.CodeAnalysis ;
2025-04-28 13:42:24 -04:00
using Content.Shared.Warps ;
2023-04-13 16:21:24 +10:00
namespace Content.Server.Pinpointer ;
/// <summary>
/// Handles data to be used for in-grid map displays.
/// </summary>
2024-04-17 12:59:31 -05:00
public sealed partial class NavMapSystem : SharedNavMapSystem
2023-04-13 16:21:24 +10:00
{
2023-12-28 19:02:21 -05:00
[Dependency] private readonly IAdminLogManager _adminLog = default ! ;
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
2024-04-17 12:59:31 -05:00
[Dependency] private readonly SharedMapSystem _mapSystem = default ! ;
[Dependency] private readonly SharedTransformSystem _transformSystem = default ! ;
2024-03-28 01:53:18 -04:00
[Dependency] private readonly IMapManager _mapManager = default ! ;
2024-04-17 12:59:31 -05:00
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
2025-06-21 08:23:19 -07:00
[Dependency] private readonly TurfSystem _turfSystem = default ! ;
2023-09-16 18:11:47 +10:00
2024-03-28 01:53:18 -04:00
public const float CloseDistance = 15f ;
public const float FarDistance = 30f ;
2024-05-02 10:18:38 +10:00
private EntityQuery < AirtightComponent > _airtightQuery ;
private EntityQuery < MapGridComponent > _gridQuery ;
private EntityQuery < NavMapComponent > _navQuery ;
2023-04-13 16:21:24 +10:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2023-09-16 18:11:47 +10:00
2024-05-02 14:51:21 +12:00
var categories = Enum . GetNames ( typeof ( NavMapChunkType ) ) . Length - 1 ; // -1 due to "Invalid" entry.
if ( Categories ! = categories )
throw new Exception ( $"{nameof(Categories)} must be equal to the number of chunk types" ) ;
2024-05-02 10:18:38 +10:00
_airtightQuery = GetEntityQuery < AirtightComponent > ( ) ;
_gridQuery = GetEntityQuery < MapGridComponent > ( ) ;
_navQuery = GetEntityQuery < NavMapComponent > ( ) ;
2024-04-17 12:59:31 -05:00
// Initialization events
2023-09-16 18:11:47 +10:00
SubscribeLocalEvent < StationGridAddedEvent > ( OnStationInit ) ;
2024-04-17 12:59:31 -05:00
// Grid change events
Power monitoring console overhaul (#20927)
* Prototyping whole station wire map
* More prototyping
* Added icons for the different power distributors and toggleable cable displays
* Power cable layouts are now only sent to the client when the power monitor is open
* UI prototyping
* Power monitors can now see the sprites of distant entities, long entity names are truncated
* Updated how network devices are added to the player's PVS
* More feature prototypes
* Added source / load symbols
* Final prototype! Time to actually code it properly...
* Start of code clean up
* Continuing code clean up
* Fixed UI appearance
* Code clean up complete
* Removed unnecessary changes
* Updated how power values are calculated, added UI warnings for power sinks and power net checks
* Updated how power values are calculated again, added support for portable generators
* Removed unnecessary files
* Map beacons start toggled off, console map now works outside the station, fixed substation icon
* Made some of Sloth's requested changes. Power distributors don't blink anymore, unless selected
* Moved a number of static variables in PowerMonitoringHelper to sensible places in the main files. Added a NavMapTrackableComponent so that you can specify how individual entities appear on the navmap
* Updated the colors/positions of HV cables and SMESes to improve contrast
* Fixed SMES color in map legend
* Partially fixed auto-scrolling on device selection, made sublists alphabetical
* Changed how auto-scroll is handled
* Changed the font color of the console warning messages
* Reduced the font size of beacon labels
* Added the station name to the console
* Organized references
* Removed unwanted changes to RobustToolbox
* Fix merge conflict
* Fix merge conflict, maybe
* Fix merge conflict
* Updated outdated reference
* Fixed portable_generator.yml
* Implemented a number of requested changes, move bit masks to a shared component
* Navigate listings via the navmap
* First attempt at improving efficiency
* Second attempt at optimization, entity grouping added for solar panels
* Finished solar panel entity joining
* Finished major revisions, code clean up needed
* Finializing optimizations
* Made requested changes
* Bug fix, removed obsolete code
* Bug fixes
* Bug fixes
* STarted revisions
* Further revisions
* More revision
* Finalizing revisions. Need to make RT PR
* Code tidying
* More code tidying
* Trying to avoid merge conflicts
* Trying to avoid merge conflicts
* Removed use of PVS
* Improving efficiency
* Addressed a bunch of outstanding issues
* Clear old data on console refresh
* UI adjustments
* Made node comparison more robust. More devices can be combined into one entry
* Added missing component 'dirty'
2023-12-24 00:07:41 -06:00
SubscribeLocalEvent < GridSplitEvent > ( OnNavMapSplit ) ;
2024-04-17 12:59:31 -05:00
SubscribeLocalEvent < TileChangedEvent > ( OnTileChanged ) ;
2023-09-16 18:11:47 +10:00
2024-05-02 10:18:38 +10:00
SubscribeLocalEvent < AirtightChanged > ( OnAirtightChange ) ;
2024-04-17 12:59:31 -05:00
// Beacon events
2024-03-28 01:53:18 -04:00
SubscribeLocalEvent < NavMapBeaconComponent , MapInitEvent > ( OnNavMapBeaconMapInit ) ;
2023-09-16 18:11:47 +10:00
SubscribeLocalEvent < NavMapBeaconComponent , AnchorStateChangedEvent > ( OnNavMapBeaconAnchor ) ;
2023-12-28 19:02:21 -05:00
SubscribeLocalEvent < ConfigurableNavMapBeaconComponent , NavMapBeaconConfigureBuiMessage > ( OnConfigureMessage ) ;
SubscribeLocalEvent < ConfigurableNavMapBeaconComponent , MapInitEvent > ( OnConfigurableMapInit ) ;
SubscribeLocalEvent < ConfigurableNavMapBeaconComponent , ExaminedEvent > ( OnConfigurableExamined ) ;
2023-04-13 21:13:24 +10:00
}
private void OnStationInit ( StationGridAddedEvent ev )
{
var comp = EnsureComp < NavMapComponent > ( ev . GridId ) ;
2024-03-19 23:27:02 -04:00
RefreshGrid ( ev . GridId , comp , Comp < MapGridComponent > ( ev . GridId ) ) ;
2023-09-16 18:11:47 +10:00
}
2024-04-17 12:59:31 -05:00
#region : Grid change event handling
private void OnNavMapSplit ( ref GridSplitEvent args )
2024-03-28 01:53:18 -04:00
{
2024-05-02 10:18:38 +10:00
if ( ! _navQuery . TryComp ( args . Grid , out var comp ) )
2024-03-28 01:53:18 -04:00
return ;
2024-04-17 12:59:31 -05:00
foreach ( var grid in args . NewGrids )
{
var newComp = EnsureComp < NavMapComponent > ( grid ) ;
2024-05-02 10:18:38 +10:00
RefreshGrid ( grid , newComp , _gridQuery . GetComponent ( grid ) ) ;
}
RefreshGrid ( args . Grid , comp , _gridQuery . GetComponent ( args . Grid ) ) ;
}
private NavMapChunk EnsureChunk ( NavMapComponent component , Vector2i origin )
{
if ( ! component . Chunks . TryGetValue ( origin , out var chunk ) )
{
chunk = new ( origin ) ;
component . Chunks [ origin ] = chunk ;
2024-04-17 12:59:31 -05:00
}
2024-05-02 10:18:38 +10:00
return chunk ;
2024-03-28 01:53:18 -04:00
}
2024-04-17 12:59:31 -05:00
private void OnTileChanged ( ref TileChangedEvent ev )
2023-09-16 18:11:47 +10:00
{
2025-05-16 22:22:20 +10:00
if ( ! _navQuery . TryComp ( ev . Entity , out var navMap ) )
return ;
2025-05-15 06:26:47 -04:00
foreach ( var change in ev . Changes )
{
2025-05-16 22:22:20 +10:00
if ( ! change . EmptyChanged )
continue ;
2024-04-17 12:59:31 -05:00
2025-05-15 06:26:47 -04:00
var tile = change . GridIndices ;
var chunkOrigin = SharedMapSystem . GetChunkIndices ( tile , ChunkSize ) ;
2024-04-17 12:59:31 -05:00
2025-05-15 06:26:47 -04:00
var chunk = EnsureChunk ( navMap , chunkOrigin ) ;
2024-04-17 12:59:31 -05:00
2025-05-15 06:26:47 -04:00
// This could be easily replaced in the future to accommodate diagonal tiles
var relative = SharedMapSystem . GetChunkRelative ( tile , ChunkSize ) ;
ref var tileData = ref chunk . TileData [ GetTileIndex ( relative ) ] ;
2024-05-02 14:51:21 +12:00
2025-06-21 08:23:19 -07:00
if ( _turfSystem . IsSpace ( change . NewTile ) )
2025-05-15 06:26:47 -04:00
{
tileData = 0 ;
if ( PruneEmpty ( ( ev . Entity , navMap ) , chunk ) )
2025-05-16 22:22:20 +10:00
continue ;
2025-05-15 06:26:47 -04:00
}
else
{
tileData = FloorMask ;
}
2024-05-02 14:51:21 +12:00
2025-05-15 06:26:47 -04:00
DirtyChunk ( ( ev . Entity , navMap ) , chunk ) ;
}
2024-05-02 14:51:21 +12:00
}
private void DirtyChunk ( Entity < NavMapComponent > entity , NavMapChunk chunk )
{
if ( chunk . LastUpdate = = _gameTiming . CurTick )
return ;
chunk . LastUpdate = _gameTiming . CurTick ;
Dirty ( entity ) ;
2023-09-16 18:11:47 +10:00
}
2024-05-02 10:18:38 +10:00
private void OnAirtightChange ( ref AirtightChanged args )
2023-09-16 18:11:47 +10:00
{
2024-05-02 10:18:38 +10:00
if ( args . AirBlockedChanged )
return ;
2024-04-17 12:59:31 -05:00
2024-05-02 10:18:38 +10:00
var gridUid = args . Position . Grid ;
if ( ! _navQuery . TryComp ( gridUid , out var navMap ) | |
! _gridQuery . TryComp ( gridUid , out var mapGrid ) )
{
2024-04-17 12:59:31 -05:00
return ;
2024-05-02 10:18:38 +10:00
}
2024-04-17 12:59:31 -05:00
2024-05-02 10:18:38 +10:00
var chunkOrigin = SharedMapSystem . GetChunkIndices ( args . Position . Tile , ChunkSize ) ;
2024-05-02 14:51:21 +12:00
var ( newValue , chunk ) = RefreshTileEntityContents ( gridUid , navMap , mapGrid , chunkOrigin , args . Position . Tile , setFloor : false ) ;
2024-04-17 12:59:31 -05:00
2024-05-02 14:51:21 +12:00
if ( newValue = = 0 & & PruneEmpty ( ( gridUid , navMap ) , chunk ) )
return ;
DirtyChunk ( ( gridUid , navMap ) , chunk ) ;
2023-09-16 18:11:47 +10:00
}
2024-04-17 12:59:31 -05:00
#endregion
#region : Beacon event handling
private void OnNavMapBeaconMapInit ( EntityUid uid , NavMapBeaconComponent component , MapInitEvent args )
2024-01-11 08:14:20 -05:00
{
2024-04-17 12:59:31 -05:00
if ( component . DefaultText = = null | | component . Text ! = null )
return ;
component . Text = Loc . GetString ( component . DefaultText ) ;
Dirty ( uid , component ) ;
UpdateNavMapBeaconData ( uid , component ) ;
2024-01-11 08:14:20 -05:00
}
2024-04-17 12:59:31 -05:00
private void OnNavMapBeaconAnchor ( EntityUid uid , NavMapBeaconComponent component , ref AnchorStateChangedEvent args )
2024-01-11 08:14:20 -05:00
{
2024-04-17 12:59:31 -05:00
UpdateBeaconEnabledVisuals ( ( uid , component ) ) ;
UpdateNavMapBeaconData ( uid , component ) ;
2024-01-11 08:14:20 -05:00
}
2023-12-28 19:02:21 -05:00
private void OnConfigureMessage ( Entity < ConfigurableNavMapBeaconComponent > ent , ref NavMapBeaconConfigureBuiMessage args )
{
2024-04-17 12:59:31 -05:00
if ( ! TryComp < NavMapBeaconComponent > ( ent , out var beacon ) )
2023-12-28 19:02:21 -05:00
return ;
2024-04-17 12:59:31 -05:00
if ( beacon . Text = = args . Text & &
beacon . Color = = args . Color & &
beacon . Enabled = = args . Enabled )
2023-12-28 19:02:21 -05:00
return ;
_adminLog . Add ( LogType . Action , LogImpact . Medium ,
2024-04-26 18:16:24 +10:00
$"{ToPrettyString(args.Actor):player} configured NavMapBeacon \'{ToPrettyString(ent):entity}\' with text \'{args.Text}\', color {args.Color.ToHexNoAlpha()}, and {(args.Enabled ? " enabled " : " disabled ")} it." ) ;
2023-12-28 19:02:21 -05:00
if ( TryComp < WarpPointComponent > ( ent , out var warpPoint ) )
{
warpPoint . Location = args . Text ;
}
2024-04-17 12:59:31 -05:00
beacon . Text = args . Text ;
beacon . Color = args . Color ;
beacon . Enabled = args . Enabled ;
2025-05-02 12:14:16 -04:00
Dirty ( ent , beacon ) ;
2024-04-17 12:59:31 -05:00
UpdateBeaconEnabledVisuals ( ( ent , beacon ) ) ;
UpdateNavMapBeaconData ( ent , beacon ) ;
2023-12-28 19:02:21 -05:00
}
private void OnConfigurableMapInit ( Entity < ConfigurableNavMapBeaconComponent > ent , ref MapInitEvent args )
{
if ( ! TryComp < NavMapBeaconComponent > ( ent , out var navMap ) )
return ;
// We set this on mapinit just in case the text was edited via VV or something.
if ( TryComp < WarpPointComponent > ( ent , out var warpPoint ) )
warpPoint . Location = navMap . Text ;
UpdateBeaconEnabledVisuals ( ( ent , navMap ) ) ;
}
private void OnConfigurableExamined ( Entity < ConfigurableNavMapBeaconComponent > ent , ref ExaminedEvent args )
{
if ( ! args . IsInDetailsRange | | ! TryComp < NavMapBeaconComponent > ( ent , out var navMap ) )
return ;
args . PushMarkup ( Loc . GetString ( "nav-beacon-examine-text" ,
( "enabled" , navMap . Enabled ) ,
( "color" , navMap . Color . ToHexNoAlpha ( ) ) ,
( "label" , navMap . Text ? ? string . Empty ) ) ) ;
}
2024-04-17 12:59:31 -05:00
#endregion
2023-09-16 18:11:47 +10:00
2024-04-17 12:59:31 -05:00
#region : Grid functions
2023-09-16 18:11:47 +10:00
2024-04-17 12:59:31 -05:00
private void RefreshGrid ( EntityUid uid , NavMapComponent component , MapGridComponent mapGrid )
2023-09-16 18:11:47 +10:00
{
2024-04-17 12:59:31 -05:00
// Clear stale data
component . Chunks . Clear ( ) ;
component . Beacons . Clear ( ) ;
2023-09-16 18:11:47 +10:00
2024-06-02 19:30:27 +02:00
// Refresh beacons
var query = EntityQueryEnumerator < NavMapBeaconComponent , TransformComponent > ( ) ;
while ( query . MoveNext ( out var qUid , out var qNavComp , out var qTransComp ) )
{
if ( qTransComp . ParentUid ! = uid )
continue ;
UpdateNavMapBeaconData ( qUid , qNavComp ) ;
}
2024-04-17 12:59:31 -05:00
// Loop over all tiles
var tileRefs = _mapSystem . GetAllTiles ( uid , mapGrid ) ;
2023-09-16 18:11:47 +10:00
2024-04-17 12:59:31 -05:00
foreach ( var tileRef in tileRefs )
{
var tile = tileRef . GridIndices ;
var chunkOrigin = SharedMapSystem . GetChunkIndices ( tile , ChunkSize ) ;
2023-09-16 18:11:47 +10:00
2024-05-02 10:18:38 +10:00
var chunk = EnsureChunk ( component , chunkOrigin ) ;
2024-04-17 12:59:31 -05:00
chunk . LastUpdate = _gameTiming . CurTick ;
2024-05-02 14:51:21 +12:00
RefreshTileEntityContents ( uid , component , mapGrid , chunkOrigin , tile , setFloor : true ) ;
2023-04-13 16:21:24 +10:00
}
2024-04-17 12:59:31 -05:00
Dirty ( uid , component ) ;
2023-04-13 16:21:24 +10:00
}
2024-05-02 14:51:21 +12:00
private ( int NewVal , NavMapChunk Chunk ) RefreshTileEntityContents ( EntityUid uid ,
NavMapComponent component ,
MapGridComponent mapGrid ,
Vector2i chunkOrigin ,
Vector2i tile ,
bool setFloor )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
var relative = SharedMapSystem . GetChunkRelative ( tile , ChunkSize ) ;
2024-05-02 10:18:38 +10:00
var chunk = EnsureChunk ( component , chunkOrigin ) ;
2024-05-02 14:51:21 +12:00
ref var tileData = ref chunk . TileData [ GetTileIndex ( relative ) ] ;
2023-04-13 16:21:24 +10:00
2024-05-02 14:51:21 +12:00
// Clear all data except for floor bits
if ( setFloor )
tileData = FloorMask ;
else
tileData & = FloorMask ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
var enumerator = _mapSystem . GetAnchoredEntitiesEnumerator ( uid , mapGrid , tile ) ;
while ( enumerator . MoveNext ( out var ent ) )
2023-04-13 16:21:24 +10:00
{
2024-05-02 10:18:38 +10:00
if ( ! _airtightQuery . TryComp ( ent , out var airtight ) )
2024-04-17 12:59:31 -05:00
continue ;
2023-04-13 16:21:24 +10:00
2024-05-02 14:51:21 +12:00
var category = GetEntityType ( ent . Value ) ;
if ( category = = NavMapChunkType . Invalid )
continue ;
2023-09-16 18:11:47 +10:00
2024-05-02 14:51:21 +12:00
var directions = ( int ) airtight . AirBlockedDirection ;
tileData | = directions < < ( int ) category ;
2023-09-16 18:11:47 +10:00
}
2024-04-17 12:59:31 -05:00
// Remove walls that intersect with doors (unless they can both physically fit on the same tile)
2024-05-02 14:51:21 +12:00
// TODO NAVMAP why can this even happen?
// Is this for blast-doors or something?
2024-05-02 10:18:38 +10:00
2024-05-02 14:51:21 +12:00
// Shift airlock bits over to the wall bits
var shiftedAirlockBits = ( tileData & AirlockMask ) > > ( ( int ) NavMapChunkType . Airlock - ( int ) NavMapChunkType . Wall ) ;
2024-05-02 10:18:38 +10:00
2024-05-02 14:51:21 +12:00
// And then mask door bits
tileData & = ~ shiftedAirlockBits ;
return ( tileData , chunk ) ;
2024-05-02 10:18:38 +10:00
}
private bool PruneEmpty ( Entity < NavMapComponent > entity , NavMapChunk chunk )
{
2024-05-02 14:51:21 +12:00
foreach ( var val in chunk . TileData )
2024-05-02 10:18:38 +10:00
{
2024-05-02 14:51:21 +12:00
// TODO NAVMAP SIMD
if ( val ! = 0 )
return false ;
2024-01-11 08:14:20 -05:00
}
2024-05-02 10:18:38 +10:00
entity . Comp . Chunks . Remove ( chunk . Origin ) ;
Dirty ( entity ) ;
return true ;
2023-04-13 16:21:24 +10:00
}
2024-04-17 12:59:31 -05:00
#endregion
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
#region : Beacon functions
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
private void UpdateNavMapBeaconData ( EntityUid uid , NavMapBeaconComponent component , TransformComponent ? xform = null )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
if ( ! Resolve ( uid , ref xform ) )
2023-04-13 16:21:24 +10:00
return ;
2024-04-17 12:59:31 -05:00
if ( xform . GridUid = = null )
return ;
2023-04-13 16:21:24 +10:00
2024-05-02 10:18:38 +10:00
if ( ! _navQuery . TryComp ( xform . GridUid , out var navMap ) )
2024-04-17 12:59:31 -05:00
return ;
2023-04-13 16:21:24 +10:00
2024-05-02 14:51:21 +12:00
var meta = MetaData ( uid ) ;
var changed = navMap . Beacons . Remove ( meta . NetEntity ) ;
2023-04-13 16:21:24 +10:00
2024-05-02 14:51:21 +12:00
if ( TryCreateNavMapBeaconData ( uid , component , xform , meta , out var beaconData ) )
2023-04-13 16:21:24 +10:00
{
2024-05-02 14:51:21 +12:00
navMap . Beacons . Add ( meta . NetEntity , beaconData . Value ) ;
2024-04-17 12:59:31 -05:00
changed = true ;
2023-04-13 16:21:24 +10:00
}
2024-04-17 12:59:31 -05:00
if ( changed )
Dirty ( xform . GridUid . Value , navMap ) ;
}
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
private void UpdateBeaconEnabledVisuals ( Entity < NavMapBeaconComponent > ent )
{
_appearance . SetData ( ent , NavMapBeaconVisuals . Enabled , ent . Comp . Enabled & & Transform ( ent ) . Anchored ) ;
2023-04-13 16:21:24 +10:00
}
2023-09-23 20:15:05 +01:00
/// <summary>
/// Sets the beacon's Enabled field and refreshes the grid.
/// </summary>
public void SetBeaconEnabled ( EntityUid uid , bool enabled , NavMapBeaconComponent ? comp = null )
{
if ( ! Resolve ( uid , ref comp ) | | comp . Enabled = = enabled )
return ;
comp . Enabled = enabled ;
2023-12-28 19:02:21 -05:00
UpdateBeaconEnabledVisuals ( ( uid , comp ) ) ;
2023-09-23 20:15:05 +01:00
}
/// <summary>
/// Toggles the beacon's Enabled field and refreshes the grid.
/// </summary>
public void ToggleBeacon ( EntityUid uid , NavMapBeaconComponent ? comp = null )
{
if ( ! Resolve ( uid , ref comp ) )
return ;
SetBeaconEnabled ( uid , ! comp . Enabled , comp ) ;
}
2024-03-28 01:53:18 -04:00
/// <summary>
/// For a given position, tries to find the nearest configurable beacon that is marked as visible.
/// This is used for things like announcements where you want to find the closest "landmark" to something.
/// </summary>
[PublicAPI]
public bool TryGetNearestBeacon ( Entity < TransformComponent ? > ent ,
[NotNullWhen(true)] out Entity < NavMapBeaconComponent > ? beacon ,
[NotNullWhen(true)] out MapCoordinates ? beaconCoords )
{
beacon = null ;
beaconCoords = null ;
if ( ! Resolve ( ent , ref ent . Comp ) )
return false ;
2024-04-17 12:59:31 -05:00
return TryGetNearestBeacon ( _transformSystem . GetMapCoordinates ( ent , ent . Comp ) , out beacon , out beaconCoords ) ;
2024-03-28 01:53:18 -04:00
}
/// <summary>
/// For a given position, tries to find the nearest configurable beacon that is marked as visible.
/// This is used for things like announcements where you want to find the closest "landmark" to something.
/// </summary>
public bool TryGetNearestBeacon ( MapCoordinates coordinates ,
[NotNullWhen(true)] out Entity < NavMapBeaconComponent > ? beacon ,
[NotNullWhen(true)] out MapCoordinates ? beaconCoords )
{
beacon = null ;
beaconCoords = null ;
var minDistance = float . PositiveInfinity ;
var query = EntityQueryEnumerator < ConfigurableNavMapBeaconComponent , NavMapBeaconComponent , TransformComponent > ( ) ;
while ( query . MoveNext ( out var uid , out _ , out var navBeacon , out var xform ) )
{
if ( ! navBeacon . Enabled )
continue ;
if ( navBeacon . Text = = null )
continue ;
if ( coordinates . MapId ! = xform . MapID )
continue ;
2024-04-17 12:59:31 -05:00
var coords = _transformSystem . GetWorldPosition ( xform ) ;
2024-03-28 01:53:18 -04:00
var distanceSquared = ( coordinates . Position - coords ) . LengthSquared ( ) ;
if ( ! float . IsInfinity ( minDistance ) & & distanceSquared > = minDistance )
continue ;
minDistance = distanceSquared ;
beacon = ( uid , navBeacon ) ;
beaconCoords = new MapCoordinates ( coords , xform . MapID ) ;
}
return beacon ! = null ;
}
2024-06-11 18:18:30 -04:00
/// <summary>
/// Returns a string describing the rough distance and direction
/// to the position of <paramref name="ent"/> from the nearest beacon.
/// </summary>
2024-03-28 01:53:18 -04:00
[PublicAPI]
2025-04-18 19:43:17 -04:00
public string GetNearestBeaconString ( Entity < TransformComponent ? > ent , bool onlyName = false )
2024-03-28 01:53:18 -04:00
{
if ( ! Resolve ( ent , ref ent . Comp ) )
return Loc . GetString ( "nav-beacon-pos-no-beacons" ) ;
2025-04-18 19:43:17 -04:00
return GetNearestBeaconString ( _transformSystem . GetMapCoordinates ( ent , ent . Comp ) , onlyName ) ;
2024-03-28 01:53:18 -04:00
}
2024-06-11 18:18:30 -04:00
/// <summary>
/// Returns a string describing the rough distance and direction
/// to <paramref name="coordinates"/> from the nearest beacon.
/// </summary>
2025-04-18 19:43:17 -04:00
public string GetNearestBeaconString ( MapCoordinates coordinates , bool onlyName = false )
2024-03-28 01:53:18 -04:00
{
if ( ! TryGetNearestBeacon ( coordinates , out var beacon , out var pos ) )
return Loc . GetString ( "nav-beacon-pos-no-beacons" ) ;
2025-04-18 19:43:17 -04:00
if ( onlyName )
return beacon . Value . Comp . Text ! ;
2024-03-28 01:53:18 -04:00
var gridOffset = Angle . Zero ;
if ( _mapManager . TryFindGridAt ( pos . Value , out var grid , out _ ) )
gridOffset = Transform ( grid ) . LocalRotation ;
// get the angle between the two positions, adjusted for the grid rotation so that
// we properly preserve north in relation to the grid.
2024-06-11 18:18:30 -04:00
var offset = coordinates . Position - pos . Value . Position ;
var dir = offset . ToWorldAngle ( ) ;
2024-03-28 01:53:18 -04:00
var adjustedDir = ( dir - gridOffset ) . GetDir ( ) ;
2024-06-11 18:18:30 -04:00
var length = offset . Length ( ) ;
2024-03-28 01:53:18 -04:00
if ( length < CloseDistance )
{
return Loc . GetString ( "nav-beacon-pos-format" ,
( "color" , beacon . Value . Comp . Color ) ,
( "marker" , beacon . Value . Comp . Text ! ) ) ;
}
var modifier = length > FarDistance
? Loc . GetString ( "nav-beacon-pos-format-direction-mod-far" )
: string . Empty ;
2024-04-17 12:59:31 -05:00
// we can null suppress the text being null because TryGetNearestVisibleStationBeacon always gives us a beacon with not-null text.
2024-03-28 01:53:18 -04:00
return Loc . GetString ( "nav-beacon-pos-format-direction" ,
( "modifier" , modifier ) ,
( "direction" , ContentLocalizationManager . FormatDirection ( adjustedDir ) . ToLowerInvariant ( ) ) ,
( "color" , beacon . Value . Comp . Color ) ,
( "marker" , beacon . Value . Comp . Text ! ) ) ;
}
2024-04-17 12:59:31 -05:00
#endregion
2023-04-13 16:21:24 +10:00
}