Files
tbd-station-14/Content.Client/Atmos/AlignAtmosPipeLayers.cs
Pieter-Jan Briers 0c97520276 Fix usages of TryIndex() (#39124)
* Fix usages of TryIndex()

Most usages of TryIndex() were using it incorrectly. Checking whether prototype IDs specified in prototypes actually existed before using them. This is not appropriate as it's just hiding bugs that should be getting caught by the YAML linter and other tools. (#39115)

This then resulted in TryIndex() getting modified to log errors (94f98073b0), which is incorrect as it causes false-positive errors in proper uses of the API: external data validation. (#39098)

This commit goes through and checks every call site of TryIndex() to see whether they were correct. Most call sites were replaced with the new Resolve(), which is suitable for these "defensive programming" use cases.

Fixes #39115

Breaking change: while doing this I noticed IdCardComponent and related systems were erroneously using ProtoId<AccessLevelPrototype> for job prototypes. This has been corrected.

* fix tests

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-09-09 18:17:56 +02:00

204 lines
8.3 KiB
C#

using Content.Client.Construction;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Construction.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.Placement.Modes;
using Robust.Client.Utility;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Numerics;
using static Robust.Client.Placement.PlacementManager;
namespace Content.Client.Atmos;
/// <summary>
/// Allows users to place atmos pipes on different layers depending on how the mouse cursor is positioned within a grid tile.
/// </summary>
/// <remarks>
/// This placement mode is not on the engine because it is content specific.
/// </remarks>
public sealed class AlignAtmosPipeLayers : SnapgridCenter
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
private readonly SharedMapSystem _mapSystem;
private readonly SharedTransformSystem _transformSystem;
private readonly SharedAtmosPipeLayersSystem _pipeLayersSystem;
private readonly SpriteSystem _spriteSystem;
private const float SearchBoxSize = 2f;
private EntityCoordinates _unalignedMouseCoords = default;
private const float MouseDeadzoneRadius = 0.25f;
private Color _guideColor = new Color(0, 0, 0.5785f);
private const float GuideRadius = 0.1f;
private const float GuideOffset = 0.21875f;
public AlignAtmosPipeLayers(PlacementManager pMan) : base(pMan)
{
IoCManager.InjectDependencies(this);
_mapSystem = _entityManager.System<SharedMapSystem>();
_transformSystem = _entityManager.System<SharedTransformSystem>();
_pipeLayersSystem = _entityManager.System<SharedAtmosPipeLayersSystem>();
_spriteSystem = _entityManager.System<SpriteSystem>();
}
/// <inheritdoc/>
public override void Render(in OverlayDrawArgs args)
{
var gridUid = _entityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
if (gridUid == null || Grid == null)
return;
// Draw guide circles for each pipe layer if we are not in line/grid placing mode
if (pManager.PlacementType == PlacementTypes.None)
{
var gridRotation = _transformSystem.GetWorldRotation(gridUid.Value);
var worldPosition = _mapSystem.LocalToWorld(gridUid.Value, Grid, MouseCoords.Position);
var direction = (_eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
var multi = (direction == Direction.North || direction == Direction.South) ? -1f : 1f;
args.WorldHandle.DrawCircle(worldPosition, GuideRadius, _guideColor);
args.WorldHandle.DrawCircle(worldPosition + gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
args.WorldHandle.DrawCircle(worldPosition - gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
}
base.Render(args);
}
/// <inheritdoc/>
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
base.AlignPlacementMode(mouseScreen);
// Exit early if we are in line/grid placing mode
if (pManager.PlacementType != PlacementTypes.None)
return;
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
var gridId = _transformSystem.GetGrid(MouseCoords);
if (!_entityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
return;
var gridRotation = _transformSystem.GetWorldRotation(gridId.Value);
CurrentTile = _mapSystem.GetTileRef(gridId.Value, mapGrid, MouseCoords);
float tileSize = mapGrid.TileSize;
GridDistancing = tileSize;
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2 + pManager.PlacementOffset.X,
CurrentTile.Y + tileSize / 2 + pManager.PlacementOffset.Y));
// Calculate the position of the mouse cursor with respect to the center of the tile to determine which layer to use
var mouseCoordsDiff = _unalignedMouseCoords.Position - MouseCoords.Position;
var layer = AtmosPipeLayer.Primary;
if (mouseCoordsDiff.Length() > MouseDeadzoneRadius)
{
// Determine the direction of the mouse is relative to the center of the tile, adjusting for the player eye and grid rotation
var direction = (new Angle(mouseCoordsDiff) + _eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
layer = (direction == Direction.North || direction == Direction.East) ? AtmosPipeLayer.Secondary : AtmosPipeLayer.Tertiary;
}
// Update the construction menu placer
if (pManager.Hijack != null)
UpdateHijackedPlacer(layer, mouseScreen);
// Otherwise update the debug placer
else
UpdatePlacer(layer);
}
private void UpdateHijackedPlacer(AtmosPipeLayer layer, ScreenCoordinates mouseScreen)
{
// Try to get alternative prototypes from the construction prototype
var constructionSystem = (pManager.Hijack as ConstructionPlacementHijack)?.CurrentConstructionSystem;
var altPrototypes = (pManager.Hijack as ConstructionPlacementHijack)?.CurrentPrototype?.AlternativePrototypes;
if (constructionSystem == null || altPrototypes == null || (int)layer >= altPrototypes.Length)
return;
var newProtoId = altPrototypes[(int)layer];
if (!_protoManager.Resolve(newProtoId, out var newProto))
return;
if (newProto.Type != ConstructionType.Structure)
{
pManager.Clear();
return;
}
if (newProto.ID == (pManager.Hijack as ConstructionPlacementHijack)?.CurrentPrototype?.ID)
return;
// Start placing
pManager.BeginPlacing(new PlacementInformation()
{
IsTile = false,
PlacementOption = newProto.PlacementMode,
}, new ConstructionPlacementHijack(constructionSystem, newProto));
if (pManager.CurrentMode is AlignAtmosPipeLayers { } newMode)
newMode.RefreshGrid(mouseScreen);
// Update construction guide
constructionSystem.GetGuide(newProto);
}
private void UpdatePlacer(AtmosPipeLayer layer)
{
// Try to get alternative prototypes from the entity atmos pipe layer component
if (pManager.CurrentPermission?.EntityType == null)
return;
if (!_protoManager.TryIndex<EntityPrototype>(pManager.CurrentPermission.EntityType, out var currentProto))
return;
if (!currentProto.TryGetComponent<AtmosPipeLayersComponent>(out var atmosPipeLayers, _entityManager.ComponentFactory))
return;
if (!_pipeLayersSystem.TryGetAlternativePrototype(atmosPipeLayers, layer, out var newProtoId))
return;
if (_protoManager.TryIndex<EntityPrototype>(newProtoId, out var newProto))
{
// Update the placed prototype
pManager.CurrentPermission.EntityType = newProtoId;
// Update the appearance of the ghost sprite
if (newProto.TryGetComponent<SpriteComponent>(out var sprite, _entityManager.ComponentFactory))
{
var textures = new List<IDirectionalTextureProvider>();
foreach (var spriteLayer in sprite.AllLayers)
{
if (spriteLayer.ActualRsi?.Path != null && spriteLayer.RsiState.Name != null)
textures.Add(_spriteSystem.RsiStateLike(new SpriteSpecifier.Rsi(spriteLayer.ActualRsi.Path, spriteLayer.RsiState.Name)));
}
pManager.CurrentTextures = textures;
}
}
}
private void RefreshGrid(ScreenCoordinates mouseScreen)
{
base.AlignPlacementMode(mouseScreen);
}
}