diff --git a/Content.Client/Salvage/UI/OfferingWindow.xaml b/Content.Client/Salvage/UI/OfferingWindow.xaml index 12f1f688c8..63b41aa652 100644 --- a/Content.Client/Salvage/UI/OfferingWindow.xaml +++ b/Content.Client/Salvage/UI/OfferingWindow.xaml @@ -1,6 +1,5 @@ diff --git a/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs b/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs index ac480614a4..8f1723d1f2 100644 --- a/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs +++ b/Content.Client/Salvage/UI/SalvageExpeditionConsoleBoundUserInterface.cs @@ -31,6 +31,7 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf { base.Open(); _window = new OfferingWindow(); + _window.Title = Loc.GetString("salvage-expedition-window-title"); _window.OnClose += Close; _window?.OpenCenteredLeft(); } diff --git a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs index 0c1994c36f..36fbdd90e2 100644 --- a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs +++ b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs @@ -21,6 +21,7 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface { base.Open(); _window = new OfferingWindow(); + _window.Title = Loc.GetString("salvage-magnet-window-title"); _window.OnClose += Close; _window.OpenCenteredLeft(); } diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index 871373e9f8..f4514ac228 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Numerics; using System.Threading.Tasks; using Content.Server.Atmos; @@ -553,54 +554,19 @@ public sealed partial class BiomeSystem : SharedBiomeSystem bool emptyTiles = true) { DebugTools.Assert(count > 0); + var remainingTiles = _tilePool.Get(); + var nodeEntities = new Dictionary(); + var nodeMask = new Dictionary(); - var frontier = new ValueList(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(); - var visited = _tilePool.Get(); - existingEnts = new HashSet(); - - // 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++) + // 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)) @@ -631,21 +597,77 @@ public sealed partial class BiomeSystem : SharedBiomeSystem } DebugTools.Assert(layerProto.EntityMask.Count == 0 || !string.IsNullOrEmpty(proto)); - - // Don't fight other layers. - if (!spawnSet.TryAdd(node, proto)) - continue; - - groupSize--; - - if (existing != null) - { - existingEnts.Add(existing.Value); - } + remainingTiles.Add(node); + nodeEntities.Add(node, existing); + nodeMask.Add(node, proto); } } - _tilePool.Return(visited); + var frontier = new ValueList(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(); + existingEnts = new HashSet(); + + // 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); + + // While we have remaining tiles keep iterating + while (groupSize >= 0 && remainingTiles.Count > 0) + { + var startNode = rand.PickAndTake(remainingTiles); + frontier.Clear(); + frontier.Add(startNode); + + // 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) + { + // 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(remainingTiles); } /// diff --git a/Content.Server/Salvage/SalvageSystem.Magnet.cs b/Content.Server/Salvage/SalvageSystem.Magnet.cs index 26fd1c01af..e4711a5876 100644 --- a/Content.Server/Salvage/SalvageSystem.Magnet.cs +++ b/Content.Server/Salvage/SalvageSystem.Magnet.cs @@ -317,20 +317,29 @@ public sealed partial class SalvageSystem } } - 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); @@ -376,26 +385,25 @@ public sealed partial class SalvageSystem 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); @@ -404,7 +412,7 @@ public sealed partial class SalvageSystem if (_mapManager.FindGridsIntersecting(finalCoords.MapId, box2Rot).Any()) { // Bump it further and further just in case. - minActualDistance += 4f; + fraction += 0.25f; continue; } @@ -412,6 +420,7 @@ public sealed partial class SalvageSystem return true; } + angle = Angle.Zero; coords = MapCoordinates.Nullspace; return false; } diff --git a/Resources/Locale/en-US/salvage/salvage-magnet.ftl b/Resources/Locale/en-US/salvage/salvage-magnet.ftl index 82d84fe74c..bc99860194 100644 --- a/Resources/Locale/en-US/salvage/salvage-magnet.ftl +++ b/Resources/Locale/en-US/salvage/salvage-magnet.ftl @@ -4,6 +4,7 @@ salvage-system-announcement-spawn-no-debris-available = No debris could be recov salvage-system-announcement-arrived = A piece of salvagable debris has been pulled in. Estimated hold time: {$timeLeft} seconds. salvage-asteroid-name = Asteroid +salvage-magnet-window-title = Salvage magnet salvage-expedition-window-progression = Progression salvage-magnet-resources = {$resource -> @@ -19,8 +20,10 @@ salvage-magnet-resources = {$resource -> salvage-magnet-resources-count = {$count -> [1] (Poor) - [2] (Rich) - [3] (Rich) + [2] (Moderate) + [3] (Moderate) + [4] (Rich) + [5] (Rich) *[other] (Extraordinary) }