CatStation/Content.Server/Procedural/DungeonSystem.cs
Princess Cheeseballs 2c6ffecf62
Move GameTicker to Shared (#45834)
* Scope creep my ass

* why was this shit not pushed????

* gone...

* fix that

* review plus buildd

* asfsafsafsafsafas

* grug

* burber

* I'll fix this later

* shrimply die

* also this

* it's snerf or snuthin

* assaads

* round snend

* aaaa

* fix this shit for real

* actually, don't start GameRules until MapInit now that I think aboot it.

* forgot u

* don't actually need this part nullable

* Deffer comp removal

* one liner

* do that too

* chungus

* Honestly makes more sense here, plus WHY are we doing all that shit on "GameRuleAdded" rather than GameRuleStarted(?)

* deferred removal :V

* aaaaaaaaaaaaaaa

* whhop

* Whoop

* better

* landmine that I placed for myself then forgot about lmao

* cleanup

* FUCKING `!`

* gotovo

* amogus

* please don't check by metadata I beg

* waawsasfdfsasfa

* sfafsasfafsas

* fdsaasffsasfa

* code

* fix

* fasasffasfasfsad

* chungaas

* I may be stupid

* gra

* graaaaaaaaaaaaaaaaa

* comment

* gra

* clearing is not predicted and would require major refactors, so don't cache on client.

* lame shit :P

* last holes

* nukeopsfsafsasafsfafsa

* why  was that shit public bruh

* bubrbgwahrghfaka

* dynamic

* revert change that did nothing lmao
2026-09-16 19:34:19 +00:00

272 lines
8.3 KiB
C#

using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.CPUJob.JobQueues.Queues;
using Content.Server.Decals;
using Content.Server.GameTicking.Events;
using Content.Shared.CCVar;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.GameTicking;
using Content.Shared.GameTicking.Events;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Procedural;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Procedural;
public sealed partial class DungeonSystem : SharedDungeonSystem
{
[Dependency] private IConfigurationManager _configManager = default!;
[Dependency] private IConsoleHost _console = default!;
[Dependency] private IRobustRandom _random = default!;
[Dependency] private ITileDefinitionManager _tileDefManager = default!;
[Dependency] private AnchorableSystem _anchorable = default!;
[Dependency] private DecalSystem _decals = default!;
[Dependency] private EntityLookupSystem _lookup = default!;
[Dependency] private TileSystem _tile = default!;
[Dependency] private TurfSystem _turf = default!;
[Dependency] private MapLoaderSystem _loader = default!;
[Dependency] private SharedMapSystem _maps = default!;
[Dependency] private SharedTransformSystem _transform = default!;
private readonly List<(Vector2i, Tile)> _tiles = new();
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
private const double DungeonJobTime = 0.005;
public const int CollisionMask = (int) CollisionGroup.Impassable;
public const int CollisionLayer = (int) CollisionGroup.Impassable;
private readonly JobQueue _dungeonJobQueue = new(DungeonJobTime);
private readonly Dictionary<DungeonJob.DungeonJob, CancellationTokenSource> _dungeonJobs = new();
public static readonly ProtoId<ContentTileDefinition> FallbackTileId = "FloorSteel";
public override void Initialize()
{
base.Initialize();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_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_pack_vis", Loc.GetString("cmd-dungen_pack_vis-desc"), Loc.GetString("cmd-dungen_pack_vis-help"), DungeonPackVis, PackCallback);
SubscribeLocalEvent<PrototypesReloadedEventArgs>(PrototypeReload);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundCleanup);
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
_dungeonJobQueue.Process();
}
private void OnRoundCleanup(RoundRestartCleanupEvent ev)
{
foreach (var token in _dungeonJobs.Values)
{
token.Cancel();
}
_dungeonJobs.Clear();
}
private void OnRoundStart(RoundStartingEvent ev)
{
var query = AllEntityQuery<DungeonAtlasTemplateComponent>();
while (query.MoveNext(out var uid, out _))
{
QueueDel(uid);
}
if (!_configManager.GetCVar(CCVars.ProcgenPreload))
return;
// Force all templates to be setup.
foreach (var room in ProtoMan.EnumeratePrototypes<DungeonRoomPrototype>())
{
GetOrCreateTemplate(room);
}
}
public override void Shutdown()
{
base.Shutdown();
foreach (var token in _dungeonJobs.Values)
{
token.Cancel();
}
_dungeonJobs.Clear();
}
private void PrototypeReload(PrototypesReloadedEventArgs obj)
{
if (!obj.ByType.TryGetValue(typeof(DungeonRoomPrototype), out var rooms))
{
return;
}
foreach (var proto in rooms.Modified.Values)
{
var roomProto = (DungeonRoomPrototype) proto;
var query = AllEntityQuery<DungeonAtlasTemplateComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (!roomProto.AtlasPath.Equals(comp.Path))
continue;
QueueDel(uid);
break;
}
}
if (!_configManager.GetCVar(CCVars.ProcgenPreload))
return;
foreach (var proto in rooms.Modified.Values)
{
var roomProto = (DungeonRoomPrototype) proto;
var query = AllEntityQuery<DungeonAtlasTemplateComponent>();
var found = false;
while (query.MoveNext(out var comp))
{
if (!roomProto.AtlasPath.Equals(comp.Path))
continue;
found = true;
break;
}
if (!found)
{
GetOrCreateTemplate(roomProto);
}
}
}
public MapId GetOrCreateTemplate(DungeonRoomPrototype proto)
{
var query = AllEntityQuery<DungeonAtlasTemplateComponent>();
DungeonAtlasTemplateComponent? comp;
while (query.MoveNext(out var uid, out comp))
{
// Exists
if (comp.Path.Equals(proto.AtlasPath))
return Transform(uid).MapID;
}
var opts = new MapLoadOptions
{
DeserializationOptions = DeserializationOptions.Default with {PauseMaps = true},
ExpectedCategory = FileCategory.Map
};
if (!_loader.TryLoadGeneric(proto.AtlasPath, out var res, opts) || !res.Maps.TryFirstOrNull(out var map))
throw new Exception($"Failed to load dungeon template.");
comp = AddComp<DungeonAtlasTemplateComponent>(map.Value.Owner);
comp.Path = proto.AtlasPath;
return map.Value.Comp.MapId;
}
/// <summary>
/// Generates a dungeon in the background with the specified config.
/// </summary>
/// <param name="coordinates">Coordinates to move the dungeon to afterwards. Will delete the original map</param>
public void GenerateDungeon(DungeonConfig gen,
EntityUid gridUid,
MapGridComponent grid,
Vector2i position,
int seed,
EntityCoordinates? coordinates = null)
{
var cancelToken = new CancellationTokenSource();
var job = new DungeonJob.DungeonJob(
Log,
DungeonJobTime,
EntityManager,
ProtoMan,
_tileDefManager,
_anchorable,
_decals,
this,
_lookup,
_tile,
_turf,
_transform,
gen,
grid,
gridUid,
seed,
position,
coordinates,
cancelToken.Token);
_dungeonJobs.Add(job, cancelToken);
_dungeonJobQueue.EnqueueJob(job);
}
public async Task<List<Dungeon>> GenerateDungeonAsync(
DungeonConfig gen,
EntityUid gridUid,
MapGridComponent grid,
Vector2i position,
int seed)
{
var cancelToken = new CancellationTokenSource();
var job = new DungeonJob.DungeonJob(
Log,
DungeonJobTime,
EntityManager,
ProtoMan,
_tileDefManager,
_anchorable,
_decals,
this,
_lookup,
_tile,
_turf,
_transform,
gen,
grid,
gridUid,
seed,
position,
null,
cancelToken.Token);
_dungeonJobs.Add(job, cancelToken);
_dungeonJobQueue.EnqueueJob(job);
await job.AsTask;
if (job.Exception != null)
{
throw job.Exception;
}
return job.Result!;
}
public Angle GetDungeonRotation(int seed)
{
// Mask 0 | 1 for rotation seed
var dungeonRotationSeed = 3 & seed;
return Math.PI / 2 * dungeonRotationSeed;
}
}