Biome rework (#37735)
* DungeonData rework Back to fields, serializes better, just make new layers dumby. * wawawewa * Fix this * Fixes * Port the work over * wawawewa * zoom * Kinda workin * Adjust wawa * Unloading work * Ore + entitytable fixes Iterate every dungeon not just last. * Big shot * wawawewa * Fixes * true * Fixes # Conflicts: # Content.Server/Procedural/DungeonJob/DungeonJob.cs * wawawewa * Fixes * Fix * Lot of work * wawawewa * Fixing * eh? * a * Fix a heap of stuff * Better ignored check * Reserve tile changes * biome * changes * wawawewa * Fixes & snow * Shadow fixes * wawawewa * smol * Add layer API * More work * wawawewa * Preloads and running again * wawawewa * Modified * Replacements and command * Runtime support * werk * Fix expeds + dungeon alltiles * reh --------- Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class BiomeDebugOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
|
||||
private BiomeSystem _biomes;
|
||||
private SharedMapSystem _maps;
|
||||
|
||||
private Font _font;
|
||||
|
||||
public BiomeDebugOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_biomes = _entManager.System<BiomeSystem>();
|
||||
_maps = _entManager.System<SharedMapSystem>();
|
||||
|
||||
_font = new VectorFont(_cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12);
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
var mapUid = _maps.GetMapOrInvalid(args.MapId);
|
||||
|
||||
return _entManager.HasComponent<BiomeComponent>(mapUid);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var mousePos = _eyeManager.ScreenToMap(mouseScreenPos);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace || mousePos.MapId != args.MapId)
|
||||
return;
|
||||
|
||||
var mapUid = _maps.GetMapOrInvalid(args.MapId);
|
||||
|
||||
if (!_entManager.TryGetComponent(mapUid, out BiomeComponent? biomeComp) || !_entManager.TryGetComponent(mapUid, out MapGridComponent? grid))
|
||||
return;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var nodePos = _maps.WorldToTile(mapUid, grid, mousePos.Position);
|
||||
|
||||
if (_biomes.TryGetEntity(nodePos, biomeComp, (mapUid, grid), out var ent))
|
||||
{
|
||||
var text = $"Entity: {ent}";
|
||||
sb.AppendLine(text);
|
||||
}
|
||||
|
||||
if (_biomes.TryGetDecals(nodePos, biomeComp.Layers, biomeComp.Seed, (mapUid, grid), out var decals))
|
||||
{
|
||||
var text = $"Decals: {decals.Count}";
|
||||
sb.AppendLine(text);
|
||||
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
var decalText = $"- {decal.ID}";
|
||||
sb.AppendLine(decalText);
|
||||
}
|
||||
}
|
||||
|
||||
if (_biomes.TryGetBiomeTile(nodePos, biomeComp.Layers, biomeComp.Seed, (mapUid, grid), out var tile))
|
||||
{
|
||||
var tileText = $"Tile: {_tileDefManager[tile.Value.TypeId].ID}";
|
||||
sb.AppendLine(tileText);
|
||||
}
|
||||
|
||||
args.ScreenHandle.DrawString(_font, mouseScreenPos.Position + new Vector2(0f, 32f), sb.ToString());
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
|
||||
namespace Content.Client.Parallax;
|
||||
|
||||
public sealed class BiomeSystem : SharedBiomeSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Parallax.Commands;
|
||||
|
||||
public sealed class ShowBiomeCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
|
||||
|
||||
public override string Command => "showbiome";
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (_overlayMgr.HasOverlay<BiomeDebugOverlay>())
|
||||
{
|
||||
_overlayMgr.RemoveOverlay<BiomeDebugOverlay>();
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayMgr.AddOverlay(new BiomeDebugOverlay());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -19,7 +17,6 @@ public sealed class ParallaxOverlay : Overlay
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IParallaxManager _manager = default!;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
|
||||
@@ -28,13 +25,12 @@ public sealed class ParallaxOverlay : Overlay
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_parallax = _entManager.System<ParallaxSystem>();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapSystem.GetMapOrInvalid(args.MapId)))
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Computer;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Salvage.UI;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Content.Shared.Procedural;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed partial class GatewayGeneratorComponent : Component
|
||||
/// Mob layers to pick from.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<BiomeMarkerLayerPrototype>> MobLayers = new()
|
||||
public List<ProtoId<DungeonConfigPrototype>> MobLayers = new()
|
||||
{
|
||||
"Carps",
|
||||
"Xenos",
|
||||
@@ -54,7 +54,7 @@ public sealed partial class GatewayGeneratorComponent : Component
|
||||
/// <summary>
|
||||
/// Loot layers to pick from.
|
||||
/// </summary>
|
||||
public List<ProtoId<BiomeMarkerLayerPrototype>> LootLayers = new()
|
||||
public List<ProtoId<DungeonConfigPrototype>> LootLayers = new()
|
||||
{
|
||||
"OreIron",
|
||||
"OreQuartz",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Gateway.Components;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Components;
|
||||
using Content.Shared.Salvage;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
@@ -111,7 +110,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
};
|
||||
AddComp(mapUid, restricted);
|
||||
|
||||
_biome.EnsurePlanet(mapUid, _protoManager.Index<BiomeTemplatePrototype>("Continental"), seed);
|
||||
_biome.EnsurePlanet(mapUid, _protoManager.Index("BiomeContinental"), seed);
|
||||
|
||||
var grid = Comp<MapGridComponent>(mapUid);
|
||||
|
||||
@@ -199,7 +198,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
var layer = lootLayers[layerIdx];
|
||||
lootLayers.RemoveSwap(layerIdx);
|
||||
|
||||
_biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
|
||||
_biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
|
||||
}
|
||||
|
||||
// - Mobs
|
||||
@@ -211,7 +210,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
|
||||
var layer = mobLayers[layerIdx];
|
||||
mobLayers.RemoveSwap(layerIdx);
|
||||
|
||||
_biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
|
||||
_biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Audio;
|
||||
using Content.Shared.Procedural.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Maps;
|
||||
|
||||
@@ -52,7 +43,7 @@ public sealed class PlanetCommand : LocalizedEntityCommands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_protoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var biomeTemplate))
|
||||
if (!_protoManager.TryIndex<EntityPrototype>(args[1], out var biomeTemplate))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1])));
|
||||
return;
|
||||
@@ -70,9 +61,12 @@ public sealed class PlanetCommand : LocalizedEntityCommands
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), "Map Id");
|
||||
|
||||
var biomeName = _entManager.ComponentFactory.GetComponentName<BiomeComponent>();
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
var options = _protoManager.EnumeratePrototypes<BiomeTemplatePrototype>()
|
||||
var options = _protoManager.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(o => o.Components.ContainsKey(biomeName))
|
||||
.Select(o => new CompletionOption(o.ID, "Biome"));
|
||||
return CompletionResult.FromOptions(options);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ public sealed partial class PathfindingSystem
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighbor = node + new Vector2i(x, y);
|
||||
var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f;
|
||||
|
||||
@@ -121,8 +124,7 @@ public sealed partial class PathfindingSystem
|
||||
cameFrom[neighbor] = node;
|
||||
costSoFar[neighbor] = gScore;
|
||||
|
||||
// Still use octile even for manhattan distance.
|
||||
var hScore = OctileDistance(args.End, neighbor) * 1.001f;
|
||||
var hScore = ManhattanDistance(args.End, neighbor);
|
||||
var fScore = gScore + hScore;
|
||||
frontier.Enqueue(neighbor, fScore);
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Parallax;
|
||||
|
||||
public sealed partial class BiomeSystem
|
||||
{
|
||||
private void InitializeCommands()
|
||||
{
|
||||
_console.RegisterCommand("biome_clear", Loc.GetString("cmd-biome_clear-desc"), Loc.GetString("cmd-biome_clear-help"), BiomeClearCallback, BiomeClearCallbackHelper);
|
||||
_console.RegisterCommand("biome_addlayer", Loc.GetString("cmd-biome_addlayer-desc"), Loc.GetString("cmd-biome_addlayer-help"), AddLayerCallback, AddLayerCallbackHelp);
|
||||
_console.RegisterCommand("biome_addmarkerlayer", Loc.GetString("cmd-biome_addmarkerlayer-desc"), Loc.GetString("cmd-biome_addmarkerlayer-desc"), AddMarkerLayerCallback, AddMarkerLayerCallbackHelper);
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void BiomeClearCallback(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int.TryParse(args[0], out var mapInt);
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
|
||||
|
||||
if (_mapSystem.MapExists(mapId) ||
|
||||
!TryComp<BiomeComponent>(mapUid, out var biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ClearTemplate(mapUid, biome);
|
||||
}
|
||||
|
||||
private CompletionResult BiomeClearCallbackHelper(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.Components<BiomeComponent>(args[0], EntityManager), "Biome");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void AddLayerCallback(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length < 3 || args.Length > 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
|
||||
|
||||
if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(mapUid, out var biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ProtoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var template))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
|
||||
if (args.Length == 4)
|
||||
{
|
||||
int.TryParse(args[3], out offset);
|
||||
}
|
||||
|
||||
AddTemplate(mapUid, biome, args[2], template, offset);
|
||||
}
|
||||
|
||||
private CompletionResult AddLayerCallbackHelp(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), "Map ID");
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIDs<BiomeTemplatePrototype>(proto: ProtoManager), "Biome template");
|
||||
}
|
||||
|
||||
if (args.Length == 3)
|
||||
{
|
||||
if (int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
|
||||
{
|
||||
var results = new List<string>();
|
||||
|
||||
foreach (var layer in biome.Layers)
|
||||
{
|
||||
if (layer is not BiomeDummyLayer dummy)
|
||||
continue;
|
||||
|
||||
results.Add(dummy.ID);
|
||||
}
|
||||
|
||||
return CompletionResult.FromHintOptions(results, "Dummy layer ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Length == 4)
|
||||
{
|
||||
return CompletionResult.FromHint("Seed offset");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void AddMarkerLayerCallback(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ProtoManager.HasIndex<BiomeMarkerLayerPrototype>(args[1]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!biome.MarkerLayers.Add(args[1]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
biome.ForcedMarkerLayers.Add(args[1]);
|
||||
}
|
||||
|
||||
private CompletionResult AddMarkerLayerCallbackHelper(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var allQuery = AllEntityQuery<MapComponent, BiomeComponent>();
|
||||
var options = new List<CompletionOption>();
|
||||
|
||||
while (allQuery.MoveNext(out var mapComp, out _))
|
||||
{
|
||||
options.Add(new CompletionOption(mapComp.MapId.ToString()));
|
||||
}
|
||||
|
||||
return CompletionResult.FromHintOptions(options, "Biome");
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIDs<BiomeMarkerLayerPrototype>(proto: ProtoManager), "Marker");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
105
Content.Server/Procedural/BiomeSystem.Commands.cs
Normal file
105
Content.Server/Procedural/BiomeSystem.Commands.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Procedural;
|
||||
|
||||
public sealed partial class BiomeSystem
|
||||
{
|
||||
[AdminCommand(AdminFlags.Mapping)]
|
||||
public sealed class BiomeAddLayerCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
public override string Command => "biome_addlayer";
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 3)
|
||||
{
|
||||
shell.WriteError(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var entInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entId = new EntityUid(entInt);
|
||||
|
||||
if (!EntityManager.TryGetComponent(entId, out BiomeComponent? biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_protoManager.TryIndex(args[2], out DungeonConfigPrototype? config))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var system = EntityManager.System<BiomeSystem>();
|
||||
system.AddLayer((entId, biome), args[1], config);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromOptions(CompletionHelper.Components<BiomeComponent>(args[0]));
|
||||
case 2:
|
||||
return CompletionResult.FromHint("layerId");
|
||||
case 3:
|
||||
return CompletionResult.FromOptions(CompletionHelper.PrototypeIDs<DungeonConfigPrototype>());
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Mapping)]
|
||||
public sealed class BiomeRemoveLayerCommand : LocalizedEntityCommands
|
||||
{
|
||||
public override string Command => "biome_removelayer";
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var entInt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entId = new EntityUid(entInt);
|
||||
|
||||
if (!EntityManager.TryGetComponent(entId, out BiomeComponent? biome))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var system = EntityManager.System<BiomeSystem>();
|
||||
system.RemoveLayer((entId, biome), args[1]);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
return CompletionResult.FromOptions(CompletionHelper.Components<BiomeComponent>(args[0]));
|
||||
case 1:
|
||||
return CompletionResult.FromHint("layerId");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Content.Server/Procedural/BiomeSystem.Planet.cs
Normal file
65
Content.Server/Procedural/BiomeSystem.Planet.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Procedural.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Procedural;
|
||||
|
||||
public sealed partial class BiomeSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies the biomecomponent to the specified map.
|
||||
/// </summary>
|
||||
public BiomeComponent? AddBiome(Entity<BiomeComponent?> mapUid, EntProtoId biomeTemplate, int? seed = null)
|
||||
{
|
||||
if (!_protomanager.Index(biomeTemplate).Components.TryGetComponent(Factory.GetComponentName<BiomeComponent>(), out var template))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var biome = Factory.GetComponent<BiomeComponent>();
|
||||
var biomeObj = (object)biome;
|
||||
_serManager.CopyTo(template, ref biomeObj, notNullableOverride: true);
|
||||
seed ??= _random.Next();
|
||||
biome.Seed = seed.Value;
|
||||
AddComp(mapUid, biome, true);
|
||||
return biome;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a simple planet setup for a map.
|
||||
/// </summary>
|
||||
public void EnsurePlanet(EntityUid mapUid, EntProtoId biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null)
|
||||
{
|
||||
if (!Resolve(mapUid, ref metadata))
|
||||
return;
|
||||
|
||||
EnsureComp<MapGridComponent>(mapUid);
|
||||
AddBiome(mapUid, biomeTemplate, seed);
|
||||
var gravity = EnsureComp<GravityComponent>(mapUid);
|
||||
gravity.Enabled = true;
|
||||
gravity.Inherent = true;
|
||||
Dirty(mapUid, gravity, metadata);
|
||||
|
||||
var light = EnsureComp<MapLightComponent>(mapUid);
|
||||
light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
|
||||
Dirty(mapUid, light, metadata);
|
||||
|
||||
EnsureComp<RoofComponent>(mapUid);
|
||||
|
||||
EnsureComp<LightCycleComponent>(mapUid);
|
||||
|
||||
EnsureComp<SunShadowComponent>(mapUid);
|
||||
EnsureComp<SunShadowCycleComponent>(mapUid);
|
||||
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int)Gas.Oxygen] = 21.824779f;
|
||||
moles[(int)Gas.Nitrogen] = 82.10312f;
|
||||
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
|
||||
_atmos.SetMapAtmosphere(mapUid, false, mixture);
|
||||
}
|
||||
}
|
||||
661
Content.Server/Procedural/BiomeSystem.cs
Normal file
661
Content.Server/Procedural/BiomeSystem.cs
Normal file
@@ -0,0 +1,661 @@
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Components;
|
||||
using Content.Shared.Procedural.DungeonGenerators;
|
||||
using Content.Shared.Sprite;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.CPUJob.JobQueues;
|
||||
using Robust.Shared.CPUJob.JobQueues.Queues;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Procedural;
|
||||
|
||||
public sealed partial class BiomeSystem : EntitySystem
|
||||
{
|
||||
/*
|
||||
* Handles loading in biomes around players.
|
||||
* These are essentially chunked-areas that load in dungeons and can also be unloaded.
|
||||
*/
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protomanager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Ignored components for default checks
|
||||
/// </summary>
|
||||
public static readonly List<string> IgnoredComponents = new();
|
||||
|
||||
/// <summary>
|
||||
/// Jobs for biomes to load.
|
||||
/// </summary>
|
||||
private JobQueue _biomeQueue = default!;
|
||||
|
||||
private float _loadRange = 1f;
|
||||
private float _loadTime;
|
||||
|
||||
private EntityQuery<GhostComponent> _ghostQuery;
|
||||
private EntityQuery<BiomeComponent> _biomeQuery;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> AllowBiomeLoadingTag = "AllowBiomeLoading";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_ghostQuery = GetEntityQuery<GhostComponent>();
|
||||
_biomeQuery = GetEntityQuery<BiomeComponent>();
|
||||
|
||||
IgnoredComponents.Add(Factory.GetComponentName<RandomSpriteComponent>());
|
||||
|
||||
Subs.CVar(_cfgManager, CCVars.BiomeLoadRange, OnLoadRange, true);
|
||||
Subs.CVar(_cfgManager, CCVars.BiomeLoadTime, OnLoadTime, true);
|
||||
|
||||
SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
|
||||
}
|
||||
|
||||
private void OnLoadTime(float obj)
|
||||
{
|
||||
_biomeQueue = new JobQueue(obj);
|
||||
_loadTime = obj;
|
||||
}
|
||||
|
||||
private void OnLoadRange(float obj)
|
||||
{
|
||||
_loadRange = obj;
|
||||
}
|
||||
|
||||
private void OnFTLStarted(ref FTLStartedEvent ev)
|
||||
{
|
||||
var targetMap = _xforms.ToMapCoordinates(ev.TargetCoordinates);
|
||||
var targetMapUid = _maps.GetMapOrInvalid(targetMap.MapId);
|
||||
|
||||
if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
|
||||
return;
|
||||
|
||||
var preloadArea = new Vector2(32f, 32f);
|
||||
var targetArea = new Box2(targetMap.Position - preloadArea, targetMap.Position + preloadArea);
|
||||
Preload(targetMapUid, biome, (Box2i) targetArea);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preloads biome for the specified area.
|
||||
/// </summary>
|
||||
public void Preload(EntityUid uid, BiomeComponent component, Box2i area)
|
||||
{
|
||||
component.PreloadAreas.Add(area);
|
||||
}
|
||||
|
||||
private bool CanLoad(EntityUid uid)
|
||||
{
|
||||
return !_ghostQuery.HasComp(uid) || _tags.HasTag(uid, AllowBiomeLoadingTag);
|
||||
}
|
||||
|
||||
public bool RemoveLayer(Entity<BiomeComponent?> biome, string label)
|
||||
{
|
||||
if (!Resolve(biome.Owner, ref biome.Comp))
|
||||
return false;
|
||||
|
||||
if (!biome.Comp.Layers.ContainsKey(label))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Technically this can race-condition with adds but uhh tell people to not do that.
|
||||
biome.Comp.PendingRemovals.Add(label);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddLayer(Entity<BiomeComponent?> biome, string label, BiomeMetaLayer layer)
|
||||
{
|
||||
if (!Resolve(biome.Owner, ref biome.Comp))
|
||||
return;
|
||||
|
||||
if (!biome.Comp.Layers.TryAdd(label, layer))
|
||||
{
|
||||
Log.Warning($"Tried to add layer {label} to biome {ToPrettyString(biome)} that already has it?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddLayer(Entity<BiomeComponent?> biome, string label, ProtoId<DungeonConfigPrototype> layer)
|
||||
{
|
||||
if (!Resolve(biome.Owner, ref biome.Comp))
|
||||
return;
|
||||
|
||||
var metaLayer = new BiomeMetaLayer()
|
||||
{
|
||||
Dungeon = layer,
|
||||
};
|
||||
|
||||
AddLayer(biome, label, metaLayer);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = AllEntityQuery<BiomeComponent>();
|
||||
|
||||
while (query.MoveNext(out var biome))
|
||||
{
|
||||
// If it's still loading then don't touch the observer bounds.
|
||||
if (biome.Loading)
|
||||
continue;
|
||||
|
||||
biome.LoadedBounds.Clear();
|
||||
|
||||
// Make sure preloads go in.
|
||||
foreach (var preload in biome.PreloadAreas)
|
||||
{
|
||||
biome.LoadedBounds.Add(preload);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all relevant players.
|
||||
foreach (var player in _player.Sessions)
|
||||
{
|
||||
if (player.AttachedEntity != null)
|
||||
{
|
||||
TryAddBiomeBounds(player.AttachedEntity.Value);
|
||||
}
|
||||
|
||||
foreach (var viewer in player.ViewSubscriptions)
|
||||
{
|
||||
TryAddBiomeBounds(viewer);
|
||||
}
|
||||
}
|
||||
|
||||
// Unload first in case we can't catch up.
|
||||
UnloadChunks();
|
||||
|
||||
var loadQuery = AllEntityQuery<BiomeComponent, MapGridComponent>();
|
||||
|
||||
// Check if any biomes are intersected and queue up loads.
|
||||
while (loadQuery.MoveNext(out var uid, out var biome, out var grid))
|
||||
{
|
||||
if (biome.Loading || biome.LoadedBounds.Count == 0)
|
||||
continue;
|
||||
|
||||
biome.Loading = true;
|
||||
var job = new BiomeLoadJob(_loadTime)
|
||||
{
|
||||
Grid = (uid, biome, grid),
|
||||
};
|
||||
_biomeQueue.EnqueueJob(job);
|
||||
}
|
||||
|
||||
// Process jobs.
|
||||
_biomeQueue.Process();
|
||||
}
|
||||
|
||||
private void UnloadChunks()
|
||||
{
|
||||
var query = AllEntityQuery<BiomeComponent, MapGridComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var biome, out var grid))
|
||||
{
|
||||
// Only start unloading if it's currently not loading anything.
|
||||
if (biome.Loading)
|
||||
continue;
|
||||
|
||||
var toUnload = new Dictionary<string, List<Vector2i>>();
|
||||
|
||||
foreach (var (layerId, loadedLayer) in biome.LoadedData)
|
||||
{
|
||||
var layer = biome.Layers[layerId];
|
||||
|
||||
// If it can't unload then ignore it.
|
||||
if (!layer.CanUnload)
|
||||
continue;
|
||||
|
||||
var size = GetSize(_protomanager.Index(layer.Dungeon), layer);
|
||||
|
||||
if (size == null)
|
||||
continue;
|
||||
|
||||
// Go through each loaded chunk and check if they can be unloaded by checking if any players are in range.
|
||||
foreach (var chunk in loadedLayer.Keys)
|
||||
{
|
||||
var chunkBounds = new Box2i(chunk, chunk + size.Value);
|
||||
var canUnload = true;
|
||||
|
||||
foreach (var playerView in biome.LoadedBounds)
|
||||
{
|
||||
// Give a buffer range so we don't immediately unload if we wiggle, we'll just double the load area.
|
||||
var enlarged = playerView.Enlarged((int) _loadRange);
|
||||
|
||||
// Still relevant
|
||||
if (chunkBounds.Intersects(enlarged))
|
||||
{
|
||||
canUnload = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canUnload)
|
||||
continue;
|
||||
|
||||
toUnload.GetOrNew(layerId).Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if (biome.PendingRemovals.Count > 0)
|
||||
{
|
||||
foreach (var label in biome.PendingRemovals)
|
||||
{
|
||||
var bounds = toUnload.GetOrNew(label);
|
||||
|
||||
foreach (var chunkOrigin in biome.LoadedData[label].Keys)
|
||||
{
|
||||
bounds.Add(chunkOrigin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toUnload.Count == 0)
|
||||
continue;
|
||||
|
||||
// Queue up unloads.
|
||||
biome.Loading = true;
|
||||
var job = new BiomeUnloadJob(_loadTime)
|
||||
{
|
||||
Biome = (uid, grid, biome),
|
||||
ToUnload = toUnload,
|
||||
};
|
||||
_biomeQueue.EnqueueJob(job);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full bounds to be loaded. Considers layer dependencies where they may have different chunk sizes.
|
||||
/// </summary>
|
||||
private Box2i GetFullBounds(BiomeComponent component, Box2i bounds)
|
||||
{
|
||||
var result = bounds;
|
||||
|
||||
foreach (var layer in component.Layers.Values)
|
||||
{
|
||||
var layerBounds = GetLayerBounds(layer, result);
|
||||
|
||||
if (layer.DependsOn != null)
|
||||
{
|
||||
foreach (var sub in layer.DependsOn)
|
||||
{
|
||||
var depLayer = component.Layers[sub];
|
||||
|
||||
layerBounds = layerBounds.Union(GetLayerBounds(depLayer, layerBounds));
|
||||
}
|
||||
}
|
||||
|
||||
result = result.Union(layerBounds);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add the viewer bounds of this entity for loading.
|
||||
/// </summary>
|
||||
private void TryAddBiomeBounds(EntityUid uid)
|
||||
{
|
||||
if (!CanLoad(uid))
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
|
||||
// No biome to load
|
||||
if (!_biomeQuery.TryComp(xform.MapUid, out var biome))
|
||||
return;
|
||||
|
||||
// Currently already loading.
|
||||
if (biome.Loading)
|
||||
return;
|
||||
|
||||
var center = _xforms.GetWorldPosition(uid);
|
||||
|
||||
var bounds = new Box2i((center - new Vector2(_loadRange, _loadRange)).Floored(), (center + new Vector2(_loadRange, _loadRange)).Floored());
|
||||
|
||||
// If it's moving then preload in that direction
|
||||
if (TryComp(uid, out PhysicsComponent? physics))
|
||||
{
|
||||
bounds = bounds.Union(bounds.Translated((physics.LinearVelocity * 2f).Floored()));
|
||||
}
|
||||
|
||||
var adjustedBounds = GetFullBounds(biome, bounds);
|
||||
biome.LoadedBounds.Add(adjustedBounds);
|
||||
}
|
||||
|
||||
public int? GetSize(DungeonConfigPrototype config, BiomeMetaLayer layer)
|
||||
{
|
||||
var size = layer.Size;
|
||||
|
||||
if (size == null && config.Layers[0] is ChunkDunGen chunkGen)
|
||||
{
|
||||
size = chunkGen.Size;
|
||||
}
|
||||
// No size
|
||||
else
|
||||
{
|
||||
Log.Warning($"Unable to infer chunk size for biome {layer} / config {config.ID}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return size.Value;
|
||||
}
|
||||
|
||||
public Box2i GetLayerBounds(BiomeMetaLayer layer, Box2i layerBounds)
|
||||
{
|
||||
var size = GetSize(_protomanager.Index(layer.Dungeon), layer);
|
||||
|
||||
if (size == null)
|
||||
return Box2i.Empty;
|
||||
|
||||
var chunkSize = new Vector2(size.Value, size.Value);
|
||||
|
||||
// Need to round the bounds to our chunk size to ensure we load whole chunks.
|
||||
// We also need to know the minimum bounds for our dependencies to load.
|
||||
var layerBL = (layerBounds.BottomLeft / chunkSize).Floored() * chunkSize;
|
||||
var layerTR = (layerBounds.TopRight / chunkSize).Ceiled() * chunkSize;
|
||||
|
||||
var loadBounds = new Box2i(layerBL.Floored(), layerTR.Ceiled());
|
||||
return loadBounds;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BiomeLoadJob : Job<bool>
|
||||
{
|
||||
[Dependency] private IEntityManager _entManager = default!;
|
||||
[Dependency] private IPrototypeManager _protoManager = default!;
|
||||
|
||||
private BiomeSystem System = default!;
|
||||
private DungeonSystem DungeonSystem = default!;
|
||||
|
||||
public Entity<BiomeComponent, MapGridComponent> Grid;
|
||||
|
||||
internal ISawmill _sawmill = default!;
|
||||
|
||||
public BiomeLoadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
System = _entManager.System<BiomeSystem>();
|
||||
DungeonSystem = _entManager.System<DungeonSystem>();
|
||||
}
|
||||
|
||||
protected override async Task<bool> Process()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var bound in Grid.Comp1.LoadedBounds)
|
||||
{
|
||||
foreach (var (layerId, layer) in Grid.Comp1.Layers)
|
||||
{
|
||||
await LoadLayer(layerId, layer, bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Finished
|
||||
DebugTools.Assert(Grid.Comp1.Loading);
|
||||
Grid.Comp1.Loading = false;
|
||||
}
|
||||
|
||||
// If we have any preloads then mark those as modified so they persist.
|
||||
foreach (var preload in Grid.Comp1.PreloadAreas)
|
||||
{
|
||||
for (var x = preload.Left; x <= preload.Right; x++)
|
||||
{
|
||||
for (var y = preload.Bottom; y <= preload.Top; y++)
|
||||
{
|
||||
var index = new Vector2i(x, y);
|
||||
Grid.Comp1.ModifiedTiles.Add(index);
|
||||
}
|
||||
}
|
||||
|
||||
await SuspendIfOutOfTime();
|
||||
}
|
||||
|
||||
Grid.Comp1.PreloadAreas.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task LoadLayer(string layerId, BiomeMetaLayer layer, Box2i parentBounds)
|
||||
{
|
||||
// Nothing to do
|
||||
var dungeon = _protoManager.Index(layer.Dungeon);
|
||||
|
||||
if (dungeon.Layers.Count == 0)
|
||||
return;
|
||||
|
||||
var loadBounds = System.GetLayerBounds(layer, parentBounds);
|
||||
|
||||
// Make sure our dependencies are loaded first.
|
||||
if (layer.DependsOn != null)
|
||||
{
|
||||
foreach (var sub in layer.DependsOn)
|
||||
{
|
||||
var actualLayer = Grid.Comp1.Layers[sub];
|
||||
|
||||
await LoadLayer(sub, actualLayer, loadBounds);
|
||||
}
|
||||
}
|
||||
|
||||
var size = System.GetSize(dungeon, layer);
|
||||
|
||||
if (size == null)
|
||||
return;
|
||||
|
||||
// The reason we do this is so if we dynamically add similar layers (e.g. we add 3 mob layers at runtime)
|
||||
// they don't all have the same seeds.
|
||||
var layerSeed = Grid.Comp1.Seed + layerId.GetHashCode();
|
||||
|
||||
// Okay all of our dependencies loaded so we can send it.
|
||||
var chunkEnumerator = new NearestChunkEnumerator(loadBounds, size.Value);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var chunkOrigin = chunk.Value;
|
||||
var layerLoaded = Grid.Comp1.LoadedData.GetOrNew(layerId);
|
||||
|
||||
// Layer already loaded for this chunk.
|
||||
// This can potentially happen if we're moving and the player's bounds changed but some existing chunks remain.
|
||||
if (layerLoaded.ContainsKey(chunkOrigin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load dungeon here async await and all that jaz.
|
||||
var (_, data) = await WaitAsyncTask(DungeonSystem
|
||||
.GenerateDungeonAsync(dungeon, Grid.Owner, Grid.Comp2, chunkOrigin, layerSeed, reservedTiles: Grid.Comp1.ModifiedTiles));
|
||||
|
||||
// If we can unload it then store the data to check for later.
|
||||
if (layer.CanUnload)
|
||||
{
|
||||
layerLoaded.Add(chunkOrigin, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BiomeUnloadJob : Job<bool>
|
||||
{
|
||||
[Dependency] private EntityManager _entManager = default!;
|
||||
|
||||
public Entity<MapGridComponent, BiomeComponent> Biome;
|
||||
public Dictionary<string, List<Vector2i>> ToUnload = default!;
|
||||
|
||||
public BiomeUnloadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public BiomeUnloadJob(double maxTime, IStopwatch stopwatch, CancellationToken cancellation = default) : base(maxTime, stopwatch, cancellation)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<bool> Process()
|
||||
{
|
||||
try
|
||||
{
|
||||
var grid = Biome.Comp1;
|
||||
var biome = Biome.Comp2;
|
||||
DebugTools.Assert(biome.Loading);
|
||||
var maps = _entManager.System<SharedMapSystem>();
|
||||
var decals = _entManager.System<DecalSystem>();
|
||||
var lookup = _entManager.System<EntityLookupSystem>();
|
||||
_entManager.TryGetComponent(Biome.Owner, out DecalGridComponent? decalGrid);
|
||||
var forceUnload = _entManager.GetEntityQuery<BiomeForceUnloadComponent>();
|
||||
var entities = new HashSet<EntityUid>();
|
||||
var tiles = new List<(Vector2i, Tile)>();
|
||||
|
||||
foreach (var (layer, chunkOrigins) in ToUnload)
|
||||
{
|
||||
if (!biome.Layers.TryGetValue(layer, out var meta))
|
||||
continue;
|
||||
|
||||
if (!biome.LoadedData.TryGetValue(layer, out var data))
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(meta.CanUnload);
|
||||
|
||||
foreach (var chunk in chunkOrigins)
|
||||
{
|
||||
// Not loaded anymore?
|
||||
if (!data.Remove(chunk, out var loaded))
|
||||
continue;
|
||||
|
||||
tiles.Clear();
|
||||
|
||||
foreach (var (ent, pos) in loaded.Entities)
|
||||
{
|
||||
// Already flagged as modified so go next.
|
||||
if (biome.ModifiedTiles.Contains(pos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// IsDefault is actually super expensive so really need to run this check in the loop.
|
||||
await SuspendIfOutOfTime();
|
||||
|
||||
if (forceUnload.HasComp(ent))
|
||||
{
|
||||
_entManager.DeleteEntity(ent);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deleted so counts as modified.
|
||||
if (!_entManager.TransformQuery.TryComp(ent, out var xform))
|
||||
{
|
||||
biome.ModifiedTiles.Add(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it stayed still and had no data change then keep it.
|
||||
if (pos == xform.LocalPosition.Floored() && xform.GridUid == Biome.Owner && _entManager.IsDefault(ent, BiomeSystem.IgnoredComponents))
|
||||
{
|
||||
_entManager.DeleteEntity(ent);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Need the entity's current tile to be flagged for unloading.
|
||||
if (Biome.Owner == xform.GridUid)
|
||||
{
|
||||
var entTile = maps.LocalToTile(Biome.Owner, grid, xform.Coordinates);
|
||||
biome.ModifiedTiles.Add(entTile);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (decal, pos) in loaded.Decals)
|
||||
{
|
||||
// Modified go NEXT
|
||||
if (biome.ModifiedTiles.Contains(pos.Floored()))
|
||||
continue;
|
||||
|
||||
// Should just be able to remove them as you can't actually edit a decal.
|
||||
if (!decals.RemoveDecal(Biome.Owner, decal, decalGrid))
|
||||
{
|
||||
biome.ModifiedTiles.Add(pos.Floored());
|
||||
}
|
||||
}
|
||||
|
||||
await SuspendIfOutOfTime();
|
||||
|
||||
foreach (var (index, tile) in loaded.Tiles)
|
||||
{
|
||||
await SuspendIfOutOfTime();
|
||||
|
||||
if (Biome.Comp2.ModifiedTiles.Contains(index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!maps.TryGetTileRef(Biome.Owner, Biome.Comp1, index, out var tileRef) ||
|
||||
tileRef.Tile != tile)
|
||||
{
|
||||
Biome.Comp2.ModifiedTiles.Add(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
entities.Clear();
|
||||
var tileBounds = lookup.GetLocalBounds(index, Biome.Comp1.TileSize).Enlarged(-0.05f);
|
||||
|
||||
lookup.GetEntitiesIntersecting(Biome.Owner,
|
||||
tileBounds,
|
||||
entities);
|
||||
|
||||
// Still entities remaining so just leave the tile.
|
||||
if (entities.Count > 0)
|
||||
{
|
||||
Biome.Comp2.ModifiedTiles.Add(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (decals.GetDecalsIntersecting(Biome.Owner, tileBounds, component: decalGrid).Count > 0)
|
||||
{
|
||||
Biome.Comp2.ModifiedTiles.Add(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clear it
|
||||
tiles.Add((index, Tile.Empty));
|
||||
}
|
||||
|
||||
maps.SetTiles(Biome.Owner, Biome.Comp1, tiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Biome.Comp2.Loading = false;
|
||||
}
|
||||
|
||||
Biome.Comp2.PendingRemovals.Clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,8 @@ public sealed partial class DungeonJob
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
_entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
|
||||
var ent = _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
|
||||
AddLoadedEntity(tile, ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.PostGeneration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BiomeDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(BiomeDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
|
||||
return;
|
||||
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
|
||||
var seed = random.Next();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
|
||||
while (tiles.MoveNext(out var tileRef))
|
||||
{
|
||||
var node = tileRef.Value.GridIndices;
|
||||
|
||||
if (reservedTiles.Contains(node))
|
||||
continue;
|
||||
|
||||
if (dunGen.TileMask is not null)
|
||||
{
|
||||
if (!dunGen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Value.Tile.TypeId]).ID))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Need to set per-tile to override data.
|
||||
if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var tile))
|
||||
{
|
||||
_maps.SetTile(_gridUid, _grid, node, tile.Value);
|
||||
}
|
||||
|
||||
if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var decals))
|
||||
{
|
||||
foreach (var decal in decals)
|
||||
{
|
||||
_decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _);
|
||||
}
|
||||
}
|
||||
|
||||
if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Value.Tile, seed, (_gridUid, _grid), out var entityProto))
|
||||
{
|
||||
var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
|
||||
var xform = xformQuery.Get(ent);
|
||||
|
||||
if (!xform.Comp.Anchored)
|
||||
{
|
||||
_transform.AnchorEntity(ent, xform);
|
||||
}
|
||||
|
||||
// TODO: Engine bug with SpawnAtPosition
|
||||
DebugTools.Assert(xform.Comp.Anchored);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.PostGeneration;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BiomeMarkerLayerDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(BiomeMarkerLayerDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
// If we're adding biome then disable it and just use for markers.
|
||||
if (_entManager.EnsureComponent(_gridUid, out BiomeComponent biomeComp))
|
||||
{
|
||||
biomeComp.Enabled = false;
|
||||
}
|
||||
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
var weightedRandom = _prototype.Index(dunGen.MarkerTemplate);
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var templates = new Dictionary<string, int>();
|
||||
|
||||
for (var i = 0; i < dunGen.Count; i++)
|
||||
{
|
||||
var template = weightedRandom.Pick(random);
|
||||
var count = templates.GetOrNew(template);
|
||||
count++;
|
||||
templates[template] = count;
|
||||
}
|
||||
|
||||
foreach (var (template, count) in templates)
|
||||
{
|
||||
var markerTemplate = _prototype.Index<BiomeMarkerLayerPrototype>(template);
|
||||
|
||||
var bounds = new Box2i();
|
||||
|
||||
foreach (var tile in dungeon.RoomTiles)
|
||||
{
|
||||
bounds = bounds.UnionTile(tile);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
|
||||
biomeSystem.GetMarkerNodes(_gridUid, biomeComp, _grid, markerTemplate, true, bounds, count,
|
||||
random, out var spawnSet, out var existing, false);
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
|
||||
var checkTile = reservedTiles.Count > 0;
|
||||
|
||||
foreach (var ent in existing)
|
||||
{
|
||||
if (checkTile && reservedTiles.Contains(_maps.LocalToTile(_gridUid, _grid, _xformQuery.GetComponent(ent).Coordinates)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_entManager.DeleteEntity(ent);
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (node, mask) in spawnSet)
|
||||
{
|
||||
if (reservedTiles.Contains(node))
|
||||
continue;
|
||||
|
||||
string? proto;
|
||||
|
||||
if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto))
|
||||
{
|
||||
proto = maskedProto;
|
||||
}
|
||||
else
|
||||
{
|
||||
proto = markerTemplate.Prototype;
|
||||
}
|
||||
|
||||
var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
|
||||
var xform = xformQuery.Get(ent);
|
||||
|
||||
if (!xform.Comp.Anchored)
|
||||
_transform.AnchorEntity(ent, xform);
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,10 @@ public sealed partial class DungeonJob
|
||||
if (!_anchorable.TileFree((_gridUid, _grid), neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
||||
continue;
|
||||
|
||||
tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
|
||||
var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
tiles.Add((neighbor, tile));
|
||||
AddLoadedTile(neighbor, tile);
|
||||
DebugTools.Assert(dungeon.AllTiles.Contains(neighbor));
|
||||
}
|
||||
|
||||
foreach (var index in dungeon.CorridorExteriorTiles)
|
||||
@@ -43,7 +46,10 @@ public sealed partial class DungeonJob
|
||||
if (!_anchorable.TileFree((_gridUid, _grid), index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
||||
continue;
|
||||
|
||||
tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random)));
|
||||
var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
tiles.Add((index, tile));
|
||||
AddLoadedTile(index, tile);
|
||||
DebugTools.Assert(dungeon.AllTiles.Contains(index));
|
||||
}
|
||||
|
||||
_maps.SetTiles(_gridUid, _grid, tiles);
|
||||
@@ -82,18 +88,21 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
|
||||
if (isCorner)
|
||||
_entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
|
||||
{
|
||||
var uid = _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
|
||||
AddLoadedEntity(index.Index, uid);
|
||||
}
|
||||
|
||||
if (!isCorner)
|
||||
_entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
|
||||
|
||||
if (i % 20 == 0)
|
||||
{
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
var uid = _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
|
||||
AddLoadedEntity(index.Index, uid);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
42
Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs
Normal file
42
Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonGenerators;
|
||||
using Content.Shared.Procedural.PostGeneration;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="BiomeDunGen"/>
|
||||
/// </summary>
|
||||
private async Task<Dungeon> PostGen(ChunkDunGen dunGen, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
var dungeon = new Dungeon();
|
||||
var tiles = new HashSet<Vector2i>();
|
||||
var tr = _position + new Vector2i(dunGen.Size, dunGen.Size);
|
||||
var oldSeed = dunGen.Noise?.GetSeed() ?? 0;
|
||||
dunGen.Noise?.SetSeed(_seed + oldSeed);
|
||||
|
||||
for (var x = 0; x < dunGen.Size; x++)
|
||||
{
|
||||
for (var y = 0; y < dunGen.Size; y++)
|
||||
{
|
||||
var index = new Vector2i(_position.X + x, _position.Y + y);
|
||||
|
||||
if (reservedTiles.Contains(index))
|
||||
continue;
|
||||
|
||||
if (dunGen.Noise?.GetNoise(x, y) < dunGen.Threshold)
|
||||
continue;
|
||||
|
||||
tiles.Add(index);
|
||||
}
|
||||
}
|
||||
|
||||
dunGen.Noise?.SetSeed(oldSeed);
|
||||
var room = new DungeonRoom(tiles, (tr - _position) / 2 + _position, new Box2i(_position, tr), new HashSet<Vector2i>());
|
||||
dungeon.AddRoom(room);
|
||||
return dungeon;
|
||||
}
|
||||
}
|
||||
@@ -94,12 +94,14 @@ public sealed partial class DungeonJob
|
||||
var setTiles = new List<(Vector2i, Tile)>();
|
||||
var tileDef = (ContentTileDefinition) _tileDefManager[gen.Tile];
|
||||
|
||||
foreach (var tile in corridorTiles)
|
||||
foreach (var node in corridorTiles)
|
||||
{
|
||||
if (reservedTiles.Contains(tile))
|
||||
if (reservedTiles.Contains(node))
|
||||
continue;
|
||||
|
||||
setTiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
|
||||
var tile = _tile.GetVariantTile(tileDef, random);
|
||||
setTiles.Add((node, tile));
|
||||
AddLoadedTile(node, tile);
|
||||
}
|
||||
|
||||
_maps.SetTiles(_gridUid, _grid, setTiles);
|
||||
|
||||
@@ -48,7 +48,13 @@ public sealed partial class DungeonJob
|
||||
|
||||
var protos = _entTable.GetSpawns(contents, random);
|
||||
var coords = _maps.ToCenterCoordinates(_gridUid, tile, _grid);
|
||||
_entManager.SpawnEntitiesAttachedTo(coords, protos);
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(coords, protos);
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(tile, uid);
|
||||
}
|
||||
|
||||
await SuspendIfOutOfTime();
|
||||
|
||||
if (!ValidateResume())
|
||||
|
||||
@@ -83,7 +83,8 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
// Decals not being centered biting my ass again
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
|
||||
_decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
|
||||
_decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
|
||||
AddLoadedDecal(tile, did);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +97,8 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
// Decals not being centered biting my ass again
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
|
||||
_decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
|
||||
_decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
|
||||
AddLoadedDecal(tile, did);
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -111,7 +113,8 @@ public sealed partial class DungeonJob
|
||||
if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir))
|
||||
{
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
|
||||
_decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
|
||||
_decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
|
||||
AddLoadedDecal(tile, did);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed partial class DungeonJob
|
||||
Random random)
|
||||
{
|
||||
var tiles = new List<(Vector2i, Tile)>();
|
||||
var matrix = Matrix3Helpers.CreateTranslation(position);
|
||||
var matrix = Matrix3Helpers.CreateTranslation(_position + position);
|
||||
|
||||
foreach (var layer in dungen.Layers)
|
||||
{
|
||||
@@ -76,7 +76,9 @@ public sealed partial class DungeonJob
|
||||
break;
|
||||
}
|
||||
|
||||
tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
|
||||
var tile = new Tile(tileDef.TileId, variant: variant);
|
||||
tiles.Add((adjusted, tile));
|
||||
AddLoadedTile(adjusted, tile);
|
||||
roomTiles.Add(adjusted);
|
||||
break;
|
||||
}
|
||||
@@ -101,6 +103,8 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
switch (distance)
|
||||
{
|
||||
case DunGenDistanceSquared:
|
||||
return dx * dx + dy * dy;
|
||||
case DunGenEuclideanSquaredDistance:
|
||||
return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2));
|
||||
case DunGenSquareBump:
|
||||
|
||||
@@ -196,7 +196,9 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(index))
|
||||
continue;
|
||||
|
||||
tiles.Add((index, new Tile(_tileDefManager[fallbackTile.Value].TileId)));
|
||||
var tile = new Tile(_tileDefManager[fallbackTile.Value].TileId);
|
||||
tiles.Add((index, tile));
|
||||
AddLoadedTile(index, tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +232,14 @@ public sealed partial class DungeonJob
|
||||
var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform);
|
||||
|
||||
// The expensive bit yippy.
|
||||
_dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
|
||||
var data = _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
|
||||
|
||||
_data.Merge(data);
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return dungeon;
|
||||
|
||||
var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize;
|
||||
var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
|
||||
|
||||
@@ -42,6 +42,7 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
|
||||
replacements.Add((node, tile));
|
||||
AddLoadedTile(node, tile);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,14 +71,17 @@ public sealed partial class DungeonJob
|
||||
isValid = true;
|
||||
|
||||
// Entrance wew
|
||||
_maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random));
|
||||
var tileVariant = _tile.GetVariantTile(tileDef, random);
|
||||
_maps.SetTile(_gridUid, _grid, tile, tileVariant);
|
||||
AddLoadedTile(tile, tileVariant);
|
||||
ClearDoor(dungeon, _grid, tile);
|
||||
var gridCoords = _maps.GridTileToLocal(_gridUid, _grid, tile);
|
||||
// Need to offset the spawn to avoid spawning in the room.
|
||||
|
||||
foreach (var ent in _entTable.GetSpawns(contents, random))
|
||||
{
|
||||
_entManager.SpawnAtPosition(ent, gridCoords);
|
||||
var uid = _entManager.SpawnAtPosition(ent, gridCoords);
|
||||
AddLoadedEntity(tile, uid);
|
||||
}
|
||||
|
||||
// Clear out any biome tiles nearby to avoid blocking it
|
||||
|
||||
@@ -51,6 +51,7 @@ public sealed partial class DungeonJob
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
var uid = _entManager.SpawnAtPosition(ent, _maps.GridTileToLocal(_gridUid, _grid, tile));
|
||||
AddLoadedEntity(tile, uid);
|
||||
_entManager.RemoveComponent<GhostRoleComponent>(uid);
|
||||
_entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
|
||||
npcs.SleepNPC(uid);
|
||||
|
||||
@@ -35,7 +35,9 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(neighbor))
|
||||
continue;
|
||||
|
||||
tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
|
||||
var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
tiles.Add((neighbor, tile));
|
||||
AddLoadedTile(neighbor, tile);
|
||||
spawnPositions.Add(neighbor);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +47,12 @@ public sealed partial class DungeonJob
|
||||
|
||||
foreach (var entrance in spawnPositions)
|
||||
{
|
||||
_entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(entrance, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.NPC;
|
||||
@@ -13,15 +14,22 @@ public sealed partial class DungeonJob
|
||||
/// <summary>
|
||||
/// <see cref="ExteriorDunGen"/>
|
||||
/// </summary>
|
||||
private async Task<List<Dungeon>> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
|
||||
private async Task<List<Dungeon>> GenerateExteriorDungen(int runCount, int maxRuns, Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
DebugTools.Assert(_grid.ChunkCount > 0);
|
||||
|
||||
var aabb = new Box2i(_grid.LocalAABB.BottomLeft.Floored(), _grid.LocalAABB.TopRight.Floored());
|
||||
var angle = random.NextAngle();
|
||||
// TODO: Cross-layer seeding. Need this because we need to be able to spread the dungeons out.
|
||||
var angle = new Random(_seed).NextAngle();
|
||||
var divisors = new Angle(Angle.FromDegrees(360) / maxRuns);
|
||||
|
||||
// Offset each dungeon so they don't generate on top of each other.
|
||||
for (var i = 0; i < runCount; i++)
|
||||
{
|
||||
angle += (random.NextFloat(0.6f, 1.4f)) * divisors;
|
||||
}
|
||||
|
||||
var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f);
|
||||
|
||||
var startTile = new Vector2i(0, (int) distance).Rotate(angle);
|
||||
|
||||
Vector2i? dungeonSpawn = null;
|
||||
@@ -47,9 +55,19 @@ public sealed partial class DungeonJob
|
||||
};
|
||||
}
|
||||
|
||||
var config = _prototype.Index(dungen.Proto);
|
||||
// Move it further in based on the spawn angle.
|
||||
if (dungen.Penetration.Y > 0)
|
||||
{
|
||||
var penetration = random.Next(dungen.Penetration.X, dungen.Penetration.Y);
|
||||
var diff = dungeonSpawn.Value - startTile;
|
||||
var diffVec = new Vector2(diff.X, diff.Y);
|
||||
dungeonSpawn = (diffVec.Normalized() * (penetration + diffVec.Length())).Floored() + startTile;
|
||||
}
|
||||
|
||||
var subConfig = _prototype.Index(dungen.Proto);
|
||||
var nextSeed = random.Next();
|
||||
var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Layers, reservedTiles, nextSeed, new Random(nextSeed));
|
||||
var (dungeons, newReserved) = await GetDungeons(dungeonSpawn.Value, subConfig, subConfig.Layers, nextSeed, new Random(nextSeed), reserved: reservedTiles);
|
||||
reservedTiles.UnionWith(newReserved);
|
||||
|
||||
return dungeons;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,9 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(neighbor))
|
||||
continue;
|
||||
|
||||
tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
|
||||
var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
tiles.Add((neighbor, tileVariant));
|
||||
AddLoadedTile(neighbor, tileVariant);
|
||||
index++;
|
||||
takenTiles.Add(neighbor);
|
||||
}
|
||||
@@ -119,7 +121,13 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1);
|
||||
|
||||
_entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(tile.Item1, uid);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
|
||||
@@ -18,21 +18,45 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(tile))
|
||||
continue;
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
|
||||
if (!_maps.TryGetTileDef(_grid, tile, out var tileDef))
|
||||
continue;
|
||||
|
||||
if (fill.AllowedTiles != null && !fill.AllowedTiles.Contains(tileDef.ID))
|
||||
continue;
|
||||
|
||||
// If noise then check it matches.
|
||||
if (fill.ReservedNoise != null)
|
||||
{
|
||||
var value = fill.ReservedNoise.GetNoise(tile.X, tile.Y);
|
||||
|
||||
if (fill.DistanceConfig != null)
|
||||
{
|
||||
// Need to get dx - dx in a range from -1 -> 1
|
||||
var dx = 2 * tile.X / fill.Size.X;
|
||||
var dy = 2 * tile.Y / fill.Size.Y;
|
||||
|
||||
var distance = GetDistance(dx, dy, fill.DistanceConfig);
|
||||
|
||||
value = MathHelper.Lerp(value, 1f - distance, fill.DistanceConfig.BlendWeight);
|
||||
}
|
||||
|
||||
value *= (fill.Invert ? -1 : 1);
|
||||
|
||||
if (value < fill.Threshold)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_anchorable.TileFree((_gridUid, _grid), tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
||||
continue;
|
||||
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
|
||||
_entManager.SpawnEntity(fill.Entity, gridPos);
|
||||
var uid = _entManager.SpawnEntity(fill.Entity, gridPos);
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
break;
|
||||
AddLoadedEntity(tile, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dungeon.RefreshAllTiles();
|
||||
}
|
||||
|
||||
private void WidenCorridor(Dungeon dungeon, float width, ICollection<Vector2i> corridorTiles)
|
||||
|
||||
@@ -81,9 +81,16 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
var tile = validTiles[j];
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
|
||||
_maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
|
||||
var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
_maps.SetTile(_gridUid, _grid, tile, tileVariant);
|
||||
AddLoadedTile(tile, tileVariant);
|
||||
|
||||
_entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(tile, uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (validTiles.Count > 0)
|
||||
|
||||
@@ -113,10 +113,17 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(weh))
|
||||
continue;
|
||||
|
||||
_maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
|
||||
var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
_maps.SetTile(_gridUid, _grid, weh, tileVariant);
|
||||
AddLoadedTile(weh, tileVariant);
|
||||
|
||||
var coords = _maps.GridTileToLocal(_gridUid, _grid, weh);
|
||||
_entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(weh, uid);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed partial class DungeonJob
|
||||
// Grab all of the room bounds
|
||||
// Then, work out connections between them
|
||||
var roomBorders = new Dictionary<DungeonRoom, HashSet<Vector2i>>(dungeon.Rooms.Count);
|
||||
var flank = gen.Flank;
|
||||
|
||||
foreach (var room in dungeon.Rooms)
|
||||
{
|
||||
@@ -107,18 +108,30 @@ public sealed partial class DungeonJob
|
||||
continue;
|
||||
|
||||
width--;
|
||||
_maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
|
||||
var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
_maps.SetTile(_gridUid, _grid, node, tileVariant);
|
||||
AddLoadedTile(node, tileVariant);
|
||||
|
||||
if (flankContents != null && nodeDistances.Count - i <= 2)
|
||||
{
|
||||
_entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(node, uid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Iterate neighbors and check for blockers, if so bulldoze
|
||||
ClearDoor(dungeon, _grid, node);
|
||||
|
||||
_entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(node, uid);
|
||||
}
|
||||
}
|
||||
|
||||
if (width == 0)
|
||||
|
||||
@@ -49,6 +49,7 @@ public sealed partial class DungeonJob
|
||||
_entManager.RemoveComponent<GhostRoleComponent>(uid);
|
||||
_entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
|
||||
npcs.SleepNPC(uid);
|
||||
AddLoadedEntity(tile, uid);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -98,7 +98,9 @@ public sealed partial class DungeonJob
|
||||
var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random);
|
||||
var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored();
|
||||
|
||||
tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
|
||||
var tileVariant = new Tile(tileDef.TileId, variant: variant);
|
||||
tiles.Add((adjusted, tileVariant));
|
||||
AddLoadedTile(adjusted, tileVariant);
|
||||
roomTiles.Add(adjusted);
|
||||
tileCount++;
|
||||
break;
|
||||
@@ -127,8 +129,7 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
}
|
||||
|
||||
await SuspendIfOutOfTime();
|
||||
ValidateResume();
|
||||
await SuspendDungeon();
|
||||
}
|
||||
|
||||
var center = Vector2.Zero;
|
||||
@@ -140,8 +141,7 @@ public sealed partial class DungeonJob
|
||||
|
||||
center /= roomTiles.Count;
|
||||
rooms.Add(new DungeonRoom(roomTiles, center, roomArea, new HashSet<Vector2i>()));
|
||||
await SuspendIfOutOfTime();
|
||||
ValidateResume();
|
||||
await SuspendDungeon();
|
||||
}
|
||||
|
||||
_maps.SetTiles(_gridUid, _grid, tiles);
|
||||
|
||||
@@ -19,12 +19,26 @@ public sealed partial class DungeonJob
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
Random random)
|
||||
{
|
||||
var emptyTiles = false;
|
||||
var replaceEntities = new Dictionary<Vector2i, EntityUid>();
|
||||
var availableTiles = new List<Vector2i>();
|
||||
var remapName = _entManager.ComponentFactory.GetComponentName<EntityRemapComponent>();
|
||||
var replacementRemapping = new Dictionary<EntProtoId, EntProtoId>();
|
||||
|
||||
if (_prototype.TryIndex(gen.Replacement, out var replacementProto) &&
|
||||
replacementProto.Components.TryGetComponent(remapName, out var replacementComps))
|
||||
{
|
||||
var remappingComp = (EntityRemapComponent) replacementComps;
|
||||
replacementRemapping = remappingComp.Mask;
|
||||
}
|
||||
|
||||
if (gen.Replacement != null)
|
||||
{
|
||||
replacementRemapping[gen.Replacement.Value] = gen.Entity;
|
||||
}
|
||||
|
||||
foreach (var dungeon in dungeons)
|
||||
{
|
||||
var emptyTiles = false;
|
||||
var replaceEntities = new Dictionary<Vector2i, EntityUid>();
|
||||
var availableTiles = new List<Vector2i>();
|
||||
|
||||
foreach (var node in dungeon.AllTiles)
|
||||
{
|
||||
if (reservedTiles.Contains(node))
|
||||
@@ -41,19 +55,23 @@ public sealed partial class DungeonJob
|
||||
// We use existing entities as a mark to spawn in place
|
||||
// OR
|
||||
// We check for any existing entities to see if we can spawn there.
|
||||
while (enumerator.MoveNext(out var uid))
|
||||
// We can't replace so just stop here.
|
||||
if (gen.Replacement != null)
|
||||
{
|
||||
// We can't replace so just stop here.
|
||||
if (gen.Replacement == null)
|
||||
break;
|
||||
|
||||
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
|
||||
|
||||
if (prototype?.ID == gen.Replacement)
|
||||
while (enumerator.MoveNext(out var uid))
|
||||
{
|
||||
replaceEntities[node] = uid.Value;
|
||||
found = true;
|
||||
break;
|
||||
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
|
||||
|
||||
if (string.IsNullOrEmpty(prototype?.ID))
|
||||
continue;
|
||||
|
||||
// It has a valid remapping so take it over.
|
||||
if (replacementRemapping.ContainsKey(prototype.ID))
|
||||
{
|
||||
replaceEntities[node] = uid.Value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,83 +86,86 @@ public sealed partial class DungeonJob
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var remapping = new Dictionary<EntProtoId, EntProtoId>();
|
||||
var remapping = new Dictionary<EntProtoId, EntProtoId>();
|
||||
|
||||
// TODO: Move this to engine
|
||||
if (_prototype.TryIndex(gen.Entity, out var proto) &&
|
||||
proto.Components.TryGetComponent("EntityRemap", out var comps))
|
||||
// TODO: Move this to engine
|
||||
if (_prototype.TryIndex(gen.Entity, out var proto) &&
|
||||
proto.Components.TryGetComponent(remapName, out var comps))
|
||||
{
|
||||
var remappingComp = (EntityRemapComponent) comps;
|
||||
remapping = remappingComp.Mask;
|
||||
}
|
||||
|
||||
var frontier = new ValueList<Vector2i>(32);
|
||||
|
||||
// Iterate the group counts and pathfind out each group.
|
||||
for (var i = 0; i < gen.Count; i++)
|
||||
{
|
||||
var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
|
||||
|
||||
// While we have remaining tiles keep iterating
|
||||
while (groupSize > 0 && availableTiles.Count > 0)
|
||||
{
|
||||
var remappingComp = (EntityRemapComponent) comps;
|
||||
remapping = remappingComp.Mask;
|
||||
var startNode = random.PickAndTake(availableTiles);
|
||||
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 = random.Next(frontier.Count);
|
||||
var node = frontier[frontierIndex];
|
||||
frontier.RemoveSwap(frontierIndex);
|
||||
availableTiles.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++)
|
||||
{
|
||||
var neighbor = new Vector2i(node.X + x, node.Y + y);
|
||||
|
||||
if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor))
|
||||
continue;
|
||||
|
||||
frontier.Add(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
var prototype = gen.Entity;
|
||||
|
||||
// May have been deleted while iteration was suspended.
|
||||
if (replaceEntities.TryGetValue(node, out var existingEnt) && _entManager.TryGetComponent(existingEnt, out MetaDataComponent? metadata))
|
||||
{
|
||||
var existingProto = metadata.EntityPrototype;
|
||||
_entManager.DeleteEntity(existingEnt);
|
||||
|
||||
if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
|
||||
{
|
||||
prototype = remapped;
|
||||
}
|
||||
}
|
||||
|
||||
// Tile valid salad so add it.
|
||||
var uid = _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
|
||||
AddLoadedEntity(node, uid);
|
||||
|
||||
groupSize--;
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var frontier = new ValueList<Vector2i>(32);
|
||||
|
||||
// Iterate the group counts and pathfind out each group.
|
||||
for (var i = 0; i < gen.Count; i++)
|
||||
if (groupSize > 0)
|
||||
{
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
|
||||
var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
|
||||
|
||||
// While we have remaining tiles keep iterating
|
||||
while (groupSize > 0 && availableTiles.Count > 0)
|
||||
{
|
||||
var startNode = random.PickAndTake(availableTiles);
|
||||
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 = random.Next(frontier.Count);
|
||||
var node = frontier[frontierIndex];
|
||||
frontier.RemoveSwap(frontierIndex);
|
||||
availableTiles.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++)
|
||||
{
|
||||
var neighbor = new Vector2i(node.X + x, node.Y + y);
|
||||
|
||||
if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor))
|
||||
continue;
|
||||
|
||||
frontier.Add(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
var prototype = gen.Entity;
|
||||
|
||||
if (replaceEntities.TryGetValue(node, out var existingEnt))
|
||||
{
|
||||
var existingProto = _entManager.GetComponent<MetaDataComponent>(existingEnt).EntityPrototype;
|
||||
_entManager.DeleteEntity(existingEnt);
|
||||
|
||||
if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
|
||||
{
|
||||
prototype = remapped;
|
||||
}
|
||||
}
|
||||
|
||||
// Tile valid salad so add it.
|
||||
_entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
|
||||
|
||||
groupSize--;
|
||||
}
|
||||
}
|
||||
|
||||
if (groupSize > 0)
|
||||
{
|
||||
_sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!");
|
||||
}
|
||||
// Not super worried depending on the gen it's fine.
|
||||
_sawmill.Debug($"Found remaining group size for ore veins of {gen.Replacement ?? "null"} / {gen.Entity}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs
Normal file
38
Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
public async Task RoofGen(RoofDunGen roof, List<Dungeon> dungeons, HashSet<Vector2i> reservedTiles, Random random)
|
||||
{
|
||||
var roofComp = _entManager.EnsureComponent<RoofComponent>(_gridUid);
|
||||
|
||||
var noise = roof.Noise;
|
||||
var oldSeed = noise?.GetSeed() ?? 0;
|
||||
noise?.SetSeed(_seed + oldSeed);
|
||||
var rooves = _entManager.System<RoofSystem>();
|
||||
|
||||
foreach (var dungeon in dungeons)
|
||||
{
|
||||
foreach (var tile in dungeon.AllTiles)
|
||||
{
|
||||
if (reservedTiles.Contains(tile))
|
||||
continue;
|
||||
|
||||
var value = noise?.GetNoise(tile.X, tile.Y) ?? 1f;
|
||||
|
||||
if (value < roof.Threshold)
|
||||
continue;
|
||||
|
||||
rooves.SetRoof((_gridUid, _grid, roofComp), tile, true);
|
||||
}
|
||||
}
|
||||
|
||||
noise?.SetSeed(oldSeed);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(entrance))
|
||||
continue;
|
||||
|
||||
setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
|
||||
var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
|
||||
setTiles.Add((entrance, tileVariant));
|
||||
AddLoadedTile(entrance, tileVariant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +40,15 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(entrance))
|
||||
continue;
|
||||
|
||||
_entManager.SpawnEntitiesAttachedTo(
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(
|
||||
_maps.GridTileToLocal(_gridUid, _grid, entrance),
|
||||
_entTable.GetSpawns(contents, random));
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(entrance, uid);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="SampleDecalDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(SampleDecalDunGen gen,
|
||||
List<Dungeon> dungeons,
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
Random random)
|
||||
{
|
||||
var oldSeed = gen.Noise.GetSeed();
|
||||
gen.Noise.SetSeed(_seed + oldSeed);
|
||||
|
||||
foreach (var dungeon in dungeons)
|
||||
{
|
||||
foreach (var tile in dungeon.AllTiles)
|
||||
{
|
||||
if (reservedTiles.Contains(tile))
|
||||
continue;
|
||||
|
||||
var invert = gen.Invert;
|
||||
var value = gen.Noise.GetNoise(tile.X, tile.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < gen.Threshold)
|
||||
continue;
|
||||
|
||||
// Not allowed
|
||||
if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
|
||||
!gen.AllowedTiles.Contains(_tileDefManager[tileRef.Tile.TypeId].ID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Occupied?
|
||||
if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
||||
continue;
|
||||
|
||||
_decals.TryAddDecal(random.Pick(gen.Decals), new EntityCoordinates(_gridUid, tile), out var did);
|
||||
AddLoadedDecal(tile, did);
|
||||
|
||||
if (gen.ReserveTiles)
|
||||
{
|
||||
reservedTiles.Add(tile);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gen.Noise.SetSeed(oldSeed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="SampleEntityDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(
|
||||
SampleEntityDunGen gen,
|
||||
List<Dungeon> dungeons,
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
Random random)
|
||||
{
|
||||
var oldSeed = gen.Noise.GetSeed();
|
||||
gen.Noise.SetSeed(_seed + oldSeed);
|
||||
|
||||
foreach (var dungeon in dungeons)
|
||||
{
|
||||
foreach (var tile in dungeon.AllTiles)
|
||||
{
|
||||
if (reservedTiles.Contains(tile))
|
||||
continue;
|
||||
|
||||
var invert = gen.Invert;
|
||||
var value = gen.Noise.GetNoise(tile.X, tile.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < gen.Threshold)
|
||||
continue;
|
||||
|
||||
// Not allowed
|
||||
if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
|
||||
!gen.AllowedTiles.Contains(_tileDefManager[tileRef.Tile.TypeId].ID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gridTile = _maps.GridTileToLocal(_gridUid, _grid, tile);
|
||||
var uid = _entManager.SpawnAttachedTo(random.Pick(gen.Entities), gridTile);
|
||||
AddLoadedEntity(tile, uid);
|
||||
|
||||
if (gen.ReserveTiles)
|
||||
{
|
||||
reservedTiles.Add(tile);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gen.Noise.SetSeed(oldSeed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="SampleTileDunGen"/>
|
||||
/// </summary>
|
||||
private async Task PostGen(SampleTileDunGen gen,
|
||||
List<Dungeon> dungeons,
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
Random random)
|
||||
{
|
||||
var noise = gen.Noise;
|
||||
var oldSeed = noise.GetSeed();
|
||||
noise.SetSeed(_seed + oldSeed);
|
||||
var tiles = new List<(Vector2i Index, Tile Tile)>();
|
||||
var tileDef = _prototype.Index(gen.Tile);
|
||||
var variants = tileDef.PlacementVariants.Length;
|
||||
|
||||
foreach (var dungeon in dungeons)
|
||||
{
|
||||
foreach (var tile in dungeon.AllTiles)
|
||||
{
|
||||
if (reservedTiles.Contains(tile))
|
||||
continue;
|
||||
|
||||
var invert = gen.Invert;
|
||||
var value = noise.GetNoise(tile.X, tile.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < gen.Threshold)
|
||||
continue;
|
||||
|
||||
var variantValue = (noise.GetNoise(tile.X * 8, tile.Y * 8, variants) + 1f) * 100;
|
||||
var variant = _tile.PickVariant(tileDef, (int)variantValue);
|
||||
var tileVariant = new Tile(tileDef.TileId, variant: variant);
|
||||
|
||||
tiles.Add((tile, tileVariant));
|
||||
AddLoadedTile(tile, tileVariant);
|
||||
|
||||
await SuspendDungeon();
|
||||
|
||||
if (!ValidateResume())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gen.Noise.SetSeed(oldSeed);
|
||||
_maps.SetTiles(_gridUid, _grid, tiles);
|
||||
|
||||
if (gen.ReserveTiles)
|
||||
{
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
reservedTiles.Add(tile.Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.NPC.Pathfinding;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.DungeonLayers;
|
||||
using Content.Shared.Procedural.PostGeneration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
@@ -11,10 +12,10 @@ namespace Content.Server.Procedural.DungeonJob;
|
||||
public sealed partial class DungeonJob
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="SplineDungeonConnectorDunGen"/>
|
||||
/// <see cref="Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen"/>
|
||||
/// </summary>
|
||||
private async Task<Dungeon> PostGen(
|
||||
SplineDungeonConnectorDunGen gen,
|
||||
Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen gen,
|
||||
List<Dungeon> dungeons,
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
Random random)
|
||||
@@ -59,6 +60,7 @@ public sealed partial class DungeonJob
|
||||
{
|
||||
Start = pair.Start,
|
||||
End = pair.End,
|
||||
Diagonals = false,
|
||||
TileCost = node =>
|
||||
{
|
||||
// We want these to get prioritised internally and into space if it's a space dungeon.
|
||||
@@ -110,6 +112,7 @@ public sealed partial class DungeonJob
|
||||
}
|
||||
|
||||
tiles.Add((node, tile));
|
||||
AddLoadedTile(node, tile);
|
||||
}
|
||||
|
||||
_maps.SetTiles(_gridUid, _grid, tiles);
|
||||
@@ -123,6 +126,7 @@ public sealed partial class DungeonJob
|
||||
|
||||
allTiles.Add(node);
|
||||
tiles.Add((node, pathTile));
|
||||
AddLoadedTile(node, pathTile);
|
||||
}
|
||||
|
||||
_maps.SetTiles(_gridUid, _grid, tiles);
|
||||
|
||||
@@ -32,11 +32,18 @@ public sealed partial class DungeonJob
|
||||
if (reservedTiles.Contains(neighbor))
|
||||
continue;
|
||||
|
||||
_maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random));
|
||||
var tileVariant = _tile.GetVariantTile(tileDef, random);
|
||||
_maps.SetTile(_gridUid, _grid, neighbor, tileVariant);
|
||||
AddLoadedTile(neighbor, tileVariant);
|
||||
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor);
|
||||
var protoNames = _entTable.GetSpawns(contents, random);
|
||||
|
||||
_entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
|
||||
var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
AddLoadedEntity(neighbor, uid);
|
||||
}
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.NPC.Components;
|
||||
using Content.Server.NPC.HTN;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
@@ -23,11 +22,10 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using IDunGenLayer = Content.Shared.Procedural.IDunGenLayer;
|
||||
|
||||
namespace Content.Server.Procedural.DungeonJob;
|
||||
|
||||
public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
|
||||
{
|
||||
public bool TimeSlice = true;
|
||||
|
||||
@@ -60,6 +58,10 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private DungeonData _data = new();
|
||||
|
||||
private HashSet<Vector2i>? _reservedTiles;
|
||||
|
||||
public DungeonJob(
|
||||
ISawmill sawmill,
|
||||
double maxTime,
|
||||
@@ -79,12 +81,14 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
int seed,
|
||||
Vector2i position,
|
||||
EntityCoordinates? targetCoordinates = null,
|
||||
CancellationToken cancellation = default) : base(maxTime, cancellation)
|
||||
CancellationToken cancellation = default,
|
||||
HashSet<Vector2i>? reservedTiles = null) : base(maxTime, cancellation)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
_entManager = entManager;
|
||||
_prototype = prototype;
|
||||
_tileDefManager = tileDefManager;
|
||||
_reservedTiles = reservedTiles;
|
||||
|
||||
_anchorable = anchorable;
|
||||
_decals = decals;
|
||||
@@ -111,17 +115,18 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
/// <summary>
|
||||
/// Gets the relevant dungeon, running recursively as relevant.
|
||||
/// </summary>
|
||||
/// <param name="reserve">Should we reserve tiles even if the config doesn't specify.</param>
|
||||
private async Task<List<Dungeon>> GetDungeons(
|
||||
/// <param name="reservedTiles">Should we reserve tiles even if the config doesn't specify.</param>
|
||||
private async Task<(List<Dungeon>, HashSet<Vector2i>)> GetDungeons(
|
||||
Vector2i position,
|
||||
DungeonConfig config,
|
||||
List<IDunGenLayer> layers,
|
||||
HashSet<Vector2i> reservedTiles,
|
||||
int seed,
|
||||
Random random,
|
||||
HashSet<Vector2i>? reserved = null,
|
||||
List<Dungeon>? existing = null)
|
||||
{
|
||||
var dungeons = new List<Dungeon>();
|
||||
var reservedTiles = reserved == null ? new HashSet<Vector2i>() : new HashSet<Vector2i>(reserved);
|
||||
|
||||
// Don't pass dungeons back up the "stack". They are ref types though it's a caller problem if they start trying to mutate it.
|
||||
if (existing != null)
|
||||
@@ -137,8 +142,8 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
var dungCount = dungeons.Count;
|
||||
await RunLayer(dungeons, position, layer, reservedTiles, seed, random);
|
||||
var dungCount = dungeons.Count;
|
||||
await RunLayer(i, count, config, dungeons, position, layer, reservedTiles, seed, random);
|
||||
|
||||
if (config.ReserveTiles)
|
||||
{
|
||||
@@ -152,24 +157,23 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
|
||||
await SuspendDungeon();
|
||||
if (!ValidateResume())
|
||||
return new List<Dungeon>();
|
||||
return (new List<Dungeon>(), new HashSet<Vector2i>());
|
||||
}
|
||||
}
|
||||
|
||||
return dungeons;
|
||||
// Only return the new dungeons and applicable reserved tiles.
|
||||
return (dungeons[(existing?.Count ?? 0)..], config.ReturnReserved ? reservedTiles : new HashSet<Vector2i>());
|
||||
}
|
||||
|
||||
protected override async Task<List<Dungeon>?> Process()
|
||||
protected override async Task<(List<Dungeon>, DungeonData)> Process()
|
||||
{
|
||||
_sawmill.Info($"Generating dungeon {_gen} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}");
|
||||
_grid.CanSplit = false;
|
||||
var random = new Random(_seed);
|
||||
var oldTileCount = _reservedTiles?.Count ?? 0;
|
||||
var position = (_position + random.NextPolarVector2(_gen.MinOffset, _gen.MaxOffset)).Floored();
|
||||
|
||||
// Tiles we can no longer generate on due to being reserved elsewhere.
|
||||
var reservedTiles = new HashSet<Vector2i>();
|
||||
|
||||
var dungeons = await GetDungeons(position, _gen, _gen.Layers, reservedTiles, _seed, random);
|
||||
var (dungeons, _) = await GetDungeons(position, _gen, _gen.Layers, _seed, random, reserved: _reservedTiles);
|
||||
// To make it slightly more deterministic treat this RNG as separate ig.
|
||||
|
||||
// Post-processing after finishing loading.
|
||||
@@ -181,6 +185,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
}
|
||||
|
||||
// Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way.
|
||||
DebugTools.Assert(oldTileCount == (_reservedTiles?.Count ?? 0));
|
||||
_grid.CanSplit = true;
|
||||
_entManager.System<GridFixtureSystem>().CheckSplits(_gridUid);
|
||||
var npcSystem = _entManager.System<NPCSystem>();
|
||||
@@ -194,10 +199,13 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
}
|
||||
|
||||
_sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}");
|
||||
return dungeons;
|
||||
return (dungeons, _data);
|
||||
}
|
||||
|
||||
private async Task RunLayer(
|
||||
int runCount,
|
||||
int maxRuns,
|
||||
DungeonConfig config,
|
||||
List<Dungeon> dungeons,
|
||||
Vector2i position,
|
||||
IDunGenLayer layer,
|
||||
@@ -205,7 +213,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
int seed,
|
||||
Random random)
|
||||
{
|
||||
_sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen} with seed {_seed}");
|
||||
// _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen} with seed {_seed}");
|
||||
|
||||
// If there's a way to just call the methods directly for the love of god tell me.
|
||||
// Some of these don't care about reservedtiles because they only operate on dungeon tiles (which should
|
||||
@@ -219,15 +227,12 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
case AutoCablingDunGen cabling:
|
||||
await PostGen(cabling, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
case BiomeMarkerLayerDunGen markerPost:
|
||||
await PostGen(markerPost, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
case BiomeDunGen biome:
|
||||
await PostGen(biome, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
case BoundaryWallDunGen boundary:
|
||||
await PostGen(boundary, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
case ChunkDunGen chunk:
|
||||
dungeons.Add(await PostGen(chunk, reservedTiles, random));
|
||||
break;
|
||||
case CornerClutterDunGen clutter:
|
||||
await PostGen(clutter, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
@@ -244,7 +249,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
await PostGen(flank, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
case ExteriorDunGen exterior:
|
||||
dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random));
|
||||
dungeons.AddRange(await GenerateExteriorDungen(runCount, maxRuns, position, exterior, reservedTiles, random));
|
||||
break;
|
||||
case FillGridDunGen fill:
|
||||
await GenerateFillDunGen(fill, dungeons, reservedTiles);
|
||||
@@ -285,27 +290,67 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
case PrototypeDunGen prototypo:
|
||||
var groupConfig = _prototype.Index(prototypo.Proto);
|
||||
position = (position + random.NextPolarVector2(groupConfig.MinOffset, groupConfig.MaxOffset)).Floored();
|
||||
List<Dungeon>? inheritedDungeons = null;
|
||||
HashSet<Vector2i>? inheritedReserved = null;
|
||||
|
||||
switch (prototypo.InheritReserved)
|
||||
{
|
||||
case ReservedInheritance.All:
|
||||
inheritedReserved = new HashSet<Vector2i>(reservedTiles);
|
||||
break;
|
||||
case ReservedInheritance.None:
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
switch (prototypo.InheritDungeons)
|
||||
{
|
||||
case DungeonInheritance.All:
|
||||
dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons));
|
||||
inheritedDungeons = dungeons;
|
||||
break;
|
||||
case DungeonInheritance.Last:
|
||||
dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons.GetRange(dungeons.Count - 1, 1)));
|
||||
inheritedDungeons = dungeons.GetRange(dungeons.Count - 1, 1);
|
||||
break;
|
||||
case DungeonInheritance.None:
|
||||
dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var (newDungeons, newReserved) = await GetDungeons(position,
|
||||
groupConfig,
|
||||
groupConfig.Layers,
|
||||
seed,
|
||||
random,
|
||||
reserved: inheritedReserved,
|
||||
existing: inheritedDungeons);
|
||||
dungeons.AddRange(newDungeons);
|
||||
|
||||
if (groupConfig.ReturnReserved)
|
||||
{
|
||||
reservedTiles.UnionWith(newReserved);
|
||||
}
|
||||
|
||||
break;
|
||||
case ReplaceTileDunGen replace:
|
||||
await GenerateTileReplacementDunGen(replace, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case RoofDunGen roof:
|
||||
await RoofGen(roof, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case RoomEntranceDunGen rEntrance:
|
||||
await PostGen(rEntrance, dungeons[^1], reservedTiles, random);
|
||||
break;
|
||||
case SampleDecalDunGen sdec:
|
||||
await PostGen(sdec, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case SampleEntityDunGen sent:
|
||||
await PostGen(sent, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case SampleTileDunGen stile:
|
||||
await PostGen(stile, dungeons, reservedTiles, random);
|
||||
break;
|
||||
case SplineDungeonConnectorDunGen spline:
|
||||
dungeons.Add(await PostGen(spline, dungeons, reservedTiles, random));
|
||||
break;
|
||||
@@ -320,11 +365,6 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
}
|
||||
}
|
||||
|
||||
private void LogDataError(Type type)
|
||||
{
|
||||
_sawmill.Error($"Unable to find dungeon data keys for {type}");
|
||||
}
|
||||
|
||||
[Pure]
|
||||
private bool ValidateResume()
|
||||
{
|
||||
@@ -346,4 +386,19 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
|
||||
|
||||
await SuspendIfOutOfTime();
|
||||
}
|
||||
|
||||
private void AddLoadedEntity(Vector2i tile, EntityUid ent)
|
||||
{
|
||||
_data.Entities[ent] = tile;
|
||||
}
|
||||
|
||||
private void AddLoadedDecal(Vector2 tile, uint decal)
|
||||
{
|
||||
_data.Decals[decal] = tile;
|
||||
}
|
||||
|
||||
private void AddLoadedTile(Vector2i index, Tile tile)
|
||||
{
|
||||
_data.Tiles[index] = tile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public sealed partial class DungeonSystem
|
||||
return roomRotation;
|
||||
}
|
||||
|
||||
public void SpawnRoom(
|
||||
public DungeonData SpawnRoom(
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
Matrix3x2 roomTransform,
|
||||
@@ -126,6 +126,7 @@ public sealed partial class DungeonSystem
|
||||
var templateMapUid = _maps.GetMapOrInvalid(roomMap);
|
||||
var templateGrid = Comp<MapGridComponent>(templateMapUid);
|
||||
var roomDimensions = room.Size;
|
||||
var data = new DungeonData();
|
||||
|
||||
var finalRoomRotation = roomTransform.Rotation();
|
||||
|
||||
@@ -154,6 +155,7 @@ public sealed partial class DungeonSystem
|
||||
}
|
||||
|
||||
_tiles.Add((rounded, tileRef.Tile));
|
||||
data.Tiles[rounded] = tileRef.Tile;
|
||||
|
||||
if (clearExisting)
|
||||
{
|
||||
@@ -186,6 +188,7 @@ public sealed partial class DungeonSystem
|
||||
|
||||
// TODO: Copy the templated entity as is with serv
|
||||
var ent = Spawn(protoId, new EntityCoordinates(gridUid, childPos));
|
||||
data.Entities.Add(ent, childPos.Floored());
|
||||
|
||||
var childXform = _xformQuery.GetComponent(ent);
|
||||
var anchored = templateXform.Anchored;
|
||||
@@ -256,14 +259,18 @@ public sealed partial class DungeonSystem
|
||||
var result = _decals.TryAddDecal(
|
||||
decal.Id,
|
||||
new EntityCoordinates(gridUid, position),
|
||||
out _,
|
||||
out var did,
|
||||
decal.Color,
|
||||
angle,
|
||||
decal.ZIndex,
|
||||
decal.Cleanable);
|
||||
|
||||
data.Decals.Add(did, position);
|
||||
|
||||
DebugTools.Assert(result);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,8 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
MapGridComponent grid,
|
||||
Vector2i position,
|
||||
int seed,
|
||||
EntityCoordinates? coordinates = null)
|
||||
EntityCoordinates? coordinates = null,
|
||||
HashSet<Vector2i>? reservedTiles = null)
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
var job = new DungeonJob.DungeonJob(
|
||||
@@ -221,18 +222,20 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
seed,
|
||||
position,
|
||||
coordinates,
|
||||
cancelToken.Token);
|
||||
cancelToken.Token,
|
||||
reservedTiles);
|
||||
|
||||
_dungeonJobs.Add(job, cancelToken);
|
||||
_dungeonJobQueue.EnqueueJob(job);
|
||||
}
|
||||
|
||||
public async Task<List<Dungeon>> GenerateDungeonAsync(
|
||||
public async Task<(List<Dungeon>, DungeonData)> GenerateDungeonAsync(
|
||||
DungeonConfig gen,
|
||||
EntityUid gridUid,
|
||||
MapGridComponent grid,
|
||||
Vector2i position,
|
||||
int seed)
|
||||
int seed,
|
||||
HashSet<Vector2i>? reservedTiles = null)
|
||||
{
|
||||
var cancelToken = new CancellationTokenSource();
|
||||
var job = new DungeonJob.DungeonJob(
|
||||
@@ -254,7 +257,8 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
seed,
|
||||
position,
|
||||
null,
|
||||
cancelToken.Token);
|
||||
cancelToken.Token,
|
||||
reservedTiles);
|
||||
|
||||
_dungeonJobs.Add(job, cancelToken);
|
||||
_dungeonJobQueue.EnqueueJob(job);
|
||||
@@ -265,7 +269,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
||||
throw job.Exception;
|
||||
}
|
||||
|
||||
return job.Result!;
|
||||
return job.Result;
|
||||
}
|
||||
|
||||
public Angle GetDungeonRotation(int seed)
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Robust.Shared.CPUJob.JobQueues;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Salvage.Expeditions;
|
||||
using Content.Server.Salvage.Expeditions.Structure;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Procedural;
|
||||
using Content.Shared.Procedural.Loot;
|
||||
@@ -25,15 +20,14 @@ using Content.Shared.Salvage;
|
||||
using Content.Shared.Salvage.Expeditions;
|
||||
using Content.Shared.Salvage.Expeditions.Modifiers;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Procedural.Components;
|
||||
|
||||
namespace Content.Server.Salvage;
|
||||
|
||||
@@ -120,14 +114,13 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
.GetMission(difficultyProto, _missionParams.Seed);
|
||||
|
||||
var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
|
||||
BiomeComponent? biome = null;
|
||||
|
||||
if (missionBiome.BiomePrototype != null)
|
||||
{
|
||||
var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
|
||||
var biomeSystem = _entManager.System<BiomeSystem>();
|
||||
biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
|
||||
biomeSystem.SetSeed(mapUid, biome, mission.Seed);
|
||||
_entManager.Dirty(mapUid, biome);
|
||||
|
||||
biome = biomeSystem.AddBiome(mapUid, missionBiome.BiomePrototype.Value, mission.Seed);
|
||||
|
||||
// Gravity
|
||||
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
|
||||
@@ -172,7 +165,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
|
||||
var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
|
||||
var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto);
|
||||
var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
|
||||
var (dungeons, data) = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
|
||||
_missionParams.Seed));
|
||||
|
||||
var dungeon = dungeons.First();
|
||||
@@ -183,18 +176,20 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
return false;
|
||||
}
|
||||
|
||||
expedition.DungeonLocation = dungeonOffset;
|
||||
|
||||
List<Vector2i> reservedTiles = new();
|
||||
|
||||
foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false))
|
||||
// Don't modify any dungeon tiles with chunk gen.
|
||||
// Have to defer biome loading until the primo dungen is generated.
|
||||
if (biome != null)
|
||||
{
|
||||
if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
|
||||
continue;
|
||||
foreach (var tile in dungeon.AllTiles)
|
||||
{
|
||||
biome.ModifiedTiles.Add(tile);
|
||||
}
|
||||
|
||||
reservedTiles.Add(tile.GridIndices);
|
||||
biome.Enabled = true;
|
||||
}
|
||||
|
||||
expedition.DungeonLocation = dungeonOffset;
|
||||
|
||||
var budgetEntries = new List<IBudgetEntry>();
|
||||
|
||||
/*
|
||||
@@ -208,13 +203,14 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
if (!lootProto.Guaranteed)
|
||||
continue;
|
||||
|
||||
try
|
||||
foreach (var rule in lootProto.LootRules)
|
||||
{
|
||||
await SpawnDungeonLoot(lootProto, mapUid);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Failed to spawn guaranteed loot {lootProto.ID}: {e}");
|
||||
switch (rule)
|
||||
{
|
||||
case BiomeLoot biomeLoot:
|
||||
_biome.AddLayer(mapUid, $"{rule}", biomeLoot.Proto);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,32 +319,4 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
|
||||
|
||||
// oh noooooooooooo
|
||||
}
|
||||
|
||||
private async Task SpawnDungeonLoot(SalvageLootPrototype loot, EntityUid gridUid)
|
||||
{
|
||||
for (var i = 0; i < loot.LootRules.Count; i++)
|
||||
{
|
||||
var rule = loot.LootRules[i];
|
||||
|
||||
switch (rule)
|
||||
{
|
||||
case BiomeMarkerLoot biomeLoot:
|
||||
{
|
||||
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
|
||||
{
|
||||
_biome.AddMarkerLayer(gridUid, biome, biomeLoot.Prototype);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BiomeTemplateLoot biomeLoot:
|
||||
{
|
||||
if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
|
||||
{
|
||||
_biome.AddTemplate(gridUid, biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Screens.Components;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
@@ -22,7 +23,6 @@ using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Tiles;
|
||||
@@ -82,11 +82,11 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
/// </summary>
|
||||
private const float RoundStartFTLDuration = 10f;
|
||||
|
||||
private readonly List<ProtoId<BiomeTemplatePrototype>> _arrivalsBiomeOptions = new()
|
||||
private readonly List<EntProtoId> _arrivalsBiomeOptions = new()
|
||||
{
|
||||
"Grasslands",
|
||||
"LowDesert",
|
||||
"Snow",
|
||||
"BiomeGrasslands",
|
||||
"BiomeLowDesert",
|
||||
"BiomeSnow",
|
||||
};
|
||||
|
||||
public override void Initialize()
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Events;
|
||||
using Content.Server.Station.Events;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Parallax;
|
||||
@@ -956,6 +958,7 @@ public sealed partial class ShuttleSystem
|
||||
var transform = _physics.GetRelativePhysicsTransform((uid, xform), xform.MapUid.Value);
|
||||
var aabbs = new List<Box2>(manager.Fixtures.Count);
|
||||
var tileSet = new List<(Vector2i, Tile)>();
|
||||
TryComp(xform.MapUid.Value, out DecalGridComponent? decalGrid);
|
||||
|
||||
foreach (var fixture in manager.Fixtures.Values)
|
||||
{
|
||||
@@ -969,9 +972,15 @@ public sealed partial class ShuttleSystem
|
||||
aabb = aabb.Enlarged(0.2f);
|
||||
aabbs.Add(aabb);
|
||||
|
||||
// Handle clearing biome stuff as relevant.
|
||||
if (decalGrid != null)
|
||||
{
|
||||
foreach (var decal in _decals.GetDecalsIntersecting(xform.MapUid.Value, aabb))
|
||||
{
|
||||
_decals.RemoveDecal(xform.MapUid.Value, decal.Index, decalGrid);
|
||||
}
|
||||
}
|
||||
|
||||
tileSet.Clear();
|
||||
_biomes.ReserveTiles(xform.MapUid.Value, aabb, tileSet);
|
||||
_lookupEnts.Clear();
|
||||
_immuneEnts.Clear();
|
||||
// TODO: Ideally we'd query first BEFORE moving grid but needs adjustments above.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Buckle.Systems;
|
||||
using Content.Server.Decals;
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Shuttles.Components;
|
||||
@@ -41,10 +42,12 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
[Dependency] private readonly BiomeSystem _biomes = default!;
|
||||
[Dependency] private readonly BodySystem _bobby = default!;
|
||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageSys = default!;
|
||||
[Dependency] private readonly DecalSystem _decals = default!;
|
||||
[Dependency] private readonly DockingSystem _dockSystem = default!;
|
||||
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Station.Components;
|
||||
@@ -11,7 +10,7 @@ namespace Content.Server.Station.Components;
|
||||
public sealed partial class StationBiomeComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public ProtoId<BiomeTemplatePrototype> Biome = "Grasslands";
|
||||
public EntProtoId Biome = "BiomeGrasslands";
|
||||
|
||||
// If null, its random
|
||||
[DataField]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Parallax;
|
||||
using Content.Server.Procedural;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace Content.Server.Tabletop
|
||||
if (!TryComp(uid, out ActorComponent? actor))
|
||||
{
|
||||
RemComp<TabletopGamerComponent>(uid);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (actor.PlayerSession.Status != SessionStatus.InGame || !CanSeeTable(uid, gamer.Tabletop))
|
||||
|
||||
18
Content.Shared/CCVar/CCVars.Biome.cs
Normal file
18
Content.Shared/CCVar/CCVars.Biome.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared.CCVar;
|
||||
|
||||
public sealed partial class CCVars
|
||||
{
|
||||
/// <summary>
|
||||
/// Load range for biomes. Set this higher than PVS so server can have some time to load in before the client arrives.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> BiomeLoadRange =
|
||||
CVarDef.Create("biome.load_range", 20f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Time allocation (ms) for how long biomes are allowed to load.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> BiomeLoadTime =
|
||||
CVarDef.Create("biome.load_time", 0.03f, CVar.SERVERONLY);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedBiomeSystem))]
|
||||
public sealed partial class BiomeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Do we load / deload.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public bool Enabled = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("seed")]
|
||||
[AutoNetworkedField]
|
||||
public int Seed = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying entity, decal, and tile layers for the biome.
|
||||
/// </summary>
|
||||
[DataField("layers")]
|
||||
[AutoNetworkedField]
|
||||
public List<IBiomeLayer> Layers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Templates to use for <see cref="Layers"/>.
|
||||
/// If this is set on mapinit, it will fill out layers automatically.
|
||||
/// If not set, use <c>BiomeSystem</c> to do it.
|
||||
/// Prototype reloading will also use this.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<BiomeTemplatePrototype>? Template;
|
||||
|
||||
/// <summary>
|
||||
/// If we've already generated a tile and couldn't deload it then we won't ever reload it in future.
|
||||
/// Stored by [Chunkorigin, Tiles]
|
||||
/// </summary>
|
||||
[DataField("modifiedTiles")]
|
||||
public Dictionary<Vector2i, HashSet<Vector2i>> ModifiedTiles = new();
|
||||
|
||||
/// <summary>
|
||||
/// Decals that have been loaded as a part of this biome.
|
||||
/// </summary>
|
||||
[DataField("decals")]
|
||||
public Dictionary<Vector2i, Dictionary<uint, Vector2i>> LoadedDecals = new();
|
||||
|
||||
[DataField("entities")]
|
||||
public Dictionary<Vector2i, Dictionary<EntityUid, Vector2i>> LoadedEntities = new();
|
||||
|
||||
/// <summary>
|
||||
/// Currently active chunks
|
||||
/// </summary>
|
||||
[DataField("loadedChunks")]
|
||||
public HashSet<Vector2i> LoadedChunks = new();
|
||||
|
||||
#region Markers
|
||||
|
||||
/// <summary>
|
||||
/// Work out entire marker tiles in advance but only load the entities when in range.
|
||||
/// </summary>
|
||||
[DataField("pendingMarkers")]
|
||||
public Dictionary<Vector2i, Dictionary<string, List<Vector2i>>> PendingMarkers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Track what markers we've loaded already to avoid double-loading.
|
||||
/// </summary>
|
||||
[DataField("loadedMarkers", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<Vector2i>, BiomeMarkerLayerPrototype>))]
|
||||
public Dictionary<string, HashSet<Vector2i>> LoadedMarkers = new();
|
||||
|
||||
[DataField]
|
||||
public HashSet<ProtoId<BiomeMarkerLayerPrototype>> MarkerLayers = new();
|
||||
|
||||
/// <summary>
|
||||
/// One-tick forcing of marker layers to bulldoze any entities in the way.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<ProtoId<BiomeMarkerLayerPrototype>> ForcedMarkerLayers = new();
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
/// <summary>
|
||||
/// A preset group of biome layers to be used for a <see cref="BiomeComponent"/>
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class BiomeTemplatePrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("layers")]
|
||||
public List<IBiomeLayer> Layers = new();
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class BiomeDecalLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Divide each tile up by this amount.
|
||||
/// </summary>
|
||||
[DataField("divisions")]
|
||||
public float Divisions = 1f;
|
||||
|
||||
[DataField("noise")]
|
||||
public FastNoiseLite Noise { get; private set; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; private set; } = 0.8f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("invert")] public bool Invert { get; private set; } = false;
|
||||
|
||||
[DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DecalPrototype>))]
|
||||
public List<string> Decals = new();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
/// <summary>
|
||||
/// Dummy layer that specifies a marker to be replaced by external code.
|
||||
/// For example if they wish to add their own layers at specific points across different templates.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class BiomeDummyLayer : IBiomeLayer
|
||||
{
|
||||
[DataField("id", required: true)] public string ID = string.Empty;
|
||||
|
||||
public FastNoiseLite Noise { get; } = new();
|
||||
public float Threshold { get; }
|
||||
public bool Invert { get; }
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class BiomeEntityLayer : IBiomeWorldLayer
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; private set; } = new();
|
||||
|
||||
[DataField("noise")] public FastNoiseLite Noise { get; private set; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; private set; } = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("invert")] public bool Invert { get; private set; } = false;
|
||||
|
||||
[DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> Entities = new();
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
/// <summary>
|
||||
/// Contains more biome layers recursively via a biome template.
|
||||
/// Can be used for sub-biomes.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class BiomeMetaLayer : IBiomeLayer
|
||||
{
|
||||
[DataField("noise")]
|
||||
public FastNoiseLite Noise { get; private set; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("threshold")]
|
||||
public float Threshold { get; private set; } = -1f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField("invert")]
|
||||
public bool Invert { get; private set; }
|
||||
|
||||
[DataField("template", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
|
||||
public string Template = string.Empty;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Robust.Shared.Noise;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public partial interface IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Seed is used an offset from the relevant BiomeComponent's seed.
|
||||
/// </summary>
|
||||
FastNoiseLite Noise { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for this layer to be present. If set to 0 forces it for every tile.
|
||||
/// </summary>
|
||||
float Threshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the thresold inverted so we need to be lower than it.
|
||||
/// </summary>
|
||||
public bool Invert { get; }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
|
||||
/// <summary>
|
||||
/// Handles actual objects such as decals and entities.
|
||||
/// </summary>
|
||||
public partial interface IBiomeWorldLayer : IBiomeLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// What tiles we're allowed to spawn on, real or biome.
|
||||
/// </summary>
|
||||
List<string> AllowedTiles { get; }
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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]
|
||||
public sealed partial class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
|
||||
{
|
||||
[IdDataField] public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Checks for the relevant entity for the tile before spawning. Useful for substituting walls with ore veins for example.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<EntProtoId, EntProtoId> EntityMask { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Default prototype to spawn. If null will fall back to entity mask.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? Prototype { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum radius between 2 points
|
||||
/// </summary>
|
||||
[DataField("radius")]
|
||||
public float Radius = 32f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount of group spawns
|
||||
/// </summary>
|
||||
[DataField("maxCount")]
|
||||
public int MaxCount = int.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum entities to spawn in one group.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int MinGroupSize = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum entities to spawn in one group.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int MaxGroupSize = 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataField("size")]
|
||||
public int Size { get; private set; } = 128;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Markers;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies one-off marker points to be used. This could be for dungeon markers, mob markers, etc.
|
||||
/// These are run outside of the tile / decal / entity layers.
|
||||
/// </summary>
|
||||
public interface IBiomeMarkerLayer : IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// Biome template to use as a mask for this layer.
|
||||
/// </summary>
|
||||
public Dictionary<EntProtoId, EntProtoId> EntityMask { get; }
|
||||
|
||||
public string? Prototype { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How large the pre-generated points area is.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Parallax.Biomes.Layers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes;
|
||||
|
||||
public abstract class SharedBiomeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IPrototypeManager ProtoManager = default!;
|
||||
[Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
protected const byte ChunkSize = 8;
|
||||
|
||||
private T Pick<T>(List<T> collection, float value)
|
||||
{
|
||||
// Listen I don't need this exact and I'm too lazy to finetune just for random ent picking.
|
||||
value %= 1f;
|
||||
value = Math.Clamp(value, 0f, 1f);
|
||||
|
||||
if (collection.Count == 1)
|
||||
return collection[0];
|
||||
|
||||
var randValue = value * collection.Count;
|
||||
|
||||
foreach (var item in collection)
|
||||
{
|
||||
randValue -= 1f;
|
||||
|
||||
if (randValue <= 0f)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
private int Pick(int count, float value)
|
||||
{
|
||||
value %= 1f;
|
||||
value = Math.Clamp(value, 0f, 1f);
|
||||
|
||||
if (count == 1)
|
||||
return 0;
|
||||
|
||||
value *= count;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
value -= 1f;
|
||||
|
||||
if (value <= 0f)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (_map.TryGetTileRef(uid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
|
||||
{
|
||||
tile = tileRef.Tile;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryComp<BiomeComponent>(uid, out var biome))
|
||||
{
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetBiomeTile(indices, biome.Layers, biome.Seed, (uid, grid), out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the tile, real or otherwise, for the specified indices.
|
||||
/// </summary>
|
||||
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
if (grid is { } gridEnt && _map.TryGetTileRef(gridEnt, gridEnt.Comp, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
|
||||
{
|
||||
tile = tileRef.Tile;
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryGetTile(indices, layers, seed, grid, out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the tile, real or otherwise, for the specified indices.
|
||||
/// </summary>
|
||||
[Obsolete("Use the Entity<MapGridComponent>? overload")]
|
||||
public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
return TryGetBiomeTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||
/// </summary>
|
||||
public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = layers[i];
|
||||
var noiseCopy = GetNoise(layer.Noise, seed);
|
||||
|
||||
var invert = layer.Invert;
|
||||
var value = noiseCopy.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
// Check if the tile is from meta layer, otherwise fall back to default layers.
|
||||
if (layer is BiomeMetaLayer meta)
|
||||
{
|
||||
if (TryGetBiomeTile(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out tile))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (layer is not BiomeTileLayer tileLayer)
|
||||
continue;
|
||||
|
||||
if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Variants, out tile))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||
/// </summary>
|
||||
[Obsolete("Use the Entity<MapGridComponent>? overload")]
|
||||
public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
return TryGetTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying biome tile, ignoring any existing tile that may be there.
|
||||
/// </summary>
|
||||
private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
|
||||
{
|
||||
var found = noise.GetNoise(indices.X, indices.Y);
|
||||
found = invert ? found * -1 : found;
|
||||
|
||||
if (found < threshold)
|
||||
{
|
||||
tile = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
byte variant = 0;
|
||||
var variantCount = variants?.Count ?? tileDef.Variants;
|
||||
|
||||
// Pick a variant tile if they're available as well
|
||||
if (variantCount > 1)
|
||||
{
|
||||
var variantValue = (noise.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) * 100;
|
||||
variant = _tile.PickVariant(tileDef, (int)variantValue);
|
||||
}
|
||||
|
||||
tile = new Tile(tileDef.TileId, variant);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant entity for this tile.
|
||||
/// </summary>
|
||||
public bool TryGetEntity(Vector2i indices, BiomeComponent component, Entity<MapGridComponent>? grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, component.Layers, component.Seed, grid, out var tile))
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetEntity(indices, component.Layers, tile.Value, component.Seed, grid, out entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant entity for this tile.
|
||||
/// </summary>
|
||||
[Obsolete("Use the Entity<MapGridComponent>? overload")]
|
||||
public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
return TryGetEntity(indices, component, grid == null ? null : (grid.Owner, grid), out entity);
|
||||
}
|
||||
|
||||
public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, Entity<MapGridComponent>? grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
var tileId = TileDefManager[tileRef.TypeId].ID;
|
||||
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = layers[i];
|
||||
|
||||
switch (layer)
|
||||
{
|
||||
case BiomeDummyLayer:
|
||||
continue;
|
||||
case IBiomeWorldLayer worldLayer:
|
||||
if (!worldLayer.AllowedTiles.Contains(tileId))
|
||||
continue;
|
||||
|
||||
break;
|
||||
case BiomeMetaLayer:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
var noiseCopy = GetNoise(layer.Noise, seed);
|
||||
|
||||
var invert = layer.Invert;
|
||||
var value = noiseCopy.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
if (layer is BiomeMetaLayer meta)
|
||||
{
|
||||
if (TryGetEntity(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, tileRef, seed, grid, out entity))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decals might block entity so need to check if there's one in front of us.
|
||||
if (layer is not BiomeEntityLayer biomeLayer)
|
||||
{
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var noiseValue = noiseCopy.GetNoise(indices.X, indices.Y, i);
|
||||
entity = Pick(biomeLayer.Entities, (noiseValue + 1f) / 2f);
|
||||
return true;
|
||||
}
|
||||
|
||||
entity = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
[Obsolete("Use the Entity<MapGridComponent>? overload")]
|
||||
public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out string? entity)
|
||||
{
|
||||
return TryGetEntity(indices, layers, tileRef, seed, grid == null ? null : (grid.Owner, grid), out entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant decals for this tile.
|
||||
/// </summary>
|
||||
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid,
|
||||
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
|
||||
{
|
||||
if (!TryGetBiomeTile(indices, layers, seed, grid, out var tileRef))
|
||||
{
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tileId = TileDefManager[tileRef.Value.TypeId].ID;
|
||||
|
||||
for (var i = layers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var layer = layers[i];
|
||||
|
||||
// Entities might block decal so need to check if there's one in front of us.
|
||||
switch (layer)
|
||||
{
|
||||
case BiomeDummyLayer:
|
||||
continue;
|
||||
case IBiomeWorldLayer worldLayer:
|
||||
if (!worldLayer.AllowedTiles.Contains(tileId))
|
||||
continue;
|
||||
|
||||
break;
|
||||
case BiomeMetaLayer:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
var invert = layer.Invert;
|
||||
var noiseCopy = GetNoise(layer.Noise, seed);
|
||||
var value = noiseCopy.GetNoise(indices.X, indices.Y);
|
||||
value = invert ? value * -1 : value;
|
||||
|
||||
if (value < layer.Threshold)
|
||||
continue;
|
||||
|
||||
if (layer is BiomeMetaLayer meta)
|
||||
{
|
||||
if (TryGetDecals(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out decals))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the other layer should even render, if not then keep going.
|
||||
if (layer is not BiomeDecalLayer decalLayer)
|
||||
{
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
decals = new List<(string ID, Vector2 Position)>();
|
||||
|
||||
for (var x = 0; x < decalLayer.Divisions; x++)
|
||||
{
|
||||
for (var y = 0; y < decalLayer.Divisions; y++)
|
||||
{
|
||||
var index = new Vector2(indices.X + x * 1f / decalLayer.Divisions, indices.Y + y * 1f / decalLayer.Divisions);
|
||||
var decalValue = noiseCopy.GetNoise(index.X, index.Y);
|
||||
decalValue = invert ? decalValue * -1 : decalValue;
|
||||
|
||||
if (decalValue < decalLayer.Threshold)
|
||||
continue;
|
||||
|
||||
decals.Add((Pick(decalLayer.Decals, (noiseCopy.GetNoise(indices.X, indices.Y, x + y * decalLayer.Divisions) + 1f) / 2f), index));
|
||||
}
|
||||
}
|
||||
|
||||
// Check other layers
|
||||
if (decals.Count == 0)
|
||||
continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
decals = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the relevant decals for this tile.
|
||||
/// </summary>
|
||||
[Obsolete("Use the Entity<MapGridComponent>? overload")]
|
||||
public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
|
||||
[NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
|
||||
{
|
||||
return TryGetDecals(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out decals);
|
||||
}
|
||||
|
||||
private FastNoiseLite GetNoise(FastNoiseLite seedNoise, int seed)
|
||||
{
|
||||
var noiseCopy = new FastNoiseLite();
|
||||
_serManager.CopyTo(seedNoise, ref noiseCopy, notNullableOverride: true);
|
||||
noiseCopy.SetSeed(noiseCopy.GetSeed() + seed);
|
||||
// Ensure re-calculate is run.
|
||||
noiseCopy.SetFractalOctaves(noiseCopy.GetFractalOctaves());
|
||||
return noiseCopy;
|
||||
}
|
||||
}
|
||||
86
Content.Shared/Procedural/Components/BiomeComponent.cs
Normal file
86
Content.Shared/Procedural/Components/BiomeComponent.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A layer inside of <see cref="BiomeComponent"/>
|
||||
/// </summary>
|
||||
[DataRecord]
|
||||
public sealed record BiomeMetaLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Chunk dimensions for this meta layer. Will try to infer it from the first layer of the dungeon if null.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int? Size;
|
||||
|
||||
/// <summary>
|
||||
/// Meta layers that this one requires to be loaded first.
|
||||
/// Will ensure all of the chunks for our corresponding area are loaded.
|
||||
/// </summary>
|
||||
public List<string>? DependsOn;
|
||||
|
||||
/// <summary>
|
||||
/// Can this layer be unloaded if no one is in range.
|
||||
/// </summary>
|
||||
public bool CanUnload = true;
|
||||
|
||||
/// <summary>
|
||||
/// Dungeon config to load inside the specified area.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<DungeonConfigPrototype> Dungeon = new();
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class BiomeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Can we load / unload chunks.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Areas queued for preloading. Will add these during <see cref="BiomeLoadJob"/> and then flag as modified so they retain.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<Box2i> PreloadAreas = new();
|
||||
|
||||
/// <summary>
|
||||
/// Is there currently a job that's loading.
|
||||
/// </summary>
|
||||
public bool Loading = false;
|
||||
|
||||
[DataField]
|
||||
public int Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Layer key and associated data.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public Dictionary<string, BiomeMetaLayer> Layers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Layer removals that are pending.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> PendingRemovals = new();
|
||||
|
||||
/// <summary>
|
||||
/// Data that is currently loaded.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<string, Dictionary<Vector2i, DungeonData>> LoadedData = new();
|
||||
|
||||
/// <summary>
|
||||
/// Flag modified tiles so we don't try and unload / reload them.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<Vector2i> ModifiedTiles = new();
|
||||
|
||||
/// <summary>
|
||||
/// Bounds loaded by players for this tick.
|
||||
/// </summary>
|
||||
public List<Box2i> LoadedBounds = new();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Procedural.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Will forcibly unload an entity no matter what. Useful if you have consistent entities that will never be default or the likes.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BiomeForceUnloadComponent : Component;
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Shared.Procedural.Distance;
|
||||
|
||||
public sealed partial class DunGenDistanceSquared : IDunGenDistance
|
||||
{
|
||||
[DataField]
|
||||
public float BlendWeight { get; set; } = 0.50f;
|
||||
}
|
||||
@@ -12,11 +12,18 @@ public partial class DungeonConfig
|
||||
public List<IDunGenLayer> Layers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Should we reserve the tiles generated by this config so no other dungeons can spawn on it within the same job?
|
||||
/// Should we reserve the tiles generated by this config so no other layers at the same level can spawn on this tile?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ReserveTiles;
|
||||
|
||||
/// <summary>
|
||||
/// Should we return the reserved tiles to the upper level.
|
||||
/// Set to false if you don't care if this dungeon has its tiles overwritten at higher levels.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ReturnReserved = true;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum times to run the config.
|
||||
/// </summary>
|
||||
|
||||
41
Content.Shared/Procedural/DungeonData.cs
Normal file
41
Content.Shared/Procedural/DungeonData.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Shared.Procedural;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the loaded data for a dungeon.
|
||||
/// </summary>
|
||||
[DataDefinition]
|
||||
public sealed partial class DungeonData
|
||||
{
|
||||
[DataField]
|
||||
public Dictionary<uint, Vector2> Decals = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, Vector2i> Entities = new();
|
||||
|
||||
[DataField]
|
||||
public Dictionary<Vector2i, Tile> Tiles = new();
|
||||
|
||||
public static DungeonData Empty = new();
|
||||
|
||||
public void Merge(DungeonData data)
|
||||
{
|
||||
foreach (var did in data.Decals)
|
||||
{
|
||||
Decals[did.Key] = did.Value;
|
||||
}
|
||||
|
||||
foreach (var ent in data.Entities)
|
||||
{
|
||||
Entities[ent.Key] = ent.Value;
|
||||
}
|
||||
|
||||
foreach (var tile in data.Tiles)
|
||||
{
|
||||
Tiles[tile.Key] = tile.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs
Normal file
24
Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.Noise;
|
||||
|
||||
namespace Content.Shared.Procedural.DungeonGenerators;
|
||||
|
||||
/// <summary>
|
||||
/// Turns a chunked area into a dungeon for layer purposes. Assumes the position is the BL origin.
|
||||
/// </summary>
|
||||
public sealed partial class ChunkDunGen : IDunGenLayer
|
||||
{
|
||||
[DataField]
|
||||
public int Size = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Noise to apply for each tile conditionally.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FastNoiseLite? Noise;
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for noise. Does nothing if <see cref="Noise"/> is null.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Threshold = -1f;
|
||||
}
|
||||
@@ -10,4 +10,10 @@ public sealed partial class ExteriorDunGen : IDunGenLayer
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public ProtoId<DungeonConfigPrototype> Proto;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum and maximum penetration.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Vector2i Penetration = new Vector2i(5, 15);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ public sealed partial class PrototypeDunGen : IDunGenLayer
|
||||
[DataField]
|
||||
public DungeonInheritance InheritDungeons = DungeonInheritance.None;
|
||||
|
||||
/// <summary>
|
||||
/// Should we pass in the current level's reserved tiles to the prototype.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ReservedInheritance InheritReserved = ReservedInheritance.All;
|
||||
|
||||
[DataField(required: true)]
|
||||
public ProtoId<DungeonConfigPrototype> Proto;
|
||||
}
|
||||
@@ -35,3 +41,16 @@ public enum DungeonInheritance : byte
|
||||
/// </summary>
|
||||
All,
|
||||
}
|
||||
|
||||
public enum ReservedInheritance : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't inherit any reserved tiles.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Inherit reserved tiles,
|
||||
/// </summary>
|
||||
All,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.PostGeneration;
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.PostGeneration;
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Procedural.Distance;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
@@ -6,10 +9,6 @@ namespace Content.Shared.Procedural.DungeonLayers;
|
||||
/// <summary>
|
||||
/// Fills unreserved tiles with the specified entity prototype.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// DungeonData keys are:
|
||||
/// - Fill
|
||||
/// </remarks>
|
||||
public sealed partial class FillGridDunGen : IDunGenLayer
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,4 +19,29 @@ public sealed partial class FillGridDunGen : IDunGenLayer
|
||||
|
||||
[DataField(required: true)]
|
||||
public EntProtoId Entity;
|
||||
|
||||
#region Noise
|
||||
|
||||
[DataField]
|
||||
public bool Invert;
|
||||
|
||||
/// <summary>
|
||||
/// Optionally don't spawn entities if the noise value matches.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public FastNoiseLite? ReservedNoise;
|
||||
|
||||
/// <summary>
|
||||
/// Noise threshold for <see cref="ReservedNoise"/>. Does nothing without it.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Threshold = -1f;
|
||||
|
||||
[DataField]
|
||||
public IDunGenDistance? DistanceConfig;
|
||||
|
||||
[DataField]
|
||||
public Vector2 Size;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Spawns mobs inside of the dungeon randomly.
|
||||
/// </summary>
|
||||
|
||||
15
Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs
Normal file
15
Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.Shared.Noise;
|
||||
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
/// <summary>
|
||||
/// Sets tiles as rooved.
|
||||
/// </summary>
|
||||
public sealed partial class RoofDunGen : IDunGenLayer
|
||||
{
|
||||
[DataField]
|
||||
public float Threshold = -1f;
|
||||
|
||||
[DataField]
|
||||
public FastNoiseLite? Noise;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.EntityTable;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.PostGeneration;
|
||||
36
Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs
Normal file
36
Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
public sealed partial class SampleDecalDunGen : IDunGenLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserve any tiles we update.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ReserveTiles = true;
|
||||
|
||||
[DataField(customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Divide each tile up by this amount.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Divisions = 1f;
|
||||
|
||||
[DataField]
|
||||
public FastNoiseLite Noise { get; private set; } = new(0);
|
||||
|
||||
[DataField]
|
||||
public float Threshold { get; private set; } = 0.8f;
|
||||
|
||||
[DataField] public bool Invert { get; private set; } = false;
|
||||
|
||||
[DataField(required: true)]
|
||||
public List<ProtoId<DecalPrototype>> Decals = new();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
/// <summary>
|
||||
/// Samples noise to spawn the specified entity
|
||||
/// </summary>
|
||||
public sealed partial class SampleEntityDunGen : IDunGenLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserve any tiles we update.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ReserveTiles = true;
|
||||
|
||||
[DataField(customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
|
||||
public List<string> AllowedTiles { get; private set; } = new();
|
||||
|
||||
[DataField] public FastNoiseLite Noise { get; private set; } = new(0);
|
||||
|
||||
[DataField]
|
||||
public float Threshold { get; private set; } = 0.5f;
|
||||
|
||||
[DataField] public bool Invert { get; private set; } = false;
|
||||
|
||||
[DataField]
|
||||
public List<EntProtoId> Entities = new();
|
||||
}
|
||||
@@ -2,20 +2,26 @@ using Content.Shared.Maps;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Parallax.Biomes.Layers;
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
/// <summary>
|
||||
/// Samples noise and spawns the specified tile in the dungeon area.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class BiomeTileLayer : IBiomeLayer
|
||||
public sealed partial class SampleTileDunGen : IDunGenLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserve any tiles we update.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ReserveTiles = true;
|
||||
|
||||
[DataField] public FastNoiseLite Noise { get; private set; } = new(0);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField]
|
||||
public float Threshold { get; private set; } = 0.5f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[DataField] public bool Invert { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.PostGeneration;
|
||||
namespace Content.Shared.Procedural.DungeonLayers;
|
||||
|
||||
/// <summary>
|
||||
/// Connects dungeons via points that get subdivided.
|
||||
@@ -18,11 +18,11 @@ public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer
|
||||
/// Will divide the distance between the start and end points so that no subdivision is more than these metres away.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int DivisionDistance = 10;
|
||||
public int DivisionDistance = 20;
|
||||
|
||||
/// <summary>
|
||||
/// How much each subdivision can vary from the middle.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float VarianceMax = 0.35f;
|
||||
public float VarianceMax = 0.15f;
|
||||
}
|
||||
12
Content.Shared/Procedural/Loot/BiomeLoot.cs
Normal file
12
Content.Shared/Procedural/Loot/BiomeLoot.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Procedural.Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the prototype as a biome layer.
|
||||
/// </summary>
|
||||
public sealed partial class BiomeLoot : IDungeonLoot
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public ProtoId<DungeonConfigPrototype> Proto;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Content.Shared.Parallax.Biomes.Markers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
|
||||
|
||||
namespace Content.Shared.Procedural.Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a biome marker layer for dungeon loot.
|
||||
/// </summary>
|
||||
public sealed partial class BiomeMarkerLoot : IDungeonLoot
|
||||
{
|
||||
[DataField("proto", required: true)]
|
||||
public ProtoId<BiomeMarkerLayerPrototype> Prototype = new();
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Procedural.Loot;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a biome template layer for dungeon loot.
|
||||
/// </summary>
|
||||
public sealed partial class BiomeTemplateLoot : IDungeonLoot
|
||||
{
|
||||
[DataField("proto", required: true)]
|
||||
public ProtoId<BiomeTemplatePrototype> Prototype = string.Empty;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user