diff --git a/Content.Server/Atmos/Commands/PauseAtmosCommand.cs b/Content.Server/Atmos/Commands/PauseAtmosCommand.cs new file mode 100644 index 0000000000..984f2a0869 --- /dev/null +++ b/Content.Server/Atmos/Commands/PauseAtmosCommand.cs @@ -0,0 +1,69 @@ +using Content.Server.Administration; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.Atmos.Commands; + +[AdminCommand(AdminFlags.Debug)] +public sealed class PauseAtmosCommand : LocalizedEntityCommands +{ + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + + public override string Command => "pauseatmos"; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + var grid = default(EntityUid); + + switch (args.Length) + { + case 0: + if (!EntityManager.TryGetComponent(shell.Player?.AttachedEntity, + out var playerxform) || + playerxform.GridUid == null) + { + shell.WriteError(Loc.GetString("cmd-error-no-grid-provided-or-invalid-grid")); + return; + } + + grid = playerxform.GridUid.Value; + break; + case 1: + if (!EntityUid.TryParse(args[0], out var parsedGrid) || !EntityManager.EntityExists(parsedGrid)) + { + shell.WriteError(Loc.GetString("cmd-error-couldnt-parse-entity")); + return; + } + + grid = parsedGrid; + break; + } + + if (!EntityManager.TryGetComponent(grid, out var gridAtmos)) + { + shell.WriteError(Loc.GetString("cmd-error-no-gridatmosphere")); + return; + } + + var newEnt = new Entity(grid, gridAtmos); + + _atmosphereSystem.SetAtmosphereSimulation(newEnt, !newEnt.Comp.Simulated); + shell.WriteLine(Loc.GetString("cmd-pauseatmos-set-atmos-simulation", + ("grid", EntityManager.ToPrettyString(grid)), + ("state", newEnt.Comp.Simulated))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + CompletionHelper.Components(args[0], EntityManager), + Loc.GetString("cmd-pauseatmos-completion-grid-pause")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Atmos/Commands/SubstepAtmosCommand.cs b/Content.Server/Atmos/Commands/SubstepAtmosCommand.cs new file mode 100644 index 0000000000..554abff4a8 --- /dev/null +++ b/Content.Server/Atmos/Commands/SubstepAtmosCommand.cs @@ -0,0 +1,104 @@ +using Content.Server.Administration; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Shared.Administration; +using Content.Shared.Atmos.Components; +using Robust.Shared.Console; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace Content.Server.Atmos.Commands; + +[AdminCommand(AdminFlags.Debug)] +public sealed class SubstepAtmosCommand : LocalizedEntityCommands +{ + [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; + + public override string Command => "substepatmos"; + + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + var grid = default(EntityUid); + + switch (args.Length) + { + case 0: + if (!EntityManager.TryGetComponent(shell.Player?.AttachedEntity, + out var playerxform) || + playerxform.GridUid == null) + { + shell.WriteError(Loc.GetString("cmd-error-no-grid-provided-or-invalid-grid")); + return; + } + + grid = playerxform.GridUid.Value; + break; + case 1: + if (!EntityUid.TryParse(args[0], out var parsedGrid) || !EntityManager.EntityExists(parsedGrid)) + { + shell.WriteError(Loc.GetString("cmd-error-couldnt-parse-entity")); + return; + } + + grid = parsedGrid; + break; + } + + // i'm straight piratesoftwaremaxxing + if (!EntityManager.TryGetComponent(grid, out var gridAtmos)) + { + shell.WriteError(Loc.GetString("cmd-error-no-gridatmosphere")); + return; + } + + if (!EntityManager.TryGetComponent(grid, out var gasTile)) + { + shell.WriteError(Loc.GetString("cmd-error-no-gastileoverlay")); + return; + } + + if (!EntityManager.TryGetComponent(grid, out var mapGrid)) + { + shell.WriteError(Loc.GetString("cmd-error-no-mapgrid")); + return; + } + + var xform = EntityManager.GetComponent(grid); + + if (xform.MapUid == null || xform.MapID == MapId.Nullspace) + { + shell.WriteError(Loc.GetString("cmd-error-no-valid-map")); + return; + } + + var newEnt = + new Entity(grid, + gridAtmos, + gasTile, + mapGrid, + xform); + + if (gridAtmos.Simulated) + { + shell.WriteLine(Loc.GetString("cmd-substepatmos-info-implicitly-paused-simulation", + ("grid", EntityManager.ToPrettyString(grid)))); + } + + _atmosphereSystem.SetAtmosphereSimulation(newEnt, false); + _atmosphereSystem.RunProcessingFull(newEnt, xform.MapUid.Value, _atmosphereSystem.AtmosTickRate); + + shell.WriteLine(Loc.GetString("cmd-substepatmos-info-substepped-grid", ("grid", EntityManager.ToPrettyString(grid)))); + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions( + CompletionHelper.Components(args[0], EntityManager), + Loc.GetString("cmd-substepatmos-completion-grid-substep")); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BenchmarkHelpers.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BenchmarkHelpers.cs index f86ebcee73..62cbbae68a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BenchmarkHelpers.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.BenchmarkHelpers.cs @@ -46,4 +46,27 @@ public sealed partial class AtmosphereSystem return processingPaused; } + + /// + /// Fully runs one entity through the entire Atmos processing loop. + /// + /// The entity to simulate. + /// The that belongs to the grid's map. + /// Elapsed time to simulate. Recommended value is . + public void RunProcessingFull(Entity ent, + Entity mapAtmosphere, + float frameTime) + { + while (ProcessAtmosphere(ent, mapAtmosphere, frameTime) != AtmosphereProcessingCompletionState.Finished) { } + } + + /// + /// Allows or disallows atmosphere simulation on a . + /// + /// The atmosphere to pause or unpause processing. + /// The state to set. True means that the atmosphere is allowed to simulate, false otherwise. + public void SetAtmosphereSimulation(Entity ent, bool simulate) + { + ent.Comp.Simulated = simulate; + } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 9b8654af6d..72e4b5f151 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -649,143 +649,196 @@ namespace Content.Server.Atmos.EntitySystems if (atmosphere.LifeStage >= ComponentLifeStage.Stopping || Paused(owner) || !atmosphere.Simulated) continue; - atmosphere.Timer += frameTime; - - if (atmosphere.Timer < AtmosTime) - continue; - - // We subtract it so it takes lost time into account. - atmosphere.Timer -= AtmosTime; - var map = new Entity(xform.MapUid.Value, _mapAtmosQuery.CompOrNull(xform.MapUid.Value)); - switch (atmosphere.State) + var completionState = ProcessAtmosphere(ent, map, frameTime); + + switch (completionState) { - case AtmosphereProcessingState.Revalidate: - if (!ProcessRevalidate(ent)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - - // Next state depends on whether monstermos equalization is enabled or not. - // Note: We do this here instead of on the tile equalization step to prevent ending it early. - // Therefore, a change to this CVar might only be applied after that step is over. - atmosphere.State = MonstermosEqualization - ? AtmosphereProcessingState.TileEqualize - : AtmosphereProcessingState.ActiveTiles; + case AtmosphereProcessingCompletionState.Return: + return; + case AtmosphereProcessingCompletionState.Continue: continue; - case AtmosphereProcessingState.TileEqualize: - if (!ProcessTileEqualize(ent)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.ActiveTiles; - continue; - case AtmosphereProcessingState.ActiveTiles: - if (!ProcessActiveTiles(ent)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - // Next state depends on whether excited groups are enabled or not. - atmosphere.State = ExcitedGroups ? AtmosphereProcessingState.ExcitedGroups : AtmosphereProcessingState.HighPressureDelta; - continue; - case AtmosphereProcessingState.ExcitedGroups: - if (!ProcessExcitedGroups(ent)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.HighPressureDelta; - continue; - case AtmosphereProcessingState.HighPressureDelta: - if (!ProcessHighPressureDelta((ent, ent))) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = DeltaPressureDamage - ? AtmosphereProcessingState.DeltaPressure - : AtmosphereProcessingState.Hotspots; - continue; - case AtmosphereProcessingState.DeltaPressure: - if (!ProcessDeltaPressure(ent)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.Hotspots; - continue; - case AtmosphereProcessingState.Hotspots: - if (!ProcessHotspots(ent)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - // Next state depends on whether superconduction is enabled or not. - // Note: We do this here instead of on the tile equalization step to prevent ending it early. - // Therefore, a change to this CVar might only be applied after that step is over. - atmosphere.State = Superconduction - ? AtmosphereProcessingState.Superconductivity - : AtmosphereProcessingState.PipeNet; - continue; - case AtmosphereProcessingState.Superconductivity: - if (!ProcessSuperconductivity(atmosphere)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.PipeNet; - continue; - case AtmosphereProcessingState.PipeNet: - if (!ProcessPipeNets(atmosphere)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.AtmosDevices; - continue; - case AtmosphereProcessingState.AtmosDevices: - if (!ProcessAtmosDevices(ent, map)) - { - atmosphere.ProcessingPaused = true; - return; - } - - atmosphere.ProcessingPaused = false; - atmosphere.State = AtmosphereProcessingState.Revalidate; - - // We reached the end of this atmosphere's update tick. Break out of the switch. + case AtmosphereProcessingCompletionState.Finished: break; } - - // And increase the update counter. - atmosphere.UpdateCounter++; } // We finished processing all atmospheres successfully, therefore we won't be paused next tick. _simulationPaused = false; } + + /// + /// Processes a through its processing stages. + /// + /// The entity to process. + /// The belonging to the + /// 's map. + /// The elapsed time since the last frame. + /// An that represents the completion state. + private AtmosphereProcessingCompletionState ProcessAtmosphere(Entity ent, + Entity mapAtmosphere, + float frameTime) + { + // They call me the deconstructor the way i be deconstructing it + // and by it, i mean... my entity + var (owner, atmosphere, visuals, grid, xform) = ent; + + atmosphere.Timer += frameTime; + + if (atmosphere.Timer < AtmosTime) + return AtmosphereProcessingCompletionState.Continue; + + // We subtract it so it takes lost time into account. + atmosphere.Timer -= AtmosTime; + + switch (atmosphere.State) + { + case AtmosphereProcessingState.Revalidate: + if (!ProcessRevalidate(ent)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + + // Next state depends on whether monstermos equalization is enabled or not. + // Note: We do this here instead of on the tile equalization step to prevent ending it early. + // Therefore, a change to this CVar might only be applied after that step is over. + atmosphere.State = MonstermosEqualization + ? AtmosphereProcessingState.TileEqualize + : AtmosphereProcessingState.ActiveTiles; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.TileEqualize: + if (!ProcessTileEqualize(ent)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = AtmosphereProcessingState.ActiveTiles; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.ActiveTiles: + if (!ProcessActiveTiles(ent)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + // Next state depends on whether excited groups are enabled or not. + atmosphere.State = ExcitedGroups ? AtmosphereProcessingState.ExcitedGroups : AtmosphereProcessingState.HighPressureDelta; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.ExcitedGroups: + if (!ProcessExcitedGroups(ent)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = AtmosphereProcessingState.HighPressureDelta; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.HighPressureDelta: + if (!ProcessHighPressureDelta((ent, ent))) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = DeltaPressureDamage + ? AtmosphereProcessingState.DeltaPressure + : AtmosphereProcessingState.Hotspots; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.DeltaPressure: + if (!ProcessDeltaPressure(ent)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = AtmosphereProcessingState.Hotspots; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.Hotspots: + if (!ProcessHotspots(ent)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + // Next state depends on whether superconduction is enabled or not. + // Note: We do this here instead of on the tile equalization step to prevent ending it early. + // Therefore, a change to this CVar might only be applied after that step is over. + atmosphere.State = Superconduction + ? AtmosphereProcessingState.Superconductivity + : AtmosphereProcessingState.PipeNet; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.Superconductivity: + if (!ProcessSuperconductivity(atmosphere)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = AtmosphereProcessingState.PipeNet; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.PipeNet: + if (!ProcessPipeNets(atmosphere)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = AtmosphereProcessingState.AtmosDevices; + return AtmosphereProcessingCompletionState.Continue; + case AtmosphereProcessingState.AtmosDevices: + if (!ProcessAtmosDevices(ent, mapAtmosphere)) + { + atmosphere.ProcessingPaused = true; + return AtmosphereProcessingCompletionState.Return; + } + + atmosphere.ProcessingPaused = false; + atmosphere.State = AtmosphereProcessingState.Revalidate; + + // We reached the end of this atmosphere's update tick. Break out of the switch. + break; + } + + atmosphere.UpdateCounter++; + + return AtmosphereProcessingCompletionState.Finished; + } + } + + /// + /// An enum representing the completion state of a 's processing steps. + /// The processing of a spans over multiple stages and sticks, + /// with the method handling the processing having multiple return types. + /// + public enum AtmosphereProcessingCompletionState : byte + { + /// + /// Method is returning, ex. due to delegating processing to the next tick. + /// + Return, + + /// + /// Method is continuing, ex. due to finishing a single processing stage. + /// + Continue, + + /// + /// Method is finished with the GridAtmosphere. + /// + Finished, } public enum AtmosphereProcessingState : byte diff --git a/Resources/Locale/en-US/commands/pauseatmos-command.ftl b/Resources/Locale/en-US/commands/pauseatmos-command.ftl new file mode 100644 index 0000000000..1eb2e13f57 --- /dev/null +++ b/Resources/Locale/en-US/commands/pauseatmos-command.ftl @@ -0,0 +1,6 @@ +cmd-pauseatmos-desc = Pauses or unpauses the atmosphere simulation for the provided grid entity. +cmd-pauseatmos-help = Usage: {$command} + +cmd-pauseatmos-set-atmos-simulation = Set atmospherics simulation on {$grid} to state {$state}. + +cmd-pauseatmos-completion-grid-pause = EntityUid of the grid you want to pause/unpause. Automatically uses the grid you're standing on if empty. diff --git a/Resources/Locale/en-US/commands/substepatmos-command.ftl b/Resources/Locale/en-US/commands/substepatmos-command.ftl new file mode 100644 index 0000000000..bb8908572f --- /dev/null +++ b/Resources/Locale/en-US/commands/substepatmos-command.ftl @@ -0,0 +1,15 @@ +cmd-substepatmos-desc = Substeps the atmosphere simulation by a single atmostick for the provided grid entity. Implicitly pauses atmospherics simulation. +cmd-substepatmos-help = Usage: {$command} + +cmd-error-no-grid-provided-or-invalid-grid = You must either provide a grid entity or be standing on a grid to substep. +cmd-error-couldnt-parse-entity = Entity provided could not be parsed or does not exist. Try standing on a grid you want to substep. +cmd-error-no-gridatmosphere = Entity provided doesn't have a GridAtmosphereComponent. +cmd-error-no-gastileoverlay = Entity provided doesn't have a GasTileOverlayComponent. +cmd-error-no-mapgrid = Entity provided doesn't have a MapGridComponent. +cmd-error-no-xform = Entity provided doesn't have a TransformComponent? +cmd-error-no-valid-map = The grid provided is not on a valid map? + +cmd-substepatmos-info-implicitly-paused-simulation = Implicitly paused atmospherics simulation on {$grid}. +cmd-substepatmos-info-substepped-grid = Substepped atmospherics simulation by one atmostick on {$grid}. + +cmd-substepatmos-completion-grid-substep = EntityUid of the grid you want to substep. Automatically uses the grid you're standing on if empty.