]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Make ore loot use walls as a mask (#16377)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Mon, 15 May 2023 02:01:29 +0000 (12:01 +1000)
committerGitHub <noreply@github.com>
Mon, 15 May 2023 02:01:29 +0000 (22:01 -0400)
Content.Server/Parallax/BiomeSystem.cs
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs
Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs
Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs [new file with mode: 0644]
Content.Shared/Weapons/Reflect/SharedReflectSystem.cs
Resources/Prototypes/Procedural/biome_ore_templates.yml
Resources/Prototypes/Procedural/salvage_loot.yml
Resources/Prototypes/ore.yml

index 35bfd7b8ffe4e312c2c18951935f03803b0b6c76..2fc9a368cb0d918280a71887aaef32aaadcc25e2 100644 (file)
@@ -5,9 +5,9 @@ using Content.Shared.Decals;
 using Content.Shared.Parallax.Biomes;
 using Content.Shared.Parallax.Biomes.Layers;
 using Content.Shared.Parallax.Biomes.Markers;
-using Content.Shared.Parallax.Biomes.Points;
 using Robust.Server.Player;
 using Robust.Shared;
+using Robust.Shared.Collections;
 using Robust.Shared.Configuration;
 using Robust.Shared.Console;
 using Robust.Shared.Map;
@@ -326,6 +326,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
     {
         var markers = _markerChunks[component];
         var loadedMarkers = component.LoadedMarkers;
+        var spawnSet = new HashSet<Vector2i>();
+        var spawns = new List<Vector2i>();
+        var frontier = new Queue<Vector2i>();
 
         foreach (var (layer, chunks) in markers)
         {
@@ -334,45 +337,112 @@ public sealed partial class BiomeSystem : SharedBiomeSystem
                 if (loadedMarkers.TryGetValue(layer, out var mobChunks) && mobChunks.Contains(chunk))
                     continue;
 
+                spawns.Clear();
+                spawnSet.Clear();
                 var layerProto = _proto.Index<BiomeMarkerLayerPrototype>(layer);
                 var buffer = layerProto.Radius / 2f;
                 mobChunks ??= new HashSet<Vector2i>();
                 mobChunks.Add(chunk);
                 loadedMarkers[layer] = mobChunks;
-                var rand = new Random(noise.GetSeed() + chunk.X * 8 + chunk.Y);
+                var rand = new Random(noise.GetSeed() + chunk.X * 8 + chunk.Y + layerProto.GetHashCode());
+
+                // We treat a null entity mask as requiring nothing else on the tile
+                var lower = (int) Math.Floor(buffer);
+                var upper = (int) Math.Ceiling(layerProto.Size - buffer);
+
+                // TODO: Okay this is inefficient as FUCK
+                // I think the ideal is pick a random tile then BFS outwards from it probably ig
+                // It will bias edge tiles significantly more but will make the CPU cry less.
+                for (var x = lower; x <= upper; x++)
+                {
+                    for (var y = lower; y <= upper; y++)
+                    {
+                        var index = new Vector2i(x + chunk.X, y + chunk.Y);
+                        TryGetEntity(index, component.Layers, component.Noise, grid, out var proto);
+
+                        if (proto != layerProto.EntityMask)
+                        {
+                            continue;
+                        }
+
+                        spawns.Add(index);
+                        spawnSet.Add(index);
+                    }
+                }
 
                 // Load NOW
-                // TODO: Need poisson but crashes whenever I use moony's due to inputs or smth
+                // TODO: Need poisson but crashes whenever I use moony's due to inputs or smth idk
                 var count = (int) ((layerProto.Size - buffer) * (layerProto.Size - buffer) / (layerProto.Radius * layerProto.Radius));
+                count = Math.Min(count, layerProto.MaxCount);
 
                 for (var i = 0; i < count; i++)
                 {
-                    for (var j = 0; j < 5; j++)
+                    if (spawns.Count == 0)
+                        break;
+
+                    var index = rand.Next(spawns.Count);
+                    var point = spawns[index];
+                    spawns.RemoveSwap(index);
+
+                    // Point was potentially used in BFS search below but we hadn't updated the list yet.
+                    if (!spawnSet.Remove(point))
                     {
-                        var point = new Vector2(
-                            chunk.X + buffer + rand.NextFloat() * (layerProto.Size - buffer),
-                            chunk.Y + buffer + rand.NextFloat() * (layerProto.Size - buffer));
+                        i--;
+                        continue;
+                    }
+
+                    // BFS search
+                    frontier.Enqueue(point);
+                    var groupCount = layerProto.GroupCount;
 
-                        var coords = new EntityCoordinates(gridUid, point);
-                        var tile = grid.LocalToTile(coords);
+                    while (frontier.TryDequeue(out var node) && groupCount > 0)
+                    {
+                        var enumerator = grid.GetAnchoredEntitiesEnumerator(node);
 
-                        // Blocked spawn, try again.
-                        if (grid.GetAnchoredEntitiesEnumerator(tile).MoveNext(out _))
+                        if (enumerator.MoveNext(out _))
                             continue;
 
-                        for (var k = 0; k < layerProto.GroupCount; k++)
+                        // Need to ensure the tile under it has loaded for anchoring.
+                        if (TryGetBiomeTile(node, component.Layers, component.Noise, grid, out var tile))
                         {
-                            // If it is a ghost role then purge it
-                            // TODO: This is *kind* of a bandaid but natural mobs spawns needs a lot more work.
-                            // Ideally we'd just have ghost role and non-ghost role variants for some stuff.
-                            var uid = EntityManager.CreateEntityUninitialized(layerProto.Prototype, new EntityCoordinates(gridUid, point));
-                            RemComp<GhostTakeoverAvailableComponent>(uid);
-                            RemComp<GhostRoleComponent>(uid);
-                            EntityManager.InitializeAndStartEntity(uid);
+                            grid.SetTile(node, tile.Value);
                         }
 
-                        break;
+                        // If it is a ghost role then purge it
+                        // TODO: This is *kind* of a bandaid but natural mobs spawns needs a lot more work.
+                        // Ideally we'd just have ghost role and non-ghost role variants for some stuff.
+                        var uid = EntityManager.CreateEntityUninitialized(layerProto.Prototype, new EntityCoordinates(gridUid, node));
+                        RemComp<GhostTakeoverAvailableComponent>(uid);
+                        RemComp<GhostRoleComponent>(uid);
+                        EntityManager.InitializeAndStartEntity(uid);
+                        groupCount--;
+
+                        for (var x = -1; x <= 1; x++)
+                        {
+                            for (var y = -1; y <= 1; y++)
+                            {
+                                if (x != 0 && y != 0)
+                                    continue;
+
+                                var neighbor = new Vector2i(x + node.X, y + node.Y);
+
+                                if (!spawnSet.Contains(neighbor))
+                                    continue;
+
+                                frontier.Enqueue(neighbor);
+                                // Rather than doing some uggo remove check on the list we'll defer it until later
+                                spawnSet.Remove(neighbor);
+                            }
+                        }
                     }
+
+                    // Add the unused nodes back in
+                    foreach (var node in frontier)
+                    {
+                        spawnSet.Add(node);
+                    }
+
+                    frontier.Clear();
                 }
             }
         }
index 2d27927b45bb46fa74de96a1dc736f363536df76..cbc4fb2b04705ce96a7292f25b93e794019a88ea 100644 (file)
@@ -13,6 +13,7 @@ using Content.Shared.Atmos;
 using Content.Shared.Dataset;
 using Content.Shared.Gravity;
 using Content.Shared.Parallax.Biomes;
+using Content.Shared.Parallax.Biomes.Markers;
 using Content.Shared.Procedural;
 using Content.Shared.Procedural.Loot;
 using Content.Shared.Random;
@@ -220,10 +221,20 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
 
             switch (rule)
             {
+                case BiomeMarkerLoot biomeLoot:
+                    {
+                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
+                        {
+                            _biome.AddMarkerLayer(biome, biomeLoot.Prototype);
+                        }
+                    }
+                    break;
                 case BiomeTemplateLoot biomeLoot:
-                    if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
                     {
-                        _biome.AddTemplate(biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
+                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
+                        {
+                            _biome.AddTemplate(biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
+                        }
                     }
                     break;
                 // Spawns a cluster (like an ore vein) nearby.
index cff75ad91796b9546915d9d15a01ae2ef9f1ebe3..4ac68364ba56e6c8329c87f8fdd49b019891ddc2 100644 (file)
@@ -1,20 +1,36 @@
-using Content.Shared.Parallax.Biomes.Points;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Parallax.Biomes.Markers;
 
+/// <summary>
+/// Spawns entities inside of the specified area with the minimum specified radius.
+/// </summary>
 [Prototype("biomeMarkerLayer")]
 public sealed class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
 {
     [IdDataField] public string ID { get; } = default!;
 
     [DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
-    public string Prototype = string.Empty;
+    public string Prototype { get; } = string.Empty;
 
-    /// <inheritdoc />
+    /// <summary>
+    /// Checks for the relevant entity for the tile before spawning. Useful for substituting walls with ore veins for example.
+    /// </summary>
+    [DataField("entityMask", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
+    public string? EntityMask { get; }
+
+    /// <summary>
+    /// Minimum radius between 2 points
+    /// </summary>
     [DataField("radius")]
-    public float Radius { get; } = 32f;
+    public float Radius = 32f;
+
+    /// <summary>
+    /// Maximum amount of group spawns
+    /// </summary>
+    [DataField("maxCount")]
+    public int MaxCount = int.MaxValue;
 
     /// <summary>
     /// How many mobs to spawn in one group.
index 7c47cf9a2f3bb2558ae6e1940040ddf27a2994b2..f92aa6881123af421932d97e6c3ec070355e3bb0 100644 (file)
@@ -1,6 +1,6 @@
 using Robust.Shared.Prototypes;
 
-namespace Content.Shared.Parallax.Biomes.Points;
+namespace Content.Shared.Parallax.Biomes.Markers;
 
 /// <summary>
 /// Specifies one-off marker points to be used. This could be for dungeon markers, mob markers, etc.
@@ -9,14 +9,14 @@ namespace Content.Shared.Parallax.Biomes.Points;
 public interface IBiomeMarkerLayer : IPrototype
 {
     /// <summary>
-    /// Minimum radius between 2 points
+    /// Biome template to use as a mask for this layer.
     /// </summary>
-    [DataField("radius")]
-    public float Radius { get; }
+    public string? EntityMask { get; }
+
+    public string Prototype { get; }
 
     /// <summary>
     /// How large the pre-generated points area is.
     /// </summary>
-    [DataField("size")]
     public int Size { get; }
 }
diff --git a/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs b/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs
new file mode 100644 (file)
index 0000000..9ab5173
--- /dev/null
@@ -0,0 +1,13 @@
+using Content.Shared.Parallax.Biomes.Markers;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Procedural.Loot;
+
+/// <summary>
+/// Adds a biome marker layer for dungeon loot.
+/// </summary>
+public sealed class BiomeMarkerLoot : IDungeonLoot
+{
+    [DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeMarkerLayerPrototype>))]
+    public string Prototype = string.Empty;
+}
index baca795bacc28b1934b088a885d7534a5f3e89ac..7ab56ab8aac6829482b561de26da9a338b4aee2b 100644 (file)
@@ -3,12 +3,8 @@ using Content.Shared.Audio;
 using Content.Shared.Hands.Components;
 using Robust.Shared.GameStates;
 using Content.Shared.Weapons.Ranged.Events;
-using System.Diagnostics.CodeAnalysis;
 using Robust.Shared.Physics.Components;
 using Content.Shared.Popups;
-using Content.Shared.Projectiles;
-using Content.Shared.Weapons.Ranged.Events;
-using Robust.Shared.GameStates;
 using Robust.Shared.Physics.Systems;
 using Robust.Shared.Random;
 
index 536b97e997a97cd496719dcb658ad46c30f9c4c8..37686eef5721d654cffefa5cc097648eb6d487d7 100644 (file)
-# Allowed
-#allowedTiles:
-#- FloorPlanetGrass
-#- FloorPlanetDirt
-#- FloorSnow
-#- FloorBasalt
-#- FloorAsteroidSand
-
-- type: biomeTemplate
+# Low value
+- type: biomeMarkerLayer
   id: OreTin
-  layers:
-    - !type:BiomeEntityLayer
-      threshold: 0.90
-      allowedTiles:
-        - FloorPlanetGrass
-        - FloorPlanetDirt
-        - FloorSnow
-        - FloorBasalt
-        - FloorAsteroidSand
-      noise:
-        seed: 100
-        noiseType: OpenSimplex2
-        frequency: 0.04
-        fractalType: None
-      entities:
-        - WallRockTin
+  proto: WallRockTin
+  entityMask: WallRock
+  maxCount: 5
+  groupCount: 10
+  radius: 4
 
 # Medium value
 # Gold
-- type: biomeTemplate
+- type: biomeMarkerLayer
   id: OreGold
-  layers:
-    - !type:BiomeEntityLayer
-      threshold: 0.95
-      allowedTiles:
-        - FloorPlanetGrass
-        - FloorPlanetDirt
-        - FloorSnow
-        - FloorBasalt
-        - FloorAsteroidSand
-      noise:
-        seed: 100
-        noiseType: OpenSimplex2
-        frequency: 0.04
-        fractalType: None
-      entities:
-        - WallRockGold
+  proto: WallRockGold
+  entityMask: WallRock
+  maxCount: 5
+  groupCount: 5
+  radius: 4
 
 # Silver
-- type: biomeTemplate
+- type: biomeMarkerLayer
   id: OreSilver
-  layers:
-    - !type:BiomeEntityLayer
-      threshold: 0.95
-      allowedTiles:
-        - FloorPlanetGrass
-        - FloorPlanetDirt
-        - FloorSnow
-        - FloorBasalt
-        - FloorAsteroidSand
-      noise:
-        seed: 100
-        noiseType: OpenSimplex2
-        frequency: 0.05
-        fractalType: None
-      entities:
-        - WallRockSilver
+  proto: WallRockSilver
+  entityMask: WallRock
+  maxCount: 5
+  groupCount: 5
+  radius: 4
 
 # High value
 # Plasma
-- type: biomeTemplate
+- type: biomeMarkerLayer
   id: OrePlasma
-  layers:
-    - !type:BiomeEntityLayer
-      threshold: 0.99
-      allowedTiles:
-        - FloorPlanetGrass
-        - FloorPlanetDirt
-        - FloorSnow
-        - FloorBasalt
-        - FloorAsteroidSand
-      noise:
-        seed: 100
-        noiseType: OpenSimplex2
-        frequency: 0.04
-        fractalType: None
-      entities:
-        - WallRockPlasma
+  proto: WallRockPlasma
+  entityMask: WallRock
+  maxCount: 2
+  groupCount: 5
+  radius: 4
 
 # Uranium
-- type: biomeTemplate
+- type: biomeMarkerLayer
   id: OreUranium
-  layers:
-    - !type:BiomeEntityLayer
-      threshold: 0.99
-      allowedTiles:
-        - FloorPlanetGrass
-        - FloorPlanetDirt
-        - FloorSnow
-        - FloorBasalt
-        - FloorAsteroidSand
-      noise:
-        seed: 100
-        noiseType: OpenSimplex2
-        frequency: 0.04
-        fractalType: None
-      entities:
-        - WallRockUranium
+  proto: WallRockUranium
+  entityMask: WallRock
+  maxCount: 2
+  groupCount: 5
+  radius: 4
+
+- type: biomeMarkerLayer
+  id: OreBananium
+  proto: WallRockBananium
+  entityMask: WallRock
+  maxCount: 2
+  groupCount: 5
+  radius: 4
index 25b905a4b027252c2a06a0ce05a6799e0e6be48d..c02177c617affc635c289708167d089828a1bc6f 100644 (file)
@@ -56,7 +56,7 @@
   id: OreTin
   desc: Veins of steel
   loots:
-    - !type:BiomeTemplateLoot
+    - !type:BiomeMarkerLoot
       proto: OreTin
 
 # - Medium value
   id: OreGold
   desc: Veins of gold ore
   loots:
-    - !type:BiomeTemplateLoot
+    - !type:BiomeMarkerLoot
       proto: OreGold
 
 - type: salvageLoot
   id: OreSilver
   desc: Veins of silver ore
   loots:
-    - !type:BiomeTemplateLoot
+    - !type:BiomeMarkerLoot
       proto: OreSilver
 
 # - High value
   id: OrePlasma
   desc: Veins of plasma ore
   loots:
-    - !type:BiomeTemplateLoot
+    - !type:BiomeMarkerLoot
       proto: OrePlasma
 
 - type: salvageLoot
   id: OreUranium
   desc: Veins of uranium ore
   loots:
-    - !type:BiomeTemplateLoot
+    - !type:BiomeMarkerLoot
       proto: OreUranium
index 8e6d90bfb4b6a042fdf14c11bcbba9c13bf591b1..0aa111b9f032a2a3ca6da6a25e5f790d449b0b9d 100644 (file)
@@ -41,7 +41,7 @@
   id: OreBananium
   oreEntity: BananiumOre1
   minOreYield: 1
-  maxOreYield: 2
+  maxOreYield: 3
 
 - type: weightedRandom
   id: RandomOreDistributionStandard