using System.Numerics; using System.Threading.Tasks; using Content.Shared.Maps; using Content.Shared.Procedural; using Content.Shared.Procedural.Distance; using Content.Shared.Procedural.DungeonGenerators; using Robust.Shared.Map; namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { /* * See https://www.redblobgames.com/maps/terrain-from-noise/#islands * Really it's just blending from the original noise (which may occupy the entire area) * with some other shape to confine it into a bounds more naturally. * https://old.reddit.com/r/proceduralgeneration/comments/kaen7h/new_video_on_procedural_island_noise_generation/gfjmgen/ also has more variations */ /// /// /// private async Task GenerateNoiseDistanceDunGen( Vector2i position, NoiseDistanceDunGen dungen, HashSet reservedTiles, int seed, Random random) { var tiles = new List<(Vector2i, Tile)>(); var matrix = Matrix3Helpers.CreateTranslation(position); foreach (var layer in dungen.Layers) { layer.Noise.SetSeed(seed); } // First we have to find a seed tile, then floodfill from there until we get to noise // at which point we floodfill the entire noise. var area = Box2i.FromDimensions(-dungen.Size / 2, dungen.Size); var roomTiles = new HashSet(); var width = (float) area.Width; var height = (float) area.Height; for (var x = area.Left; x <= area.Right; x++) { for (var y = area.Bottom; y <= area.Top; y++) { var node = new Vector2i(x, y); foreach (var layer in dungen.Layers) { var value = layer.Noise.GetNoise(node.X, node.Y); if (dungen.DistanceConfig != null) { // Need to get dx - dx in a range from -1 -> 1 var dx = 2 * x / width; var dy = 2 * y / height; var distance = GetDistance(dx, dy, dungen.DistanceConfig); value = MathHelper.Lerp(value, 1f - distance, dungen.DistanceConfig.BlendWeight); } if (value < layer.Threshold) continue; var tileDef = _tileDefManager[layer.Tile]; var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random); var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored(); // Do this down here because noise has a much higher chance of failing than reserved tiles. if (reservedTiles.Contains(adjusted)) { break; } tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); roomTiles.Add(adjusted); break; } } await SuspendDungeon(); } var room = new DungeonRoom(roomTiles, area.Center, area, new HashSet()); _maps.SetTiles(_gridUid, _grid, tiles); var dungeon = new Dungeon(new List() { room, }); await SuspendDungeon(); return dungeon; } private float GetDistance(float dx, float dy, IDunGenDistance distance) { switch (distance) { case DunGenEuclideanSquaredDistance: return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2)); case DunGenSquareBump: return 1f - (1f - dx * dx) * (1f - dy * dy); default: throw new ArgumentOutOfRangeException(); } } }