Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions Content.Server/_CorvaxGoob/Rotation/AlignToNeighborTagsSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

using System.Numerics;
using Content.Server.Construction;
using Content.Shared._CorvaxGoob.Rotation;
using Content.Shared.Tag;
using Robust.Shared.Map;

namespace Content.Server._CorvaxGoob.Rotation;

/// <summary>
/// Aligns an entity's rotation to nearby anchored entities that have any of the configured tags.
/// </summary>
/// <remarks>
/// The system chooses the horizontal or vertical rotation only when neighbors clearly exist on one axis.
/// If both axes match, or no matching neighbors are found, the current rotation is preserved.
/// </remarks>
public sealed class AlignToNeighborTagsSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tag = default!;

public override void Initialize()
{
base.Initialize();

// Re-evaluate alignment whenever placement, anchoring, or construction replacement can change the visual fit.
SubscribeLocalEvent<AlignToNeighborTagsComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<AlignToNeighborTagsComponent, MoveEvent>(OnMove);
SubscribeLocalEvent<AlignToNeighborTagsComponent, AnchorStateChangedEvent>(OnAnchorChanged);
SubscribeLocalEvent<AlignToNeighborTagsComponent, AfterConstructionChangeEntityEvent>(OnAfterConstructionChange);
}

private void OnMapInit(EntityUid uid, AlignToNeighborTagsComponent component, MapInitEvent args)
{
Align(uid, component);
}

private void OnMove(EntityUid uid, AlignToNeighborTagsComponent component, ref MoveEvent args)
{
Align(uid, component, args.Component);
}

private void OnAnchorChanged(EntityUid uid, AlignToNeighborTagsComponent component, ref AnchorStateChangedEvent args)
{
Align(uid, component, args.Transform);
}

private void OnAfterConstructionChange(EntityUid uid, AlignToNeighborTagsComponent component, ref AfterConstructionChangeEntityEvent args)
{
Align(uid, component);
}

private void Align(EntityUid uid, AlignToNeighborTagsComponent component, TransformComponent? transform = null)
{
if (!Resolve(uid, ref transform))
return;

if (component.RequireAnchored && !transform.Anchored)
return;

if (component.Tags.Length == 0)
return;

var vertical = HasTaggedNeighbor(uid, transform, new Vector2(0, 1), component)
|| HasTaggedNeighbor(uid, transform, new Vector2(0, -1), component);
var horizontal = HasTaggedNeighbor(uid, transform, new Vector2(1, 0), component)
|| HasTaggedNeighbor(uid, transform, new Vector2(-1, 0), component);

// Keep the existing rotation when there is no clear axis, such as corners or isolated entities.
if (vertical == horizontal)
return;

var rotation = vertical
? component.VerticalRotation
: component.HorizontalRotation;

if (transform.LocalRotation.Equals(rotation))
return;

_transform.SetLocalRotation(uid, rotation, transform);
}

private bool HasTaggedNeighbor(
EntityUid uid,
TransformComponent transform,
Vector2 offset,
AlignToNeighborTagsComponent component)
{
var coordinates = transform.Coordinates.Offset(offset);

// A tiny lookup around the neighboring tile center is enough for anchored full-tile structures.
foreach (var entity in _lookup.GetEntitiesInRange(coordinates, 0.1f, LookupFlags.StaticSundries))
{
if (entity == uid)
continue;

if (!_tag.HasAnyTag(entity, component.Tags))
continue;

if (component.RequireAnchored
&& (!TryComp(entity, out TransformComponent? neighborTransform) || !neighborTransform.Anchored))
continue;

return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

using Content.Shared.Tag;
using Robust.Shared.Prototypes;

namespace Content.Shared._CorvaxGoob.Rotation;

/// <summary>
/// Rotates an entity to match neighboring anchored entities with the configured tags.
/// Useful for construction intermediates that should visually line up with nearby walls.
/// </summary>
[RegisterComponent]
public sealed partial class AlignToNeighborTagsComponent : Component
{
/// <summary>
/// Neighbor tags that count as valid alignment anchors.
/// </summary>
[DataField(required: true)]
public ProtoId<TagPrototype>[] Tags = [];

/// <summary>
/// Rotation used when matching east/west neighbors.
/// </summary>
[DataField]
public Angle HorizontalRotation = Angle.Zero;

/// <summary>
/// Rotation used when matching north/south neighbors.
/// </summary>
[DataField]
public Angle VerticalRotation = Angle.FromDegrees(90);

/// <summary>
/// If true, both this entity and matching neighbors must be anchored.
/// </summary>
[DataField]
public bool RequireAnchored = true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ construction-recipe-diagonal-reinforced-girder = reinforced girder (diagonal)
construction-recipe-diagonal-wall = wall (diagonal)
construction-recipe-diagonal-reinforced-wall = reinforced wall (diagonal)
construction-recipe-shuttle-window-diagonal = shuttle window (diagonal)
construction-recipe-secret-door-reinforced = secret reinforced door
.desc = A secret door disguised as a reinforced wall.
construction-recipe-secret-door-airlock = secret door (airlock)
.desc = A secret door disguised as a wall. Shows up as an airlock on the station map.
construction-recipe-secret-door-reinforced-airlock = secret reinforced door (airlock)
.desc = A secret door disguised as a reinforced wall. Shows up as an airlock on the station map.
2 changes: 0 additions & 2 deletions Resources/Locale/en-US/_Goobstation/recipes/recipes.ftl
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
recipes-secret-door-reinforced-name = reinforced secret door

recipes-wooden-wand-name = wooden wand
recipe-anomaly-core-name = anomaly core
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ent-CorvaxGoobSecretDoorAssembly = secret door assembly
.desc = { ent-BaseSecretDoorAssembly.desc }

ent-CorvaxGoobReinforcedSecretDoorAssembly = reinforced secret door assembly
.desc = { ent-ReinforcedSecretDoorAssembly.desc }

ent-SolidSecretDoorAirlock = { ent-WallSolid }
.desc = { ent-WallSolid.desc }

ent-ReinforcedSecretDoorAirlock = { ent-WallReinforced }
.desc = { ent-WallReinforced.desc }
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ construction-recipe-diagonal-reinforced-girder = укреплённый карк
construction-recipe-diagonal-wall = стена (диагональ)
construction-recipe-diagonal-reinforced-wall = укреплённая стена (диагональ)
construction-recipe-shuttle-window-diagonal = окно шаттла (диагональ)
construction-recipe-secret-door-reinforced = потайная укреплённая дверь
.desc = Потайная дверь, замаскированная под укреплённую стену.
construction-recipe-secret-door-airlock = потайная дверь (шлюз)
.desc = Потайная дверь, замаскированная под стену. На карте станции отображается как шлюз.
construction-recipe-secret-door-reinforced-airlock = потайная укреплённая дверь (шлюз)
.desc = Потайная дверь, замаскированная под укреплённую стену. На карте станции отображается как шлюз.
1 change: 0 additions & 1 deletion Resources/Locale/ru-RU/_Goobstation/recipes/recipes.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
recipes-secret-door-reinforced-name = укреплённая секретная дверь
recipes-wooden-wand-name = деревянная палочка
recipe-anomaly-core-name = аномальное ядро
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ent-CorvaxGoobSecretDoorAssembly = каркас потайной двери
.desc = { ent-BaseSecretDoorAssembly.desc }

ent-CorvaxGoobReinforcedSecretDoorAssembly = каркас укреплённой потайной двери
.desc = { ent-ReinforcedSecretDoorAssembly.desc }

ent-SolidSecretDoorAirlock = { ent-WallSolid }
.desc = { ent-WallSolid.desc }

ent-ReinforcedSecretDoorAirlock = { ent-WallReinforced }
.desc = { ent-WallReinforced.desc }
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
parent: BaseReinforcedSecretDoor
components:
- type: Construction
graph: ReinforcedSecretDoor
graph: Girder # CorvaxGoob Edit - enhance-secret-door
node: ReinforcedSecretDoor
containers:
- battery-container
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
parent: BaseSecretDoor
components:
- type: Construction
graph: SecretDoor
graph: Girder # CorvaxGoob Edit - enhance-secret-door
node: solidSecretDoor
containers:
- battery-container
Expand Down
Loading
Loading