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;
{
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)
{
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();
}
}
}
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;
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.
-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.
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.
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; }
}
--- /dev/null
+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;
+}
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;
-# 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
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
id: OreBananium
oreEntity: BananiumOre1
minOreYield: 1
- maxOreYield: 2
+ maxOreYield: 3
- type: weightedRandom
id: RandomOreDistributionStandard