+using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Content.Server.Atmos;
bool emptyTiles = true)
{
DebugTools.Assert(count > 0);
-
- var frontier = new ValueList<Vector2i>(32);
- // TODO: Need poisson but crashes whenever I use moony's due to inputs or smth idk
- // Get the total amount of groups to spawn across the entire chunk.
- // We treat a null entity mask as requiring nothing else on the tile
-
- spawnSet = new Dictionary<Vector2i, string?>();
- var visited = _tilePool.Get();
- existingEnts = new HashSet<EntityUid>();
-
- // Pick a random tile then BFS outwards from it
- // It will bias edge tiles significantly more but will make the CPU cry less.
- for (var i = 0; i < count; i++)
+ var remainingTiles = _tilePool.Get();
+ var nodeEntities = new Dictionary<Vector2i, EntityUid?>();
+ var nodeMask = new Dictionary<Vector2i, string?>();
+
+ // Okay so originally we picked a random tile and BFS outwards
+ // the problem is if you somehow get a cooked frontier then it might drop entire veins
+ // hence we'll grab all valid tiles up front and use that as possible seeds.
+ // It's hella more expensive but stops issues.
+ for (var x = bounds.Left; x < bounds.Right; x++)
{
- var groupSize = rand.Next(layerProto.MinGroupSize, layerProto.MaxGroupSize + 1);
- var startNodeX = rand.Next(bounds.Left, bounds.Right);
- var startNodeY = rand.Next(bounds.Bottom, bounds.Top);
- var startNode = new Vector2i(startNodeX, startNodeY);
- frontier.Clear();
- frontier.Add(startNode);
- visited.Add(startNode);
-
- while (groupSize >= 0 && frontier.Count > 0)
+ for (var y = bounds.Bottom; y < bounds.Top; y++)
{
- var frontierIndex = rand.Next(frontier.Count);
- var node = frontier[frontierIndex];
- frontier.RemoveSwap(frontierIndex);
-
- // Add neighbors regardless.
- for (var x = -1; x <= 1; x++)
- {
- for (var y = -1; y <= 1; y++)
- {
- if (x != 0 && y != 0)
- continue;
-
- var neighbor = new Vector2i(node.X + x, node.Y + y);
-
- // Check if it's inbounds.
- if (!bounds.Contains(neighbor))
- continue;
-
- if (!visited.Add(neighbor))
- continue;
-
- frontier.Add(neighbor);
- }
- }
+ var node = new Vector2i(x, y);
// Empty tile, skip if relevant.
if (!emptyTiles && (!_mapSystem.TryGetTile(grid, node, out var tile) || tile.IsEmpty))
}
DebugTools.Assert(layerProto.EntityMask.Count == 0 || !string.IsNullOrEmpty(proto));
+ remainingTiles.Add(node);
+ nodeEntities.Add(node, existing);
+ nodeMask.Add(node, proto);
+ }
+ }
- // Don't fight other layers.
- if (!spawnSet.TryAdd(node, proto))
- continue;
+ var frontier = new ValueList<Vector2i>(32);
+ // TODO: Need poisson but crashes whenever I use moony's due to inputs or smth idk
+ // Get the total amount of groups to spawn across the entire chunk.
+ // We treat a null entity mask as requiring nothing else on the tile
+
+ spawnSet = new Dictionary<Vector2i, string?>();
+ existingEnts = new HashSet<EntityUid>();
+
+ // Iterate the group counts and pathfind out each group.
+ for (var i = 0; i < count; i++)
+ {
+ var groupSize = rand.Next(layerProto.MinGroupSize, layerProto.MaxGroupSize + 1);
- groupSize--;
+ // While we have remaining tiles keep iterating
+ while (groupSize >= 0 && remainingTiles.Count > 0)
+ {
+ var startNode = rand.PickAndTake(remainingTiles);
+ frontier.Clear();
+ frontier.Add(startNode);
- if (existing != null)
+ // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
+ while (frontier.Count > 0 && groupSize >= 0)
{
- existingEnts.Add(existing.Value);
+ // Need to pick a random index so we don't just get straight lines of ores.
+ var frontierIndex = rand.Next(frontier.Count);
+ var node = frontier[frontierIndex];
+ frontier.RemoveSwap(frontierIndex);
+ remainingTiles.Remove(node);
+
+ // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
+ for (var x = -1; x <= 1; x++)
+ {
+ for (var y = -1; y <= 1; y++)
+ {
+ if (x != 0 && y != 0)
+ continue;
+
+ var neighbor = new Vector2i(node.X + x, node.Y + y);
+
+ if (frontier.Contains(neighbor) || !remainingTiles.Contains(neighbor))
+ continue;
+
+ frontier.Add(neighbor);
+ }
+ }
+
+ // Tile valid salad so add it.
+ var mask = nodeMask[node];
+ spawnSet.Add(node, mask);
+ groupSize--;
+
+ if (nodeEntities.TryGetValue(node, out var existing))
+ {
+ Del(existing);
+ }
}
}
+
+ if (groupSize > 0)
+ {
+ Log.Warning($"Found remaining group size for ore veins!");
+ }
}
- _tilePool.Return(visited);
+ _tilePool.Return(remainingTiles);
}
/// <summary>
}
}
- var magnetGridUid = _xformQuery.GetComponent(magnet.Owner).GridUid;
- Box2 attachedBounds = Box2.Empty;
- MapId mapId = MapId.Nullspace;
+ var magnetXform = _xformQuery.GetComponent(magnet.Owner);
+ var magnetGridUid = magnetXform.GridUid;
+ var attachedBounds = new Box2Rotated();
+ var mapId = MapId.Nullspace;
+ Angle worldAngle;
if (magnetGridUid != null)
{
var magnetGridXform = _xformQuery.GetComponent(magnetGridUid.Value);
- attachedBounds = _transform.GetWorldMatrix(magnetGridXform)
- .TransformBox(_gridQuery.GetComponent(magnetGridUid.Value).LocalAABB);
+ var (gridPos, gridRot) = _transform.GetWorldPositionRotation(magnetGridXform);
+ var gridAABB = _gridQuery.GetComponent(magnetGridUid.Value).LocalAABB;
+ attachedBounds = new Box2Rotated(gridAABB.Translated(gridPos), gridRot, gridPos);
+
+ worldAngle = (gridRot + magnetXform.LocalRotation) - MathF.PI / 2;
mapId = magnetGridXform.MapID;
}
+ else
+ {
+ worldAngle = _random.NextAngle();
+ }
- if (!TryGetSalvagePlacementLocation(mapId, attachedBounds, bounds!.Value, out var spawnLocation, out var spawnAngle))
+ if (!TryGetSalvagePlacementLocation(mapId, attachedBounds, bounds!.Value, worldAngle, out var spawnLocation, out var spawnAngle))
{
Report(magnet.Owner, MagnetChannel, "salvage-system-announcement-spawn-no-debris-available");
_mapManager.DeleteMap(salvMap);
RaiseLocalEvent(ref active);
}
- private bool TryGetSalvagePlacementLocation(MapId mapId, Box2 attachedBounds, Box2 bounds, out MapCoordinates coords, out Angle angle)
+ private bool TryGetSalvagePlacementLocation(MapId mapId, Box2Rotated attachedBounds, Box2 bounds, Angle worldAngle, out MapCoordinates coords, out Angle angle)
{
- const float OffsetRadiusMin = 4f;
- const float OffsetRadiusMax = 16f;
+ // Grid intersection only does AABB atm.
+ var attachedAABB = attachedBounds.CalcBoundingBox();
- var minDistance = (attachedBounds.Height < attachedBounds.Width ? attachedBounds.Width : attachedBounds.Height) / 2f;
+ var minDistance = (attachedAABB.Height < attachedAABB.Width ? attachedAABB.Width : attachedAABB.Height) / 2f;
var minActualDistance = bounds.Height < bounds.Width ? minDistance + bounds.Width / 2f : minDistance + bounds.Height / 2f;
- var attachedCenter = attachedBounds.Center;
-
- angle = _random.NextAngle();
+ var attachedCenter = attachedAABB.Center;
+ var fraction = 0.25f;
// Thanks 20kdc
for (var i = 0; i < 20; i++)
{
var randomPos = attachedCenter +
- _random.NextAngle().ToVec() * (minActualDistance +
- _random.NextFloat(OffsetRadiusMin, OffsetRadiusMax));
+ worldAngle.ToVec() * (minActualDistance * fraction);
var finalCoords = new MapCoordinates(randomPos, mapId);
+ angle = _random.NextAngle();
var box2 = Box2.CenteredAround(finalCoords.Position, bounds.Size);
var box2Rot = new Box2Rotated(box2, angle, finalCoords.Position);
if (_mapManager.FindGridsIntersecting(finalCoords.MapId, box2Rot).Any())
{
// Bump it further and further just in case.
- minActualDistance += 4f;
+ fraction += 0.25f;
continue;
}
return true;
}
+ angle = Angle.Zero;
coords = MapCoordinates.Nullspace;
return false;
}