Make ore loot use walls as a mask (#16377)

This commit is contained in:
metalgearsloth
2023-05-15 12:01:29 +10:00
committed by GitHub
parent 7065341171
commit ea4440be44
9 changed files with 187 additions and 135 deletions

View 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;
}
var coords = new EntityCoordinates(gridUid, point);
var tile = grid.LocalToTile(coords);
// BFS search
frontier.Enqueue(point);
var groupCount = layerProto.GroupCount;
// Blocked spawn, try again.
if (grid.GetAnchoredEntitiesEnumerator(tile).MoveNext(out _))
while (frontier.TryDequeue(out var node) && groupCount > 0)
{
var enumerator = grid.GetAnchoredEntitiesEnumerator(node);
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))
{
grid.SetTile(node, tile.Value);
}
// 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));
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);
}
}
}
break;
// Add the unused nodes back in
foreach (var node in frontier)
{
spawnSet.Add(node);
}
frontier.Clear();
}
}
}

View 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,11 +221,21 @@ 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);
}
}
break;
// Spawns a cluster (like an ore vein) nearby.
case DungeonClusterLoot clusterLoot:

View 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.

View 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; }
}

View File

@@ -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;
}

View 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;

View File

@@ -1,108 +1,54 @@
# 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

View File

@@ -56,7 +56,7 @@
id: OreTin
desc: Veins of steel
loots:
- !type:BiomeTemplateLoot
- !type:BiomeMarkerLoot
proto: OreTin
# - Medium value
@@ -64,14 +64,14 @@
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
@@ -79,12 +79,12 @@
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

View File

@@ -41,7 +41,7 @@
id: OreBananium
oreEntity: BananiumOre1
minOreYield: 1
maxOreYield: 2
maxOreYield: 3
- type: weightedRandom
id: RandomOreDistributionStandard