Add RoomFill markers (#22293)
* Add RoomFill markers * weh * Also deez * Working * Randomised fills working * Fixes * Fix lack of prototypes * Fix tests * Fix tests?
This commit is contained in:
@@ -191,59 +191,29 @@ public sealed partial class DungeonJob
|
|||||||
|
|
||||||
grid.SetTiles(tiles);
|
grid.SetTiles(tiles);
|
||||||
tiles.Clear();
|
tiles.Clear();
|
||||||
Logger.Error($"Unable to find room variant for {roomDimensions}, leaving empty.");
|
_sawmill.Error($"Unable to find room variant for {roomDimensions}, leaving empty.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
roomRotation = new Angle(Math.PI / 2);
|
roomRotation = new Angle(Math.PI / 2);
|
||||||
Logger.Debug($"Using rotated variant for room");
|
_sawmill.Debug($"Using rotated variant for room");
|
||||||
}
|
|
||||||
|
|
||||||
if (roomDimensions.X == roomDimensions.Y)
|
|
||||||
{
|
|
||||||
// Give it a random rotation
|
|
||||||
roomRotation = random.Next(4) * Math.PI / 2;
|
|
||||||
}
|
|
||||||
else if (random.Next(2) == 1)
|
|
||||||
{
|
|
||||||
roomRotation += Math.PI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomTransform = Matrix3.CreateTransform(roomSize.Center - packCenter, roomRotation);
|
var roomTransform = Matrix3.CreateTransform(roomSize.Center - packCenter, roomRotation);
|
||||||
var finalRoomRotation = roomRotation + packRotation + dungeonRotation;
|
|
||||||
|
|
||||||
Matrix3.Multiply(roomTransform, packTransform, out matty);
|
Matrix3.Multiply(roomTransform, packTransform, out matty);
|
||||||
Matrix3.Multiply(matty, dungeonTransform, out var dungeonMatty);
|
Matrix3.Multiply(matty, dungeonTransform, out var dungeonMatty);
|
||||||
|
|
||||||
|
// The expensive bit yippy.
|
||||||
var room = roomProto[random.Next(roomProto.Count)];
|
var room = roomProto[random.Next(roomProto.Count)];
|
||||||
var roomMap = _dungeon.GetOrCreateTemplate(room);
|
_dungeon.SpawnRoom(gridUid, grid, matty, room, random, rotation: true);
|
||||||
var templateMapUid = _mapManager.GetMapEntityId(roomMap);
|
|
||||||
var templateGrid = _entManager.GetComponent<MapGridComponent>(templateMapUid);
|
|
||||||
var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
|
var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
|
||||||
var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
|
var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
|
||||||
var exterior = new HashSet<Vector2i>(room.Size.X * 2 + room.Size.Y * 2);
|
var exterior = new HashSet<Vector2i>(room.Size.X * 2 + room.Size.Y * 2);
|
||||||
var tileOffset = -roomCenter + grid.TileSizeHalfVector;
|
var tileOffset = -roomCenter + grid.TileSizeHalfVector;
|
||||||
Box2i? mapBounds = null;
|
Box2i? mapBounds = null;
|
||||||
|
|
||||||
// Load tiles
|
|
||||||
for (var x = 0; x < room.Size.X; x++)
|
|
||||||
{
|
|
||||||
for (var y = 0; y < room.Size.Y; y++)
|
|
||||||
{
|
|
||||||
var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y);
|
|
||||||
var tileRef = templateGrid.GetTileRef(indices);
|
|
||||||
|
|
||||||
var tilePos = dungeonMatty.Transform(indices + tileOffset);
|
|
||||||
var rounded = tilePos.Floored();
|
|
||||||
tiles.Add((rounded, tileRef.Tile));
|
|
||||||
roomTiles.Add(rounded);
|
|
||||||
|
|
||||||
// If this were a Box2 we'd add tilesize although here I think that's undesirable as
|
|
||||||
// for example, a box2i of 0,0,1,1 is assumed to also include the tile at 1,1
|
|
||||||
mapBounds = mapBounds?.Union(new Box2i(rounded, rounded)) ?? new Box2i(rounded, rounded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var x = -1; x <= room.Size.X; x++)
|
for (var x = -1; x <= room.Size.X; x++)
|
||||||
{
|
{
|
||||||
for (var y = -1; y <= room.Size.Y; y++)
|
for (var y = -1; y <= room.Size.Y; y++)
|
||||||
@@ -258,111 +228,16 @@ public sealed partial class DungeonJob
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bounds = new Box2(room.Offset, room.Offset + room.Size);
|
|
||||||
var center = Vector2.Zero;
|
var center = Vector2.Zero;
|
||||||
|
|
||||||
foreach (var tile in roomTiles)
|
foreach (var tile in roomTiles)
|
||||||
{
|
{
|
||||||
center += (Vector2) tile + grid.TileSizeHalfVector;
|
center += tile + grid.TileSizeHalfVector;
|
||||||
}
|
}
|
||||||
|
|
||||||
center /= roomTiles.Count;
|
center /= roomTiles.Count;
|
||||||
|
|
||||||
dungeon.Rooms.Add(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior));
|
dungeon.Rooms.Add(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior));
|
||||||
grid.SetTiles(tiles);
|
|
||||||
tiles.Clear();
|
|
||||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
|
||||||
var metaQuery = _entManager.GetEntityQuery<MetaDataComponent>();
|
|
||||||
|
|
||||||
// Load entities
|
|
||||||
// TODO: I don't think engine supports full entity copying so we do this piece of shit.
|
|
||||||
|
|
||||||
foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained))
|
|
||||||
{
|
|
||||||
var templateXform = xformQuery.GetComponent(templateEnt);
|
|
||||||
var childPos = dungeonMatty.Transform(templateXform.LocalPosition - roomCenter);
|
|
||||||
var childRot = templateXform.LocalRotation + finalRoomRotation;
|
|
||||||
var protoId = metaQuery.GetComponent(templateEnt).EntityPrototype?.ID;
|
|
||||||
|
|
||||||
// TODO: Copy the templated entity as is with serv
|
|
||||||
var ent = _entManager.SpawnEntity(protoId,
|
|
||||||
new EntityCoordinates(gridUid, childPos));
|
|
||||||
|
|
||||||
var childXform = xformQuery.GetComponent(ent);
|
|
||||||
var anchored = templateXform.Anchored;
|
|
||||||
_transform.SetLocalRotation(ent, childRot, childXform);
|
|
||||||
|
|
||||||
// If the templated entity was anchored then anchor us too.
|
|
||||||
if (anchored && !childXform.Anchored)
|
|
||||||
_transform.AnchorEntity(ent, childXform, grid);
|
|
||||||
else if (!anchored && childXform.Anchored)
|
|
||||||
_transform.Unanchor(ent, childXform);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load decals
|
|
||||||
if (_entManager.TryGetComponent<DecalGridComponent>(templateMapUid, out var loadedDecals))
|
|
||||||
{
|
|
||||||
_entManager.EnsureComponent<DecalGridComponent>(gridUid);
|
|
||||||
|
|
||||||
foreach (var (_, decal) in _decals.GetDecalsIntersecting(templateMapUid, bounds, loadedDecals))
|
|
||||||
{
|
|
||||||
// Offset by 0.5 because decals are offset from bot-left corner
|
|
||||||
// So we convert it to center of tile then convert it back again after transform.
|
|
||||||
// Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles.
|
|
||||||
var position = dungeonMatty.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter);
|
|
||||||
position -= Vector2Helpers.Half;
|
|
||||||
|
|
||||||
// Umm uhh I love decals so uhhhh idk what to do about this
|
|
||||||
var angle = (decal.Angle + finalRoomRotation).Reduced();
|
|
||||||
|
|
||||||
// Adjust because 32x32 so we can't rotate cleanly
|
|
||||||
// Yeah idk about the uhh vectors here but it looked visually okay but they may still be off by 1.
|
|
||||||
// Also EyeManager.PixelsPerMeter should really be in shared.
|
|
||||||
if (angle.Equals(Math.PI))
|
|
||||||
{
|
|
||||||
position += new Vector2(-1f / 32f, 1f / 32f);
|
|
||||||
}
|
|
||||||
else if (angle.Equals(-Math.PI / 2f))
|
|
||||||
{
|
|
||||||
position += new Vector2(-1f / 32f, 0f);
|
|
||||||
}
|
|
||||||
else if (angle.Equals(Math.PI / 2f))
|
|
||||||
{
|
|
||||||
position += new Vector2(0f, 1f / 32f);
|
|
||||||
}
|
|
||||||
else if (angle.Equals(Math.PI * 1.5f))
|
|
||||||
{
|
|
||||||
// I hate this but decals are bottom-left rather than center position and doing the
|
|
||||||
// matrix ops is a PITA hence this workaround for now; I also don't want to add a stupid
|
|
||||||
// field for 1 specific op on decals
|
|
||||||
if (decal.Id != "DiagonalCheckerAOverlay" &&
|
|
||||||
decal.Id != "DiagonalCheckerBOverlay")
|
|
||||||
{
|
|
||||||
position += new Vector2(-1f / 32f, 0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tilePos = position.Floored();
|
|
||||||
|
|
||||||
// Fallback because uhhhhhhhh yeah, a corner tile might look valid on the original
|
|
||||||
// but place 1 nanometre off grid and fail the add.
|
|
||||||
if (!grid.TryGetTileRef(tilePos, out var tileRef) || tileRef.Tile.IsEmpty)
|
|
||||||
{
|
|
||||||
grid.SetTile(tilePos, fallbackTile);
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = _decals.TryAddDecal(
|
|
||||||
decal.Id,
|
|
||||||
new EntityCoordinates(gridUid, position),
|
|
||||||
out _,
|
|
||||||
decal.Color,
|
|
||||||
angle,
|
|
||||||
decal.ZIndex,
|
|
||||||
decal.Cleanable);
|
|
||||||
|
|
||||||
DebugTools.Assert(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await SuspendIfOutOfTime();
|
await SuspendIfOutOfTime();
|
||||||
ValidateResume();
|
ValidateResume();
|
||||||
|
|||||||
240
Content.Server/Procedural/DungeonSystem.Rooms.cs
Normal file
240
Content.Server/Procedural/DungeonSystem.Rooms.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Shared.Decals;
|
||||||
|
using Content.Shared.Procedural;
|
||||||
|
using Content.Shared.Random.Helpers;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Procedural;
|
||||||
|
|
||||||
|
public sealed partial class DungeonSystem
|
||||||
|
{
|
||||||
|
// Temporary caches.
|
||||||
|
private readonly HashSet<EntityUid> _entitySet = new();
|
||||||
|
private readonly List<DungeonRoomPrototype> _availableRooms = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a random dungeon room matching the specified area and whitelist.
|
||||||
|
/// </summary>
|
||||||
|
public DungeonRoomPrototype? GetRoomPrototype(Vector2i size, Random random, EntityWhitelist? whitelist = null)
|
||||||
|
{
|
||||||
|
// Can never be true.
|
||||||
|
if (whitelist is { Tags: null })
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_availableRooms.Clear();
|
||||||
|
|
||||||
|
foreach (var proto in _prototype.EnumeratePrototypes<DungeonRoomPrototype>())
|
||||||
|
{
|
||||||
|
if (proto.Size != size)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (whitelist == null)
|
||||||
|
{
|
||||||
|
_availableRooms.Add(proto);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tag in whitelist.Tags)
|
||||||
|
{
|
||||||
|
if (!proto.Tags.Contains(tag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_availableRooms.Add(proto);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_availableRooms.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var room = _availableRooms[random.Next(_availableRooms.Count)];
|
||||||
|
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SpawnRoom(
|
||||||
|
EntityUid gridUid,
|
||||||
|
MapGridComponent grid,
|
||||||
|
Vector2i origin,
|
||||||
|
DungeonRoomPrototype room,
|
||||||
|
Random random,
|
||||||
|
bool clearExisting = false,
|
||||||
|
bool rotation = false)
|
||||||
|
{
|
||||||
|
var originTransform = Matrix3.CreateTranslation(origin);
|
||||||
|
SpawnRoom(gridUid, grid, originTransform, room, random, clearExisting, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SpawnRoom(
|
||||||
|
EntityUid gridUid,
|
||||||
|
MapGridComponent grid,
|
||||||
|
Matrix3 transform,
|
||||||
|
DungeonRoomPrototype room,
|
||||||
|
Random random,
|
||||||
|
bool clearExisting = false,
|
||||||
|
bool rotation = false)
|
||||||
|
{
|
||||||
|
// Ensure the underlying template exists.
|
||||||
|
var roomMap = GetOrCreateTemplate(room);
|
||||||
|
var templateMapUid = _mapManager.GetMapEntityId(roomMap);
|
||||||
|
var templateGrid = Comp<MapGridComponent>(templateMapUid);
|
||||||
|
var roomRotation = Angle.Zero;
|
||||||
|
var roomDimensions = room.Size;
|
||||||
|
|
||||||
|
if (rotation)
|
||||||
|
{
|
||||||
|
if (roomDimensions.X == roomDimensions.Y)
|
||||||
|
{
|
||||||
|
// Give it a random rotation
|
||||||
|
roomRotation = random.Next(4) * Math.PI / 2;
|
||||||
|
}
|
||||||
|
else if (random.Next(2) == 1)
|
||||||
|
{
|
||||||
|
roomRotation += Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomTransform = Matrix3.CreateTransform((Vector2) room.Size / 2f, roomRotation);
|
||||||
|
Matrix3.Multiply(roomTransform, transform, out var finalTransform);
|
||||||
|
var finalRoomRotation = finalTransform.Rotation();
|
||||||
|
|
||||||
|
// go BRRNNTTT on existing stuff
|
||||||
|
if (clearExisting)
|
||||||
|
{
|
||||||
|
var gridBounds = new Box2(transform.Transform(Vector2.Zero), transform.Transform(room.Size));
|
||||||
|
_entitySet.Clear();
|
||||||
|
// Polygon skin moment
|
||||||
|
gridBounds = gridBounds.Enlarged(-0.05f);
|
||||||
|
_lookup.GetLocalEntitiesIntersecting(gridUid, gridBounds, _entitySet, LookupFlags.Uncontained);
|
||||||
|
|
||||||
|
foreach (var templateEnt in _entitySet)
|
||||||
|
{
|
||||||
|
Del(templateEnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryComp(gridUid, out DecalGridComponent? decalGrid))
|
||||||
|
{
|
||||||
|
foreach (var decal in _decals.GetDecalsIntersecting(gridUid, gridBounds, decalGrid))
|
||||||
|
{
|
||||||
|
_decals.RemoveDecal(gridUid, decal.Index, decalGrid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
|
||||||
|
var tileOffset = -roomCenter + grid.TileSizeHalfVector;
|
||||||
|
_tiles.Clear();
|
||||||
|
|
||||||
|
// Load tiles
|
||||||
|
for (var x = 0; x < roomDimensions.X; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < roomDimensions.Y; y++)
|
||||||
|
{
|
||||||
|
var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y);
|
||||||
|
var tileRef = _maps.GetTileRef(templateMapUid, templateGrid, indices);
|
||||||
|
|
||||||
|
var tilePos = finalTransform.Transform(indices + tileOffset);
|
||||||
|
var rounded = tilePos.Floored();
|
||||||
|
_tiles.Add((rounded, tileRef.Tile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds = new Box2(room.Offset, room.Offset + room.Size);
|
||||||
|
|
||||||
|
_maps.SetTiles(gridUid, grid, _tiles);
|
||||||
|
|
||||||
|
// Load entities
|
||||||
|
// TODO: I don't think engine supports full entity copying so we do this piece of shit.
|
||||||
|
|
||||||
|
foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained))
|
||||||
|
{
|
||||||
|
var templateXform = _xformQuery.GetComponent(templateEnt);
|
||||||
|
var childPos = finalTransform.Transform(templateXform.LocalPosition - roomCenter);
|
||||||
|
var childRot = templateXform.LocalRotation + finalRoomRotation;
|
||||||
|
var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID;
|
||||||
|
|
||||||
|
// TODO: Copy the templated entity as is with serv
|
||||||
|
var ent = Spawn(protoId, new EntityCoordinates(gridUid, childPos));
|
||||||
|
|
||||||
|
var childXform = _xformQuery.GetComponent(ent);
|
||||||
|
var anchored = templateXform.Anchored;
|
||||||
|
_transform.SetLocalRotation(ent, childRot, childXform);
|
||||||
|
|
||||||
|
// If the templated entity was anchored then anchor us too.
|
||||||
|
if (anchored && !childXform.Anchored)
|
||||||
|
_transform.AnchorEntity(ent, childXform, grid);
|
||||||
|
else if (!anchored && childXform.Anchored)
|
||||||
|
_transform.Unanchor(ent, childXform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load decals
|
||||||
|
if (TryComp<DecalGridComponent>(templateMapUid, out var loadedDecals))
|
||||||
|
{
|
||||||
|
EnsureComp<DecalGridComponent>(gridUid);
|
||||||
|
|
||||||
|
foreach (var (_, decal) in _decals.GetDecalsIntersecting(templateMapUid, bounds, loadedDecals))
|
||||||
|
{
|
||||||
|
// Offset by 0.5 because decals are offset from bot-left corner
|
||||||
|
// So we convert it to center of tile then convert it back again after transform.
|
||||||
|
// Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles.
|
||||||
|
var position = finalTransform.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter);
|
||||||
|
position -= Vector2Helpers.Half;
|
||||||
|
|
||||||
|
// Umm uhh I love decals so uhhhh idk what to do about this
|
||||||
|
var angle = (decal.Angle + finalRoomRotation).Reduced();
|
||||||
|
|
||||||
|
// Adjust because 32x32 so we can't rotate cleanly
|
||||||
|
// Yeah idk about the uhh vectors here but it looked visually okay but they may still be off by 1.
|
||||||
|
// Also EyeManager.PixelsPerMeter should really be in shared.
|
||||||
|
if (angle.Equals(Math.PI))
|
||||||
|
{
|
||||||
|
position += new Vector2(-1f / 32f, 1f / 32f);
|
||||||
|
}
|
||||||
|
else if (angle.Equals(-Math.PI / 2f))
|
||||||
|
{
|
||||||
|
position += new Vector2(-1f / 32f, 0f);
|
||||||
|
}
|
||||||
|
else if (angle.Equals(Math.PI / 2f))
|
||||||
|
{
|
||||||
|
position += new Vector2(0f, 1f / 32f);
|
||||||
|
}
|
||||||
|
else if (angle.Equals(Math.PI * 1.5f))
|
||||||
|
{
|
||||||
|
// I hate this but decals are bottom-left rather than center position and doing the
|
||||||
|
// matrix ops is a PITA hence this workaround for now; I also don't want to add a stupid
|
||||||
|
// field for 1 specific op on decals
|
||||||
|
if (decal.Id != "DiagonalCheckerAOverlay" &&
|
||||||
|
decal.Id != "DiagonalCheckerBOverlay")
|
||||||
|
{
|
||||||
|
position += new Vector2(-1f / 32f, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tilePos = position.Floored();
|
||||||
|
|
||||||
|
// Fallback because uhhhhhhhh yeah, a corner tile might look valid on the original
|
||||||
|
// but place 1 nanometre off grid and fail the add.
|
||||||
|
if (!_maps.TryGetTileRef(gridUid, grid, tilePos, out var tileRef) || tileRef.Tile.IsEmpty)
|
||||||
|
{
|
||||||
|
_maps.SetTile(gridUid, grid, tilePos, _tileDefManager.GetVariantTile(FallbackTileId, _random));
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = _decals.TryAddDecal(
|
||||||
|
decal.Id,
|
||||||
|
new EntityCoordinates(gridUid, position),
|
||||||
|
out _,
|
||||||
|
decal.Color,
|
||||||
|
angle,
|
||||||
|
decal.ZIndex,
|
||||||
|
decal.Cleanable);
|
||||||
|
|
||||||
|
DebugTools.Assert(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ using Content.Server.GameTicking.Events;
|
|||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Construction.EntitySystems;
|
using Content.Shared.Construction.EntitySystems;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.Maps;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
using Content.Shared.Procedural;
|
using Content.Shared.Procedural;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -15,6 +16,7 @@ using Robust.Shared.Console;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.Procedural;
|
namespace Content.Server.Procedural;
|
||||||
|
|
||||||
@@ -24,14 +26,20 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
|||||||
[Dependency] private readonly IConsoleHost _console = default!;
|
[Dependency] private readonly IConsoleHost _console = default!;
|
||||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||||
[Dependency] private readonly AnchorableSystem _anchorable = default!;
|
[Dependency] private readonly AnchorableSystem _anchorable = default!;
|
||||||
[Dependency] private readonly DecalSystem _decals = default!;
|
[Dependency] private readonly DecalSystem _decals = default!;
|
||||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||||
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
||||||
|
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private HashSet<EntityUid> _entSet = new();
|
||||||
|
private readonly List<(Vector2i, Tile)> _tiles = new();
|
||||||
|
|
||||||
|
private EntityQuery<MetaDataComponent> _metaQuery;
|
||||||
|
private EntityQuery<TransformComponent> _xformQuery;
|
||||||
|
|
||||||
private const double DungeonJobTime = 0.005;
|
private const double DungeonJobTime = 0.005;
|
||||||
|
|
||||||
@@ -41,10 +49,15 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
|||||||
private readonly JobQueue _dungeonJobQueue = new(DungeonJobTime);
|
private readonly JobQueue _dungeonJobQueue = new(DungeonJobTime);
|
||||||
private readonly Dictionary<DungeonJob, CancellationTokenSource> _dungeonJobs = new();
|
private readonly Dictionary<DungeonJob, CancellationTokenSource> _dungeonJobs = new();
|
||||||
|
|
||||||
|
[ValidatePrototypeId<ContentTileDefinition>]
|
||||||
|
public const string FallbackTileId = "FloorSteel";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
_sawmill = Logger.GetSawmill("dungen");
|
|
||||||
|
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||||
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||||
_console.RegisterCommand("dungen", Loc.GetString("cmd-dungen-desc"), Loc.GetString("cmd-dungen-help"), GenerateDungeon, CompletionCallback);
|
_console.RegisterCommand("dungen", Loc.GetString("cmd-dungen-desc"), Loc.GetString("cmd-dungen-help"), GenerateDungeon, CompletionCallback);
|
||||||
_console.RegisterCommand("dungen_preset_vis", Loc.GetString("cmd-dungen_preset_vis-desc"), Loc.GetString("cmd-dungen_preset_vis-help"), DungeonPresetVis, PresetCallback);
|
_console.RegisterCommand("dungen_preset_vis", Loc.GetString("cmd-dungen_preset_vis-desc"), Loc.GetString("cmd-dungen_preset_vis-help"), DungeonPresetVis, PresetCallback);
|
||||||
_console.RegisterCommand("dungen_pack_vis", Loc.GetString("cmd-dungen_pack_vis-desc"), Loc.GetString("cmd-dungen_pack_vis-help"), DungeonPackVis, PackCallback);
|
_console.RegisterCommand("dungen_pack_vis", Loc.GetString("cmd-dungen_pack_vis-desc"), Loc.GetString("cmd-dungen_pack_vis-help"), DungeonPackVis, PackCallback);
|
||||||
@@ -176,7 +189,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
|||||||
{
|
{
|
||||||
var cancelToken = new CancellationTokenSource();
|
var cancelToken = new CancellationTokenSource();
|
||||||
var job = new DungeonJob(
|
var job = new DungeonJob(
|
||||||
_sawmill,
|
Log,
|
||||||
DungeonJobTime,
|
DungeonJobTime,
|
||||||
EntityManager,
|
EntityManager,
|
||||||
_mapManager,
|
_mapManager,
|
||||||
@@ -207,7 +220,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
|
|||||||
{
|
{
|
||||||
var cancelToken = new CancellationTokenSource();
|
var cancelToken = new CancellationTokenSource();
|
||||||
var job = new DungeonJob(
|
var job = new DungeonJob(
|
||||||
_sawmill,
|
Log,
|
||||||
DungeonJobTime,
|
DungeonJobTime,
|
||||||
EntityManager,
|
EntityManager,
|
||||||
_mapManager,
|
_mapManager,
|
||||||
|
|||||||
37
Content.Server/Procedural/RoomFillComponent.cs
Normal file
37
Content.Server/Procedural/RoomFillComponent.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Content.Shared.Procedural;
|
||||||
|
using Content.Shared.Whitelist;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server.Procedural;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marker that indicates the specified room prototype should occupy this space.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class RoomFillComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Are we allowed to rotate room templates?
|
||||||
|
/// If the room is not a square this will only do 180 degree rotations.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool Rotation = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of the room to fill.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public Vector2i Size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rooms allowed for the marker.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityWhitelist? RoomWhitelist;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should any existing entities / decals be bulldozed first.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool ClearExisting;
|
||||||
|
}
|
||||||
50
Content.Server/Procedural/RoomFillSystem.cs
Normal file
50
Content.Server/Procedural/RoomFillSystem.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Procedural;
|
||||||
|
|
||||||
|
public sealed class RoomFillSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly DungeonSystem _dungeon = default!;
|
||||||
|
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<RoomFillComponent, MapInitEvent>(OnRoomFillMapInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRoomFillMapInit(EntityUid uid, RoomFillComponent component, MapInitEvent args)
|
||||||
|
{
|
||||||
|
// Just test things.
|
||||||
|
if (component.Size == Vector2i.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var xform = Transform(uid);
|
||||||
|
|
||||||
|
if (xform.GridUid != null)
|
||||||
|
{
|
||||||
|
var random = new Random();
|
||||||
|
var room = _dungeon.GetRoomPrototype(component.Size, random, component.RoomWhitelist);
|
||||||
|
|
||||||
|
if (room != null)
|
||||||
|
{
|
||||||
|
var mapGrid = Comp<MapGridComponent>(xform.GridUid.Value);
|
||||||
|
_dungeon.SpawnRoom(
|
||||||
|
xform.GridUid.Value,
|
||||||
|
mapGrid,
|
||||||
|
_maps.LocalToTile(xform.GridUid.Value, mapGrid, xform.Coordinates),
|
||||||
|
room,
|
||||||
|
random,
|
||||||
|
clearExisting: component.ClearExisting,
|
||||||
|
rotation: component.Rotation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error($"Unable to find matching room prototype for {ToPrettyString(uid)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final cleanup
|
||||||
|
QueueDel(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Resources/Prototypes/Entities/Markers/rooms.yml
Normal file
13
Resources/Prototypes/Entities/Markers/rooms.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
- type: entity
|
||||||
|
id: BaseRoomMarker
|
||||||
|
name: Room marker
|
||||||
|
parent: MarkerBase
|
||||||
|
suffix: Weh
|
||||||
|
components:
|
||||||
|
- type: RoomFill
|
||||||
|
size: 5,5
|
||||||
|
- type: Sprite
|
||||||
|
layers:
|
||||||
|
- state: red
|
||||||
|
- sprite: Mobs/Aliens/elemental.rsi
|
||||||
|
state: alive
|
||||||
Reference in New Issue
Block a user