diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.cs index 5a6a8510ff..cdf1f00205 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.cs @@ -4,6 +4,7 @@ using Content.Server.Decals; using Content.Server.NPC.Components; using Content.Server.NPC.HTN; using Content.Server.NPC.Systems; +using Content.Server.Shuttles.Systems; using Content.Shared.Construction.EntitySystems; using Content.Shared.Maps; using Content.Shared.Procedural; @@ -51,6 +52,8 @@ public sealed partial class DungeonJob : Job> private readonly EntityUid _gridUid; private readonly MapGridComponent _grid; + private readonly EntityCoordinates? _targetCoordinates; + private readonly ISawmill _sawmill; public DungeonJob( @@ -70,6 +73,7 @@ public sealed partial class DungeonJob : Job> EntityUid gridUid, int seed, Vector2i position, + EntityCoordinates? targetCoordinates = null, CancellationToken cancellation = default) : base(maxTime, cancellation) { _sawmill = sawmill; @@ -94,6 +98,7 @@ public sealed partial class DungeonJob : Job> _gridUid = gridUid; _seed = seed; _position = position; + _targetCoordinates = targetCoordinates; } /// @@ -151,6 +156,12 @@ public sealed partial class DungeonJob : Job> // To make it slightly more deterministic treat this RNG as separate ig. // Post-processing after finishing loading. + if (_targetCoordinates != null) + { + var oldMap = _xformQuery.Comp(_gridUid).MapUid; + _entManager.System().TryFTLProximity(_gridUid, _targetCoordinates.Value); + _entManager.DeleteEntity(oldMap); + } // Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way. _grid.CanSplit = true; diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 68c4a98610..706f63ffd7 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -183,11 +183,16 @@ public sealed partial class DungeonSystem : SharedDungeonSystem return mapId; } + /// + /// Generates a dungeon in the background with the specified config. + /// + /// Coordinates to move the dungeon to afterwards. Will delete the original map public void GenerateDungeon(DungeonConfig gen, EntityUid gridUid, MapGridComponent grid, Vector2i position, - int seed) + int seed, + EntityCoordinates? coordinates = null) { var cancelToken = new CancellationTokenSource(); var job = new DungeonJob.DungeonJob( @@ -207,6 +212,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem gridUid, seed, position, + coordinates, cancelToken.Token); _dungeonJobs.Add(job, cancelToken); @@ -238,6 +244,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem gridUid, seed, position, + null, cancelToken.Token); _dungeonJobs.Add(job, cancelToken); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 518867b555..afe7a7b6db 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -66,7 +66,7 @@ public sealed partial class ShuttleSystem /// /// How many times we try to proximity warp close to something before falling back to map-wideAABB. /// - private const int FTLProximityIterations = 3; + private const int FTLProximityIterations = 5; private readonly HashSet _lookupEnts = new(); private readonly HashSet _immuneEnts = new(); @@ -321,7 +321,7 @@ public sealed partial class ShuttleSystem hyperspace.TargetCoordinates = config.Coordinates; hyperspace.TargetAngle = config.Angle; } - else if (TryGetFTLProximity(shuttleUid, target, out var coords, out var targAngle)) + else if (TryGetFTLProximity(shuttleUid, new EntityCoordinates(target, Vector2.Zero), out var coords, out var targAngle)) { hyperspace.TargetCoordinates = coords; hyperspace.TargetAngle = targAngle; @@ -377,10 +377,11 @@ public sealed partial class ShuttleSystem var fromMatrix = _transform.GetWorldMatrix(xform); var fromRotation = _transform.GetWorldRotation(xform); - var width = Comp(uid).LocalAABB.Width; + var grid = Comp(uid); + var width = grid.LocalAABB.Width; var ftlMap = EnsureFTLMap(); var body = _physicsQuery.GetComponent(entity); - var shuttleCenter = body.LocalCenter; + var shuttleCenter = grid.LocalAABB.Center; // Leave audio at the old spot // Just so we don't clip @@ -500,6 +501,7 @@ public sealed partial class ShuttleSystem // Position ftl else { + // TODO: This should now use tryftlproximity mapId = target.GetMapId(EntityManager); _transform.SetCoordinates(uid, xform, target, rotation: entity.Comp1.TargetAngle); } @@ -698,16 +700,23 @@ public sealed partial class ShuttleSystem } /// - /// Tries to get the target position to FTL near to another grid. + /// Tries to get the target position to FTL near the target coordinates. + /// If the target coordinates have a mapgrid then will try to offset the AABB. /// - private bool TryGetFTLProximity(EntityUid shuttleUid, EntityUid targetUid, + /// Min offset for the final FTL. + /// Max offset for the final FTL from the box we spawn. + private bool TryGetFTLProximity( + EntityUid shuttleUid, + EntityCoordinates targetCoordinates, out EntityCoordinates coordinates, out Angle angle, + float minOffset = 0f, float maxOffset = 64f, TransformComponent? xform = null, TransformComponent? targetXform = null) { + DebugTools.Assert(minOffset < maxOffset); coordinates = EntityCoordinates.Invalid; angle = Angle.Zero; - if (!Resolve(targetUid, ref targetXform) || + if (!Resolve(targetCoordinates.EntityId, ref targetXform) || targetXform.MapUid == null || !targetXform.MapUid.Value.IsValid() || !Resolve(shuttleUid, ref xform)) @@ -715,26 +724,24 @@ public sealed partial class ShuttleSystem return false; } - - var xformQuery = GetEntityQuery(); - var shuttleAABB = Comp(shuttleUid).LocalAABB; - Box2 targetLocalAABB; - - // Spawn nearby. // We essentially expand the Box2 of the target area until nothing else is added then we know it's valid. // Can't just get an AABB of every grid as we may spawn very far away. - if (TryComp(targetXform.GridUid, out var targetGrid)) - { - targetLocalAABB = targetGrid.LocalAABB; - } - else - { - targetLocalAABB = new Box2(); - } - - var targetAABB = _transform.GetWorldMatrix(targetXform, xformQuery) - .TransformBox(targetLocalAABB).Enlarged(shuttleAABB.Size.Length()); var nearbyGrids = new HashSet(); + var shuttleAABB = Comp(shuttleUid).LocalAABB; + + // Start with small point. + // If our target pos is offset we mot even intersect our target's AABB so we don't include it. + var targetLocalAABB = Box2.CenteredAround(targetCoordinates.Position, Vector2.One); + + // How much we expand the target AABB be. + // We half it because we only need the width / height in each direction if it's placed at a particular spot. + var expansionAmount = MathF.Max(shuttleAABB.Width / 2f, shuttleAABB.Height / 2f); + + // Expand the starter AABB so we have something to query to start with. + var targetAABB = _transform.GetWorldMatrix(targetXform) + .TransformBox(targetLocalAABB) + .Enlarged(expansionAmount); + var iteration = 0; var lastCount = nearbyGrids.Count; var mapId = targetXform.MapID; @@ -743,15 +750,21 @@ public sealed partial class ShuttleSystem while (iteration < FTLProximityIterations) { grids.Clear(); - _mapManager.FindGridsIntersecting(mapId, targetAABB, ref grids); + // We pass in an expanded offset here so we can safely do a random offset later. + // We don't include this in the actual targetAABB because then we would be double-expanding it. + // Once in this loop, then again when placing the shuttle later. + // Note that targetAABB already has expansionAmount factored in already. + _mapManager.FindGridsIntersecting(mapId, targetAABB.Enlarged(maxOffset), ref grids); foreach (var grid in grids) { if (!nearbyGrids.Add(grid)) continue; - targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid, xformQuery) - .TransformBox(Comp(grid).LocalAABB)); + // Include the other grid's AABB (expanded by ours) as well. + targetAABB = targetAABB.Union( + _transform.GetWorldMatrix(grid) + .TransformBox(Comp(grid).LocalAABB.Enlarged(expansionAmount))); } // Can do proximity @@ -760,7 +773,6 @@ public sealed partial class ShuttleSystem break; } - targetAABB = targetAABB.Enlarged(shuttleAABB.Size.Length() / 2f); iteration++; lastCount = nearbyGrids.Count; @@ -775,13 +787,15 @@ public sealed partial class ShuttleSystem if (nearbyGrids.Contains(uid)) continue; - targetAABB = targetAABB.Union(_transform.GetWorldMatrix(uid, xformQuery) - .TransformBox(Comp(uid).LocalAABB)); + targetAABB = targetAABB.Union( + _transform.GetWorldMatrix(uid) + .TransformBox(Comp(uid).LocalAABB.Enlarged(expansionAmount))); } break; } + // Now we have a targetAABB. This has already been expanded to account for our fat ass. Vector2 spawnPos; if (TryComp(shuttleUid, out var shuttleBody)) @@ -790,21 +804,32 @@ public sealed partial class ShuttleSystem _physics.SetAngularVelocity(shuttleUid, 0f, body: shuttleBody); } + // TODO: This should prefer the position's angle instead. // TODO: This is pretty crude for multiple landings. if (nearbyGrids.Count > 1 || !HasComp(targetXform.GridUid)) { - var minRadius = (MathF.Max(targetAABB.Width, targetAABB.Height) + MathF.Max(shuttleAABB.Width, shuttleAABB.Height)) / 2f; - spawnPos = targetAABB.Center + _random.NextVector2(minRadius, minRadius + 64f); + // Pick a random angle + var offsetAngle = _random.NextAngle(); + + // Our valid spawn positions are away. + var minRadius = MathF.Max(targetAABB.Width / 2f, targetAABB.Height / 2f); + spawnPos = targetAABB.Center + offsetAngle.RotateVec(new Vector2(_random.NextFloat(minRadius + minOffset, minRadius + maxOffset), 0f)); } else if (shuttleBody != null) { - var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery); - var transform = new Transform(targetPos, targetRot); - spawnPos = Robust.Shared.Physics.Transform.Mul(transform, -shuttleBody.LocalCenter); + (spawnPos, angle) = _transform.GetWorldPositionRotation(targetXform); } else { - spawnPos = _transform.GetWorldPosition(targetXform, xformQuery); + spawnPos = _transform.GetWorldPosition(targetXform); + } + + var offset = Vector2.Zero; + + // Offset it because transform does not correspond to AABB position. + if (TryComp(shuttleUid, out MapGridComponent? shuttleGrid)) + { + offset = -shuttleGrid.LocalAABB.Center; } if (!HasComp(targetXform.GridUid)) @@ -816,7 +841,11 @@ public sealed partial class ShuttleSystem angle = Angle.Zero; } - coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos); + // Rotate our localcenter around so we spawn exactly where we "think" we should (center of grid on the dot). + var transform = new Transform(spawnPos, angle); + spawnPos = Robust.Shared.Physics.Transform.Mul(transform, offset); + + coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos - offset); return true; } @@ -833,13 +862,31 @@ public sealed partial class ShuttleSystem return false; } - if (!TryGetFTLProximity(shuttleUid, targetUid, out var coords, out var angle, xform, targetXform)) + if (!TryGetFTLProximity(shuttleUid, new EntityCoordinates(targetUid, Vector2.Zero), out var coords, out var angle, xform: xform, targetXform: targetXform)) return false; _transform.SetCoordinates(shuttleUid, xform, coords, rotation: angle); return true; } + /// + /// Tries to FTL to the target coordinates; will move nearby if not possible. + /// + public bool TryFTLProximity(Entity shuttle, EntityCoordinates targetCoordinates) + { + if (!Resolve(shuttle.Owner, ref shuttle.Comp) || + _transform.GetMap(targetCoordinates)?.IsValid() != true) + { + return false; + } + + if (!TryGetFTLProximity(shuttle, targetCoordinates, out var coords, out var angle)) + return false; + + _transform.SetCoordinates(shuttle, shuttle.Comp, coords, rotation: angle); + return true; + } + /// /// Flattens / deletes everything under the grid upon FTL. /// @@ -861,7 +908,6 @@ public sealed partial class ShuttleSystem var aabb = fixture.Shape.ComputeAABB(transform, 0); // Shift it slightly - aabb = aabb.Translated(-grid.TileSizeHalfVector); // Create a small border around it. aabb = aabb.Enlarged(0.2f); aabbs.Add(aabb); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index c0eff11931..4760e92e21 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -85,7 +85,7 @@ public sealed partial class ShuttleSystem _mapManager.DeleteMap(mapId); } - private bool TryDungeonSpawn(Entity targetGrid, MapId mapId, DungeonSpawnGroup group, out EntityUid spawned) + private bool TryDungeonSpawn(Entity targetGrid, DungeonSpawnGroup group, out EntityUid spawned) { spawned = EntityUid.Invalid; @@ -110,11 +110,12 @@ public sealed partial class ShuttleSystem spawnCoords = spawnCoords.Offset(_random.NextVector2(distancePadding + group.MinimumDistance, distancePadding + group.MaximumDistance)); } - var spawnMapCoords = _transform.ToMapCoordinates(spawnCoords); + _maps.CreateMap(out var mapId); + var spawnedGrid = _mapManager.CreateGridEntity(mapId); - _transform.SetMapCoordinates(spawnedGrid, spawnMapCoords); - _dungeon.GenerateDungeon(dungeonProto, spawnedGrid.Owner, spawnedGrid.Comp, Vector2i.Zero, _random.Next()); + _transform.SetMapCoordinates(spawnedGrid, new MapCoordinates(Vector2.Zero, mapId)); + _dungeon.GenerateDungeon(dungeonProto, spawnedGrid.Owner, spawnedGrid.Comp, Vector2i.Zero, _random.Next(), spawnCoords); spawned = spawnedGrid.Owner; return true; @@ -192,7 +193,7 @@ public sealed partial class ShuttleSystem switch (group) { case DungeonSpawnGroup dungeon: - if (!TryDungeonSpawn(targetGrid.Value, mapId, dungeon, out spawned)) + if (!TryDungeonSpawn(targetGrid.Value, dungeon, out spawned)) continue; break;