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 Robust.Shared.Utility ;
using System.Diagnostics.CodeAnalysis ;
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 ! ;
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 ;
2023-04-13 16:21:24 +10:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2023-09-16 18:11:47 +10:00
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-04-17 12:59:31 -05:00
// Airtight structure change event
SubscribeLocalEvent < AirtightChanged > ( OnAirtightChanged ) ;
// 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
}
2024-04-17 12:59:31 -05:00
#region : Initialization event handling
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
#endregion
#region : Grid change event handling
private void OnNavMapSplit ( ref GridSplitEvent args )
2024-03-28 01:53:18 -04:00
{
2024-04-17 12:59:31 -05:00
if ( ! TryComp ( args . Grid , out NavMapComponent ? comp ) )
2024-03-28 01:53:18 -04:00
return ;
2024-04-17 12:59:31 -05:00
var gridQuery = GetEntityQuery < MapGridComponent > ( ) ;
foreach ( var grid in args . NewGrids )
{
var newComp = EnsureComp < NavMapComponent > ( grid ) ;
RefreshGrid ( grid , newComp , gridQuery . GetComponent ( grid ) ) ;
}
RefreshGrid ( args . Grid , comp , gridQuery . GetComponent ( args . Grid ) ) ;
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
{
2024-04-17 12:59:31 -05:00
if ( ! TryComp < NavMapComponent > ( ev . NewTile . GridUid , out var navMap ) )
return ;
var tile = ev . NewTile . GridIndices ;
var chunkOrigin = SharedMapSystem . GetChunkIndices ( tile , ChunkSize ) ;
if ( ! navMap . Chunks . TryGetValue ( ( NavMapChunkType . Floor , chunkOrigin ) , out var chunk ) )
chunk = new ( chunkOrigin ) ;
// This could be easily replaced in the future to accommodate diagonal tiles
if ( ev . NewTile . IsSpace ( ) )
chunk = UnsetAllEdgesForChunkTile ( chunk , tile ) ;
else
chunk = SetAllEdgesForChunkTile ( chunk , tile ) ;
chunk . LastUpdate = _gameTiming . CurTick ;
navMap . Chunks [ ( NavMapChunkType . Floor , chunkOrigin ) ] = chunk ;
Dirty ( ev . NewTile . GridUid , navMap ) ;
2023-09-16 18:11:47 +10:00
}
2024-04-17 12:59:31 -05:00
private void OnAirtightChanged ( ref AirtightChanged ev )
2023-09-16 18:11:47 +10:00
{
2024-04-17 12:59:31 -05:00
var gridUid = ev . Position . Grid ;
if ( ! TryComp < NavMapComponent > ( gridUid , out var navMap ) | |
! TryComp < MapGridComponent > ( gridUid , out var mapGrid ) )
return ;
// Refresh the affected tile
var tile = ev . Position . Tile ;
var chunkOrigin = SharedMapSystem . GetChunkIndices ( tile , ChunkSize ) ;
RefreshTileEntityContents ( gridUid , navMap , mapGrid , chunkOrigin , tile ) ;
// Update potentially affected chunks
foreach ( var category in EntityChunkTypes )
{
if ( ! navMap . Chunks . TryGetValue ( ( category , chunkOrigin ) , out var chunk ) )
continue ;
chunk . LastUpdate = _gameTiming . CurTick ;
navMap . Chunks [ ( category , chunkOrigin ) ] = chunk ;
}
Dirty ( gridUid , navMap ) ;
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 ;
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-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-04-17 12:59:31 -05:00
if ( ! component . Chunks . TryGetValue ( ( NavMapChunkType . Floor , chunkOrigin ) , out var chunk ) )
chunk = new ( chunkOrigin ) ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
chunk . LastUpdate = _gameTiming . CurTick ;
2024-01-06 16:06:52 -05:00
2024-04-17 12:59:31 -05:00
// Refresh the floor tile
component . Chunks [ ( NavMapChunkType . Floor , chunkOrigin ) ] = SetAllEdgesForChunkTile ( chunk , tile ) ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
// Refresh the contents of the tile
RefreshTileEntityContents ( uid , component , mapGrid , chunkOrigin , tile ) ;
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-04-17 12:59:31 -05:00
private void RefreshTileEntityContents ( EntityUid uid , NavMapComponent component , MapGridComponent mapGrid , Vector2i chunkOrigin , Vector2i tile )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
var relative = SharedMapSystem . GetChunkRelative ( tile , ChunkSize ) ;
var flag = ( ushort ) GetFlag ( relative ) ;
var invFlag = ( ushort ) ~ flag ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
// Clear stale data from the tile across all entity associated chunks
foreach ( var category in EntityChunkTypes )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
if ( ! component . Chunks . TryGetValue ( ( category , chunkOrigin ) , out var chunk ) )
chunk = new ( chunkOrigin ) ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
foreach ( var ( direction , _ ) in chunk . TileData )
chunk . TileData [ direction ] & = invFlag ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
chunk . LastUpdate = _gameTiming . CurTick ;
component . Chunks [ ( category , chunkOrigin ) ] = chunk ;
2023-04-13 16:21:24 +10:00
}
2024-04-17 12:59:31 -05:00
// Update the tile data based on what entities are still anchored to the tile
var enumerator = _mapSystem . GetAnchoredEntitiesEnumerator ( uid , mapGrid , tile ) ;
2024-01-11 08:14:20 -05:00
2024-04-17 12:59:31 -05:00
while ( enumerator . MoveNext ( out var ent ) )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
if ( ! TryComp < AirtightComponent > ( ent , out var entAirtight ) )
continue ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
var category = GetAssociatedEntityChunkType ( ent . Value ) ;
2023-09-16 18:11:47 +10:00
2024-04-17 12:59:31 -05:00
if ( ! component . Chunks . TryGetValue ( ( category , chunkOrigin ) , out var chunk ) )
2023-09-16 18:11:47 +10:00
continue ;
2024-04-17 12:59:31 -05:00
foreach ( var ( direction , _ ) in chunk . TileData )
2023-09-16 18:11:47 +10:00
{
2024-04-17 12:59:31 -05:00
if ( ( direction & entAirtight . AirBlockedDirection ) > 0 )
chunk . TileData [ direction ] | = flag ;
2023-09-16 18:11:47 +10:00
}
2024-04-17 12:59:31 -05:00
chunk . LastUpdate = _gameTiming . CurTick ;
component . Chunks [ ( category , chunkOrigin ) ] = chunk ;
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)
if ( component . Chunks . TryGetValue ( ( NavMapChunkType . Wall , chunkOrigin ) , out var wallChunk ) & &
component . Chunks . TryGetValue ( ( NavMapChunkType . Airlock , chunkOrigin ) , out var airlockChunk ) )
2024-01-11 08:14:20 -05:00
{
2024-04-17 12:59:31 -05:00
foreach ( var ( direction , _ ) in wallChunk . TileData )
2024-01-11 08:14:20 -05:00
{
2024-04-17 12:59:31 -05:00
var airlockInvFlag = ( ushort ) ~ airlockChunk . TileData [ direction ] ;
wallChunk . TileData [ direction ] & = airlockInvFlag ;
2024-01-11 08:14:20 -05:00
}
2024-04-17 12:59:31 -05:00
wallChunk . LastUpdate = _gameTiming . CurTick ;
component . Chunks [ ( NavMapChunkType . Wall , chunkOrigin ) ] = wallChunk ;
2024-01-11 08:14:20 -05:00
}
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-04-17 12:59:31 -05:00
if ( ! TryComp < NavMapComponent > ( xform . GridUid , out var navMap ) )
return ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
var netEnt = GetNetEntity ( uid ) ;
var oldBeacon = navMap . Beacons . FirstOrNull ( x = > x . NetEnt = = netEnt ) ;
var changed = false ;
2023-04-13 16:21:24 +10:00
2024-04-17 12:59:31 -05:00
if ( oldBeacon ! = null )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
navMap . Beacons . Remove ( oldBeacon . Value ) ;
changed = true ;
2023-04-13 16:21:24 +10:00
}
2024-04-17 12:59:31 -05:00
if ( TryCreateNavMapBeaconData ( uid , component , xform , out var beaconData ) )
2023-04-13 16:21:24 +10:00
{
2024-04-17 12:59:31 -05:00
navMap . Beacons . Add ( beaconData . Value ) ;
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 ;
}
[PublicAPI]
public string GetNearestBeaconString ( Entity < TransformComponent ? > ent )
{
if ( ! Resolve ( ent , ref ent . Comp ) )
return Loc . GetString ( "nav-beacon-pos-no-beacons" ) ;
2024-04-17 12:59:31 -05:00
return GetNearestBeaconString ( _transformSystem . GetMapCoordinates ( ent , ent . Comp ) ) ;
2024-03-28 01:53:18 -04:00
}
public string GetNearestBeaconString ( MapCoordinates coordinates )
{
if ( ! TryGetNearestBeacon ( coordinates , out var beacon , out var pos ) )
return Loc . GetString ( "nav-beacon-pos-no-beacons" ) ;
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.
var dir = ( pos . Value . Position - coordinates . Position ) . ToWorldAngle ( ) ;
var adjustedDir = ( dir - gridOffset ) . GetDir ( ) ;
var length = ( pos . Value . Position - coordinates . Position ) . Length ( ) ;
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
}