From 5794ecd28ffe91fa1e3971784ba6f43f6bb8bf1b Mon Sep 17 00:00:00 2001 From: SlamBamActionman Date: Mon, 16 Sep 2024 11:05:57 +0200 Subject: [PATCH 001/365] Initial commit --- Resources/Prototypes/Reagents/medicine.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 4dcef67f77d..962e2d09f18 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -125,6 +125,8 @@ Radiation: -3 groups: Brute: 0.5 + - !type:ChemVomit + probability: 0.1 - type: reagent id: Bicaridine @@ -556,14 +558,20 @@ min: 4 metabolisms: Medicine: + metabolismRate: 0.1 effects: - !type:HealthChange damage: types: - Cellular: -1 - Radiation: 1 - - !type:ChemVomit - probability: 0.05 + Cellular: -0.3 + Radiation: 0.3 + - !type:HealthChange + conditions: + - !type:ReagentThreshold + min: 10 + damage: + types: + Radiation: 0.2 - type: reagent id: PolypyryliumOligomers From fe69de942f7aa13ed70bf1a774dd4ab5768eec7d Mon Sep 17 00:00:00 2001 From: SlamBamActionman Date: Fri, 1 Nov 2024 12:52:12 +0100 Subject: [PATCH 002/365] Updated values --- Resources/Prototypes/Reagents/medicine.yml | 34 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 962e2d09f18..c05d31b2f4d 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -124,9 +124,7 @@ types: Radiation: -3 groups: - Brute: 0.5 - - !type:ChemVomit - probability: 0.1 + Brute: 1.5 - type: reagent id: Bicaridine @@ -564,14 +562,23 @@ damage: types: Cellular: -0.3 - Radiation: 0.3 + Radiation: 0.15 + Caustic: 0.15 - !type:HealthChange conditions: - - !type:ReagentThreshold - min: 10 + - !type:ReagentThreshold + min: 11 damage: types: Radiation: 0.2 + - !type:HealthChange + conditions: + - !type:ReagentThreshold + reagent: Arithrazine + min: 1 + damage: + types: + Caustic: 0.3 - type: reagent id: PolypyryliumOligomers @@ -977,6 +984,21 @@ - !type:ReagentThreshold min: 30 probability: 0.02 + - !type:ChemVomit + conditions: + - !type:ReagentThreshold + reagent: Arithrazine + min: 1 + probability: 0.1 + - !type:PopupMessage + conditions: + - !type:ReagentThreshold + reagent: Arithrazine + min: 1 + type: Local + visualType: Medium + messages: [ "generic-reagent-effect-nauseous" ] + probability: 0.2 - type: reagent id: Lacerinol From 6242567aff9cf3c1281ac7cf785df30d135800a2 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sun, 22 Dec 2024 15:13:10 +1300 Subject: [PATCH 003/365] Refactor map loading & saving --- .../Tests/Body/LungTest.cs | 24 +- .../Tests/Body/SaveLoadReparentTest.cs | 11 +- .../Minds/MindTest.DeleteAllThenGhost.cs | 7 +- .../Tests/PostMapInitTest.cs | 80 +++--- Content.IntegrationTests/Tests/SalvageTest.cs | 15 +- .../Tests/SaveLoadMapTest.cs | 15 +- .../Tests/SaveLoadSaveTest.cs | 110 +++++---- .../Tests/Shuttle/DockTest.cs | 7 +- .../Commands/LoadGameMapCommand.cs | 40 ++- .../Commands/PersistenceSaveCommand.cs | 1 + .../Systems/AdminTestArenaSystem.cs | 40 ++- .../GameTicking/GameTicker.RoundFlow.cs | 230 ++++++++++++++---- Content.Server/GameTicking/GameTicker.cs | 4 +- .../Rules/Components/LoadMapRuleComponent.cs | 8 +- .../GameTicking/Rules/LoadMapRuleSystem.cs | 50 +++- .../GridPreloader/GridPreloaderSystem.cs | 20 +- Content.Server/Mapping/MappingCommand.cs | 10 +- Content.Server/Mapping/MappingManager.cs | 10 +- Content.Server/Mapping/MappingSystem.cs | 6 +- Content.Server/Maps/GameMapPrototype.cs | 5 + Content.Server/Maps/MapMigrationSystem.cs | 2 +- Content.Server/Maps/ResaveCommand.cs | 62 +++-- Content.Server/Procedural/DungeonSystem.cs | 21 +- .../Salvage/SalvageSystem.Magnet.cs | 12 +- Content.Server/Salvage/SalvageSystem.cs | 19 +- .../Shuttles/Systems/ArrivalsSystem.cs | 24 +- .../Systems/EmergencyShuttleSystem.cs | 24 +- .../Systems/ShuttleSystem.GridFill.cs | 45 ++-- .../Shuttles/Systems/ShuttleSystem.cs | 1 + .../Systems/SharedMoverController.Input.cs | 3 +- Resources/Prototypes/GameRules/events.yml | 2 +- Resources/Prototypes/Maps/centcomm.yml | 1 + RobustToolbox | 2 +- 33 files changed, 551 insertions(+), 360 deletions(-) diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index 9b5ee431f1f..8ac3a3021f1 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -11,6 +11,8 @@ using Robust.Shared.Map.Components; using System.Linq; using System.Numerics; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Body { @@ -57,7 +59,6 @@ public async Task AirConsistencyTest() await server.WaitIdleAsync(); - var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var mapLoader = entityManager.System(); var mapSys = entityManager.System(); @@ -69,17 +70,13 @@ public async Task AirConsistencyTest() GridAtmosphereComponent relevantAtmos = default; var startingMoles = 0.0f; - var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml"; + var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml"); await server.WaitPost(() => { mapSys.CreateMap(out var mapId); - Assert.That(mapLoader.TryLoad(mapId, testMapName, out var roots)); - - var query = entityManager.GetEntityQuery(); - var grids = roots.Where(x => query.HasComponent(x)); - Assert.That(grids, Is.Not.Empty); - grid = grids.First(); + Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt)); + grid = gridEnt!.Value.Owner; }); Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found."); @@ -148,18 +145,13 @@ public async Task NoSuffocationTest() RespiratorComponent respirator = null; EntityUid human = default; - var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml"; + var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml"); await server.WaitPost(() => { mapSys.CreateMap(out var mapId); - - Assert.That(mapLoader.TryLoad(mapId, testMapName, out var ents), Is.True); - var query = entityManager.GetEntityQuery(); - grid = ents - .Select(x => x) - .FirstOrDefault((uid) => uid.HasValue && query.HasComponent(uid.Value), null); - Assert.That(grid, Is.Not.Null); + Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt)); + grid = gridEnt!.Value.Owner; }); Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found."); diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs index 01482ba8ee2..f56b5c28501 100644 --- a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs +++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs @@ -4,8 +4,10 @@ using Content.Shared.Body.Systems; using Robust.Server.GameObjects; using Robust.Shared.Containers; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Body; @@ -111,13 +113,12 @@ await server.WaitAssertion(() => Is.Not.Empty ); - const string mapPath = $"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml"; + var mapPath = new ResPath($"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml"); mapLoader.SaveMap(mapId, mapPath); - maps.DeleteMap(mapId); + mapSys.DeleteMap(mapId); - mapSys.CreateMap(out mapId); - Assert.That(mapLoader.TryLoad(mapId, mapPath, out _), Is.True); + Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _), Is.True); var query = EnumerateQueryEnumerator( entities.EntityQueryEnumerator() @@ -173,7 +174,7 @@ await server.WaitAssertion(() => }); } - maps.DeleteMap(mapId); + entities.DeleteEntity(map); } }); diff --git a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs index 7bc62dfe2bc..ad4ddc26125 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs @@ -1,5 +1,6 @@ #nullable enable using Robust.Shared.Console; +using Robust.Shared.GameObjects; using Robust.Shared.Map; namespace Content.IntegrationTests.Tests.Minds; @@ -37,8 +38,8 @@ public async Task DeleteAllThenGhost() Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0)); // Create a new map. - int mapId = 1; - await pair.Server.WaitPost(() => conHost.ExecuteCommand($"addmap {mapId}")); + MapId mapId = default; + await pair.Server.WaitPost(() => pair.Server.System().CreateMap(out mapId)); await pair.RunTicksSync(5); // Client is not attached to anything @@ -54,7 +55,7 @@ public async Task DeleteAllThenGhost() Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity)); Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind)); var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value); - Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId))); + Assert.That(xform.MapID, Is.EqualTo(mapId)); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 635a9dbe0f9..f7562d934dd 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -9,7 +9,6 @@ using Content.Server.Station.Components; using Content.Shared.CCVar; using Content.Shared.Roles; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; @@ -17,7 +16,8 @@ using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Content.Shared.Station.Components; -using FastAccessors; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; @@ -66,7 +66,7 @@ public sealed class PostMapInitTest "Amber", "Loop" - + }; /// @@ -81,33 +81,23 @@ public async Task GridsLoadableTest(string mapFile) var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); var mapSystem = entManager.System(); - var mapManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + var path = new ResPath(mapFile); await server.WaitPost(() => { mapSystem.CreateMap(out var mapId); try { -#pragma warning disable NUnit2045 - Assert.That(mapLoader.TryLoad(mapId, mapFile, out var roots)); - Assert.That(roots.Where(uid => entManager.HasComponent(uid)), Is.Not.Empty); -#pragma warning restore NUnit2045 + Assert.That(mapLoader.TryLoadGrid(mapId, path, out var grid)); } catch (Exception ex) { throw new Exception($"Failed to load map {mapFile}, was it saved as a map instead of a grid?", ex); } - try - { - mapManager.DeleteMap(mapId); - } - catch (Exception ex) - { - throw new Exception($"Failed to delete map {mapFile}", ex); - } + mapSystem.DeleteMap(mapId); }); await server.WaitRunTicks(1); @@ -172,16 +162,16 @@ public async Task GameMapsLoadableTest(string mapProto) var protoManager = server.ResolveDependency(); var ticker = entManager.EntitySysManager.GetEntitySystem(); var shuttleSystem = entManager.EntitySysManager.GetEntitySystem(); - var xformQuery = entManager.GetEntityQuery(); var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { - mapSystem.CreateMap(out var mapId); + MapId mapId; try { - ticker.LoadGameMap(protoManager.Index(mapProto), mapId, null); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + ticker.LoadGameMap(protoManager.Index(mapProto), out mapId, opts); } catch (Exception ex) { @@ -218,21 +208,17 @@ await server.WaitPost(() => if (entManager.TryGetComponent(station, out var stationEvac)) { var shuttlePath = stationEvac.EmergencyShuttlePath; -#pragma warning disable NUnit2045 - Assert.That(mapLoader.TryLoad(shuttleMap, shuttlePath.ToString(), out var roots)); - EntityUid shuttle = default!; - Assert.DoesNotThrow(() => - { - shuttle = roots.First(uid => entManager.HasComponent(uid)); - }, $"Failed to load {shuttlePath}"); + Assert.That(mapLoader.TryLoadGrid(shuttleMap, shuttlePath, out var shuttle), + $"Failed to load {shuttlePath}"); + Assert.That( - shuttleSystem.TryFTLDock(shuttle, - entManager.GetComponent(shuttle), targetGrid.Value), + shuttleSystem.TryFTLDock(shuttle!.Value.Owner, + entManager.GetComponent(shuttle!.Value.Owner), + targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}"); -#pragma warning restore NUnit2045 } - mapManager.DeleteMap(shuttleMap); + mapSystem.DeleteMap(shuttleMap); if (entManager.HasComponent(station)) { @@ -269,7 +255,7 @@ await server.WaitPost(() => try { - mapManager.DeleteMap(mapId); + mapSystem.DeleteMap(mapId); } catch (Exception ex) { @@ -333,11 +319,9 @@ public async Task NonGameMapsLoadableTest() var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); - var mapManager = server.ResolveDependency(); var resourceManager = server.ResolveDependency(); var protoManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); - var mapSystem = server.System(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); var gameMaps = protoManager.EnumeratePrototypes().Select(o => o.MapPath).ToHashSet(); @@ -348,7 +332,7 @@ public async Task NonGameMapsLoadableTest() .Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal)) .ToArray(); - var mapNames = new List(); + var mapPaths = new List(); foreach (var map in maps) { if (gameMaps.Contains(map)) @@ -359,32 +343,46 @@ public async Task NonGameMapsLoadableTest() { continue; } - mapNames.Add(rootedPath.ToString()); + mapPaths.Add(rootedPath); } await server.WaitPost(() => { Assert.Multiple(() => { - foreach (var mapName in mapNames) + // This bunch of files contains a random mixture of both map and grid files. + // TODO MAPPING organize files + var opts = MapLoadOptions.Default with + { + DeserializationOptions = DeserializationOptions.Default with + { + InitializeMaps = true, + LogOrphanedGrids = false + } + }; + + HashSet> maps; + foreach (var path in mapPaths) { - mapSystem.CreateMap(out var mapId); try { - Assert.That(mapLoader.TryLoad(mapId, mapName, out _)); + Assert.That(mapLoader.TryLoadEntities(path, out maps, out _, opts)); } catch (Exception ex) { - throw new Exception($"Failed to load map {mapName}", ex); + throw new Exception($"Failed to load map {path}", ex); } try { - mapManager.DeleteMap(mapId); + foreach (var map in maps) + { + server.EntMan.DeleteEntity(map); + } } catch (Exception ex) { - throw new Exception($"Failed to delete map {mapName}", ex); + throw new Exception($"Failed to delete map {path}", ex); } } }); diff --git a/Content.IntegrationTests/Tests/SalvageTest.cs b/Content.IntegrationTests/Tests/SalvageTest.cs index 5dfba82308f..0059db6292b 100644 --- a/Content.IntegrationTests/Tests/SalvageTest.cs +++ b/Content.IntegrationTests/Tests/SalvageTest.cs @@ -1,11 +1,8 @@ -using System.Linq; -using Content.Shared.CCVar; +using Content.Shared.CCVar; using Content.Shared.Salvage; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; @@ -24,7 +21,6 @@ public async Task AllSalvageMapsLoadableTest() var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); - var mapManager = server.ResolveDependency(); var prototypeManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); var mapSystem = entManager.System(); @@ -34,13 +30,10 @@ await server.WaitPost(() => { foreach (var salvage in prototypeManager.EnumeratePrototypes()) { - var mapFile = salvage.MapPath; - mapSystem.CreateMap(out var mapId); try { - Assert.That(mapLoader.TryLoad(mapId, mapFile.ToString(), out var roots)); - Assert.That(roots.Where(uid => entManager.HasComponent(uid)), Is.Not.Empty); + Assert.That(mapLoader.TryLoadGrid(mapId, salvage.MapPath, out var grid)); } catch (Exception ex) { @@ -49,7 +42,7 @@ await server.WaitPost(() => try { - mapManager.DeleteMap(mapId); + mapSystem.DeleteMap(mapId); } catch (Exception ex) { diff --git a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs index 213da5d7862..b1f6dd84330 100644 --- a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs @@ -3,6 +3,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -16,7 +17,7 @@ public sealed class SaveLoadMapTest [Test] public async Task SaveLoadMultiGridMap() { - const string mapPath = @"/Maps/Test/TestMap.yml"; + var mapPath = new ResPath("/Maps/Test/TestMap.yml"); await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; @@ -31,7 +32,7 @@ public async Task SaveLoadMultiGridMap() await server.WaitAssertion(() => { - var dir = new ResPath(mapPath).Directory; + var dir = mapPath.Directory; resManager.UserData.CreateDir(dir); mapSystem.CreateMap(out var mapId); @@ -48,14 +49,16 @@ await server.WaitAssertion(() => } Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath)); - Assert.Multiple(() => mapManager.DeleteMap(mapId)); + Assert.Multiple(() => mapSystem.DeleteMap(mapId)); }); await server.WaitIdleAsync(); + MapId newMap = default; await server.WaitAssertion(() => { - Assert.That(mapLoader.TryLoad(new MapId(10), mapPath, out _)); + Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _)); + newMap = map!.Value.Comp.MapId; }); await server.WaitIdleAsync(); @@ -63,7 +66,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { { - if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(10, 10), out var gridUid, out var mapGrid) || + if (!mapManager.TryFindGridAt(newMap, new Vector2(10, 10), out var gridUid, out var mapGrid) || !sEntities.TryGetComponent(gridUid, out var gridXform)) { Assert.Fail(); @@ -77,7 +80,7 @@ await server.WaitAssertion(() => }); } { - if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(-8, -8), out var gridUid, out var mapGrid) || + if (!mapManager.TryFindGridAt(newMap, new Vector2(-8, -8), out var gridUid, out var mapGrid) || !sEntities.TryGetComponent(gridUid, out var gridXform)) { Assert.Fail(); diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs index 4facd5ee40c..a398b559197 100644 --- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs @@ -1,25 +1,25 @@ using System.IO; using System.Linq; using Content.Shared.CCVar; -using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Map.Components; +using Robust.Shared.Map.Events; +using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests { /// - /// Tests that a map's yaml does not change when saved consecutively. + /// Tests that a grid's yaml does not change when saved consecutively. /// [TestFixture] public sealed class SaveLoadSaveTest { [Test] - public async Task SaveLoadSave() + public async Task CreateSaveLoadSaveGrid() { await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; @@ -30,22 +30,21 @@ public async Task SaveLoadSave() var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + var testSystem = server.System(); + testSystem.Enabled = true; + + var rp1 = new ResPath("/save load save 1.yml"); + var rp2 = new ResPath("/save load save 2.yml"); + await server.WaitPost(() => { mapSystem.CreateMap(out var mapId0); - // TODO: Properly find the "main" station grid. var grid0 = mapManager.CreateGridEntity(mapId0); - mapLoader.Save(grid0.Owner, "save load save 1.yml"); + entManager.RunMapInit(grid0.Owner, entManager.GetComponent(grid0)); + mapLoader.SaveGrid(grid0.Owner, rp1); mapSystem.CreateMap(out var mapId1); - EntityUid grid1 = default!; -#pragma warning disable NUnit2045 - Assert.That(mapLoader.TryLoad(mapId1, "save load save 1.yml", out var roots, new MapLoadOptions() { LoadMap = false }), $"Failed to load test map {TestMap}"); - Assert.DoesNotThrow(() => - { - grid1 = roots.First(uid => entManager.HasComponent(uid)); - }); -#pragma warning restore NUnit2045 - mapLoader.Save(grid1, "save load save 2.yml"); + Assert.That(mapLoader.TryLoadGrid(mapId1, rp1, out var grid1)); + mapLoader.SaveGrid(grid1!.Value, rp2); }); await server.WaitIdleAsync(); @@ -54,14 +53,12 @@ await server.WaitPost(() => string one; string two; - var rp1 = new ResPath("/save load save 1.yml"); await using (var stream = userData.Open(rp1, FileMode.Open)) using (var reader = new StreamReader(stream)) { one = await reader.ReadToEndAsync(); } - var rp2 = new ResPath("/save load save 2.yml"); await using (var stream = userData.Open(rp2, FileMode.Open)) using (var reader = new StreamReader(stream)) { @@ -87,6 +84,7 @@ await server.WaitPost(() => TestContext.Error.WriteLine(twoTmp); } }); + testSystem.Enabled = false; await pair.CleanReturnAsync(); } @@ -101,8 +99,12 @@ public async Task LoadSaveTicksSaveBagel() await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); - var mapManager = server.ResolveDependency(); - var mapSystem = server.System(); + var mapSys = server.System(); + var testSystem = server.System(); + testSystem.Enabled = true; + + var rp1 = new ResPath("/load save ticks save 1.yml"); + var rp2 = new ResPath("/load save ticks save 2.yml"); MapId mapId = default; var cfg = server.ResolveDependency(); @@ -111,10 +113,10 @@ public async Task LoadSaveTicksSaveBagel() // Load bagel.yml as uninitialized map, and save it to ensure it's up to date. server.Post(() => { - mapSystem.CreateMap(out mapId, runMapInit: false); - mapManager.SetMapPaused(mapId, true); - Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); - mapLoader.SaveMap(mapId, "load save ticks save 1.yml"); + var path = new ResPath(TestMap); + Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); + mapId = map!.Value.Comp.MapId; + mapLoader.SaveMap(mapId, rp1); }); // Run 5 ticks. @@ -122,7 +124,7 @@ public async Task LoadSaveTicksSaveBagel() await server.WaitPost(() => { - mapLoader.SaveMap(mapId, "/load save ticks save 2.yml"); + mapLoader.SaveMap(mapId, rp2); }); await server.WaitIdleAsync(); @@ -131,13 +133,13 @@ await server.WaitPost(() => string one; string two; - await using (var stream = userData.Open(new ResPath("/load save ticks save 1.yml"), FileMode.Open)) + await using (var stream = userData.Open(rp1, FileMode.Open)) using (var reader = new StreamReader(stream)) { one = await reader.ReadToEndAsync(); } - await using (var stream = userData.Open(new ResPath("/load save ticks save 2.yml"), FileMode.Open)) + await using (var stream = userData.Open(rp2, FileMode.Open)) using (var reader = new StreamReader(stream)) { two = await reader.ReadToEndAsync(); @@ -163,7 +165,8 @@ await server.WaitPost(() => } }); - await server.WaitPost(() => mapManager.DeleteMap(mapId)); + testSystem.Enabled = false; + await server.WaitPost(() => mapSys.DeleteMap(mapId)); await pair.CleanReturnAsync(); } @@ -184,13 +187,15 @@ public async Task LoadTickLoadBagel() var server = pair.Server; var mapLoader = server.System(); - var mapSystem = server.System(); - var mapManager = server.ResolveDependency(); + var mapSys = server.System(); var userData = server.ResolveDependency().UserData; var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + var testSystem = server.System(); + testSystem.Enabled = true; - MapId mapId = default; + MapId mapId1 = default; + MapId mapId2 = default; const string fileA = "/load tick load a.yml"; const string fileB = "/load tick load b.yml"; string yamlA; @@ -199,10 +204,10 @@ public async Task LoadTickLoadBagel() // Load & save the first map server.Post(() => { - mapSystem.CreateMap(out mapId, runMapInit: false); - mapManager.SetMapPaused(mapId, true); - Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); - mapLoader.SaveMap(mapId, fileA); + var path = new ResPath(TestMap); + Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); + mapId1 = map!.Value.Comp.MapId; + mapLoader.SaveMap(mapId1, fileA); }); await server.WaitIdleAsync(); @@ -217,11 +222,10 @@ public async Task LoadTickLoadBagel() // Load & save the second map server.Post(() => { - mapManager.DeleteMap(mapId); - mapSystem.CreateMap(out mapId, runMapInit: false); - mapManager.SetMapPaused(mapId, true); - Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); - mapLoader.SaveMap(mapId, fileB); + var path = new ResPath(TestMap); + Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); + mapId2 = map!.Value.Comp.MapId; + mapLoader.SaveMap(mapId2, fileB); }); await server.WaitIdleAsync(); @@ -234,8 +238,32 @@ public async Task LoadTickLoadBagel() Assert.That(yamlA, Is.EqualTo(yamlB)); - await server.WaitPost(() => mapManager.DeleteMap(mapId)); + testSystem.Enabled = false; + await server.WaitPost(() => mapSys.DeleteMap(mapId1)); + await server.WaitPost(() => mapSys.DeleteMap(mapId2)); await pair.CleanReturnAsync(); } + + /// + /// Simple system that modifies the data saved to a yaml file by removing the timestamp. + /// Required by some tests that validate that re-saving a map does not modify it. + /// + private sealed class SaveLoadSaveTestSystem : EntitySystem + { + public bool Enabled; + public override void Initialize() + { + SubscribeLocalEvent(OnAfterSave); + } + + private void OnAfterSave(AfterSaveEvent ev) + { + if (!Enabled) + return; + + // Remove timestamp. + ((MappingDataNode)ev.Node["meta"]).Remove("time"); + } + } } } diff --git a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs index f6e99596e90..ab82a3d2f91 100644 --- a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs +++ b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs @@ -4,10 +4,12 @@ using Content.Server.Shuttles.Systems; using Content.Tests; using Robust.Server.GameObjects; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Maths; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Shuttle; @@ -106,8 +108,9 @@ await server.WaitAssertion(() => { mapGrid = entManager.AddComponent(map.MapUid); entManager.DeleteEntity(map.Grid); - Assert.That(entManager.System().TryLoad(otherMap.MapId, "/Maps/Shuttles/emergency.yml", out var rootUids)); - shuttle = rootUids[0]; + var path = new ResPath("/Maps/Shuttles/emergency.yml"); + Assert.That(entManager.System().TryLoadGrid(otherMap.MapId, path, out var grid)); + shuttle = grid!.Value.Owner; var dockingConfig = dockingSystem.GetDockingConfig(shuttle, map.MapUid); Assert.That(dockingConfig, Is.EqualTo(null)); diff --git a/Content.Server/Administration/Commands/LoadGameMapCommand.cs b/Content.Server/Administration/Commands/LoadGameMapCommand.cs index ea31c1ecb17..034b50a9c5e 100644 --- a/Content.Server/Administration/Commands/LoadGameMapCommand.cs +++ b/Content.Server/Administration/Commands/LoadGameMapCommand.cs @@ -1,11 +1,9 @@ -using System.Linq; using System.Numerics; using Content.Server.GameTicking; using Content.Server.Maps; using Content.Shared.Administration; -using Robust.Server.Maps; using Robust.Shared.Console; -using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -25,6 +23,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) var prototypeManager = IoCManager.Resolve(); var entityManager = IoCManager.Resolve(); var gameTicker = entityManager.EntitySysManager.GetEntitySystem(); + var mapSys = entityManager.EntitySysManager.GetEntitySystem(); if (args.Length is not (2 or 4 or 5)) { @@ -32,29 +31,28 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) return; } - if (prototypeManager.TryIndex(args[1], out var gameMap)) + if (!prototypeManager.TryIndex(args[1], out var gameMap)) { - if (!int.TryParse(args[0], out var mapId)) + shell.WriteError($"The given map prototype {args[0]} is invalid."); + return; + } + + if (!int.TryParse(args[0], out var mapId)) return; - var loadOptions = new MapLoadOptions() - { - LoadMap = false, - }; + var stationName = args.Length == 5 ? args[4] : null; - var stationName = args.Length == 5 ? args[4] : null; + Vector2? offset = null; + if (args.Length >= 4) + offset = new Vector2(int.Parse(args[2]), int.Parse(args[3])); - if (args.Length >= 4 && int.TryParse(args[2], out var x) && int.TryParse(args[3], out var y)) - { - loadOptions.Offset = new Vector2(x, y); - } - var grids = gameTicker.LoadGameMap(gameMap, new MapId(mapId), loadOptions, stationName); - shell.WriteLine($"Loaded {grids.Count} grids."); - } - else - { - shell.WriteError($"The given map prototype {args[0]} is invalid."); - } + var id = new MapId(mapId); + + var grids = mapSys.MapExists(id) + ? gameTicker.MergeGameMap(gameMap, id, stationName: stationName, offset: offset) + : gameTicker.LoadGameMapWithId(gameMap, id, stationName: stationName, offset: offset); + + shell.WriteLine($"Loaded {grids.Count} grids."); } public CompletionResult GetCompletion(IConsoleShell shell, string[] args) diff --git a/Content.Server/Administration/Commands/PersistenceSaveCommand.cs b/Content.Server/Administration/Commands/PersistenceSaveCommand.cs index 7ef1932c568..dffe1eff7f3 100644 --- a/Content.Server/Administration/Commands/PersistenceSaveCommand.cs +++ b/Content.Server/Administration/Commands/PersistenceSaveCommand.cs @@ -5,6 +5,7 @@ using Robust.Shared.Console; using Robust.Shared.Map; using System.Linq; +using Robust.Shared.EntitySerialization.Systems; namespace Content.Server.Administration.Commands; diff --git a/Content.Server/Administration/Systems/AdminTestArenaSystem.cs b/Content.Server/Administration/Systems/AdminTestArenaSystem.cs index e3671bcdfb3..12bf0eba648 100644 --- a/Content.Server/Administration/Systems/AdminTestArenaSystem.cs +++ b/Content.Server/Administration/Systems/AdminTestArenaSystem.cs @@ -1,8 +1,7 @@ -using Robust.Server.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Utility; namespace Content.Server.Administration.Systems; @@ -11,8 +10,7 @@ namespace Content.Server.Administration.Systems; /// public sealed class AdminTestArenaSystem : EntitySystem { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; public const string ArenaMapPath = "/Maps/Test/admin_test_arena.yml"; @@ -28,26 +26,24 @@ public sealed class AdminTestArenaSystem : EntitySystem { return (arenaMap, arenaGrid); } - else - { - ArenaGrid[admin.UserId] = null; - return (arenaMap, null); - } - } - ArenaMap[admin.UserId] = _mapManager.GetMapEntityId(_mapManager.CreateMap()); - _metaDataSystem.SetEntityName(ArenaMap[admin.UserId], $"ATAM-{admin.Name}"); - var grids = _map.LoadMap(Comp(ArenaMap[admin.UserId]).MapId, ArenaMapPath); - if (grids.Count != 0) - { - _metaDataSystem.SetEntityName(grids[0], $"ATAG-{admin.Name}"); - ArenaGrid[admin.UserId] = grids[0]; - } - else - { + ArenaGrid[admin.UserId] = null; + return (arenaMap, null); } - return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]); + var path = new ResPath(ArenaMapPath); + if (!_loader.TryLoadMap(path, out var map, out var grids)) + throw new Exception($"Failed to load admin arena"); + + ArenaMap[admin.UserId] = map.Value.Owner; + _metaDataSystem.SetEntityName(map.Value.Owner, $"ATAM-{admin.Name}"); + + var grid = grids.FirstOrNull(); + ArenaGrid[admin.UserId] = grid?.Owner; + if (grid != null) + _metaDataSystem.SetEntityName(grid.Value.Owner, $"ATAG-{admin.Name}"); + + return (map.Value.Owner, grid?.Owner); } } diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index a7dd5d6ab62..37e7a4e1ce5 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Numerics; using Content.Server.Announcements; using Content.Server.Discord; using Content.Server.GameTicking.Events; @@ -13,10 +14,12 @@ using Content.Shared.Preferences; using JetBrains.Annotations; using Prometheus; -using Robust.Server.Maps; using Robust.Shared.Asynchronous; using Robust.Shared.Audio; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Random; @@ -92,9 +95,6 @@ private void LoadMaps() AddGamePresetRules(); - DefaultMap = _mapManager.CreateMap(); - _mapManager.AddUninitializedMap(DefaultMap); - var maps = new List(); // the map might have been force-set by something @@ -132,52 +132,203 @@ private void LoadMaps() // Let game rules dictate what maps we should load. RaiseLocalEvent(new LoadingMapsEvent(maps)); - foreach (var map in maps) + if (maps.Count == 0) { - var toLoad = DefaultMap; - if (maps[0] != map) - { - // Create other maps for the others since we need to. - toLoad = _mapManager.CreateMap(); - _mapManager.AddUninitializedMap(toLoad); - } + _map.CreateMap(out var mapId, runMapInit: false); + DefaultMap = mapId; + return; + } + + for (var i = 0; i < maps.Count; i++) + { + LoadGameMap(maps[i], out var mapId); + DebugTools.Assert(!_map.IsInitialized(mapId)); - LoadGameMap(map, toLoad, null); + if (i == 0) + DefaultMap = mapId; } } + public PreGameMapLoad RaisePreLoad( + GameMapPrototype proto, + DeserializationOptions? opts = null, + Vector2? offset = null, + Angle? rot = null) + { + offset ??= proto.MaxRandomOffset != 0f + ? _robustRandom.NextVector2(proto.MaxRandomOffset) + : Vector2.Zero; + + rot ??= proto.RandomRotation + ? _robustRandom.NextAngle() + : Angle.Zero; + + opts ??= DeserializationOptions.Default; + var ev = new PreGameMapLoad(proto, opts.Value, offset.Value, rot.Value); + RaiseLocalEvent(ev); + return ev; + } /// /// Loads a new map, allowing systems interested in it to handle loading events. /// In the base game, this is required to be used if you want to load a station. + /// This does not initialze maps, unles specified via the . /// - /// Game map prototype to load in. - /// Map to load into. - /// Map loading options, includes offset. + /// + /// This is basically a wrapper around a method that auto generate + /// some using information in a prototype, and raise some events to allow content + /// to modify the options and react to the map creation. + /// + /// Game map prototype to load in. + /// The id of the map that was loaded. + /// Entity loading options, including whether the maps should be initialized. /// Name to assign to the loaded station. /// All loaded entities and grids. - public IReadOnlyList LoadGameMap(GameMapPrototype map, MapId targetMapId, MapLoadOptions? loadOptions, string? stationName = null) + public IReadOnlyList LoadGameMap( + GameMapPrototype proto, + out MapId mapId, + DeserializationOptions? options = null, + string? stationName = null, + Vector2? offset = null, + Angle? rot = null) { - // Okay I specifically didn't set LoadMap here because this is typically called onto a new map. - // whereas the command can also be used on an existing map. - var loadOpts = loadOptions ?? new MapLoadOptions(); + var ev = RaisePreLoad(proto, options, offset, rot); - if (map.MaxRandomOffset != 0f) - loadOpts.Offset = _robustRandom.NextVector2(map.MaxRandomOffset); + if (ev.GameMap.IsGrid) + { + var mapUid = _map.CreateMap(out mapId); + if (!_loader.TryLoadGrid(mapId, + ev.GameMap.MapPath, + out var grid, + ev.Options, + ev.Offset, + ev.Rotation)) + { + throw new Exception($"Failed to load game-map grid {ev.GameMap.ID}"); + } - if (map.RandomRotation) - loadOpts.Rotation = _robustRandom.NextAngle(); + _metaData.SetEntityName(mapUid, proto.MapName); + var g = new List {grid.Value.Owner}; + RaiseLocalEvent(new PostGameMapLoad(proto, mapId, g, stationName)); + return g; + } - var ev = new PreGameMapLoad(targetMapId, map, loadOpts); - RaiseLocalEvent(ev); + if (!_loader.TryLoadMap(ev.GameMap.MapPath, + out var map, + out var grids, + ev.Options, + ev.Offset, + ev.Rotation)) + { + throw new Exception($"Failed to load game map {ev.GameMap.ID}"); + } + + mapId = map.Value.Comp.MapId; + _metaData.SetEntityName(map.Value.Owner, proto.MapName); + var gridUids = grids.Select(x => x.Owner).ToList(); + RaiseLocalEvent(new PostGameMapLoad(proto, mapId, gridUids, stationName)); + return gridUids; + } + + /// + /// Variant of that attempts to assign the provided to the + /// loaded map. + /// + public IReadOnlyList LoadGameMapWithId( + GameMapPrototype proto, + MapId mapId, + DeserializationOptions? opts = null, + string? stationName = null, + Vector2? offset = null, + Angle? rot = null) + { + var ev = RaisePreLoad(proto, opts, offset, rot); + + if (ev.GameMap.IsGrid) + { + var mapUid = _map.CreateMap(mapId); + if (!_loader.TryLoadGrid(mapId, + ev.GameMap.MapPath, + out var grid, + ev.Options, + ev.Offset, + ev.Rotation)) + { + throw new Exception($"Failed to load game-map grid {ev.GameMap.ID}"); + } - var gridIds = _map.LoadMap(targetMapId, ev.GameMap.MapPath.ToString(), ev.Options); + _metaData.SetEntityName(mapUid, proto.MapName); + var g = new List {grid.Value.Owner}; + RaiseLocalEvent(new PostGameMapLoad(proto, mapId, g, stationName)); + return g; + } - _metaData.SetEntityName(_mapManager.GetMapEntityId(targetMapId), map.MapName); + if (!_loader.TryLoadMapWithId( + mapId, + ev.GameMap.MapPath, + out var map, + out var grids, + ev.Options, + ev.Offset, + ev.Rotation)) + { + throw new Exception($"Failed to load map"); + } - var gridUids = gridIds.ToList(); - RaiseLocalEvent(new PostGameMapLoad(map, targetMapId, gridUids, stationName)); + _metaData.SetEntityName(map.Value.Owner, proto.MapName); + var gridUids = grids.Select(x => x.Owner).ToList(); + RaiseLocalEvent(new PostGameMapLoad(proto, mapId, gridUids, stationName)); + return gridUids; + } + /// + /// Variant of that loads and then merges a game map onto an existing map. + /// + public IReadOnlyList MergeGameMap( + GameMapPrototype proto, + MapId targetMap, + DeserializationOptions? opts = null, + string? stationName = null, + Vector2? offset = null, + Angle? rot = null) + { + // TODO MAP LOADING use a new event? + // This is quite different from the other methods, which will actually create a **new** map. + var ev = RaisePreLoad(proto, opts, offset, rot); + + if (ev.GameMap.IsGrid) + { + if (!_loader.TryLoadGrid(targetMap, + ev.GameMap.MapPath, + out var grid, + ev.Options, + ev.Offset, + ev.Rotation)) + { + throw new Exception($"Failed to load game-map grid {ev.GameMap.ID}"); + } + + var g = new List {grid.Value.Owner}; + // TODO MAP LOADING use a new event? + RaiseLocalEvent(new PostGameMapLoad(proto, targetMap, g, stationName)); + return g; + } + + if (!_loader.TryMergeMap(targetMap, + ev.GameMap.MapPath, + out var map, + out var grids, + ev.Options, + ev.Offset, + ev.Rotation)) + { + throw new Exception($"Failed to load map"); + } + + var gridUids = grids.Select(x => x.Owner).ToList(); + + // TODO MAP LOADING use a new event? + RaiseLocalEvent(new PostGameMapLoad(proto, targetMap, gridUids, stationName)); return gridUids; } @@ -274,7 +425,7 @@ public void StartRound(bool force = false) } // MapInitialize *before* spawning players, our codebase is too shit to do it afterwards... - _mapManager.DoMapInitialize(DefaultMap); + _map.InitializeMap(DefaultMap); SpawnPlayers(readyPlayers, readyPlayerProfiles, force); @@ -714,21 +865,14 @@ public LoadingMapsEvent(List maps) /// You likely want to subscribe to this after StationSystem. /// [PublicAPI] - public sealed class PreGameMapLoad : EntityEventArgs + public sealed class PreGameMapLoad(GameMapPrototype gameMap, DeserializationOptions options, Vector2 offset, Angle rotation) : EntityEventArgs { - public readonly MapId Map; - public GameMapPrototype GameMap; - public MapLoadOptions Options; - - public PreGameMapLoad(MapId map, GameMapPrototype gameMap, MapLoadOptions options) - { - Map = map; - GameMap = gameMap; - Options = options; - } + public readonly GameMapPrototype GameMap = gameMap; + public DeserializationOptions Options = options; + public Vector2 Offset = offset; + public Angle Rotation = rotation; } - /// /// Event raised after the game loads a given map. /// diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index f8ba7e1d7d9..d286fd641eb 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -18,6 +18,7 @@ using Robust.Server.GameStates; using Robust.Shared.Audio.Systems; using Robust.Shared.Console; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -48,7 +49,8 @@ public sealed partial class GameTicker : SharedGameTicker [Dependency] private readonly IServerPreferencesManager _prefsManager = default!; [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; + [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly PlayTimeTrackingSystem _playTimeTrackings = default!; diff --git a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs index 1f0505c60fc..f09fff5eafa 100644 --- a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs @@ -20,11 +20,17 @@ public sealed partial class LoadMapRuleComponent : Component public ProtoId? GameMap; /// - /// A map path to load on a new map. + /// A map to load. /// [DataField] public ResPath? MapPath; + /// + /// A grid to load on a new map. + /// + [DataField] + public ResPath? GridPath; + /// /// A to move to a new map. /// If there are no instances left nothing is done. diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index f18115d3cf6..003a74ef4bf 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -1,10 +1,14 @@ +using System.Linq; using Content.Server.GameTicking.Rules.Components; using Content.Server.GridPreloader; using Content.Server.StationEvents.Events; using Content.Shared.GameTicking.Components; using Robust.Server.GameObjects; -using Robust.Server.Maps; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Server.GameTicking.Rules; @@ -26,29 +30,50 @@ protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRule return; } - // grid preloading needs map to init after moving it - var mapUid = _map.CreateMap(out var mapId, runMapInit: comp.PreloadedGrid == null); - - Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}"); - + MapId mapId; IReadOnlyList grids; if (comp.GameMap != null) { + // Component has one of three modes, only one of the three fields should ever be populated. + DebugTools.AssertNull(comp.MapPath); + DebugTools.AssertNull(comp.GridPath); + DebugTools.AssertNull(comp.PreloadedGrid); + var gameMap = _prototypeManager.Index(comp.GameMap.Value); - grids = GameTicker.LoadGameMap(gameMap, mapId, new MapLoadOptions()); + grids = GameTicker.LoadGameMap(gameMap, out mapId, null); + Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}"); } else if (comp.MapPath is {} path) { - var options = new MapLoadOptions { LoadMap = true }; - if (!_mapLoader.TryLoad(mapId, path.ToString(), out var roots, options)) + DebugTools.AssertNull(comp.GridPath); + DebugTools.AssertNull(comp.PreloadedGrid); + + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + if (!_mapLoader.TryLoadMap(path, out var map, out var gridSet, opts)) { Log.Error($"Failed to load map from {path}!"); - Del(mapUid); ForceEndSelf(uid, rule); return; } - grids = roots; + grids = gridSet.Select( x => x.Owner).ToList(); + mapId = map.Value.Comp.MapId; + } + else if (comp.GridPath is { } gPath) + { + DebugTools.AssertNull(comp.PreloadedGrid); + + // I fucking love it when "map paths" choses to ar + _map.CreateMap(out mapId); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + if (!_mapLoader.TryLoadGrid(mapId, gPath, out var grid, opts)) + { + Log.Error($"Failed to load grid from {gPath}!"); + ForceEndSelf(uid, rule); + return; + } + + grids = new List {grid.Value.Owner}; } else if (comp.PreloadedGrid is {} preloaded) { @@ -56,11 +81,11 @@ protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRule if (!_gridPreloader.TryGetPreloadedGrid(preloaded, out var loadedShuttle)) { Log.Error($"Failed to get a preloaded grid with {preloaded}!"); - Del(mapUid); ForceEndSelf(uid, rule); return; } + var mapUid = _map.CreateMap(out mapId, runMapInit: false); _transform.SetParent(loadedShuttle.Value, mapUid); grids = new List() { loadedShuttle.Value }; _map.InitializeMap(mapUid); @@ -68,7 +93,6 @@ protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRule else { Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}"); - Del(mapUid); ForceEndSelf(uid, rule); return; } diff --git a/Content.Server/GridPreloader/GridPreloaderSystem.cs b/Content.Server/GridPreloader/GridPreloaderSystem.cs index e12ce41a31e..d648acbb06c 100644 --- a/Content.Server/GridPreloader/GridPreloaderSystem.cs +++ b/Content.Server/GridPreloader/GridPreloaderSystem.cs @@ -3,7 +3,6 @@ using Content.Shared.GridPreloader.Prototypes; using Content.Shared.GridPreloader.Systems; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -13,6 +12,7 @@ using Content.Server.GameTicking; using Content.Shared.GameTicking; using JetBrains.Annotations; +using Robust.Shared.EntitySerialization.Systems; namespace Content.Server.GridPreloader; public sealed class GridPreloaderSystem : SharedGridPreloaderSystem @@ -72,23 +72,13 @@ private void EnsurePreloadedGridMap() { for (var i = 0; i < proto.Copies; i++) { - var options = new MapLoadOptions + if (!_mapLoader.TryLoadGrid(mapId, proto.Path, out var grid)) { - LoadMap = false, - }; - - if (!_mapLoader.TryLoad(mapId, proto.Path.ToString(), out var roots, options)) - continue; - - // only supports loading maps with one grid. - if (roots.Count != 1) + Log.Error($"Failed to preload grid prototype {proto.ID}"); continue; + } - var gridUid = roots[0]; - - // gets grid + also confirms that the root we loaded is actually a grid - if (!TryComp(gridUid, out var mapGrid)) - continue; + var (gridUid, mapGrid) = grid.Value; if (!TryComp(gridUid, out var physics)) continue; diff --git a/Content.Server/Mapping/MappingCommand.cs b/Content.Server/Mapping/MappingCommand.cs index 70647d32814..b85b0953ddd 100644 --- a/Content.Server/Mapping/MappingCommand.cs +++ b/Content.Server/Mapping/MappingCommand.cs @@ -3,12 +3,13 @@ using Content.Server.GameTicking; using Content.Shared.Administration; using Content.Shared.CCVar; -using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; +using Robust.Shared.Utility; namespace Content.Server.Mapping { @@ -91,8 +92,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) } else { - var loadOptions = new MapLoadOptions {StoreMapUids = true}; - _entities.System().TryLoad(mapId, args[1], out _, loadOptions); + var path = new ResPath(args[1]); + var opts = new DeserializationOptions {StoreYamlUids = true}; + _entities.System().TryLoadMapWithId(mapId, path, out _, out _, opts); } // was the map actually created or did it fail somehow? diff --git a/Content.Server/Mapping/MappingManager.cs b/Content.Server/Mapping/MappingManager.cs index e8c6eca204c..be6f503bf0b 100644 --- a/Content.Server/Mapping/MappingManager.cs +++ b/Content.Server/Mapping/MappingManager.cs @@ -4,6 +4,8 @@ using Content.Shared.Mapping; using Robust.Server.GameObjects; using Robust.Server.Player; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -21,6 +23,7 @@ public sealed class MappingManager : IPostInjectInit [Dependency] private readonly IServerNetManager _net = default!; [Dependency] private readonly IPlayerManager _players = default!; [Dependency] private readonly IEntitySystemManager _systems = default!; + [Dependency] private readonly IEntityManager _ent = default!; private ISawmill _sawmill = default!; private ZStdCompressionContext _zstd = default!; @@ -45,14 +48,13 @@ private void OnMappingSaveMap(MappingSaveMapMessage message) if (!_players.TryGetSessionByChannel(message.MsgChannel, out var session) || !_admin.IsAdmin(session, true) || !_admin.HasAdminFlag(session, AdminFlags.Host) || - session.AttachedEntity is not { } player) + !_ent.TryGetComponent(session.AttachedEntity, out TransformComponent? xform) || + xform.MapUid is not {} mapUid) { return; } - var mapId = _systems.GetEntitySystem().GetMapCoordinates(player).MapId; - var mapEntity = _map.GetMapEntityIdOrThrow(mapId); - var data = _systems.GetEntitySystem().GetSaveData(mapEntity); + var data = _systems.GetEntitySystem().SerializeEntityRecursive(mapUid).Node; var document = new YamlDocument(data.ToYaml()); var stream = new YamlStream { document }; var writer = new StringWriter(); diff --git a/Content.Server/Mapping/MappingSystem.cs b/Content.Server/Mapping/MappingSystem.cs index 28bb3afbe10..ecdbbe081c1 100644 --- a/Content.Server/Mapping/MappingSystem.cs +++ b/Content.Server/Mapping/MappingSystem.cs @@ -3,10 +3,10 @@ using Content.Shared.Administration; using Content.Shared.CCVar; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -23,7 +23,7 @@ public sealed class MappingSystem : EntitySystem [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceManager _resMan = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; // Not a comp because I don't want to deal with this getting saved onto maps ever /// @@ -78,7 +78,7 @@ public override void Update(float frameTime) var path = Path.Combine(saveDir, $"{DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss")}-AUTO.yml"); _currentlyAutosaving[map] = (CalculateNextTime(), name); Log.Info($"Autosaving map {name} ({map}) to {path}. Next save in {ReadableTimeLeft(map)} seconds."); - _map.SaveMap(map, path); + _loader.SaveMap(map, path); } } diff --git a/Content.Server/Maps/GameMapPrototype.cs b/Content.Server/Maps/GameMapPrototype.cs index 5942a9930eb..c39ca348da0 100644 --- a/Content.Server/Maps/GameMapPrototype.cs +++ b/Content.Server/Maps/GameMapPrototype.cs @@ -25,6 +25,11 @@ public sealed partial class GameMapPrototype : IPrototype [DataField] public float MaxRandomOffset = 1000f; + /// + /// Turns out some of the map files are actually secretly grids. Excellent. I love map loading code. + /// + [DataField] public bool IsGrid; + [DataField] public bool RandomRotation = true; diff --git a/Content.Server/Maps/MapMigrationSystem.cs b/Content.Server/Maps/MapMigrationSystem.cs index 83c2abc5e38..3341034a356 100644 --- a/Content.Server/Maps/MapMigrationSystem.cs +++ b/Content.Server/Maps/MapMigrationSystem.cs @@ -2,8 +2,8 @@ using System.IO; using System.Linq; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map.Events; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Markdown; diff --git a/Content.Server/Maps/ResaveCommand.cs b/Content.Server/Maps/ResaveCommand.cs index cc4d13ddedf..0d48f946be8 100644 --- a/Content.Server/Maps/ResaveCommand.cs +++ b/Content.Server/Maps/ResaveCommand.cs @@ -1,11 +1,11 @@ using System.Linq; using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Console; using Robust.Shared.ContentPack; -using Robust.Shared.Map; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Components; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Utility; namespace Content.Server.Maps; @@ -17,8 +17,8 @@ namespace Content.Server.Maps; public sealed class ResaveCommand : LocalizedCommands { [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceManager _res = default!; + [Dependency] private readonly ILogManager _log = default!; public override string Command => "resave"; @@ -26,32 +26,56 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { var loader = _entManager.System(); - foreach (var fn in _res.ContentFindFiles(new ResPath("/Maps/"))) + var opts = MapLoadOptions.Default with { - var mapId = _mapManager.CreateMap(); - _mapManager.AddUninitializedMap(mapId); - loader.Load(mapId, fn.ToString(), new MapLoadOptions() + + DeserializationOptions = DeserializationOptions.Default with + { + StoreYamlUids = true, + LogOrphanedGrids = false + } + }; + + var log = _log.GetSawmill(Command); + var files = _res.ContentFindFiles(new ResPath("/Maps/")).ToList(); + + for (var i = 0; i < files.Count; i++) + { + var fn = files[i]; + log.Info($"Re-saving file {i}/{files.Count} : {fn}"); + + if (!loader.TryLoadEntities(fn, out var result, opts)) + continue; + + if (result.Maps.Count != 1) { - StoreMapUids = true, - LoadMap = true, - }); + shell.WriteError( + $"Multi-map or multi-grid files like {fn} are not yet supported by the {Command} command"); + loader.Delete(result); + continue; + } + + var map = result.Maps.First(); // Process deferred component removals. _entManager.CullRemovedComponents(); - var mapUid = _mapManager.GetMapEntityId(mapId); - var mapXform = _entManager.GetComponent(mapUid); - - if (_entManager.HasComponent(mapUid) || mapXform.ChildCount != 1) + if (_entManager.HasComponent(map)) { - loader.SaveMap(mapId, fn.ToString()); + loader.SaveMap(map.Comp.MapId, fn); } - else if (mapXform.ChildEnumerator.MoveNext(out var child)) + else if (result.Grids.Count == 1) { - loader.Save(child, fn.ToString()); + loader.SaveGrid(result.Grids.First(), fn); + } + else + { + shell.WriteError($"Failed to resave {fn}"); } - _mapManager.DeleteMap(mapId); + loader.Delete(result); } + + shell.WriteLine($"Resaved all maps"); } } diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 706f63ffd7d..75cdb69130e 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -15,10 +15,13 @@ using Robust.Shared.Collections; 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; @@ -173,14 +176,18 @@ public MapId GetOrCreateTemplate(DungeonRoomPrototype proto) return Transform(uid).MapID; } - var mapId = _mapManager.CreateMap(); - _mapManager.AddUninitializedMap(mapId); - _loader.Load(mapId, proto.AtlasPath.ToString()); - var mapUid = _mapManager.GetMapEntityId(mapId); - _mapManager.SetMapPaused(mapId, true); - comp = AddComp(mapUid); + var opts = new MapLoadOptions + { + DeserializationOptions = DeserializationOptions.Default with {PauseMaps = true}, + ExpectedCategory = FileCategory.Map + }; + + if (!_loader.TryLoadEntities(proto.AtlasPath, out var res, opts) || !res.Maps.TryFirstOrNull(out var map)) + throw new Exception($"Failed to load dungeon template."); + + comp = AddComp(map.Value.Owner); comp.Path = proto.AtlasPath; - return mapId; + return map.Value.Comp.MapId; } /// diff --git a/Content.Server/Salvage/SalvageSystem.Magnet.cs b/Content.Server/Salvage/SalvageSystem.Magnet.cs index 81db78fb201..f0520b6dc6c 100644 --- a/Content.Server/Salvage/SalvageSystem.Magnet.cs +++ b/Content.Server/Salvage/SalvageSystem.Magnet.cs @@ -2,14 +2,11 @@ using System.Numerics; using System.Threading.Tasks; using Content.Server.Salvage.Magnet; -using Content.Shared.Humanoid; using Content.Shared.Mobs.Components; using Content.Shared.Procedural; using Content.Shared.Radio; using Content.Shared.Salvage.Magnet; -using Robust.Server.Maps; using Robust.Shared.Map; -using Robust.Shared.Map.Components; namespace Content.Server.Salvage; @@ -278,15 +275,10 @@ private async Task TakeMagnetOffer(Entity data, int case SalvageOffering wreck: var salvageProto = wreck.SalvageMap; - var opts = new MapLoadOptions - { - Offset = new Vector2(0, 0) - }; - - if (!_map.TryLoad(salvMapXform.MapID, salvageProto.MapPath.ToString(), out _, opts)) + if (!_loader.TryLoadGrid(salvMapXform.MapID, salvageProto.MapPath, out _)) { Report(magnet, MagnetChannel, "salvage-system-announcement-spawn-debris-disintegrated"); - _mapManager.DeleteMap(salvMapXform.MapID); + _mapSystem.DeleteMap(salvMapXform.MapID); return; } diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index eb5719c892f..9115c605366 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -1,38 +1,23 @@ -using System.Linq; -using System.Numerics; -using Content.Server.Cargo.Systems; -using Content.Server.Construction; -using Content.Server.GameTicking; using Content.Server.Radio.EntitySystems; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Popups; using Content.Shared.Radio; using Content.Shared.Salvage; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Utility; using Content.Server.Chat.Managers; using Content.Server.Gravity; using Content.Server.Parallax; using Content.Server.Procedural; using Content.Server.Shuttles.Systems; using Content.Server.Station.Systems; -using Content.Shared.CCVar; using Content.Shared.Construction.EntitySystems; -using Content.Shared.Random; -using Content.Shared.Random.Helpers; -using Content.Shared.Tools.Components; -using Robust.Server.Maps; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map.Components; using Robust.Shared.Timing; using Content.Server.Labels; +using Robust.Shared.EntitySerialization.Systems; namespace Content.Server.Salvage { @@ -50,7 +35,7 @@ public sealed partial class SalvageSystem : SharedSalvageSystem [Dependency] private readonly DungeonSystem _dungeon = default!; [Dependency] private readonly GravitySystem _gravity = default!; [Dependency] private readonly LabelSystem _labelSystem = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly RadioSystem _radioSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index 1f972d96756..708f5a7a1c2 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -30,11 +30,13 @@ using Robust.Shared.Collections; using Robust.Shared.Configuration; using Robust.Shared.Console; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; namespace Content.Server.Shuttles.Systems; @@ -512,15 +514,13 @@ private void OnRoundStarting(RoundStartingEvent ev) private void SetupArrivalsStation() { - var mapUid = _mapSystem.CreateMap(out var mapId, false); - _metaData.SetEntityName(mapUid, Loc.GetString("map-name-terminal")); - - if (!_loader.TryLoad(mapId, _cfgManager.GetCVar(CCVars.ArrivalsMap), out var uids)) - { + var path = new ResPath(_cfgManager.GetCVar(CCVars.ArrivalsMap)); + if (!_loader.TryLoadMap(path, out var map, out var grids)) return; - } - foreach (var id in uids) + _metaData.SetEntityName(map.Value, Loc.GetString("map-name-terminal")); + + foreach (var id in grids) { EnsureComp(id); EnsureComp(id); @@ -531,15 +531,15 @@ private void SetupArrivalsStation() if (_cfgManager.GetCVar(CCVars.ArrivalsPlanet)) { var template = _random.Pick(_arrivalsBiomeOptions); - _biomes.EnsurePlanet(mapUid, _protoManager.Index(template)); + _biomes.EnsurePlanet(map.Value, _protoManager.Index(template)); var restricted = new RestrictedRangeComponent { Range = 32f }; - AddComp(mapUid, restricted); + AddComp(map.Value, restricted); } - _mapSystem.InitializeMap(mapId); + _mapSystem.InitializeMap(map.Value.Comp.MapId); // Handle roundstart stations. var query = AllEntityQuery(); @@ -600,9 +600,9 @@ private void SetupShuttle(EntityUid uid, StationArrivalsComponent component) var dummpMapEntity = _mapSystem.CreateMap(out var dummyMapId); if (TryGetArrivals(out var arrivals) && - _loader.TryLoad(dummyMapId, component.ShuttlePath.ToString(), out var shuttleUids)) + _loader.TryLoadGrid(dummyMapId, component.ShuttlePath, out var shuttle)) { - component.Shuttle = shuttleUids[0]; + component.Shuttle = shuttle.Value; var shuttleComp = Comp(component.Shuttle); var arrivalsComp = EnsureComp(component.Shuttle); arrivalsComp.Station = uid; diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 6c4bdc08148..afa77421bd7 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -29,10 +29,9 @@ using Content.Shared.Tag; using Content.Shared.Tiles; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; -using Robust.Shared.Map; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Random; @@ -60,7 +59,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem [Dependency] private readonly DockingSystem _dock = default!; [Dependency] private readonly IdCardSystem _idSystem = default!; [Dependency] private readonly NavMapSystem _navMap = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; @@ -531,10 +530,11 @@ private void AddCentcomm(EntityUid station, StationCentcommComponent component) } var map = _mapSystem.CreateMap(out var mapId); - var grid = _map.LoadGrid(mapId, component.Map.ToString(), new MapLoadOptions() + if (!_loader.TryLoadGrid(mapId, component.Map, out var grid)) { - LoadMap = false, - }); + Log.Error($"Failed to set up centcomm grid!"); + return; + } if (!Exists(map)) { @@ -608,15 +608,11 @@ private void AddEmergencyShuttle(Entity 0) + if (_loader.TryLoadGrid(mapId, component.Path, out var ent)) { - if (HasComp(ent[0])) - { - TryFTLProximity(ent[0], targetGrid.Value); - } + if (HasComp(ent)) + TryFTLProximity(ent.Value, targetGrid.Value); - _station.AddGridToStation(uid, ent[0]); + _station.AddGridToStation(uid, ent.Value); } - _mapManager.DeleteMap(mapId); + _mapSystem.DeleteMap(mapId); } private bool TryDungeonSpawn(Entity targetGrid, DungeonSpawnGroup group, out EntityUid spawned) @@ -143,20 +141,18 @@ private bool TryGridSpawn(EntityUid targetGrid, EntityUid stationUid, MapId mapI var path = paths[^1]; paths.RemoveAt(paths.Count - 1); - if (_loader.TryLoad(mapId, path.ToString(), out var ent) && ent.Count == 1) + if (_loader.TryLoadGrid(mapId, path, out var grid)) { - if (HasComp(ent[0])) - { - TryFTLProximity(ent[0], targetGrid); - } + if (HasComp(grid)) + TryFTLProximity(grid.Value, targetGrid); if (group.NameGrid) { var name = path.FilenameWithoutExtension; - _metadata.SetEntityName(ent[0], name); + _metadata.SetEntityName(grid.Value, name); } - spawned = ent[0]; + spawned = grid.Value; return true; } @@ -227,7 +223,7 @@ private void GridSpawns(EntityUid uid, GridSpawnComponent component) } } - _mapManager.DeleteMap(mapId); + _mapSystem.DeleteMap(mapId); } private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapInitEvent args) @@ -246,23 +242,22 @@ private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapIn _mapSystem.CreateMap(out var mapId); var valid = false; - if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) && - ent.Count == 1 && - TryComp(ent[0], out TransformComponent? shuttleXform)) + if (_loader.TryLoadGrid(mapId, component.Path, out var grid)) { - var escape = GetSingleDock(ent[0]); + var escape = GetSingleDock(grid.Value); if (escape != null) { - var config = _dockSystem.GetDockingConfig(ent[0], xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock); + var config = _dockSystem.GetDockingConfig(grid.Value, xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock); if (config != null) { - FTLDock((ent[0], shuttleXform), config); + var shuttleXform = Transform(grid.Value); + FTLDock((grid.Value, shuttleXform), config); if (TryComp(xform.GridUid, out var stationMember)) { - _station.AddGridToStation(stationMember.Station, ent[0]); + _station.AddGridToStation(stationMember.Station, grid.Value); } valid = true; @@ -273,11 +268,11 @@ private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapIn { var compType = compReg.Component.GetType(); - if (HasComp(ent[0], compType)) + if (HasComp(grid.Value, compType)) continue; var comp = _factory.GetComponent(compType); - AddComp(ent[0], comp, true); + AddComp(grid.Value, comp, true); } } @@ -286,7 +281,7 @@ private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapIn Log.Error($"Error loading gridfill dock for {ToPrettyString(uid)} / {component.Path}"); } - _mapManager.DeleteMap(mapId); + _mapSystem.DeleteMap(mapId); } private (EntityUid Entity, DockingComponent Component)? GetSingleDock(EntityUid uid) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 6e8c1a9e204..f2c58d103c9 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -17,6 +17,7 @@ using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 6f508d9038c..14c80362871 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -8,6 +8,7 @@ using Robust.Shared.GameStates; using Robust.Shared.Input; using Robust.Shared.Input.Binding; +using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -207,7 +208,7 @@ private bool TryUpdateRelative(InputMoverComponent mover, TransformComponent xfo } // If we went from grid -> map we'll preserve our worldrotation - if (relative != null && _mapManager.IsMap(relative.Value)) + if (relative != null && HasComp(relative.Value)) { targetRotation = currentRotation.FlipPositive().Reduced(); } diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 2051c24be87..9a4ca97210b 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -454,7 +454,7 @@ duration: 1 - type: RuleGrids - type: LoadMapRule - mapPath: /Maps/Shuttles/ShuttleEvent/striker.yml + gridPath: /Maps/Shuttles/ShuttleEvent/striker.yml - type: NukeopsRule roundEndBehavior: Nothing - type: AntagSelection diff --git a/Resources/Prototypes/Maps/centcomm.yml b/Resources/Prototypes/Maps/centcomm.yml index 47dc5ca1f3e..007da851d01 100644 --- a/Resources/Prototypes/Maps/centcomm.yml +++ b/Resources/Prototypes/Maps/centcomm.yml @@ -1,5 +1,6 @@ - type: gameMap id: CentComm + isGrid: true # Did you know that centcomm is the only "game map" that isn't actually a map? Send help. mapName: 'Central Command' mapPath: /Maps/centcomm.yml minPlayers: 10 diff --git a/RobustToolbox b/RobustToolbox index 5e97db435c0..3cccf5be028 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 5e97db435c05b4c188184ef90e5d77b0500403d0 +Subproject commit 3cccf5be028be75242ffc86877b4e78a72b8cafe From 37c843328f964233a5e6690468729928eb05c0b4 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 23 Dec 2024 14:22:27 +1300 Subject: [PATCH 004/365] Fix NoSavedPostMapInitTest --- .../Tests/PostMapInitTest.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index f7562d934dd..0a48730d54b 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -111,12 +111,15 @@ public async Task NoSavedPostMapInitTest() var server = pair.Server; var resourceManager = server.ResolveDependency(); + var loader = server.System(); + var mapFolder = new ResPath("/Maps"); var maps = resourceManager .ContentFindFiles(mapFolder) .Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal)) .ToArray(); + var v7Maps = new List(); foreach (var map in maps) { var rootedPath = map.ToRootedPath(); @@ -139,10 +142,41 @@ public async Task NoSavedPostMapInitTest() var root = yamlStream.Documents[0].RootNode; var meta = root["meta"]; - var postMapInit = meta["postmapinit"].AsBool(); + var version = meta["format"].AsInt(); + + if (version >= 7) + { + v7Maps.Add(map); + continue; + } + var postMapInit = meta["postmapinit"].AsBool(); Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit"); } + + var deps = server.ResolveDependency().DependencyCollection; + foreach (var map in v7Maps) + { + if (!loader.TryReadFile(map, out var data)) + { + Assert.Fail($"Failed to read {map}"); + continue; + } + + var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default); + if (!reader.TryProcessData()) + { + Assert.Fail($"Failed to process {map}"); + continue; + } + + foreach (var mapId in reader.MapYamlIds) + { + var mapData = reader.YamlEntities[mapId]; + Assert.That(!mapData.PostInit, $"Map {map.Filename} contains a postmapinit map with yaml id: {mapId}"); + } + } + await pair.CleanReturnAsync(); } From d47e72d7c2c6290563bf8519b2f6bb3a3c82f995 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 23 Dec 2024 16:38:05 +1300 Subject: [PATCH 005/365] oops --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index 3cccf5be028..5e97db435c0 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 3cccf5be028be75242ffc86877b4e78a72b8cafe +Subproject commit 5e97db435c05b4c188184ef90e5d77b0500403d0 From 43e6fd57d4e7136747bd372e5af1d1f3dfed57a6 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 23 Dec 2024 18:02:46 +1300 Subject: [PATCH 006/365] Fix MapLoadBenchmark --- Content.Benchmarks/MapLoadBenchmark.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 8c04d9a40dc..abf99f9836f 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -6,12 +6,13 @@ using Content.IntegrationTests; using Content.IntegrationTests.Pair; using Content.Server.Maps; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Benchmarks; @@ -20,7 +21,7 @@ public class MapLoadBenchmark { private TestPair _pair = default!; private MapLoaderSystem _mapLoader = default!; - private IMapManager _mapManager = default!; + private SharedMapSystem _mapSys = default!; [GlobalSetup] public void Setup() @@ -36,7 +37,7 @@ public void Setup() .ToDictionary(x => x.ID, x => x.MapPath.ToString()); _mapLoader = server.ResolveDependency().GetEntitySystem(); - _mapManager = server.ResolveDependency(); + _mapSys = server.ResolveDependency().GetEntitySystem(); } [GlobalCleanup] @@ -52,17 +53,19 @@ public async Task Cleanup() public string Map; public Dictionary Paths; + private MapId _mapId; [Benchmark] public async Task LoadMap() { - var mapPath = Paths[Map]; + var mapPath = new ResPath(Paths[Map]); var server = _pair.Server; await server.WaitPost(() => { - var success = _mapLoader.TryLoad(new MapId(10), mapPath, out _); + var success = _mapLoader.TryLoadMap(mapPath, out var map, out _); if (!success) throw new Exception("Map load failed"); + _mapId = map.Value.Comp.MapId; }); } @@ -70,9 +73,7 @@ await server.WaitPost(() => public void IterationCleanup() { var server = _pair.Server; - server.WaitPost(() => - { - _mapManager.DeleteMap(new MapId(10)); - }).Wait(); + server.WaitPost(() => _mapSys.DeleteMap(_mapId)) + .Wait(); } } From ef3d7396d496374b639c949da4d9a5bb8153e057 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Mon, 23 Dec 2024 18:13:49 +1300 Subject: [PATCH 007/365] fix other benchmarks --- Content.Benchmarks/ComponentQueryBenchmark.cs | 12 ++++++------ Content.Benchmarks/PvsBenchmark.cs | 11 ++++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs index 11c7ab9d5f5..49e9984c093 100644 --- a/Content.Benchmarks/ComponentQueryBenchmark.cs +++ b/Content.Benchmarks/ComponentQueryBenchmark.cs @@ -9,13 +9,14 @@ using Content.Shared.Clothing.Components; using Content.Shared.Doors.Components; using Content.Shared.Item; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Benchmarks; @@ -32,7 +33,6 @@ public class ComponentQueryBenchmark private TestPair _pair = default!; private IEntityManager _entMan = default!; - private MapId _mapId = new(10); private EntityQuery _itemQuery; private EntityQuery _clothingQuery; private EntityQuery _mapQuery; @@ -54,10 +54,10 @@ public void Setup() _pair.Server.ResolveDependency().SetSeed(42); _pair.Server.WaitPost(() => { - var success = _entMan.System().TryLoad(_mapId, Map, out _); - if (!success) + var map = new ResPath(Map); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + if (!_entMan.System().TryLoadMap(map, out _, out _, opts)) throw new Exception("Map load failed"); - _pair.Server.MapMan.DoMapInitialize(_mapId); }).GetAwaiter().GetResult(); _items = new EntityUid[_entMan.Count()]; diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index fa7f9d45422..2f87545426e 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -7,13 +7,15 @@ using Content.IntegrationTests.Pair; using Content.Server.Mind; using Content.Server.Warps; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Benchmarks; @@ -34,7 +36,6 @@ public class PvsBenchmark private TestPair _pair = default!; private IEntityManager _entMan = default!; - private MapId _mapId = new(10); private ICommonSession[] _players = default!; private EntityCoordinates[] _spawns = default!; public int _cycleOffset = 0; @@ -65,10 +66,10 @@ private async Task SetupAsync() _pair.Server.ResolveDependency().SetSeed(42); await _pair.Server.WaitPost(() => { - var success = _entMan.System().TryLoad(_mapId, Map, out _); - if (!success) + var path = new ResPath(Map); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + if (!_entMan.System().TryLoadMap(path, out _, out _, opts)) throw new Exception("Map load failed"); - _pair.Server.MapMan.DoMapInitialize(_mapId); }); // Get list of ghost warp positions From f58baf042850680f9a03714976a3b33c48ca5ddf Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 24 Dec 2024 03:37:31 +1300 Subject: [PATCH 008/365] Update permissions for engine toolshed PR --- Resources/toolshedEngineCommandPerms.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/toolshedEngineCommandPerms.yml b/Resources/toolshedEngineCommandPerms.yml index b9911e9468d..ae02339c1a2 100644 --- a/Resources/toolshedEngineCommandPerms.yml +++ b/Resources/toolshedEngineCommandPerms.yml @@ -74,7 +74,7 @@ - '%' - '%/' - '&~' - - '|~' + - bitornot - '^~' - '~' - '<' @@ -88,7 +88,7 @@ - '*/' - '//' - '&' - - '|' + - bitor - '^' - neg - abs From 9d05ea9f57dcb0931f820dab19648d6e6c7c30b9 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 24 Dec 2024 04:01:17 +1300 Subject: [PATCH 009/365] Poke tests From eef7d02e11c8bcf88fbee50ad87717d239ec1c48 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 24 Dec 2024 12:27:33 +1300 Subject: [PATCH 010/365] Improve NoSavedPostMapInitTest --- .../Tests/PostMapInitTest.cs | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 0a48730d54b..80e8e7b3182 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -18,6 +18,7 @@ using Content.Shared.Station.Components; using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.IoC; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; @@ -157,29 +158,54 @@ public async Task NoSavedPostMapInitTest() var deps = server.ResolveDependency().DependencyCollection; foreach (var map in v7Maps) { - if (!loader.TryReadFile(map, out var data)) - { - Assert.Fail($"Failed to read {map}"); - continue; - } + Assert.That(IsPreInit(map, loader, deps)); + } - var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default); - if (!reader.TryProcessData()) - { - Assert.Fail($"Failed to process {map}"); - continue; - } + // Check that the test actually does manage to catch post-init maps and isn't just blindly passing everything. + // To that end, create a new post-init map and try verify it. + var mapSys = server.System(); + MapId id = default; + await server.WaitPost(() => mapSys.CreateMap(out id, runMapInit: false)); + await server.WaitPost(() => server.EntMan.Spawn(null, new MapCoordinates(0, 0, id))); - foreach (var mapId in reader.MapYamlIds) - { - var mapData = reader.YamlEntities[mapId]; - Assert.That(!mapData.PostInit, $"Map {map.Filename} contains a postmapinit map with yaml id: {mapId}"); - } - } + // First check that a pre-init version passes + var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml"); + loader.SaveMap(id, path); + Assert.That(IsPreInit(path, loader, deps)); + + // and the post-init version fails. + await server.WaitPost(() => mapSys.InitializeMap(id)); + loader.SaveMap(id, path); + Assert.That(IsPreInit(path, loader, deps), Is.False); await pair.CleanReturnAsync(); } + private bool IsPreInit(ResPath map, MapLoaderSystem loader, IDependencyCollection deps) + { + if (!loader.TryReadFile(map, out var data)) + { + Assert.Fail($"Failed to read {map}"); + return false; + } + + var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default); + if (!reader.TryProcessData()) + { + Assert.Fail($"Failed to process {map}"); + return false; + } + + foreach (var mapId in reader.MapYamlIds) + { + var mapData = reader.YamlEntities[mapId]; + if (mapData.PostInit) + return false; + } + + return true; + } + [Test, TestCaseSource(nameof(GameMaps))] public async Task GameMapsLoadableTest(string mapProto) { From a8cc0397c2634a085fbb424c7bdc2c951bb4cd72 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 24 Dec 2024 18:57:52 +1300 Subject: [PATCH 011/365] Moar engine changes --- .../Tests/Body/SaveLoadReparentTest.cs | 3 +-- .../Tests/PostMapInitTest.cs | 4 ++-- .../Tests/SaveLoadMapTest.cs | 4 ++-- .../Tests/SaveLoadSaveTest.cs | 24 +++++++++---------- .../Commands/PersistenceSaveCommand.cs | 5 ++-- .../DeviceNetwork/Systems/DeviceListSystem.cs | 4 ++-- .../Systems/NetworkConfiguratorSystem.cs | 4 ++-- Content.Server/Mapping/MappingSystem.cs | 2 +- Content.Server/Maps/ResaveCommand.cs | 4 ++-- Content.Shared/Follower/FollowerSystem.cs | 4 ++-- 10 files changed, 28 insertions(+), 30 deletions(-) diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs index f56b5c28501..67163d07961 100644 --- a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs +++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs @@ -2,7 +2,6 @@ using System.Linq; using Content.Shared.Body.Components; using Content.Shared.Body.Systems; -using Robust.Server.GameObjects; using Robust.Shared.Containers; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; @@ -115,7 +114,7 @@ await server.WaitAssertion(() => var mapPath = new ResPath($"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml"); - mapLoader.SaveMap(mapId, mapPath); + Assert.That(mapLoader.TrySaveMap(mapId, mapPath)); mapSys.DeleteMap(mapId); Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _), Is.True); diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 80e8e7b3182..9e908371df5 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -170,12 +170,12 @@ public async Task NoSavedPostMapInitTest() // First check that a pre-init version passes var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml"); - loader.SaveMap(id, path); + Assert.That(loader.TrySaveMap(id, path)); Assert.That(IsPreInit(path, loader, deps)); // and the post-init version fails. await server.WaitPost(() => mapSys.InitializeMap(id)); - loader.SaveMap(id, path); + Assert.That(loader.TrySaveMap(id, path)); Assert.That(IsPreInit(path, loader, deps), Is.False); await pair.CleanReturnAsync(); diff --git a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs index b1f6dd84330..b96fbe57673 100644 --- a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs @@ -48,8 +48,8 @@ await server.WaitAssertion(() => mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(2, (TileRenderFlag) 1, 254)); } - Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath)); - Assert.Multiple(() => mapSystem.DeleteMap(mapId)); + Assert.That(mapLoader.TrySaveMap(mapId, mapPath)); + mapSystem.DeleteMap(mapId); }); await server.WaitIdleAsync(); diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs index a398b559197..b41aa0bf2f3 100644 --- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs @@ -41,10 +41,10 @@ await server.WaitPost(() => mapSystem.CreateMap(out var mapId0); var grid0 = mapManager.CreateGridEntity(mapId0); entManager.RunMapInit(grid0.Owner, entManager.GetComponent(grid0)); - mapLoader.SaveGrid(grid0.Owner, rp1); + Assert.That(mapLoader.TrySaveGrid(grid0.Owner, rp1)); mapSystem.CreateMap(out var mapId1); Assert.That(mapLoader.TryLoadGrid(mapId1, rp1, out var grid1)); - mapLoader.SaveGrid(grid1!.Value, rp2); + Assert.That(mapLoader.TrySaveGrid(grid1!.Value, rp2)); }); await server.WaitIdleAsync(); @@ -116,7 +116,7 @@ public async Task LoadSaveTicksSaveBagel() var path = new ResPath(TestMap); Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); mapId = map!.Value.Comp.MapId; - mapLoader.SaveMap(mapId, rp1); + Assert.That(mapLoader.TrySaveMap(mapId, rp1)); }); // Run 5 ticks. @@ -124,7 +124,7 @@ public async Task LoadSaveTicksSaveBagel() await server.WaitPost(() => { - mapLoader.SaveMap(mapId, rp2); + Assert.That(mapLoader.TrySaveMap(mapId, rp2)); }); await server.WaitIdleAsync(); @@ -196,8 +196,8 @@ public async Task LoadTickLoadBagel() MapId mapId1 = default; MapId mapId2 = default; - const string fileA = "/load tick load a.yml"; - const string fileB = "/load tick load b.yml"; + var fileA = new ResPath("/load tick load a.yml"); + var fileB = new ResPath("/load tick load b.yml"); string yamlA; string yamlB; @@ -207,11 +207,11 @@ public async Task LoadTickLoadBagel() var path = new ResPath(TestMap); Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); mapId1 = map!.Value.Comp.MapId; - mapLoader.SaveMap(mapId1, fileA); + Assert.That(mapLoader.TrySaveMap(mapId1, fileA)); }); await server.WaitIdleAsync(); - await using (var stream = userData.Open(new ResPath(fileA), FileMode.Open)) + await using (var stream = userData.Open(fileA, FileMode.Open)) using (var reader = new StreamReader(stream)) { yamlA = await reader.ReadToEndAsync(); @@ -225,12 +225,12 @@ public async Task LoadTickLoadBagel() var path = new ResPath(TestMap); Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); mapId2 = map!.Value.Comp.MapId; - mapLoader.SaveMap(mapId2, fileB); + Assert.That(mapLoader.TrySaveMap(mapId2, fileB)); }); await server.WaitIdleAsync(); - await using (var stream = userData.Open(new ResPath(fileB), FileMode.Open)) + await using (var stream = userData.Open(fileB, FileMode.Open)) using (var reader = new StreamReader(stream)) { yamlB = await reader.ReadToEndAsync(); @@ -253,10 +253,10 @@ private sealed class SaveLoadSaveTestSystem : EntitySystem public bool Enabled; public override void Initialize() { - SubscribeLocalEvent(OnAfterSave); + SubscribeLocalEvent(OnAfterSave); } - private void OnAfterSave(AfterSaveEvent ev) + private void OnAfterSave(AfterSerializationEvent ev) { if (!Enabled) return; diff --git a/Content.Server/Administration/Commands/PersistenceSaveCommand.cs b/Content.Server/Administration/Commands/PersistenceSaveCommand.cs index dffe1eff7f3..cae507f6d8e 100644 --- a/Content.Server/Administration/Commands/PersistenceSaveCommand.cs +++ b/Content.Server/Administration/Commands/PersistenceSaveCommand.cs @@ -1,11 +1,10 @@ using Content.Shared.Administration; using Content.Shared.CCVar; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Map; -using System.Linq; using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Utility; namespace Content.Server.Administration.Commands; @@ -49,7 +48,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) } var mapLoader = _system.GetEntitySystem(); - mapLoader.SaveMap(mapId, saveFilePath); + mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath)); shell.WriteLine(Loc.GetString("cmd-savemap-success")); } } diff --git a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs index a8d40882c4d..4defec0aaba 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs @@ -20,7 +20,7 @@ public override void Initialize() SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnBeforeBroadcast); SubscribeLocalEvent(OnBeforePacketSent); - SubscribeLocalEvent(OnMapSave); + SubscribeLocalEvent(OnMapSave); } private void OnShutdown(EntityUid uid, DeviceListComponent component, ComponentShutdown args) @@ -124,7 +124,7 @@ public void OnDeviceShutdown(Entity list, Entity toRemove = new(); var query = GetEntityQuery(); diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index efaf5680947..7e6452d955b 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -67,10 +67,10 @@ public override void Initialize() SubscribeLocalEvent(OnComponentRemoved); - SubscribeLocalEvent(OnMapSave); + SubscribeLocalEvent(OnMapSave); } - private void OnMapSave(BeforeSaveEvent ev) + private void OnMapSave(BeforeSerializationEvent ev) { var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var conf)) diff --git a/Content.Server/Mapping/MappingSystem.cs b/Content.Server/Mapping/MappingSystem.cs index ecdbbe081c1..1ef6944924e 100644 --- a/Content.Server/Mapping/MappingSystem.cs +++ b/Content.Server/Mapping/MappingSystem.cs @@ -78,7 +78,7 @@ public override void Update(float frameTime) var path = Path.Combine(saveDir, $"{DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss")}-AUTO.yml"); _currentlyAutosaving[map] = (CalculateNextTime(), name); Log.Info($"Autosaving map {name} ({map}) to {path}. Next save in {ReadableTimeLeft(map)} seconds."); - _loader.SaveMap(map, path); + _loader.TrySaveMap(map, new ResPath(path)); } } diff --git a/Content.Server/Maps/ResaveCommand.cs b/Content.Server/Maps/ResaveCommand.cs index 0d48f946be8..39507c850ff 100644 --- a/Content.Server/Maps/ResaveCommand.cs +++ b/Content.Server/Maps/ResaveCommand.cs @@ -62,11 +62,11 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) if (_entManager.HasComponent(map)) { - loader.SaveMap(map.Comp.MapId, fn); + loader.TrySaveMap(map.Comp.MapId, fn); } else if (result.Grids.Count == 1) { - loader.SaveGrid(result.Grids.First(), fn); + loader.TrySaveGrid(result.Grids.First(), fn); } else { diff --git a/Content.Shared/Follower/FollowerSystem.cs b/Content.Shared/Follower/FollowerSystem.cs index 0c7bc99c464..ec13a4f7c2a 100644 --- a/Content.Shared/Follower/FollowerSystem.cs +++ b/Content.Shared/Follower/FollowerSystem.cs @@ -42,7 +42,7 @@ public override void Initialize() SubscribeLocalEvent(OnFollowedAttempt); SubscribeLocalEvent(OnGotEquippedHand); SubscribeLocalEvent(OnFollowedTerminating); - SubscribeLocalEvent(OnBeforeSave); + SubscribeLocalEvent(OnBeforeSave); } private void OnFollowedAttempt(Entity ent, ref ComponentGetStateAttemptEvent args) @@ -60,7 +60,7 @@ private void OnFollowedAttempt(Entity ent, ref ComponentGetSt } } - private void OnBeforeSave(BeforeSaveEvent ev) + private void OnBeforeSave(BeforeSerializationEvent ev) { // Some followers will not be map savable. This ensures that maps don't get saved with empty/invalid // followers, but just stopping any following on the map being saved. From b555a6049ee6faa15ca03759f16ab32b7eaff6a3 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Tue, 24 Dec 2024 21:00:53 +1300 Subject: [PATCH 012/365] Even more engine changes --- Content.IntegrationTests/Tests/PostMapInitTest.cs | 2 +- .../DeviceNetwork/Systems/DeviceListSystem.cs | 7 +++++-- .../Systems/NetworkConfiguratorSystem.cs | 8 ++++++-- Content.Server/Mapping/MappingManager.cs | 3 ++- Content.Server/Maps/ResaveCommand.cs | 2 +- Content.Server/Procedural/DungeonSystem.cs | 2 +- Content.Shared/Follower/FollowerSystem.cs | 13 ++++++++++--- 7 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 9e908371df5..f07d1e49968 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -426,7 +426,7 @@ await server.WaitPost(() => { try { - Assert.That(mapLoader.TryLoadEntities(path, out maps, out _, opts)); + Assert.That(mapLoader.TryLoadGeneric(path, out maps, out _, opts)); } catch (Exception ex) { diff --git a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs index 4defec0aaba..1988a07cea0 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs @@ -131,7 +131,7 @@ private void OnMapSave(BeforeSerializationEvent ev) var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var device, out var xform)) { - if (xform.MapUid != ev.Map) + if (!ev.MapIds.Contains(xform.MapID)) continue; foreach (var ent in device.Devices) @@ -144,7 +144,10 @@ private void OnMapSave(BeforeSerializationEvent ev) continue; } - if (linkedXform.MapUid == ev.Map) + // This is assuming that **all** of the map is getting saved. + // Which is not necessarily true. + // AAAAAAAAAAAAAA + if (ev.MapIds.Contains(linkedXform.MapID)) continue; toRemove.Add(ent); diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index 7e6452d955b..645d28f6d24 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -75,7 +75,10 @@ private void OnMapSave(BeforeSerializationEvent ev) var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var conf)) { - if (CompOrNull(conf.ActiveDeviceList)?.MapUid != ev.Map) + if (!TryComp(conf.ActiveDeviceList, out TransformComponent? listXform)) + continue; + + if (!ev.MapIds.Contains(listXform.MapID)) continue; // The linked device list is (probably) being saved. Make sure that the configurator is also being saved @@ -83,9 +86,10 @@ private void OnMapSave(BeforeSerializationEvent ev) // containing a set of all entities that are about to be saved, which would make checking this much easier. // This is a shitty bandaid, and will force close the UI during auto-saves. // TODO Map serialization refactor + // I'm refactoring it now and I still dont know what to do var xform = Transform(uid); - if (xform.MapUid == ev.Map && IsSaveable(uid)) + if (ev.MapIds.Contains(xform.MapID) && IsSaveable(uid)) continue; _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure); diff --git a/Content.Server/Mapping/MappingManager.cs b/Content.Server/Mapping/MappingManager.cs index be6f503bf0b..0097df2e553 100644 --- a/Content.Server/Mapping/MappingManager.cs +++ b/Content.Server/Mapping/MappingManager.cs @@ -54,7 +54,8 @@ private void OnMappingSaveMap(MappingSaveMapMessage message) return; } - var data = _systems.GetEntitySystem().SerializeEntityRecursive(mapUid).Node; + var sys = _systems.GetEntitySystem(); + var data = sys.SerializeEntitiesRecursive([mapUid]).Node; var document = new YamlDocument(data.ToYaml()); var stream = new YamlStream { document }; var writer = new StringWriter(); diff --git a/Content.Server/Maps/ResaveCommand.cs b/Content.Server/Maps/ResaveCommand.cs index 39507c850ff..c48c02c1f73 100644 --- a/Content.Server/Maps/ResaveCommand.cs +++ b/Content.Server/Maps/ResaveCommand.cs @@ -44,7 +44,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) var fn = files[i]; log.Info($"Re-saving file {i}/{files.Count} : {fn}"); - if (!loader.TryLoadEntities(fn, out var result, opts)) + if (!loader.TryLoadGeneric(fn, out var result, opts)) continue; if (result.Maps.Count != 1) diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 75cdb69130e..521ebed7ec7 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -182,7 +182,7 @@ public MapId GetOrCreateTemplate(DungeonRoomPrototype proto) ExpectedCategory = FileCategory.Map }; - if (!_loader.TryLoadEntities(proto.AtlasPath, out var res, opts) || !res.Maps.TryFirstOrNull(out var 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(map.Value.Owner); diff --git a/Content.Shared/Follower/FollowerSystem.cs b/Content.Shared/Follower/FollowerSystem.cs index ec13a4f7c2a..feea40fb2ff 100644 --- a/Content.Shared/Follower/FollowerSystem.cs +++ b/Content.Shared/Follower/FollowerSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Numerics; using Content.Shared.Administration.Managers; using Content.Shared.Database; @@ -62,8 +63,14 @@ private void OnFollowedAttempt(Entity ent, ref ComponentGetSt private void OnBeforeSave(BeforeSerializationEvent ev) { - // Some followers will not be map savable. This ensures that maps don't get saved with empty/invalid - // followers, but just stopping any following on the map being saved. + // Some followers will not be map savable. This ensures that maps don't get saved with some entities that have + // empty/invalid followers, by just stopping any following happening on the map being saved. + // I hate this so much. + // TODO WeakEntityReference + // We need some way to store entity references in a way that doesn't imply that the entity still exists. + // Then we wouldn't have to deal with this shit. + + var maps = ev.Entities.Select(x => Transform(x).MapUid).ToHashSet(); var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var follower, out var xform, out var meta)) @@ -71,7 +78,7 @@ private void OnBeforeSave(BeforeSerializationEvent ev) if (meta.EntityPrototype == null || meta.EntityPrototype.MapSavable) continue; - if (xform.MapUid != ev.Map) + if (!maps.Contains(xform.MapUid)) continue; StopFollowingEntity(uid, follower.Following); From ad317f79b184e5122d91c478cb2d1b23e5a15fdc Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Wed, 25 Dec 2024 16:26:38 +1300 Subject: [PATCH 013/365] Poke engine tests From 74deed794e196e4a0c609b4c512450755c01ba98 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 27 Dec 2024 15:47:32 +1300 Subject: [PATCH 014/365] Eng changes --- Content.Server/GameTicking/GameTicker.RoundFlow.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 37e7a4e1ce5..40e3303c2d7 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -316,7 +316,6 @@ public IReadOnlyList MergeGameMap( if (!_loader.TryMergeMap(targetMap, ev.GameMap.MapPath, - out var map, out var grids, ev.Options, ev.Offset, From f95fb450fd936a4d5e73a34f4fd641c9a4dc4fcc Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Thu, 2 Jan 2025 17:00:13 +1300 Subject: [PATCH 015/365] obsolete --- Content.Server/Commands/CommandUtils.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Content.Server/Commands/CommandUtils.cs b/Content.Server/Commands/CommandUtils.cs index 30ea41b7c9f..fde52d18e1a 100644 --- a/Content.Server/Commands/CommandUtils.cs +++ b/Content.Server/Commands/CommandUtils.cs @@ -4,6 +4,7 @@ using Robust.Shared.Console; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Toolshed.Commands.Generic; namespace Content.Server.Commands { @@ -51,6 +52,7 @@ public static bool TryGetAttachedEntityByUsernameOrId(IConsoleShell shell, return true; } + [Obsolete($"Use toolshed's {nameof(EmplaceCommand)}")] public static string SubstituteEntityDetails(IConsoleShell shell, EntityUid ent, string ruleString) { var entMan = IoCManager.Resolve(); From 48c13b32f43be057775acb8a87aa950dbd92adf5 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Thu, 2 Jan 2025 17:00:45 +1300 Subject: [PATCH 016/365] Remove unused helper. Use EmplaceCommand instead --- Content.Server/Commands/CommandUtils.cs | 41 ------------------------- 1 file changed, 41 deletions(-) diff --git a/Content.Server/Commands/CommandUtils.cs b/Content.Server/Commands/CommandUtils.cs index fde52d18e1a..502c3ae0241 100644 --- a/Content.Server/Commands/CommandUtils.cs +++ b/Content.Server/Commands/CommandUtils.cs @@ -51,46 +51,5 @@ public static bool TryGetAttachedEntityByUsernameOrId(IConsoleShell shell, attachedEntity = session.AttachedEntity.Value; return true; } - - [Obsolete($"Use toolshed's {nameof(EmplaceCommand)}")] - public static string SubstituteEntityDetails(IConsoleShell shell, EntityUid ent, string ruleString) - { - var entMan = IoCManager.Resolve(); - var transform = entMan.GetComponent(ent); - var transformSystem = entMan.System(); - var worldPosition = transformSystem.GetWorldPosition(transform); - - // gross, is there a better way to do this? - ruleString = ruleString.Replace("$ID", ent.ToString()); - ruleString = ruleString.Replace("$WX", - worldPosition.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$WY", - worldPosition.Y.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$LX", - transform.LocalPosition.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$LY", - transform.LocalPosition.Y.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$NAME", entMan.GetComponent(ent).EntityName); - - if (shell.Player is { } player) - { - if (player.AttachedEntity is {Valid: true} p) - { - var pTransform = entMan.GetComponent(p); - var pWorldPosition = transformSystem.GetWorldPosition(pTransform); - - ruleString = ruleString.Replace("$PID", ent.ToString()); - ruleString = ruleString.Replace("$PWX", - pWorldPosition.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$PWY", - pWorldPosition.Y.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$PLX", - pTransform.LocalPosition.X.ToString(CultureInfo.InvariantCulture)); - ruleString = ruleString.Replace("$PLY", - pTransform.LocalPosition.Y.ToString(CultureInfo.InvariantCulture)); - } - } - return ruleString; - } } } From e5468982f5806dbf0066be3dd671f5da949414b6 Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Fri, 3 Jan 2025 18:45:02 +1300 Subject: [PATCH 017/365] Fix mark verb --- Content.Server/Administration/Systems/AdminVerbSystem.cs | 2 +- Content.Server/Administration/Toolshed/MarkedCommand.cs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 0640537f57e..43b60075837 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -105,7 +105,7 @@ private void AddAdminVerbs(GetVerbsEvent args) mark.Text = Loc.GetString("toolshed-verb-mark"); mark.Message = Loc.GetString("toolshed-verb-mark-description"); mark.Category = VerbCategory.Admin; - mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", Enumerable.Repeat(args.Target, 1), out _); + mark.Act = () => _toolshed.InvokeCommand(player, "=> $marked", new List {args.Target}, out _); mark.Impact = LogImpact.Low; args.Verbs.Add(mark); diff --git a/Content.Server/Administration/Toolshed/MarkedCommand.cs b/Content.Server/Administration/Toolshed/MarkedCommand.cs index 54d45a352de..c2b9ecf79a0 100644 --- a/Content.Server/Administration/Toolshed/MarkedCommand.cs +++ b/Content.Server/Administration/Toolshed/MarkedCommand.cs @@ -9,8 +9,7 @@ public sealed class MarkedCommand : ToolshedCommand [CommandImplementation] public IEnumerable Marked(IInvocationContext ctx) { - var res = (IEnumerable?)ctx.ReadVar("marked"); - res ??= Array.Empty(); - return res; + var marked = ctx.ReadVar("marked") as IEnumerable; + return marked ?? Array.Empty(); } } From 0d8d50de921adc9d821ec89ceb7801505a3eb7de Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sat, 18 Jan 2025 16:29:21 +1300 Subject: [PATCH 018/365] Fix elk and add new test --- .../Tests/PostMapInitTest.cs | 54 +++++++++++++++++-- .../Maps/Shuttles/emergency_elkridge.yml | 14 +---- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index b47416ec82e..fac55699708 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -38,10 +38,7 @@ public sealed class PostMapInitTest private static readonly string[] Grids = { - "/Maps/centcomm.yml", - "/Maps/Shuttles/cargo.yml", - "/Maps/Shuttles/emergency.yml", - "/Maps/Shuttles/infiltrator.yml", + "/Maps/centcomm.yml" }; private static readonly string[] GameMaps = @@ -105,6 +102,55 @@ await server.WaitPost(() => await pair.CleanReturnAsync(); } + /// + /// Asserts that shuttles are loadable and have been saved as grids and not maps. + /// + [Test] + public async Task ShuttlesLoadableTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entManager = server.ResolveDependency(); + var resMan = server.ResolveDependency(); + var mapLoader = entManager.System(); + var mapSystem = entManager.System(); + var cfg = server.ResolveDependency(); + Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + + var shuttleFolder = new ResPath("/Maps/Shuttles"); + var shuttles = resMan + .ContentFindFiles(shuttleFolder) + .Where(filePath => + filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal)) + .ToArray(); + + await server.WaitPost(() => + { + Assert.Multiple(() => + { + foreach (var path in shuttles) + { + mapSystem.CreateMap(out var mapId); + try + { + Assert.That(mapLoader.TryLoadGrid(mapId, path, out _), + $"Failed to load shuttle {path}, was it saved as a map instead of a grid?"); + } + catch (Exception ex) + { + throw new Exception($"Failed to load shuttle {path}, was it saved as a map instead of a grid?", + ex); + } + mapSystem.DeleteMap(mapId); + } + }); + }); + await server.WaitRunTicks(1); + + await pair.CleanReturnAsync(); + } + [Test] public async Task NoSavedPostMapInitTest() { diff --git a/Resources/Maps/Shuttles/emergency_elkridge.yml b/Resources/Maps/Shuttles/emergency_elkridge.yml index efeba6844bc..4dafdee84a2 100644 --- a/Resources/Maps/Shuttles/emergency_elkridge.yml +++ b/Resources/Maps/Shuttles/emergency_elkridge.yml @@ -18,7 +18,7 @@ entities: name: NT Evac Log - type: Transform pos: -0.42093527,-0.86894274 - parent: 637 + parent: invalid - type: MapGrid chunks: 0,0: @@ -804,18 +804,6 @@ entities: chunkSize: 4 - type: GasTileOverlay - type: RadiationGridResistance - - uid: 637 - components: - - type: MetaData - name: Map Entity - - type: Transform - - type: Map - mapPaused: True - - type: PhysicsMap - - type: GridTree - - type: MovedGrids - - type: Broadphase - - type: OccluderTree - proto: AirAlarm entities: - uid: 577 From b13ccfefd6fd42005928280249a81b4fde9b599a Mon Sep 17 00:00:00 2001 From: ElectroJr Date: Sat, 18 Jan 2025 16:50:50 +1300 Subject: [PATCH 019/365] poke tests From 26fe0292c47b6ec712b046998b0263bae3f7f681 Mon Sep 17 00:00:00 2001 From: juliangiebel Date: Thu, 23 Jan 2025 01:16:07 +0100 Subject: [PATCH 020/365] Update da packages --- Directory.Packages.props | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index eee8c65f9a0..69b5ad1945d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,14 +6,16 @@ --> - - + - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + \ No newline at end of file From 02f7dad6f3ce7c4008e3c98d28718d9ed2cbcaf2 Mon Sep 17 00:00:00 2001 From: Killerqu00 Date: Tue, 28 Jan 2025 17:42:17 +0100 Subject: [PATCH 021/365] Thou shall not map items with "do not map" suffix --- .../Tests/PostMapInitTest.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 9ebd32a40a1..d1420bc4922 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -42,6 +42,11 @@ public sealed class PostMapInitTest "/Maps/Shuttles/infiltrator.yml", }; + private static readonly string[] DoNotMapWhitelist = + { + "/Maps/centcomm.yml", + }; + private static readonly string[] GameMaps = { "Dev", @@ -120,6 +125,7 @@ public async Task NoSavedPostMapInitTest() var server = pair.Server; var resourceManager = server.ResolveDependency(); + var protoManager = server.ResolveDependency(); var mapFolder = new ResPath("/Maps"); var maps = resourceManager .ContentFindFiles(mapFolder) @@ -151,6 +157,21 @@ public async Task NoSavedPostMapInitTest() var postMapInit = meta["postmapinit"].AsBool(); Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit"); + + // testing that maps have nothing with the "DO NOT MAP" suffix + // I do it here because it's basically copy-paste code for the most part + var yamlEntities = root["entities"]; + foreach (var yamlEntity in (YamlSequenceNode)yamlEntities) + { + var protoId = yamlEntity["proto"].AsString(); + protoManager.TryIndex(protoId, out var proto, false); + if (proto is null || proto.EditorSuffix is null) + continue; + if (proto.EditorSuffix.ToUpper().Contains("DO NOT MAP") && !DoNotMapWhitelist.Contains(map.ToString())) + { + Assert.Fail($"\nMap {map} has the DO NOT MAP prototype {proto}"); + } + } } await pair.CleanReturnAsync(); } From d15a770078af35858857078199042eeb84c835ae Mon Sep 17 00:00:00 2001 From: Killerqu00 Date: Wed, 29 Jan 2025 12:22:38 +0100 Subject: [PATCH 022/365] convert it to entity category --- .../Tests/PostMapInitTest.cs | 6 ++-- Resources/Locale/en-US/entity-categories.ftl | 3 ++ .../Catalog/Fills/Lockers/service.yml | 1 + .../Clothing/Head/hardsuit-helmets.yml | 1 + .../Entities/Clothing/Head/helmets.yml | 2 +- .../Entities/Clothing/Head/misc.yml | 5 +-- .../Clothing/OuterClothing/hardsuits.yml | 1 + .../Entities/Clothing/OuterClothing/suits.yml | 3 +- .../Entities/Mobs/Player/silicon.yml | 9 ++--- .../Devices/Circuitboards/Machine/cannons.yml | 5 +++ .../Entities/Objects/Fun/bike_horn.yml | 2 +- .../Prototypes/Entities/Objects/Fun/toys.yml | 2 +- .../Entities/Objects/Misc/paper.yml | 4 +-- .../Entities/Objects/Misc/rubber_stamp.yml | 36 +++++++++---------- .../Medical/handheld_crew_monitor.yml | 2 +- .../Objects/Specific/Medical/healing.yml | 2 +- .../Objects/Specific/Research/disk.yml | 1 + .../Objects/Weapons/Melee/baseball_bat.yml | 2 +- .../Objects/Weapons/Melee/weapon_toolbox.yml | 2 +- .../Machines/Computers/computers.yml | 1 + .../Entities/Structures/Machines/holopad.yml | 3 +- .../Entities/Structures/Shuttles/cannons.yml | 10 +++--- .../Structures/Storage/Closets/closets.yml | 2 +- Resources/Prototypes/Entities/categories.yml | 5 +++ 24 files changed, 64 insertions(+), 46 deletions(-) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index d1420bc4922..906b96ebf02 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -161,15 +161,17 @@ public async Task NoSavedPostMapInitTest() // testing that maps have nothing with the "DO NOT MAP" suffix // I do it here because it's basically copy-paste code for the most part var yamlEntities = root["entities"]; + if (!protoManager.TryIndex("DoNotMap", out var dnmCategory)) + return; foreach (var yamlEntity in (YamlSequenceNode)yamlEntities) { var protoId = yamlEntity["proto"].AsString(); protoManager.TryIndex(protoId, out var proto, false); if (proto is null || proto.EditorSuffix is null) continue; - if (proto.EditorSuffix.ToUpper().Contains("DO NOT MAP") && !DoNotMapWhitelist.Contains(map.ToString())) + if (proto.Categories.Contains(dnmCategory) && !DoNotMapWhitelist.Contains(map.ToString())) { - Assert.Fail($"\nMap {map} has the DO NOT MAP prototype {proto}"); + Assert.Fail($"\nMap {map} has the DO NOT MAP prototype {proto.Name}"); } } } diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl index 4b6cf87942f..a5ed66dd011 100644 --- a/Resources/Locale/en-US/entity-categories.ftl +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -3,3 +3,6 @@ entity-category-name-game-rules = Game Rules entity-category-name-objectives = Objectives entity-category-name-roles = Mind Roles entity-category-name-mapping = Mapping +entity-category-name-donotmap = Do not map + +entity-category-suffix-donotmap = DO NOT MAP diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml index 2d564e0eb0d..55b2165dff7 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/service.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/service.yml @@ -133,6 +133,7 @@ id: ClosetJanitorBombFilled parent: ClosetJanitorBomb suffix: DO NOT MAP, Filled + categories: [ DoNotMap ] components: - type: StorageFill contents: diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index b97df840a74..0c2ffa423da 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -330,6 +330,7 @@ id: ClothingHeadHelmetHardsuitLuxury #DO NOT MAP - https://github.com/space-wizards/space-station-14/pull/19738#issuecomment-1703486738 name: luxury mining hardsuit helmet description: A refurbished mining hardsuit helmet, fitted with satin cushioning and an extra (non-functioning) antenna, because you're that extra. + categories: [ DoNotMap ] components: - type: Sprite sprite: Clothing/Head/Hardsuits/luxury.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index 65ad2d523e8..5fc6dd5a563 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -137,7 +137,7 @@ id: ClothingHeadHelmetJanitorBombSuit name: janitorial bombsuit helmet description: A heavy helmet designed to withstand explosions formed from reactions between chemicals. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Clothing/Head/Helmets/janitor_bombsuit.rsi diff --git a/Resources/Prototypes/Entities/Clothing/Head/misc.yml b/Resources/Prototypes/Entities/Clothing/Head/misc.yml index 254eaf37c80..28fe46e0fa0 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/misc.yml @@ -176,7 +176,7 @@ id: ClothingHeadHatCatEars name: cat ears description: "NYAH!" - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Tag tags: [] # ignore "WhitelistChameleon" tag @@ -191,6 +191,7 @@ parent: [ClothingHeadHatCatEars, BaseToggleClothing] id: ClothingHeadHatCatEarsValid suffix: Valid, DO NOT MAP + categories: [ DoNotMap ] components: - type: ToggleClothing action: ActionBecomeValid @@ -222,7 +223,7 @@ id: ClothingHeadHatDogEars name: doggy ears description: Only for good boys. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Clothing/Head/Hats/dogears.rsi diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 0e84fca06d3..dd51be6be96 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -447,6 +447,7 @@ id: ClothingOuterHardsuitLuxury #DO NOT MAP - https://github.com/space-wizards/space-station-14/pull/19738#issuecomment-1703486738 name: luxury mining hardsuit description: A refurbished mining hardsuit, fashioned after the Quartermaster's colors. Graphene lining provides less protection, but is much easier to move. + categories: [ DoNotMap ] components: - type: Sprite sprite: Clothing/OuterClothing/Hardsuits/luxury.rsi diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index f78694a0fab..e6550dc01be 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -33,7 +33,7 @@ id: ClothingOuterSuitJanitorBomb name: janitorial bomb suit description: A heavy helmet designed to withstand explosions formed from reactions between chemicals. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Clothing/OuterClothing/Suits/janitor_bombsuit.rsi @@ -289,6 +289,7 @@ parent: ClothingOuterSuitCarp id: ClothingOuterHardsuitCarp suffix: Hardsuit, DO NOT MAP + categories: [ DoNotMap ] components: - type: PressureProtection highPressureMultiplier: 0.6 diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 9bb793e47ad..5a32e57c429 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -406,8 +406,7 @@ - type: entity id: StationAiBrain parent: PositronicBrain - categories: [ HideSpawnMenu ] - suffix: DO NOT MAP + categories: [ HideSpawnMenu, DoNotMap ] components: - type: Sprite # Once it's in a core it's pretty much an abstract entity at that point. @@ -454,8 +453,7 @@ id: StationAiHolo name: AI eye description: The AI's viewer. - categories: [ HideSpawnMenu ] - suffix: DO NOT MAP + categories: [ HideSpawnMenu, DoNotMap ] components: - type: NoFTL - type: WarpPoint @@ -476,8 +474,7 @@ id: StationAiHoloLocal name: AI hologram description: A holographic representation of an AI. - categories: [ HideSpawnMenu ] - suffix: DO NOT MAP + categories: [ HideSpawnMenu, DoNotMap ] components: - type: Transform anchored: true diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml index 4b7a0ee55a3..6c6190c01a5 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/cannons.yml @@ -6,6 +6,7 @@ name: LSE-400c "Svalinn machine gun" machine board description: A machine printed circuit board for an LSE-400c "Svalinn machine gun". suffix: DO NOT MAP, Machine Board + categories: [ DoNotMap ] components: - type: Sprite state: security @@ -23,6 +24,7 @@ name: LSE-1200c "Perforator" machine board description: A machine printed circuit board for an LSE-1200c "Perforator". suffix: DO NOT MAP, Machine Board + categories: [ DoNotMap ] components: - type: Sprite state: security @@ -40,6 +42,7 @@ name: EXP-320g "Friendship" machine board description: A machine printed circuit board for an EXP-320g "Friendship". suffix: DO NOT MAP, Machine Board + categories: [ DoNotMap ] components: - type: Sprite state: security @@ -57,6 +60,7 @@ name: EXP-2100g "Duster" machine board description: A machine printed circuit board for an EXP-2100g "Duster". suffix: DO NOT MAP, Machine Board + categories: [ DoNotMap ] components: - type: Sprite state: security @@ -75,6 +79,7 @@ name: PTK-800 "Matter Dematerializer" machine board description: A machine printed circuit board for an PTK-800 "Matter Dematerializer". suffix: DO NOT MAP, Machine Board + categories: [ DoNotMap ] components: - type: Sprite state: security diff --git a/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml b/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml index 7c69aa09013..3e05c0e8ff9 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml @@ -91,7 +91,7 @@ parent: BikeHorn id: GoldenBikeHorn name: golden honker - suffix: No mapping + categories: [ DoNotMap ] description: A happy honk prize, pray to the gods for your reward. components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index c5e17c54d35..818cad77742 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -131,7 +131,7 @@ parent: PlushieGhost id: PlushieGhostRevenant name: revenant soft toy - suffix: DO NOT MAP + categories: [ DoNotMap ] description: So soft it almost makes you want to take a nap... components: - type: Item diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index e0862b88425..a1b287b8a72 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -447,7 +447,7 @@ id: BoxFolderCentCom name: CentComm folder parent: BoxFolderBase - suffix: DO NOT MAP + categories: [ DoNotMap ] description: CentComm's miserable little pile of secrets! components: - type: Sprite @@ -609,7 +609,7 @@ id: TraitorCodePaper name: syndicate codeword description: A leaked codeword to possibly get in touch with the Syndicate. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: TraitorCodePaper diff --git a/Resources/Prototypes/Entities/Objects/Misc/rubber_stamp.yml b/Resources/Prototypes/Entities/Objects/Misc/rubber_stamp.yml index 8707e6fca89..df038d3e2e4 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/rubber_stamp.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/rubber_stamp.yml @@ -41,7 +41,7 @@ name: captain's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampCaptain - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-captain @@ -54,7 +54,7 @@ name: CentComm rubber stamp parent: RubberStampBase id: RubberStampCentcom - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-centcom @@ -67,7 +67,7 @@ name: chaplain's rubber stamp parent: RubberStampBase id: RubberStampChaplain - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-chaplain @@ -80,7 +80,7 @@ name: lawyer's rubber stamp parent: RubberStampBase id: RubberStampLawyer - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-lawyer @@ -93,7 +93,7 @@ name: clown's rubber stamp parent: RubberStampBase id: RubberStampClown - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-clown @@ -109,7 +109,7 @@ name: chief engineer's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampCE - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-ce @@ -122,7 +122,7 @@ name: chief medical officer's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampCMO - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-cmo @@ -135,7 +135,7 @@ name: head of personnel's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampHop - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-hop @@ -148,7 +148,7 @@ name: head of security's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampHos - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-hos @@ -161,7 +161,7 @@ name: mime's rubber stamp parent: RubberStampBase id: RubberStampMime - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-mime @@ -175,7 +175,7 @@ name: quartermaster's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampQm - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-qm @@ -188,7 +188,7 @@ name: research director's rubber stamp parent: [RubberStampBase, BaseCommandContraband] id: RubberStampRd - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-rd @@ -213,7 +213,7 @@ name: syndicate rubber stamp parent: [RubberStampBase, BaseSyndicateContraband] id: RubberStampSyndicate - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-syndicate @@ -226,7 +226,7 @@ name: warden's rubber stamp parent: [RubberStampBase, BaseRestrictedContraband] id: RubberStampWarden - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-warden @@ -263,7 +263,7 @@ name: detective's rubber stamp parent: [RubberStampBase, BaseRestrictedContraband] id: RubberStampDetective - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Stamp stampedName: stamp-component-stamped-name-detective @@ -289,12 +289,12 @@ name: psychologist's rubber stamp parent: RubberStampBase id: RubberStampPsychologist - suffix: DO NOT MAP + categories: [ DoNotMap ] description: A rubber stamp for stamping important documents. Prescribe those treatments! components: - type: Stamp - stampedName: stamp-component-stamped-name-psychologist + stampedName: stamp-component-stamped-name-psychologist stampedColor: "#5B97BC" stampState: "paper_stamp-psychologist" - type: Sprite - state: stamp-psychologist \ No newline at end of file + state: stamp-psychologist diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml index d7a362d7253..fc252fd9f5d 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/handheld_crew_monitor.yml @@ -1,6 +1,6 @@ - type: entity name: handheld crew monitor - suffix: DO NOT MAP + categories: [ DoNotMap ] parent: [ BaseHandheldComputer, BaseGrandTheftContraband ] # CMO-only bud, don't add more. id: HandheldCrewMonitor diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml index 46410a01643..a69d2a15872 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml @@ -324,7 +324,7 @@ id: HealingToolbox name: healing toolbox description: A powerful toolbox imbued with robust energy. It can heal your wounds and fill you with murderous intent. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Specific/Medical/healing_toolbox.rsi diff --git a/Resources/Prototypes/Entities/Objects/Specific/Research/disk.yml b/Resources/Prototypes/Entities/Objects/Specific/Research/disk.yml index 0cb605cee64..dd556d75f2b 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Research/disk.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Research/disk.yml @@ -35,6 +35,7 @@ id: ResearchDiskDebug name: research point disk suffix: DEBUG, DO NOT MAP + categories: [ DoNotMap ] description: A disk for the R&D server containing all the points you could ever need. components: - type: ResearchDisk diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml index 551fedfd905..7aee3e5876b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml @@ -63,7 +63,7 @@ parent: BaseBallBat id: WeaponMeleeKnockbackStick description: And then he spleefed all over. - suffix: Do not map + categories: [ DoNotMap ] components: - type: MeleeThrowOnHit - type: MeleeWeapon diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml index 240a17a0a44..003967d33d3 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml @@ -3,7 +3,7 @@ id: WeaponMeleeToolboxRobust name: robust toolbox description: A tider's weapon. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Tools/Toolboxes/toolbox_red.rsi diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 1180b370967..fd8090a369c 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -1192,6 +1192,7 @@ # Putting this as "DO NOT MAP" until the performance issues are fixed. # And it's more fleshed out. suffix: TESTING, DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite layers: diff --git a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml index c95609c55ab..de7cfe3e278 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml @@ -150,8 +150,7 @@ - type: entity id: HolopadHologram name: hologram - categories: [ HideSpawnMenu ] - suffix: DO NOT MAP + categories: [ HideSpawnMenu, DoNotMap ] components: - type: Transform anchored: true diff --git a/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml b/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml index 47ac2ae7c94..030be38a910 100644 --- a/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml +++ b/Resources/Prototypes/Entities/Structures/Shuttles/cannons.yml @@ -61,7 +61,7 @@ parent: [ ShuttleGunBase, ConstructibleMachine] name: LSE-400c "Svalinn machine gun" description: Basic stationary laser unit. Effective against live targets and electronics. Uses regular power cells to fire, and has an extremely high rate of fire. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/laser.rsi @@ -115,7 +115,7 @@ parent: [ ShuttleGunBase, ConstructibleMachine] name: LSE-1200c "Perforator" description: Advanced stationary laser unit. Annihilates electronics and is extremely dangerous to health! Uses the power cage to fire. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/laser.rsi @@ -171,7 +171,7 @@ parent: [ShuttleGunBase, ConstructibleMachine] name: EXP-320g "Friendship" description: A small stationary grenade launcher that holds 2 grenades. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi @@ -225,7 +225,7 @@ parent: [ShuttleGunBase, ConstructibleMachine] name: EXP-2100g "Duster" description: A powerful stationary grenade launcher. A cartridge is required for use. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi @@ -323,7 +323,7 @@ parent: [ ShuttleGunBase, ConstructibleMachine] name: PTK-800 "Matter Dematerializer" description: Salvage stationary mining turret. Gradually accumulates charges on its own, extremely effective for asteroid excavation. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Sprite sprite: Objects/Weapons/Guns/Shuttles/kinetic.rsi diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml index 8c8c493aa3a..778c80e6fb6 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/closets.yml @@ -82,7 +82,7 @@ name: janitorial bomb suit closet parent: ClosetSteelBase description: It's a storage unit for janitorial explosion-protective suits. - suffix: DO NOT MAP + categories: [ DoNotMap ] components: - type: Appearance - type: EntityStorageVisuals diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml index dffc6b6aaf9..5b8e794309d 100644 --- a/Resources/Prototypes/Entities/categories.yml +++ b/Resources/Prototypes/Entities/categories.yml @@ -22,3 +22,8 @@ - type: entityCategory id: Mapping name: entity-category-name-mapping + +- type: entityCategory + id: DoNotMap + name: entity-category-name-donotmap + suffix: entity-category-suffix-donotmap From 32f7ea6c29228a65a07341537c4ec7df3eeb299a Mon Sep 17 00:00:00 2001 From: Killerqu00 Date: Wed, 29 Jan 2025 13:24:14 +0100 Subject: [PATCH 023/365] slight text adjustment --- Content.IntegrationTests/Tests/PostMapInitTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 906b96ebf02..e18271af53e 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -158,7 +158,7 @@ public async Task NoSavedPostMapInitTest() Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit"); - // testing that maps have nothing with the "DO NOT MAP" suffix + // testing that maps have nothing with the DoNotMap entity category // I do it here because it's basically copy-paste code for the most part var yamlEntities = root["entities"]; if (!protoManager.TryIndex("DoNotMap", out var dnmCategory)) @@ -171,7 +171,7 @@ public async Task NoSavedPostMapInitTest() continue; if (proto.Categories.Contains(dnmCategory) && !DoNotMapWhitelist.Contains(map.ToString())) { - Assert.Fail($"\nMap {map} has the DO NOT MAP prototype {proto.Name}"); + Assert.Fail($"\nMap {map} has the DO NOT MAP category in prototype {proto.Name}"); } } } From 7056c6051ba59fe4ffa57433fd8b6fae8c29308f Mon Sep 17 00:00:00 2001 From: MossyGreySlope Date: Fri, 14 Feb 2025 09:31:38 +1300 Subject: [PATCH 024/365] Fix dev server crashes caused by putting pills in drinks, depositing spesos, making burgers (#33431) * combine TrySpike into OnInteractUsing * mark spike drink event as handled * mark speso insertion event as handled * mark food sequence event as handled --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs index ae8215ac6ae..d5b4a0e2b36 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSequenceSystem.cs @@ -39,7 +39,7 @@ public override void Initialize() private void OnInteractUsing(Entity ent, ref InteractUsingEvent args) { if (TryComp(args.Used, out var sequenceElement)) - TryAddFoodElement(ent, (args.Used, sequenceElement), args.User); + args.Handled = TryAddFoodElement(ent, (args.Used, sequenceElement), args.User); } private void OnIngredientAdded(Entity ent, ref FoodSequenceIngredientAddedEvent args) From 562a41c00e43cd30c46dddf805bc4e4268f086a5 Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Thu, 13 Feb 2025 14:03:55 -0800 Subject: [PATCH 025/365] Vote kicks now ban the target's ip (#35131) * Make vote kicks ban the target's ip address * Make it stop crashing my game --- .../Voting/Managers/VoteManager.DefaultVotes.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs b/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs index 187295a0734..89f4acdef1e 100644 --- a/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs +++ b/Content.Server/Voting/Managers/VoteManager.DefaultVotes.cs @@ -1,4 +1,6 @@ using System.Linq; +using System.Net; +using System.Net.Sockets; using Content.Server.Administration; using Content.Server.Administration.Managers; using Content.Server.Discord.WebhookMessages; @@ -48,6 +50,8 @@ public void CreateStandardVote(ICommonSession? initiator, StandardVoteType voteT else _adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Initiated a {voteType.ToString()} vote"); + _gameTicker = _entityManager.EntitySysManager.GetEntitySystem(); + bool timeoutVote = true; switch (voteType) @@ -68,7 +72,6 @@ public void CreateStandardVote(ICommonSession? initiator, StandardVoteType voteT default: throw new ArgumentOutOfRangeException(nameof(voteType), voteType, null); } - _gameTicker = _entityManager.EntitySysManager.GetEntitySystem(); _gameTicker.UpdateInfoText(); if (timeoutVote) TimeoutStandardVote(voteType); @@ -368,6 +371,15 @@ private async void CreateVotekickVote(ICommonSession? initiator, string[]? args) } var targetUid = located.UserId; var targetHWid = located.LastHWId; + (IPAddress, int)? targetIP = null; + + if (located.LastAddress is not null) + { + targetIP = located.LastAddress.AddressFamily is AddressFamily.InterNetwork + ? (located.LastAddress, 32) // People with ipv4 addresses get a /32 address so we ban that + : (located.LastAddress, 64); // This can only be an ipv6 address. People with ipv6 address should get /64 addresses so we ban that. + } + if (!_playerManager.TryGetSessionById(located.UserId, out ICommonSession? targetSession)) { _logManager.GetSawmill("admin.votekick") @@ -532,7 +544,7 @@ private async void CreateVotekickVote(ICommonSession? initiator, string[]? args) uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration); - _bans.CreateServerBan(targetUid, target, null, null, targetHWid, minutes, severity, reason); + _bans.CreateServerBan(targetUid, target, null, targetIP, targetHWid, minutes, severity, reason); } } else From dcfcd8916c248d29361c67f67f48108e1c957d43 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 13 Feb 2025 22:05:03 +0000 Subject: [PATCH 026/365] Automatic changelog update --- Resources/Changelog/Admin.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 6b380572b09..180d6c03aed 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -752,5 +752,12 @@ Entries: id: 93 time: '2025-02-06T05:10:21.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/33426 +- author: nikthechampiongr + changes: + - message: Vote kicks now also ban the ip of the person that gets votekicked. + type: Tweak + id: 94 + time: '2025-02-13T22:03:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35131 Name: Admin Order: 1 From 12105234fec23ca5c0bb3ca6263bc7cd3ef2c504 Mon Sep 17 00:00:00 2001 From: Centronias Date: Thu, 13 Feb 2025 14:35:59 -0800 Subject: [PATCH 027/365] High Heel Boots do the Clicky Clacky (#35083) * click clack * fix quote marks Co-authored-by: Hannah Giovanna Dawson --------- Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com> Co-authored-by: Hannah Giovanna Dawson --- .../Audio/Effects/Footsteps/attributions.yml | 14 ++++++++++++-- .../Audio/Effects/Footsteps/heelsclack1.ogg | Bin 0 -> 9232 bytes .../Audio/Effects/Footsteps/heelsclack2.ogg | Bin 0 -> 8247 bytes .../Audio/Effects/Footsteps/heelsclack3.ogg | Bin 0 -> 8410 bytes .../Audio/Effects/Footsteps/heelsclack4.ogg | Bin 0 -> 8530 bytes .../Audio/Effects/Footsteps/heelsclack5.ogg | Bin 0 -> 8998 bytes .../Prototypes/Entities/Clothing/Shoes/boots.yml | 5 +++++ .../Prototypes/SoundCollections/footsteps.yml | 13 +++++++++++-- 8 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 Resources/Audio/Effects/Footsteps/heelsclack1.ogg create mode 100644 Resources/Audio/Effects/Footsteps/heelsclack2.ogg create mode 100644 Resources/Audio/Effects/Footsteps/heelsclack3.ogg create mode 100644 Resources/Audio/Effects/Footsteps/heelsclack4.ogg create mode 100644 Resources/Audio/Effects/Footsteps/heelsclack5.ogg diff --git a/Resources/Audio/Effects/Footsteps/attributions.yml b/Resources/Audio/Effects/Footsteps/attributions.yml index 7a56beec38c..7af75169b22 100644 --- a/Resources/Audio/Effects/Footsteps/attributions.yml +++ b/Resources/Audio/Effects/Footsteps/attributions.yml @@ -12,7 +12,7 @@ license: "CC-BY-SA-4.0" copyright: "Made by JustInvoke freesound.org" source: "https://freesound.org/people/JustInvoke/sounds/446100/" - + - files: - jesterstep1.ogg - jesterstep2.ogg @@ -63,7 +63,7 @@ license: "CC-BY-SA-3.0" copyright: "Taken from https://github.com/tgstation/tgstation" source: "https://github.com/tgstation/tgstation/blob/34d5ab2e46e3fb4dd9d7475f587d33441df9651c/sound/effects" - + - files: - spurs1.ogg - spurs2.ogg @@ -78,3 +78,13 @@ license: "CC-BY-SA-4.0" copyright: "Taken from IENBA freesound.org and modified by https://github.com/MilenVolf. borgwalk2 clipped my metalgearsloth." source: "https://freesound.org/people/IENBA/sounds/697379/" + +- files: + - heelsclack1.ogg + - heelsclack2.ogg + - heelsclack3.ogg + - heelsclack4.ogg + - heelsclack5.ogg + license: "CC0-1.0" + copyright: "Taken from NachtmahrTV on freesound.org, clipped by Centronias" + source: "https://freesound.org/people/NachtmahrTV/sounds/571801/" diff --git a/Resources/Audio/Effects/Footsteps/heelsclack1.ogg b/Resources/Audio/Effects/Footsteps/heelsclack1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..903f4c19ea34ae237303b30e0d5e8f0d7dcf6cde GIT binary patch literal 9232 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 JmRXyh`9F<{D53xW literal 0 HcmV?d00001 diff --git a/Resources/Audio/Effects/Footsteps/heelsclack2.ogg b/Resources/Audio/Effects/Footsteps/heelsclack2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..c81c36100f54a3ee5975a2a7be1b1d2754fcd06c GIT binary patch literal 8247 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 literal 0 HcmV?d00001 diff --git a/Resources/Audio/Effects/Footsteps/heelsclack5.ogg b/Resources/Audio/Effects/Footsteps/heelsclack5.ogg new file mode 100644 index 0000000000000000000000000000000000000000..6b9f0763fbb59dbcaaffe5a187ad7fb8fda79a12 GIT binary patch literal 8998 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 b?#Y$p+ot&exlca$ Date: Thu, 13 Feb 2025 22:37:06 +0000 Subject: [PATCH 028/365] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 021b37f5b5a..f99dca05835 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: ElectroJr - changes: - - message: Fixed a store currency duplication bug/exploit. - type: Fix - id: 7453 - time: '2024-09-29T12:13:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32524 - author: ElectroJr changes: - message: There is now a rate limit for most interactions. It should not be noticeable @@ -3887,3 +3880,10 @@ id: 7952 time: '2025-02-13T20:12:57.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/35135 +- author: Centronias + changes: + - message: High-heeled Boots now click and clack when walking + type: Tweak + id: 7953 + time: '2025-02-13T22:35:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35083 From 3b142890cd904456376c84f7fb87ba16d7648f03 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:44:27 +0100 Subject: [PATCH 029/365] minor thruster system cleanup (#35143) --- Content.Server/Shuttles/Systems/ThrusterSystem.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index a75ddc911a4..74008a6af7f 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -44,6 +44,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnActivateThruster); SubscribeLocalEvent(OnThrusterInit); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnThrusterShutdown); SubscribeLocalEvent(OnPowerChange); SubscribeLocalEvent(OnAnchorChange); @@ -232,8 +233,6 @@ private void OnAnchorChange(EntityUid uid, ThrusterComponent component, ref Anch private void OnThrusterInit(EntityUid uid, ThrusterComponent component, ComponentInit args) { - component.NextFire = _timing.CurTime + component.FireCooldown; - _ambient.SetAmbience(uid, false); if (!component.Enabled) @@ -247,6 +246,11 @@ private void OnThrusterInit(EntityUid uid, ThrusterComponent component, Componen } } + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextFire = _timing.CurTime + ent.Comp.FireCooldown; + } + private void OnThrusterShutdown(EntityUid uid, ThrusterComponent component, ComponentShutdown args) { DisableThruster(uid, component); From 26deb4cdf9909b19425bb771bbb8ed7ceff238b6 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:14:43 +1100 Subject: [PATCH 030/365] Update engine to v245.0.0 (#35146) --- RobustToolbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RobustToolbox b/RobustToolbox index fea592e1d53..55571ef5b13 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit fea592e1d53b043bda73a66248576caa6e3f65dc +Subproject commit 55571ef5b131dfa6f00a38e71a356ded34ca5911 From 0227afec4cd9061c17336db37c858d2cb09bc35b Mon Sep 17 00:00:00 2001 From: Winkarst <74284083+Winkarst-cpu@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:23:27 +0300 Subject: [PATCH 031/365] Fix: showfluids command activates for everyone (#35155) Fix --- .../Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs b/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs index 859f2d80a67..88acf4b703e 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleDebugDebugOverlaySystem.cs @@ -84,7 +84,7 @@ public override void Update(float frameTime) data.Add(new PuddleDebugOverlayData(pos, vol)); } - RaiseNetworkEvent(new PuddleOverlayDebugMessage(GetNetEntity(gridUid), data.ToArray())); + RaiseNetworkEvent(new PuddleOverlayDebugMessage(GetNetEntity(gridUid), data.ToArray()), session); } } From 244d7a629e05a1397d9c2254a77001d2a50cc8fd Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:23:35 +0300 Subject: [PATCH 032/365] Fix embeddable projectiles dissapearing (reopening) (#35153) --- .../ItemRecall/SharedItemRecallSystem.cs | 2 +- .../Projectiles/EmbeddedContainerComponent.cs | 13 +++ .../Projectiles/SharedProjectileSystem.cs | 92 +++++++++++++------ 3 files changed, 78 insertions(+), 29 deletions(-) create mode 100644 Content.Shared/Projectiles/EmbeddedContainerComponent.cs diff --git a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs index 63d38203c65..a4a49e97088 100644 --- a/Content.Shared/ItemRecall/SharedItemRecallSystem.cs +++ b/Content.Shared/ItemRecall/SharedItemRecallSystem.cs @@ -81,7 +81,7 @@ private void RecallItem(Entity ent) return; if (TryComp(ent, out var projectile)) - _proj.UnEmbed(ent, projectile, actionOwner.Value); + _proj.EmbedDetach(ent, projectile, actionOwner.Value); _popups.PopupPredicted(Loc.GetString("item-recall-item-summon", ("item", ent)), actionOwner.Value, actionOwner.Value); diff --git a/Content.Shared/Projectiles/EmbeddedContainerComponent.cs b/Content.Shared/Projectiles/EmbeddedContainerComponent.cs new file mode 100644 index 00000000000..19dd93bfbd7 --- /dev/null +++ b/Content.Shared/Projectiles/EmbeddedContainerComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Projectiles; + +/// +/// Stores a list of all stuck entities to release when this entity is deleted. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class EmbeddedContainerComponent : Component +{ + [DataField, AutoNetworkedField] + public HashSet EmbeddedObjects = new(); +} diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 1d0fc16cbd7..be86fd1af23 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; +using Robust.Shared.Utility; namespace Content.Shared.Projectiles; @@ -22,7 +23,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem { public const string ProjectileFixture = "projectile"; - [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; @@ -38,62 +39,63 @@ public override void Initialize() SubscribeLocalEvent(OnEmbedThrowDoHit); SubscribeLocalEvent(OnEmbedActivate); SubscribeLocalEvent(OnEmbedRemove); + + SubscribeLocalEvent(OnEmbeddableTermination); } - private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args) + private void OnEmbedActivate(Entity embeddable, ref ActivateInWorldEvent args) { - // Nuh uh - if (component.RemovalTime == null) + // Unremovable embeddables moment + if (embeddable.Comp.RemovalTime == null) return; - if (args.Handled || !args.Complex || !TryComp(uid, out var physics) || physics.BodyType != BodyType.Static) + if (args.Handled || !args.Complex || !TryComp(embeddable, out var physics) || + physics.BodyType != BodyType.Static) return; args.Handled = true; - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.RemovalTime.Value, - new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)); + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, + args.User, + embeddable.Comp.RemovalTime.Value, + new RemoveEmbeddedProjectileEvent(), + eventTarget: embeddable, + target: embeddable)); } - private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent component, RemoveEmbeddedProjectileEvent args) + private void OnEmbedRemove(Entity embeddable, ref RemoveEmbeddedProjectileEvent args) { // Whacky prediction issues. - if (args.Cancelled || _netManager.IsClient) + if (args.Cancelled || _net.IsClient) return; - if (component.DeleteOnRemove) - { - QueueDel(uid); - return; - } - - UnEmbed(uid, component, args.User); + EmbedDetach(embeddable, embeddable.Comp, args.User); // try place it in the user's hand - _hands.TryPickupAnyHand(args.User, uid); + _hands.TryPickupAnyHand(args.User, embeddable); } - private void OnEmbedThrowDoHit(EntityUid uid, EmbeddableProjectileComponent component, ThrowDoHitEvent args) + private void OnEmbedThrowDoHit(Entity embeddable, ref ThrowDoHitEvent args) { - if (!component.EmbedOnThrow) + if (!embeddable.Comp.EmbedOnThrow) return; - Embed(uid, args.Target, null, component); + EmbedAttach(embeddable, args.Target, null, embeddable.Comp); } - private void OnEmbedProjectileHit(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileHitEvent args) + private void OnEmbedProjectileHit(Entity embeddable, ref ProjectileHitEvent args) { - Embed(uid, args.Target, args.Shooter, component); + EmbedAttach(embeddable, args.Target, args.Shooter, embeddable.Comp); // Raise a specific event for projectiles. - if (TryComp(uid, out ProjectileComponent? projectile)) + if (TryComp(embeddable, out ProjectileComponent? projectile)) { var ev = new ProjectileEmbedEvent(projectile.Shooter!.Value, projectile.Weapon!.Value, args.Target); - RaiseLocalEvent(uid, ref ev); + RaiseLocalEvent(embeddable, ref ev); } } - private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component) + private void EmbedAttach(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component) { TryComp(uid, out var physics); _physics.SetLinearVelocity(uid, Vector2.Zero, body: physics); @@ -106,8 +108,7 @@ private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableP var rotation = xform.LocalRotation; if (TryComp(uid, out var throwingAngleComp)) rotation += throwingAngleComp.Angle; - _transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset), - xform); + _transform.SetLocalPosition(uid, xform.LocalPosition + rotation.RotateVec(component.Offset), xform); } _audio.PlayPredicted(component.Sound, uid, null); @@ -115,13 +116,32 @@ private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableP var ev = new EmbedEvent(user, target); RaiseLocalEvent(uid, ref ev); Dirty(uid, component); + + EnsureComp(target, out var embeddedContainer); + + //Assert that this entity not embed + DebugTools.AssertEqual(embeddedContainer.EmbeddedObjects.Contains(uid), false); + + embeddedContainer.EmbeddedObjects.Add(uid); } - public void UnEmbed(EntityUid uid, EmbeddableProjectileComponent? component, EntityUid? user = null) + public void EmbedDetach(EntityUid uid, EmbeddableProjectileComponent? component, EntityUid? user = null) { if (!Resolve(uid, ref component)) return; + if (component.DeleteOnRemove) + { + QueueDel(uid); + return; + } + + if (component.EmbeddedIntoUid is not null) + { + if (TryComp(component.EmbeddedIntoUid.Value, out var embeddedContainer)) + embeddedContainer.EmbeddedObjects.Remove(uid); + } + var xform = Transform(uid); TryComp(uid, out var physics); _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform); @@ -149,6 +169,22 @@ public void UnEmbed(EntityUid uid, EmbeddableProjectileComponent? component, Ent _physics.WakeBody(uid, body: physics); } + private void OnEmbeddableTermination(Entity container, ref EntityTerminatingEvent args) + { + DetachAllEmbedded(container); + } + + public void DetachAllEmbedded(Entity container) + { + foreach (var embedded in container.Comp.EmbeddedObjects) + { + if (!TryComp(embedded, out var embeddedComp)) + continue; + + EmbedDetach(embedded, embeddedComp); + } + } + private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args) { if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon)) From 498440d369480bd5c75293813b13cd8414bbb4cd Mon Sep 17 00:00:00 2001 From: Milon Date: Fri, 14 Feb 2025 15:35:27 +0100 Subject: [PATCH 033/365] make chameleon verb predicted (#35156) * ok but what if we just predicted EVERYTHING * cleanup --- .../Systems/ChameleonClothingSystem.cs | 30 +------------------ .../SharedChameleonClothingSystem.cs | 22 ++++++++++++-- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs index 3700aeb549c..5c8954cc69b 100644 --- a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs +++ b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs @@ -3,18 +3,13 @@ using Content.Shared.Clothing.EntitySystems; using Content.Shared.IdentityManagement.Components; using Content.Shared.Prototypes; -using Content.Shared.Verbs; -using Robust.Server.GameObjects; -using Robust.Shared.Player; using Robust.Shared.Prototypes; -using Robust.Shared.Utility; namespace Content.Server.Clothing.Systems; public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem { [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IdentitySystem _identity = default!; @@ -22,7 +17,6 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent>(OnVerb); SubscribeLocalEvent(OnSelected); } @@ -31,40 +25,18 @@ private void OnMapInit(EntityUid uid, ChameleonClothingComponent component, MapI SetSelectedPrototype(uid, component.Default, true, component); } - private void OnVerb(EntityUid uid, ChameleonClothingComponent component, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract || component.User != args.User) - return; - - args.Verbs.Add(new InteractionVerb() - { - Text = Loc.GetString("chameleon-component-verb-text"), - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), - Act = () => TryOpenUi(uid, args.User, component) - }); - } - private void OnSelected(EntityUid uid, ChameleonClothingComponent component, ChameleonPrototypeSelectedMessage args) { SetSelectedPrototype(uid, args.SelectedId, component: component); } - private void TryOpenUi(EntityUid uid, EntityUid user, ChameleonClothingComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - if (!TryComp(user, out ActorComponent? actor)) - return; - _uiSystem.TryToggleUi(uid, ChameleonUiKey.Key, actor.PlayerSession); - } - private void UpdateUi(EntityUid uid, ChameleonClothingComponent? component = null) { if (!Resolve(uid, ref component)) return; var state = new ChameleonBoundUserInterfaceState(component.Slot, component.Default, component.RequireTag); - _uiSystem.SetUiState(uid, ChameleonUiKey.Key, state); + UI.SetUiState(uid, ChameleonUiKey.Key, state); } /// diff --git a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs index 488b7a5b641..725b0347661 100644 --- a/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs @@ -5,8 +5,9 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Tag; +using Content.Shared.Verbs; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.Manager; +using Robust.Shared.Utility; namespace Content.Shared.Clothing.EntitySystems; @@ -14,19 +15,20 @@ public abstract class SharedChameleonClothingSystem : EntitySystem { [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly ClothingSystem _clothingSystem = default!; [Dependency] private readonly ContrabandSystem _contraband = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly SharedItemSystem _itemSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly TagSystem _tag = default!; + [Dependency] protected readonly SharedUserInterfaceSystem UI = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent>(OnVerb); } private void OnGotEquipped(EntityUid uid, ChameleonClothingComponent component, GotEquippedEvent args) @@ -94,6 +96,22 @@ protected void UpdateVisuals(EntityUid uid, ChameleonClothingComponent component } } + private void OnVerb(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || ent.Comp.User != args.User) + return; + + // Can't pass args from a ref event inside of lambdas + var user = args.User; + + args.Verbs.Add(new InteractionVerb() + { + Text = Loc.GetString("chameleon-component-verb-text"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")), + Act = () => UI.TryToggleUi(ent.Owner, ChameleonUiKey.Key, user) + }); + } + protected virtual void UpdateSprite(EntityUid uid, EntityPrototype proto) { } /// From db732a25810a2d96cdf5d51f5d95d906695bb21a Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:01:40 -0500 Subject: [PATCH 034/365] Supermatter Grenade Rework (#35122) * Renames Supermatter grenade to singularity grenade. Removes explosion from Singularity Grenade. * Adjusts Singularity Grenade to account for engine changes, small buff to max range * Adjusts further * fix order --------- Co-authored-by: Milon --- .../Locale/en-US/store/uplink-catalog.ftl | 4 +-- .../Prototypes/Catalog/uplink_catalog.yml | 8 +++--- .../Objects/Weapons/Throwable/grenades.yml | 27 +++++++----------- .../icon.png | Bin .../meta.json | 0 .../primed.png | Bin 6 files changed, 17 insertions(+), 22 deletions(-) rename Resources/Textures/Objects/Weapons/Grenades/{supermattergrenade.rsi => singularitygrenade.rsi}/icon.png (100%) rename Resources/Textures/Objects/Weapons/Grenades/{supermattergrenade.rsi => singularitygrenade.rsi}/meta.json (100%) rename Resources/Textures/Objects/Weapons/Grenades/{supermattergrenade.rsi => singularitygrenade.rsi}/primed.png (100%) diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 907e47a1266..2ff6c16f5ac 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -42,8 +42,8 @@ uplink-smoke-grenade-desc = A grenade that releases a huge cloud of smoke, perfe uplink-mini-bomb-name = Minibomb uplink-mini-bomb-desc = A low-yield, high-impact precision sabotage explosive with a 5 second long fuse. Perfect for quickly destroying a machine, dead body, or whatever else needs to go. -uplink-supermatter-grenade-name = Supermatter Grenade -uplink-supermatter-grenade-desc = Grenade that simulates delamination of a suppermatter engine, generates powerful gravity well. Explosion comparable to a Mini Bomb. +uplink-singularity-grenade-name = Singularity Grenade +uplink-singularity-grenade-desc = Grenade that simulates the power of a singularity, generates powerful gravity well. uplink-whitehole-grenade-name = Whitehole Grenade uplink-whitehole-grenade-desc = Grenade that repulses everything around for about 10 seconds. Very useful in small rooms and for chasing someone. diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 7e3b9397811..7f0f668cb75 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -263,10 +263,10 @@ - UplinkExplosives - type: listing - id: UplinkSupermatterGrenade - name: uplink-supermatter-grenade-name - description: uplink-supermatter-grenade-desc - productEntity: SupermatterGrenade + id: UplinkSingularityGrenade + name: uplink-singularity-grenade-name + description: uplink-singularity-grenade-desc + productEntity: SingularityGrenade discountCategory: usualDiscounts discountDownTo: Telecrystal: 3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index 08d6d855a88..d3a714445ef 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -165,13 +165,13 @@ - type: entity - name: supermatter grenade - description: Grenade that simulates delamination of the supermatter engine, pulling things in a heap and exploding after some time. - parent: [GrenadeBase, BaseSyndicateContraband] - id: SupermatterGrenade + parent: [ GrenadeBase, BaseSyndicateContraband ] + id: SingularityGrenade + name: singularity grenade + description: Grenade that simulates the power of a singularity, pulling things in a heap. components: - type: Sprite - sprite: Objects/Weapons/Grenades/supermattergrenade.rsi + sprite: Objects/Weapons/Grenades/singularitygrenade.rsi - type: OnUseTimerTrigger delay: 3 beepInterval: 0.46 @@ -184,11 +184,6 @@ path: /Audio/Effects/Grenades/Supermatter/smbeep.ogg params: volume: -5 - - type: Explosive - explosionType: Default - totalIntensity: 200 - intensitySlope: 30 - maxIntensity: 120 - type: SoundOnTrigger removeOnTrigger: true sound: @@ -206,10 +201,10 @@ sound: path: /Audio/Effects/Grenades/Supermatter/supermatter_loop.ogg - type: GravityWell - maxRange: 8 - baseRadialAcceleration: 145 - baseTangentialAcceleration: 5 - gravPulsePeriod: 0.01 + maxRange: 10 + baseRadialAcceleration: 5 + baseTangentialAcceleration: .5 + gravPulsePeriod: 0.03 - type: SingularityDistortion intensity: 150 falloffPower: 1.5 @@ -225,12 +220,12 @@ path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg params: volume: 5 - - type: ExplodeOnTrigger + - type: DeleteOnTrigger - type: entity name: whitehole grenade description: Grenade that repulses everything around for some time. - parent: SupermatterGrenade + parent: SingularityGrenade id: WhiteholeGrenade components: - type: Sprite diff --git a/Resources/Textures/Objects/Weapons/Grenades/supermattergrenade.rsi/icon.png b/Resources/Textures/Objects/Weapons/Grenades/singularitygrenade.rsi/icon.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Grenades/supermattergrenade.rsi/icon.png rename to Resources/Textures/Objects/Weapons/Grenades/singularitygrenade.rsi/icon.png diff --git a/Resources/Textures/Objects/Weapons/Grenades/supermattergrenade.rsi/meta.json b/Resources/Textures/Objects/Weapons/Grenades/singularitygrenade.rsi/meta.json similarity index 100% rename from Resources/Textures/Objects/Weapons/Grenades/supermattergrenade.rsi/meta.json rename to Resources/Textures/Objects/Weapons/Grenades/singularitygrenade.rsi/meta.json diff --git a/Resources/Textures/Objects/Weapons/Grenades/supermattergrenade.rsi/primed.png b/Resources/Textures/Objects/Weapons/Grenades/singularitygrenade.rsi/primed.png similarity index 100% rename from Resources/Textures/Objects/Weapons/Grenades/supermattergrenade.rsi/primed.png rename to Resources/Textures/Objects/Weapons/Grenades/singularitygrenade.rsi/primed.png From 63b9255e71bb82b9b34286f33d4c41ac69beeac2 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 14 Feb 2025 15:02:50 +0000 Subject: [PATCH 035/365] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f99dca05835..87f34230f62 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: ElectroJr - changes: - - message: There is now a rate limit for most interactions. It should not be noticeable - most of the time, but may lead to mispredicts when spam-clicking. - type: Tweak - id: 7454 - time: '2024-09-29T12:19:00.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32527 - author: ArtisticRoomba changes: - message: HoS's energy shotgun is now correctly marked as grand theft contraband. @@ -3887,3 +3879,12 @@ id: 7953 time: '2025-02-13T22:35:59.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/35083 +- author: keronshb + changes: + - message: The Supermatter Grenade has been renamed to Singularity Grenade. + type: Tweak + - message: The Singularity Grenade no longer has an explosion. + type: Remove + id: 7954 + time: '2025-02-14T15:01:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35122 From 862a2a744e1a8cb2643851b0f85c1372b1feadb6 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Fri, 14 Feb 2025 07:46:25 -0800 Subject: [PATCH 036/365] Predicted dice rolls (#34863) * Predicted dice rolls * Removed server-side dice system, make Shared no longer abstract, move visual code to client-side system * cleanup --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- Content.Client/Dice/DiceSystem.cs | 15 ++++-- Content.Server/Dice/DiceSystem.cs | 25 +-------- Content.Shared/Dice/DiceComponent.cs | 1 - Content.Shared/Dice/SharedDiceSystem.cs | 69 ++++++++++++------------- 4 files changed, 45 insertions(+), 65 deletions(-) diff --git a/Content.Client/Dice/DiceSystem.cs b/Content.Client/Dice/DiceSystem.cs index 2d36488257b..2890de5f2f1 100644 --- a/Content.Client/Dice/DiceSystem.cs +++ b/Content.Client/Dice/DiceSystem.cs @@ -5,17 +5,24 @@ namespace Content.Client.Dice; public sealed class DiceSystem : SharedDiceSystem { - protected override void UpdateVisuals(EntityUid uid, DiceComponent? die = null) + public override void Initialize() { - if (!Resolve(uid, ref die) || !TryComp(uid, out SpriteComponent? sprite)) + base.Initialize(); + + SubscribeLocalEvent(OnDiceAfterHandleState); + } + + private void OnDiceAfterHandleState(Entity entity, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(entity, out var sprite)) return; - // TODO maybe just move each diue to its own RSI? + // TODO maybe just move each die to its own RSI? var state = sprite.LayerGetState(0).Name; if (state == null) return; var prefix = state.Substring(0, state.IndexOf('_')); - sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}"); + sprite.LayerSetState(0, $"{prefix}_{entity.Comp.CurrentValue}"); } } diff --git a/Content.Server/Dice/DiceSystem.cs b/Content.Server/Dice/DiceSystem.cs index 2d13679bd09..c2cb62a250f 100644 --- a/Content.Server/Dice/DiceSystem.cs +++ b/Content.Server/Dice/DiceSystem.cs @@ -1,28 +1,5 @@ using Content.Shared.Dice; -using Content.Shared.Popups; -using JetBrains.Annotations; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Random; namespace Content.Server.Dice; -[UsedImplicitly] -public sealed class DiceSystem : SharedDiceSystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPopupSystem _popup = default!; - - public override void Roll(EntityUid uid, DiceComponent? die = null) - { - if (!Resolve(uid, ref die)) - return; - - var roll = _random.Next(1, die.Sides + 1); - SetCurrentSide(uid, roll, die); - - _popup.PopupEntity(Loc.GetString("dice-component-on-roll-land", ("die", uid), ("currentSide", die.CurrentValue)), uid); - _audio.PlayPvs(die.Sound, uid); - } -} +public sealed class DiceSystem : SharedDiceSystem; diff --git a/Content.Shared/Dice/DiceComponent.cs b/Content.Shared/Dice/DiceComponent.cs index c01ad3c4518..27f7bd70e06 100644 --- a/Content.Shared/Dice/DiceComponent.cs +++ b/Content.Shared/Dice/DiceComponent.cs @@ -1,6 +1,5 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; -using Robust.Shared.Serialization; namespace Content.Shared.Dice; diff --git a/Content.Shared/Dice/SharedDiceSystem.cs b/Content.Shared/Dice/SharedDiceSystem.cs index 8e2868e791d..71a51584d36 100644 --- a/Content.Shared/Dice/SharedDiceSystem.cs +++ b/Content.Shared/Dice/SharedDiceSystem.cs @@ -1,12 +1,18 @@ using Content.Shared.Examine; using Content.Shared.Interaction.Events; +using Content.Shared.Popups; using Content.Shared.Throwing; -using Robust.Shared.GameStates; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; namespace Content.Shared.Dice; public abstract class SharedDiceSystem : EntitySystem { + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + public override void Initialize() { base.Initialize(); @@ -14,76 +20,67 @@ public override void Initialize() SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnLand); SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnDiceAfterHandleState); - } - - private void OnDiceAfterHandleState(EntityUid uid, DiceComponent component, ref AfterAutoHandleStateEvent args) - { - UpdateVisuals(uid, component); } - private void OnUseInHand(EntityUid uid, DiceComponent component, UseInHandEvent args) + private void OnUseInHand(Entity entity, ref UseInHandEvent args) { if (args.Handled) return; + Roll(entity, args.User); args.Handled = true; - Roll(uid, component); } - private void OnLand(EntityUid uid, DiceComponent component, ref LandEvent args) + private void OnLand(Entity entity, ref LandEvent args) { - Roll(uid, component); + Roll(entity); } - private void OnExamined(EntityUid uid, DiceComponent dice, ExaminedEvent args) + private void OnExamined(Entity entity, ref ExaminedEvent args) { //No details check, since the sprite updates to show the side. using (args.PushGroup(nameof(DiceComponent))) { - args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-1", ("sidesAmount", dice.Sides))); + args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-1", ("sidesAmount", entity.Comp.Sides))); args.PushMarkup(Loc.GetString("dice-component-on-examine-message-part-2", - ("currentSide", dice.CurrentValue))); + ("currentSide", entity.Comp.CurrentValue))); } } - public void SetCurrentSide(EntityUid uid, int side, DiceComponent? die = null) + private void SetCurrentSide(Entity entity, int side) { - if (!Resolve(uid, ref die)) - return; - - if (side < 1 || side > die.Sides) + if (side < 1 || side > entity.Comp.Sides) { - Log.Error($"Attempted to set die {ToPrettyString(uid)} to an invalid side ({side})."); + Log.Error($"Attempted to set die {ToPrettyString(entity)} to an invalid side ({side})."); return; } - die.CurrentValue = (side - die.Offset) * die.Multiplier; - Dirty(uid, die); - UpdateVisuals(uid, die); + entity.Comp.CurrentValue = (side - entity.Comp.Offset) * entity.Comp.Multiplier; + Dirty(entity); } - public void SetCurrentValue(EntityUid uid, int value, DiceComponent? die = null) + public void SetCurrentValue(Entity entity, int value) { - if (!Resolve(uid, ref die)) - return; - - if (value % die.Multiplier != 0 || value/ die.Multiplier + die.Offset < 1) + if (value % entity.Comp.Multiplier != 0 || value / entity.Comp.Multiplier + entity.Comp.Offset < 1) { - Log.Error($"Attempted to set die {ToPrettyString(uid)} to an invalid value ({value})."); + Log.Error($"Attempted to set die {ToPrettyString(entity)} to an invalid value ({value})."); return; } - SetCurrentSide(uid, value / die.Multiplier + die.Offset, die); + SetCurrentSide(entity, value / entity.Comp.Multiplier + entity.Comp.Offset); } - protected virtual void UpdateVisuals(EntityUid uid, DiceComponent? die = null) + private void Roll(Entity entity, EntityUid? user = null) { - // See client system. - } + var rand = new System.Random((int)_timing.CurTick.Value); - public virtual void Roll(EntityUid uid, DiceComponent? die = null) - { - // See the server system, client cannot predict rolling. + var roll = rand.Next(1, entity.Comp.Sides + 1); + SetCurrentSide(entity, roll); + + var popupString = Loc.GetString("dice-component-on-roll-land", + ("die", entity), + ("currentSide", entity.Comp.CurrentValue)); + _popup.PopupPredicted(popupString, entity, user); + _audio.PlayPredicted(entity.Comp.Sound, entity, user); } } From ef4f5cf0a1acb338e101ac1a99a585c9c738bde4 Mon Sep 17 00:00:00 2001 From: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Date: Fri, 14 Feb 2025 19:59:19 -0800 Subject: [PATCH 037/365] Engineering guidebook improvements (#34695) * engineering guidebook improvements * whoops * Changes * this game is en-US, so it shall be gasses, not gases * make changes to AccessConfigurator.xml, SolarPanels.xml, TeslaEngine.xml --------- Co-authored-by: ScarKy0 --- .../Solar/Components/SolarPanelComponent.cs | 2 + .../Entities/Objects/Devices/flatpack.yml | 4 +- .../Power/Generation/PA/particles.yml | 4 +- .../Structures/Power/Generation/ame.yml | 4 +- .../Structures/Power/Generation/teg.yml | 8 ++-- .../Engineering/AccessConfigurator.xml | 22 ++++++++-- .../Guidebook/Engineering/AirInjector.xml | 3 +- .../Guidebook/Engineering/AirVent.xml | 4 +- .../Guidebook/Engineering/Fires.xml | 2 +- .../Engineering/MixingAndFiltering.xml | 12 +++--- .../Engineering/PortableGenerator.xml | 4 +- .../Engineering/PortableScrubber.xml | 8 ++-- .../Guidebook/Engineering/PowerStorage.xml | 8 ++-- .../Guidebook/Engineering/Pumps.xml | 4 +- .../ServerInfo/Guidebook/Engineering/RTG.xml | 2 +- .../Guidebook/Engineering/Radiators.xml | 2 + .../Guidebook/Engineering/Ramping.xml | 4 +- .../Guidebook/Engineering/Shuttlecraft.xml | 4 +- .../Guidebook/Engineering/SignalValve.xml | 4 +- .../Engineering/SingularityEngine.xml | 10 ++--- .../Guidebook/Engineering/SolarPanels.xml | 19 +++++++- .../Guidebook/Engineering/Spacing.xml | 4 +- .../ServerInfo/Guidebook/Engineering/TEG.xml | 22 +++++----- .../Guidebook/Engineering/TeslaEngine.xml | 43 ++++++++++--------- .../Guidebook/Engineering/Thermomachines.xml | 14 +++--- .../Guidebook/Engineering/WirePanels.xml | 6 +-- 26 files changed, 129 insertions(+), 94 deletions(-) diff --git a/Content.Server/Solar/Components/SolarPanelComponent.cs b/Content.Server/Solar/Components/SolarPanelComponent.cs index 870b4c78ef1..edf622d06f7 100644 --- a/Content.Server/Solar/Components/SolarPanelComponent.cs +++ b/Content.Server/Solar/Components/SolarPanelComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Solar.EntitySystems; +using Content.Shared.Guidebook; namespace Content.Server.Solar.Components { @@ -15,6 +16,7 @@ public sealed partial class SolarPanelComponent : Component /// Maximum supply output by this panel (coverage = 1) /// [DataField("maxSupply")] + [GuidebookData] public int MaxSupply = 750; /// diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml index 4facfd91b3a..fa23947c5ce 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml @@ -83,8 +83,8 @@ - state: singularity-generator - type: GuideHelp guides: - - SingularityEngine - - Power + - SingularityEngine + - Power - type: entity parent: BaseFlatpack diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml index 0a36d91e38d..6e55bfec5e8 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/PA/particles.yml @@ -48,8 +48,8 @@ Level3: {state: particle3} - type: GuideHelp # why does this even have a guidebook link in the first place guides: - - SingularityTeslaEngine - - Power + - SingularityTeslaEngine + - Power - type: entity name: anti particles diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml index 550dddc8476..b3723252d38 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml @@ -204,8 +204,8 @@ node: ameShielding - type: GuideHelp guides: - - AME - - Power + - AME + - Power - type: Electrified onHandInteract: false onInteractUsing: false diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml index 860462788b2..e6e4b6b72bf 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml @@ -98,8 +98,8 @@ path: /Audio/Ambience/Objects/vending_machine_hum.ogg - type: GuideHelp guides: - - TEG - - Power + - TEG + - Power - type: StealTarget stealGroup: Teg @@ -159,8 +159,8 @@ - type: Pullable - type: GuideHelp guides: - - TEG - - Power + - TEG + - Power # functionality - type: NodeContainer diff --git a/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml b/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml index fea8104a5b3..a73bdde959c 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AccessConfigurator.xml @@ -27,8 +27,24 @@ A device with no access requirements set, like a public access airlock, can be modified using any valid station ID card. - ## Repairing damaged ID card readers - Syndicate agents may attempt to hack access-restricted devices through the use of a [color=#a4885c]Cryptographic Sequencer (EMAG)[/color]. This nefarious tool will completely short out any ID card readers that are attached to the device. + ## Repairing access-broken ID card readers + Syndicate agents may attempt to hack access-restricted devices through the use of an [color=#a4885c]Authentication Disruptor[/color]. + This nefarious tool will completely short out any ID card readers that are attached to the device, making it all-access. + + + + + + To repair the damage, you'll commonly need to partially deconstruct the device and reconstruct it. + This will reset the access requirements to defaults, allowing you to reconfigure the device as needed. + + ## Repairing access-broken Airlocks + Airlocks can be repaired multiple ways if their access requirements have been tampered with. + + If you have an Access Configurator, you can use a [color=#a4885c]Screwdriver[/color] to remove the airlock's maintenance panel, and then use the Access Configurator to reconfigure the airlock's access requirements. + No partial deconstruction is needed. + + If you don't have an Access Configurator, you can still fix the airlock by partially deconstructing it until you remove the door electronics, and then reconstructing it. + This will reset the airlock to the default access requirements it had at the start of the shift. - Engineers will need to partially de/reconstruct affected devices, and then set appropriate access permissions afterwards using the access configurator (or network configurator, for airlocks), to re-establish access restrictions. diff --git a/Resources/ServerInfo/Guidebook/Engineering/AirInjector.xml b/Resources/ServerInfo/Guidebook/Engineering/AirInjector.xml index 3c4027dcb3b..54b9cccba8b 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AirInjector.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AirInjector.xml @@ -4,12 +4,11 @@ - It is primarily used to force gasses into high-pressure rooms like the station's [textlink="gas storage rooms" link="GasMiningAndStorage"], or a burn chamber. + It is primarily used to force gasses into high-pressure rooms like the station's [textlink="gas storage rooms" link="GasMiningAndStorage"] or a burn chamber. The air injector does not require [textlink="power" link="Power"] to function. The air injector will inject gasses into the atmosphere it's exposed to until the atmosphere reaches [color=orange][protodata="GasOutletInjector" comp="GasOutletInjector" member="MaxPressure"/] kPa[/color]. The air injector's speed is proportional to the amount of gas in the injector. - The more gas in the injector, the faster it will inject gas into the exposed atmosphere. diff --git a/Resources/ServerInfo/Guidebook/Engineering/AirVent.xml b/Resources/ServerInfo/Guidebook/Engineering/AirVent.xml index d6b81fa19b6..bbdd1a530ab 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AirVent.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AirVent.xml @@ -53,8 +53,8 @@ - If the Internal bound pressure is set to 50 kPa, the air vent will not draw gas from the connected pipe if its pressure is below 50 kPa. When the vent is in siphoning mode: - - If the External bound pressure is set to 101.3 kPa, the air vent will siphon gases until the atmosphere reaches 101.3 kPa. - - If the Internal bound pressure is set to 50 kPa, the air vent will not push gases into the pipenet if its pressure is above 50 kPa. + - If the External bound pressure is set to 101.3 kPa, the air vent will siphon gasses until the atmosphere reaches 101.3 kPa. + - If the Internal bound pressure is set to 50 kPa, the air vent will not push gasses into the pipenet if its pressure is above 50 kPa. If you're still confused about PressureBounds, here's a simple way to think about it: - You can think of the External bound as the upper limit for the exposed atmosphere. "I will not pressurize the exposed atmosphere past this pressure, or draw from the atmosphere below this pressure." diff --git a/Resources/ServerInfo/Guidebook/Engineering/Fires.xml b/Resources/ServerInfo/Guidebook/Engineering/Fires.xml index ca728f1e8d6..1afcca89f72 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Fires.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Fires.xml @@ -7,7 +7,7 @@ - They often make high-pressure and high-temperature gases, which can quickly spread throughout the station if firelocks or doors are opened carelessly, even if only for a moment. + They often make high-pressure and high-temperature gasses, which can quickly spread throughout the station if firelocks or doors are opened carelessly, even if only for a moment. If people are caught in a fire, they can quickly become incapacitated, die, and even ash, rendering them unrevivable. diff --git a/Resources/ServerInfo/Guidebook/Engineering/MixingAndFiltering.xml b/Resources/ServerInfo/Guidebook/Engineering/MixingAndFiltering.xml index 5814dd4b083..d38dd1bd99a 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/MixingAndFiltering.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/MixingAndFiltering.xml @@ -1,13 +1,13 @@ # Mixing and Filtering - Gas mixers and filters are essential tools for manipulating the composition of gases within a [textlink="pipe network" link="PipeNetworks"]. + Gas mixers and filters are essential tools for manipulating the composition of gasses within a [textlink="pipe network" link="PipeNetworks"]. ## Gas Mixer - Gas mixers are used to combine gases in specific ratios within a [textlink="pipe network." link="PipeNetworks"] + Gas mixers are used to combine gasses in specific ratios within a [textlink="pipe network." link="PipeNetworks"] They are essential for creating controlled gas mixtures for various applications. Gas mixers have 3 connections: 2 inputs and 1 output, as shown below: @@ -20,7 +20,7 @@ - Gas mixers will still respect the requested gas mixture even if one of the input gases is not available. For example: + Gas mixers will still respect the requested gas mixture even if one of the input gasses is not available. For example: - If the requested mixture is 22% oxygen and 78% nitrogen, but there is no available oxygen, the mixer will not work until oxygen is available. - If oxygen is available, but at a pressure lower than required to create the proper mixture at the requested pressure, the mixer will still create the mixture, but the output will be at a lower pressure than requested. @@ -36,7 +36,7 @@ - Mixing oxygen and plasma for plasma burning to create Tritium ## Gas Filter - Gas filters are used to separate gases from a mixture within a [textlink="pipe network." link="PipeNetworks"] + Gas filters are used to separate gasses from a mixture within a [textlink="pipe network." link="PipeNetworks"] @@ -50,6 +50,6 @@ Gas filters will become blocked and will not filter gas if either output is blocked. Gas filters can be used in a variety of applications, for example: - - Filtering out unwanted gases from a [textlink="pipe network" link="PipeNetworks"] - - Separating specific gases for storage in a station's recyclernet + - Filtering out unwanted gasses from a [textlink="pipe network" link="PipeNetworks"] + - Separating specific gasses for storage in a station's recyclernet diff --git a/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml b/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml index 55a1e7fde54..d4eee0f5971 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/PortableGenerator.xml @@ -22,7 +22,7 @@ Setup is incredibly easy: wrench it down above an [color=green]LV[/color] power cable, give it some welding fuel, and start it up. - Welding fuel is the only fuel source the J.R.P.A.C.M.A.N. can use, and it can be found in welding fuel tanks across the station, commonly in maintenance areas. + Welding fuel is the only fuel source the J.R.P.A.C.M.A.N. can use and it can be found in welding fuel tanks across the station, commonly in maintenance areas. # The Big Ones @@ -41,7 +41,7 @@ The S.U.P.E.R.P.A.C.M.A.N. boasts a larger power output (up to [color=orange][protodata="PortableGeneratorSuperPacman" comp="FuelGenerator" member="MaxTargetPower" format="N0"/] W[/color]) and longer runtime at maximum output, but scales down to lower outputs less efficiently. - They connect directly to [color=yellow]MV[/color] or [color=orange]HV[/color] [textlink="power cables" link="VoltageNetworks"], and are able to switch between them for flexibility. + They connect directly to [color=yellow]MV[/color] or [color=orange]HV[/color] [textlink="power cables" link="VoltageNetworks"] and are able to switch between them for flexibility. The S.U.P.E.R.P.A.C.M.A.N and P.A.C.M.A.N require uranium sheets and plasma sheets as fuel, respectively. diff --git a/Resources/ServerInfo/Guidebook/Engineering/PortableScrubber.xml b/Resources/ServerInfo/Guidebook/Engineering/PortableScrubber.xml index 287e591ff32..cfc82b9d728 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/PortableScrubber.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/PortableScrubber.xml @@ -5,16 +5,16 @@ - It is invaluable for quickly scrubbing unwanted gasses from a room when regular [textlink="scrubbers" link="AirScrubber"] are working too slow, or when a [textlink="scrubber" link="AirScrubber"] is not present. + It is invaluable for quickly removing unwanted gasses from a room when regular [textlink="scrubbers" link="AirScrubber"] are working too slow, or when a [textlink="scrubber" link="AirScrubber"] is not present. The portable scrubber requires [textlink="power" link="Power"] through a nearby [textlink="LV cable" link="VoltageNetworks"] to function. - The portable scrubber automatically starts scrubbing all non-breathable gasses from the room when it is bolted (wrenched) to the floor. + The portable scrubber automatically starts removing all non-breathable gasses from the room when it is bolted (wrenched) to the floor. - It will stop scrubbing when unbolted (unwrenched) from the floor, or when its internal volume is full. The scrubber has an internal capacity of [color=orange][protodata="PortableScrubber" comp="PortableScrubber" member="Volume"/] liters[/color]. + It will stop scrubbing when unbolted (unwrenched) from the floor or when its internal volume is full. The scrubber has an internal capacity of [color=orange][protodata="PortableScrubber" comp="PortableScrubber" member="Volume"/] liters[/color]. ## Dumping Waste and Storage - The scrubber's internal volume can be emptied by bolting (wrenching it) onto a [textlink="connector" link="GasCanisters"]. + The scrubber's internal volume can be emptied by anchoring (wrenching) onto a [textlink="connector" link="GasCanisters"]. Stations commonly have a dedicated emptying point to quickly transfer the waste from the scrubber to the station's wastenet. diff --git a/Resources/ServerInfo/Guidebook/Engineering/PowerStorage.xml b/Resources/ServerInfo/Guidebook/Engineering/PowerStorage.xml index efd0167eb13..729e1720e19 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/PowerStorage.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/PowerStorage.xml @@ -29,7 +29,7 @@ - SMESes can store [color=orange][protodata="SMESBasic" comp="Battery" member="MaxCharge" format="N0"/] J[/color] of energy, and can output a maximum [color=orange][protodata="SMESBasic" comp="PowerNetworkBattery" member="MaxSupply" format="N0"/] W[/color] of power. + SMESes can store [color=orange][protodata="SMESBasic" comp="Battery" member="MaxCharge" format="N0"/] J[/color] of energy and can output a maximum [color=orange][protodata="SMESBasic" comp="PowerNetworkBattery" member="MaxSupply" format="N0"/] W[/color] of power. If the battery is full, the SMES will pass through the power it receives from the input cable to the output cable. In the event of a power deficit, the SMES will ramp up to supplement the power draw. @@ -41,10 +41,10 @@ They're primarily used in station SMES arrays to store large amounts of power for the station's power grid. - They help to buy engineers time to setup power at roundstart, or to provide power in the event of a power deficit for extended periods of time. + They help to buy engineers time to setup power at roundstart or to provide power in the event of a power deficit for extended periods of time. - Advanced SMESes can store [color=orange][protodata="SMESAdvanced" comp="Battery" member="MaxCharge" format="N0"/] J[/color] of energy, and can output a maximum [color=orange][protodata="SMESAdvanced" comp="PowerNetworkBattery" member="MaxSupply" format="N0"/] W[/color] of power. + Advanced SMESes can store [color=orange][protodata="SMESAdvanced" comp="Battery" member="MaxCharge" format="N0"/] J[/color] of energy and can output a maximum [color=orange][protodata="SMESAdvanced" comp="PowerNetworkBattery" member="MaxSupply" format="N0"/] W[/color] of power. - Keep in mind that these aren't a magic solution to power deficits, and they can't store infinite energy. + Keep in mind that these aren't a magic solution to power deficits and they can't store infinite energy. A station load will drain these battries quickly if there is no power source partially supporting them. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Pumps.xml b/Resources/ServerInfo/Guidebook/Engineering/Pumps.xml index 7b399b0e5a4..3574e34a82e 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Pumps.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Pumps.xml @@ -1,7 +1,7 @@  # Pumps Pumps are the primary way of actively moving gasses through a [textlink="pipenet." link="PipeNetworks"] - They take gas from one side, and push it to the other. + They take gas from one side and push it to the other. There are two different types of pumps: @@ -16,7 +16,7 @@ - Pumps cannot move gasses into pipes with pressures or volumes exceeding their [color=#a4885c]limit[/color]. This causes them to be [color=red]blocked[/color]. Pumps will show a colorful animation when they are doing work. - If they have no gas to pump, or they are blocked, they will show a blinking [color=red]red[/color] animation. + If they have no gas to pump or they are blocked, they will show a blinking [color=red]red[/color] animation. Pumps that are off, have no power, or are unanchored will show no animation. ## Pressure Pumps diff --git a/Resources/ServerInfo/Guidebook/Engineering/RTG.xml b/Resources/ServerInfo/Guidebook/Engineering/RTG.xml index 06280d5eefb..d412251e246 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/RTG.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/RTG.xml @@ -9,7 +9,7 @@ They require no maintenance and are a reliable source of power, making them ideal for powering essential systems that need to be online at all times, like Telecoms, the AI, or the Crew Monitoring Server. - RTGs always generate [color=orange][protodata="GeneratorRTG" comp="PowerSupplier" member="MaxSupply" format="N0"/] W[/color] of power, and must be connected to an [color=orange]HV power[/color] [textlink="network" link="VoltageNetworks"] to function. + RTGs always generate [color=orange][protodata="GeneratorRTG" comp="PowerSupplier" member="MaxSupply" format="N0"/] W[/color] of power and must be connected to an [color=orange]HV power[/color] [textlink="network" link="VoltageNetworks"] to function. However, they're only accessible through salvage finding one on an expedition. Should they bring some in, make sure to thank them! diff --git a/Resources/ServerInfo/Guidebook/Engineering/Radiators.xml b/Resources/ServerInfo/Guidebook/Engineering/Radiators.xml index 0d3ec5f8f6a..54711422709 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Radiators.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Radiators.xml @@ -21,6 +21,8 @@ If you're exchanging heat with space, you can only get as cold as space. To increase the efficiency of radiation, you can build radiators on lattice, which will allow the radiator to radiate more heat, compared to being directly attached to hull tile. + Gas will flow naturally through the radiator via differences in pressure, but you can use a gas pump to increase the flow rate. + Increasing the flow rate of gas through the radiator will increase the rate of heat exchange. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Ramping.xml b/Resources/ServerInfo/Guidebook/Engineering/Ramping.xml index 6799353143f..b7ef39c2a4b 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Ramping.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Ramping.xml @@ -12,9 +12,9 @@ - After some seconds have passed, the generator will have ramped up to 100 kW of power, and the brownout will end. All devices are now satisfied with the power they are receiving. During a shift, this is most commonly observed when a generator runs out of fuel and suddenly stops producing power. - Suddenly, the grid is hit with a large deficit of power (as supply has fallen below demand), and all devices will experience a brownout until SMESes or other generators can ramp up to match the new demand. + Suddenly the grid is hit with a large deficit of power (as supply has fallen below demand), and all devices will experience a brownout until SMESes or other generators can ramp up to match the new demand. - This can also happen when a large power consuming device, or department, is reconnected to the grid. + This can also happen when a large power consuming device or department is reconnected to the grid. The sudden increase in power draw will cause a brownout until the generators can ramp up to match the new demand. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml b/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml index 8dc7cdf57dd..27151a7565b 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Shuttlecraft.xml @@ -46,7 +46,7 @@ - Head out into space with steel sheets and metal rods in hand, and click on the edge of the station to place lattice. + Head out into space with steel sheets and metal rods in hand and click on the edge of the station to place lattice. Place a line of lattice about 3-4 tiles away from the station, then start building a platform with lattice. @@ -54,7 +54,7 @@ Once you're finished constructing the base of your shuttle, you can use wirecutters to snip the connecting lattice that joins your new ship and the station. - This platform is considered a different grid from the station, and thus will not have any gravity or be held in place by a station anchor — it can move around freely. + This platform is considered a different grid from the station and thus will not have any gravity or be held in place by a station anchor — it can move around freely. You can expand your lattice platform further by clicking just off the edge with some rods in hand. diff --git a/Resources/ServerInfo/Guidebook/Engineering/SignalValve.xml b/Resources/ServerInfo/Guidebook/Engineering/SignalValve.xml index dd2991e56da..2b6b11dd58d 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/SignalValve.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/SignalValve.xml @@ -5,13 +5,13 @@ - The signal valve is similar to the manual valve. Gas can flow unrestricted in both directions, and it can be operated manually. + The signal valve is similar to the manual valve. Gas can flow unrestricted in both directions and it can be operated manually. The signal valve has 3 [textlink="signal" link="Networking"] inputs, which can open, close, or toggle the valve. Signal valves can be used in a variety of applications, for example: - Remote control of valves in hazardous areas or areas inaccessible to crew - Convenient control over a valve in a hard-to-reach area - - Automation with other [textlink="signal-enabled" link="Networking"] machines and equipment such as [textlink="air alarms" link="AirAlarms"] and remote signallers + - Automation with other [textlink="signal-enabled" link="Networking"] machines and equipment such as [textlink="air alarms" link="AirAlarms"] and remote signalers diff --git a/Resources/ServerInfo/Guidebook/Engineering/SingularityEngine.xml b/Resources/ServerInfo/Guidebook/Engineering/SingularityEngine.xml index 6b4bb1780a6..cca2bd24793 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/SingularityEngine.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/SingularityEngine.xml @@ -28,7 +28,7 @@ It is suggested to use a max size containment field for the singularity. Any smaller and the singularity may outgrow its field and escape. - Containment pylons should be arranged in a square, with 7 tiles of spacing between each pylon. + Containment field generators should be arranged in a square, with 7 tiles of spacing between each field generator. @@ -62,11 +62,11 @@ From here, you can refill the tank with plasma using a plasma canister and reinsert it into the collector. The maximum power the radiation collector can produce is determined by: - - The amount of radiation it is capturing (which is effectively the Singularity's power level), - - and the amount of plasma it has in its connected tank. + - The amount of radiation it is capturing (which is effectively the Singularity's power level) + - The amount of plasma it has in its connected tank - Over time, the collector will drain the tank of plasma, which reduces it's effective power output. - Eventually, the tank will be empty, and the collector will stop producing power. Be sure to refill the tank often! + Over time the collector will drain the tank of plasma, which reduces it's effective power output. + Eventually the tank will be empty, and the collector will stop producing power. Be sure to refill the tank often! ## Radiation Protection The singularity emits a massive amount of radiation, which can kill crew members who are not wearing proper protection. diff --git a/Resources/ServerInfo/Guidebook/Engineering/SolarPanels.xml b/Resources/ServerInfo/Guidebook/Engineering/SolarPanels.xml index d97d2a78d7f..2873e0ae100 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/SolarPanels.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/SolarPanels.xml @@ -18,6 +18,8 @@ This is because the station is occluding some panels, so don't worry about it too much. Most stations have solar arrays placed at all sides of the station, so there's always some power being generated. + When directly facing the sun with no object occlusion, regular solar panels can generate [color=orange][protodata="SolarPanel" comp="SolarPanel" member="MaxSupply" format="N0"] W[/color] of power. + Solar panels generate [color=orange]HV power[/color], and as such require an [color=orange]HV wire[/color] running underneath them to connect to the station's power grid. ## Power Bridge @@ -81,17 +83,30 @@ ## Setting Up Solar Panel Arrays Solar panel arrays are commonly found on the exterior of the station, and are used to generate large amounts of free power for the station. - You can either spacewalk to them from the outside, or you can run a loop through the station's maintenance tunnels to reach them. + You can either spacewalk to them from the outside or you can run a loop through the station's maintenance tunnels to reach them. Solar array machine rooms are often marked with signs, and locked behind engineering access. - At the start of the shift, solar panels are misaligned and disconnected from the grid. + At the start of the shift solar panels are misaligned and disconnected from the grid. You will need to align them and connect them to the station's power grid to start generating power. This usually involves running a line of [color=orange]HV wire[/color] to the pannels from the solar array machine room, and then using a Solar Control Computer to align the panels. Solar array machine rooms frequently have a Solar Control Computer nearby, as well as an [textlink="SMES" link="PowerStorage"] to store the power generated by the panels for later use. + + ## Upgrading Solar Panels + Solar panels can be upgraded to increase their power output. + + + + + + + This can be done by replacing the glass sheets in the solar assembly with plasma or uranium glass sheets. + + Plasma and Uranium solar panels generate [color=orange][protodata="SolarPanelPlasma" comp="SolarPanel" member="MaxSupply" format="N0"] W[/color] and [color=orange][protodata="SolarPanelUranium" comp="SolarPanel" member="MaxSupply" format="N0"] W[/color] of power respectively. + They also are much tougher than regular solar panels. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Spacing.xml b/Resources/ServerInfo/Guidebook/Engineering/Spacing.xml index 01097c10e2b..6fb9565ad3b 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Spacing.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Spacing.xml @@ -6,14 +6,14 @@ Fixing spacing generally follows two simple steps: - 1. Identify the area that has been spaced, and [textlink="seal the hole." link="ExpandingRepairingStation"] + 1. Identify the area that has been spaced and [textlink="seal the hole." link="ExpandingRepairingStation"] - If you're having trouble finding the hole, you can carefully listen for the flow of air rushing by you, if air is currently leaking to space. - Look for any holes underneath girders that may be hard to see. 2. Repressurize the area. - - [textlink="Air vents" link="AirVent"] enter pressure lockout when a room is spaced, so you'll need to override the vents to repressurize the area. You can do this by setting the connected [textlink="air alarm" link="AirAlarms"] to fill, or by using a screwdriver on a vent to manually override it temporarily. + - [textlink="Air vents" link="AirVent"] enter pressure lockout in order not to lose more air to spacing, so you'll need to override the vents to repressurize the area. You can do this by setting the connected [textlink="air alarm" link="AirAlarms"] to fill, or by using a screwdriver on a vent to manually override it temporarily. ## Things to Avoid - Keep in mind that while you have an infinite supply of [textlink="mined gas" link="GasMiningAndStorage"], it is not quick enough to fill up multiple rooms at once. Setting [textlink="air alarms" link="AirAlarms"] to fill first [italic]before[/italic] fixing the root problem will often lead to wasted time and gas. diff --git a/Resources/ServerInfo/Guidebook/Engineering/TEG.xml b/Resources/ServerInfo/Guidebook/Engineering/TEG.xml index f79ad044ceb..04268e2db32 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/TEG.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/TEG.xml @@ -1,8 +1,8 @@ # Thermo-electric Engine (TEG) - The TEG generates power by exchanging heat between hot and cold gases. - On the station, hot gas is usually created by burning plasma, and an array of [textlink="heat-exchanging" link="Radiators"] pipes in space radiates away heat to cool down circulated gases. + The TEG generates power by exchanging heat between hot and cold gasses. + On the station, hot gas is usually created by burning plasma, and an array of [textlink="heat-exchanging" link="Radiators"] pipes in space radiates away heat to cool down circulated gasses. The TEG relies heavily on [textlink="atmospherics" link="Atmospherics"] [textlink="piping." link="Pipes"] The only truly special component about it is the generator core and circulators; the rest is all off-the-shelf atmospherics equipment. @@ -26,7 +26,7 @@ A pressure difference is required across the input and output, so pumps are generally provided and must be turned on. There is no preference for which side must be hot or cold, there need only be a difference in temperature between them. - The gases in the two "loops" are never mixed, [color=#a4885c]only energy is exchanged between them[/color]. + The gasses in the two "loops" are never mixed, [color=#a4885c]only energy is exchanged between them[/color]. The hot side will cool down, the cold side will heat up. ## The Pipes @@ -39,14 +39,14 @@ As I'm sure a wise person once said: the best way to make something hot is to light it on fire. Well, depending on context, that may not be very wise, but luckily your engineering department has just what's needed to do it wisely after all. - As stated above, there are many different layouts one can follow to heat up (or cool down) gases; this part of the guide will cover some common methods one will often see for the hot loop, involving [color=red]the Burn Chamber[/color]. + As stated above, there are many different layouts one can follow to heat up (or cool down) gasses; this part of the guide will cover some common methods one will often see for the hot loop, involving [color=red]the Burn Chamber[/color]. Side note: Plasma fires burn relatively cool compared to, for example, Tritium fires. It may be viable to extract Tritium from an extraction setup and react it with Oxygen to get truly hellish temperatures for power. ## The Burn Chamber - The burn chamber is the preferred method for heating up gases, and it is commonly used for other purposes too. (see: Tritium production) + The burn chamber is the preferred method for heating up gasses, and it is commonly used for other purposes too. (see: Tritium production) Most (if not all) stations have the burn chamber separated from the main atmospherics block by a 1-wide spaced grid, to prevent the flow of scalding hot gas to Atmos if there was a breach. The chambers consist of 3 important parts: - The [textlink="Air Injector" link="AirInjector"]/[textlink="Passive Vent" link="PassiveVent"] @@ -89,7 +89,7 @@ There is a notable difference between the [textlink="passive vent" link="PassiveVent"] and the [textlink="air injector" link="AirInjector"]; the [textlink="air injector" link="AirInjector"] can only keep injecting air up to [color=#a4885c]9MPa[/color], which can be reached very easily with a good burn. Ideally, switch out the [textlink="air injector" link="AirInjector"] for a [textlink="passive vent" link="PassiveVent"] connected to a volume pump. - The space vent (designated as a blast door to space on one side of the burn chamber) allows waste gases to be expelled and destroyed. + The space vent (designated as a blast door to space on one side of the burn chamber) allows waste gasses to be expelled and destroyed. Open this occasionally to keep the pressure under control, or to space excess input gas. You even might find the pneumatic valve useful for occasionally spacing the gas. @@ -221,14 +221,14 @@ ## In the Pursuit of Greater Efficiency Remember, Atmospherics is a science, and as such, it is always evolving. - The setups above are just the tip of the iceberg; there are many ways to setup the TEG, and many ways to improve upon the setups above. + The setups above are just the tip of the iceberg; there are many ways to setup the TEG and many ways to improve upon the setups above. [color=#a4885c]Experiment![/color] - Always seek to improve upon the designs you see, and always seek to improve upon the designs you make. - The TEG is a powerful tool, and with great power comes great responsibility. - Make sure to use it wisely, and make sure to use it well. + Always seek to improve upon the designs you see and always seek to improve upon the designs you make. + The TEG is a powerful tool and with great power comes great responsibility. + Make sure to use it wisely and make sure to use it well. - Space Station 14 atmospherics is a complex system, and the TEG is just one part of it. + Space Station 14 atmospherics is a complex system and the TEG is just one part of it. [bold]It's like a giant puzzle, so go out and solve it![/bold] diff --git a/Resources/ServerInfo/Guidebook/Engineering/TeslaEngine.xml b/Resources/ServerInfo/Guidebook/Engineering/TeslaEngine.xml index 859ec8317eb..168f6253a8a 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/TeslaEngine.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/TeslaEngine.xml @@ -37,30 +37,13 @@ The Tesla prefers to strike some objects more than others, such as Tesla Coils and Grounding Rods. - - - - - - - - - - - Because of this, strategically placing Tesla Coils and Grounding Rods around the lightning ball can help protect sensitive equipment from being struck, and prevent a loosed tesla (tesloose). - If the tesla can't find any Tesla Coils or Grounding Rods to strike first, it will strike almost any station object capable of being powered, such as Substations, APCs, and general machinery. Certain objects aren't struck by the tesla, such as batteries, lights, PDAs, and other handheld items. It will also strike mobs and crew members, shocking them. Make sure to wear insulated gloves before approaching it. - Note that only placing Tesla Coils won't be enough to prevent the tesla from striking sensitive equipment. - Grounding Rods should also be placed to help protect nearby Emitters from being struck. - - Engineers can also use grounding rods to protect sensitive equipment from lightning strikes, such as the Emitters powering the containment field generators. - - ## Power Generation + ## Tesla Coils Lightning strikes can be harnessed using Tesla Coils, which convert the lightning strikes into power for the station. @@ -69,16 +52,34 @@ - Tesla Coils should be placed around the lightning ball to capture the lightning strikes, as well as to prevent the lightning from striking sensitive equipment further away. + Tesla Coils should be placed around the lightning ball to capture the energy from lightning strikes, as well as to prevent the lightning from striking sensitive equipment further away. Tesla Coils take damage every time they are struck by lightning, and will eventually break if not repaired. Be sure to monitor the condition of the Tesla Coils and repair them as needed. - Grounding rods, in contrast, do not take damage from lightning strikes. - When lightning strikes Tesla Coils, they fill an internal battery, which is rapidly discharged to the grid. It will discharge this power even if there is no consumer to take it, so it's a good idea to have an SMES nearby to store the power and discharge it smoothly. + ## Grounding Rods + Grounding Rods help protect sensitive equipment from being struck and prevent a loosed tesla (tesloose). + + + + + + + + + + + + + + Grounding rods do not take damage from lightning strikes. + This makes them beneficial for forming a saftey net of grounding rods to rely on in case the tesla coils are damaged or destroyed. + + Engineers should use grounding rods to protect sensitive equipment from lightning strikes, such as the Emitters powering the containment field generators. + ## Loosed Tesla (Tesloose) If the lightning ball escapes the containment field, it is referred to as a loosed tesla, or tesloose. diff --git a/Resources/ServerInfo/Guidebook/Engineering/Thermomachines.xml b/Resources/ServerInfo/Guidebook/Engineering/Thermomachines.xml index 64035cde193..7d449f33ed6 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/Thermomachines.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/Thermomachines.xml @@ -1,6 +1,6 @@  # Thermomachines - Thermomachines are devices that manipulate the temperature of gases within a [textlink="pipe network" link="PipeNetworks"] or exposed atmosphere. + Thermomachines are devices that manipulate the temperature of gasses within a [textlink="pipe network" link="PipeNetworks"] or exposed atmosphere. @@ -8,10 +8,10 @@ They are essential for maintaining the temperature of gasses for various applications. - All thermomachines work by using [textlink="electrical power" link="Power"] to preform work on the atmosphere to either heat or cool it. - The amount of work they do is directly related to the amount of power they consume. + All thermomachines work by using [textlink="electrical power" link="Power"] to heat or cool the atmosphere. + How much they heat/cool the atmosphere is directly related to the amount of power they consume. - Thermomachines also have an efficiency coefficient, which determines how much work they can do per unit of power consumed. + Thermomachines also have an efficiency coefficient, which determines how much they can heat or cool the atmosphere per unit of power consumed. To prevent overshooting their target value, thermomachines will scale back their heating/cooling power as they approach the target temperature. However, they will still consume the same amount of electrical power, even when idle. @@ -19,7 +19,7 @@ All thermomachines have a target temperature tolerance of [color=orange][protodata="GasThermoMachineFreezer" comp="GasThermoMachine" member="TemperatureTolerance"/] K[/color], meaning they will stop heating or cooling when the temperature is within [color=orange][protodata="GasThermoMachineFreezer" comp="GasThermoMachine" member="TemperatureTolerance"/] K[/color] of the target temperature. ## Space Heater - The space heater is a portable temperature control unit that preforms work to heat or cool gas in the atmosphere it's exposed to. + The space heater is a portable temperature control unit that heats or cools gas in the atmosphere it's exposed to. It's a simple and effective way to maintain the temperature of a room, without having to build a pipenet or other system. @@ -30,7 +30,7 @@ The space heater can cool to as low as [color=orange][protodata="SpaceHeater" comp="SpaceHeater" member="MinTemperature"/] K[/color] and heat to as high as [color=orange][protodata="SpaceHeater" comp="SpaceHeater" member="MaxTemperature"/] K[/color]. - It also has three power settings, which determine how much power it consumes and how much work it does. + It also has three power settings which determine how fast it heats or cools the atmosphere. Botany or science will often request these to maintain the temperature of their plants or department. @@ -44,7 +44,7 @@ They draw [color=orange][protodata="GasThermoMachineFreezer" comp="GasThermoMachine" member="HeatCapacity" format="N0"/] W[/color] of power and can heat or cool gas in a pipenet to as high as [color=orange][protodata="GasThermoMachineFreezer" comp="GasThermoMachine" member="MaxTemperature"/] K[/color] or as low as [color=orange][protodata="GasThermoMachineFreezer" comp="GasThermoMachine" member="MinTemperature"/] K[/color]. - You can swap the mode of the thermomachine by deconstructing it and using a screwdriver on its board. + You can swap the mode of the thermomachine by deconstructing it and using a screwdriver on its circuit board. The board can be printed at a circuit imprinter, commonly found in Science. diff --git a/Resources/ServerInfo/Guidebook/Engineering/WirePanels.xml b/Resources/ServerInfo/Guidebook/Engineering/WirePanels.xml index 6ac749120bc..aa86e6e9b38 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/WirePanels.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/WirePanels.xml @@ -10,16 +10,16 @@ - From here, you can use wirecutters and a multitool to interact with the wiring. Note that interacting with the wiring often requires insulated gloves, as if the wire is live, you can get shocked. + From here you can use wirecutters and a multitool to interact with the wiring. Note that interacting with the wiring often requires insulated gloves, as if the wire is live, you can get shocked. - You can cut and mend wires using the wirecutters, and pulse wires using the multitool. + You can cut and mend wires using the wirecutters and pulse wires using the multitool. Cutting wires often completely disables or restores functionality to a device. - It may also trigger unintended functionality, like shocking people, dropping door bolts, or exploding. + It may also trigger unintended functionality like shocking people, dropping door bolts, or exploding. Pulsing wires can have a variety of effects, but oftentimes it either temporarily disables or enables functionality. From b45613ad3395e3e6c1ee079fc0c3ba3037f6d8ee Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 15 Feb 2025 04:00:27 +0000 Subject: [PATCH 038/365] Automatic changelog update --- Resources/Changelog/Changelog.yml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 87f34230f62..a8f0fd717af 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: ArtisticRoomba - changes: - - message: HoS's energy shotgun is now correctly marked as grand theft contraband. - type: Tweak - id: 7455 - time: '2024-09-29T12:22:57.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32521 - author: Ilya246 changes: - message: Steel cost of conveyor belt assemblies halved. @@ -3888,3 +3881,20 @@ id: 7954 time: '2025-02-14T15:01:41.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/35122 +- author: ArtisticRoomba + changes: + - message: The Solar Panels section of the Engineering Guidebook has been updated + to include information on upgrading solar panels, as well as how much each variant + produces. + type: Add + - message: The Tesla Engine section of the Engineering Guidebook has been slightly + reorganized to better outline the differences between Grounding Rods and Tesla + Coils, as well as tips on how to use them. + type: Tweak + - message: The Access Configurator section of the Engineering Guidebook has been + updated to reflect the recent addition of the Authentication Disruptor, as well + as how to fix access-broken doors and equipment the easy way. + type: Add + id: 7955 + time: '2025-02-15T03:59:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34695 From 51a56e013c1a1a14b44edde50bc0ce579085419e Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Fri, 14 Feb 2025 23:29:40 -0500 Subject: [PATCH 039/365] Separate Udder examine into ExamineableHunger (#35164) * Separate udder hunger examine into ExamineableHunger * Fluent grammar improvements * Add ExamineableHunger to chickens and ducks. * Use starving message as "dead" message --- Content.Shared/Animals/UdderSystem.cs | 48 ------------------- .../Components/ExamineableHungerComponent.cs | 31 ++++++++++++ .../EntitySystems/ExamineableHungerSystem.cs | 41 ++++++++++++++++ .../en-US/animals/udder/udder-system.ftl | 6 --- .../examineable-hunger-component.ftl | 5 ++ .../Prototypes/Entities/Mobs/NPCs/animals.yml | 4 ++ 6 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 Content.Shared/Nutrition/Components/ExamineableHungerComponent.cs create mode 100644 Content.Shared/Nutrition/EntitySystems/ExamineableHungerSystem.cs create mode 100644 Resources/Locale/en-US/nutrition/components/examineable-hunger-component.ftl diff --git a/Content.Shared/Animals/UdderSystem.cs b/Content.Shared/Animals/UdderSystem.cs index cb6e5b307fe..177fbab2f2a 100644 --- a/Content.Shared/Animals/UdderSystem.cs +++ b/Content.Shared/Animals/UdderSystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.DoAfter; -using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; @@ -32,7 +31,6 @@ public override void Initialize() SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent>(AddMilkVerb); SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnExamine); } private void OnMapInit(EntityUid uid, UdderComponent component, MapInitEvent args) @@ -140,50 +138,4 @@ private void AddMilkVerb(Entity entity, ref GetVerbsEvent - /// Defines the text provided on examine. - /// Changes depending on the amount of hunger the target has. - /// - private void OnExamine(Entity entity, ref ExaminedEvent args) - { - - var entityIdentity = Identity.Entity(args.Examined, EntityManager); - - string message; - - // Check if the target has hunger, otherwise return not hungry. - if (!TryComp(entity, out var hunger)) - { - message = Loc.GetString("udder-system-examine-none", ("entity", entityIdentity)); - args.PushMarkup(message); - return; - } - - // Choose the correct examine string based on HungerThreshold. - switch (_hunger.GetHungerThreshold(hunger)) - { - case >= HungerThreshold.Overfed: - message = Loc.GetString("udder-system-examine-overfed", ("entity", entityIdentity)); - break; - - case HungerThreshold.Okay: - message = Loc.GetString("udder-system-examine-okay", ("entity", entityIdentity)); - break; - - case HungerThreshold.Peckish: - message = Loc.GetString("udder-system-examine-hungry", ("entity", entityIdentity)); - break; - - // There's a final hunger threshold called "dead" but animals don't actually die so we'll re-use this. - case <= HungerThreshold.Starving: - message = Loc.GetString("udder-system-examine-starved", ("entity", entityIdentity)); - break; - - default: - return; - } - - args.PushMarkup(message); - } } diff --git a/Content.Shared/Nutrition/Components/ExamineableHungerComponent.cs b/Content.Shared/Nutrition/Components/ExamineableHungerComponent.cs new file mode 100644 index 00000000000..00aba82e583 --- /dev/null +++ b/Content.Shared/Nutrition/Components/ExamineableHungerComponent.cs @@ -0,0 +1,31 @@ +using Content.Shared.Nutrition.EntitySystems; +using Robust.Shared.GameStates; + +namespace Content.Shared.Nutrition.Components; + +/// +/// Adds text to the entity's description box based on its current hunger threshold. +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(ExamineableHungerSystem))] +public sealed partial class ExamineableHungerComponent : Component +{ + /// + /// Dictionary of hunger thresholds to LocIds of the messages to display. + /// + [DataField] + public Dictionary Descriptions = new() + { + { HungerThreshold.Overfed, "examineable-hunger-component-examine-overfed"}, + { HungerThreshold.Okay, "examineable-hunger-component-examine-okay"}, + { HungerThreshold.Peckish, "examineable-hunger-component-examine-peckish"}, + { HungerThreshold.Starving, "examineable-hunger-component-examine-starving"}, + { HungerThreshold.Dead, "examineable-hunger-component-examine-starving"} + }; + + /// + /// LocId of a fallback message to display if the entity has no + /// or does not have a value in for the current threshold. + /// + public LocId NoHungerDescription = "examineable-hunger-component-examine-none"; +} diff --git a/Content.Shared/Nutrition/EntitySystems/ExamineableHungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/ExamineableHungerSystem.cs new file mode 100644 index 00000000000..e0ac767bcf5 --- /dev/null +++ b/Content.Shared/Nutrition/EntitySystems/ExamineableHungerSystem.cs @@ -0,0 +1,41 @@ +using Content.Shared.Examine; +using Content.Shared.IdentityManagement; +using Content.Shared.Nutrition.Components; + +namespace Content.Shared.Nutrition.EntitySystems; + +/// +public sealed class ExamineableHungerSystem : EntitySystem +{ + [Dependency] private readonly HungerSystem _hunger = default!; + private EntityQuery _hungerQuery; + + public override void Initialize() + { + base.Initialize(); + + _hungerQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnExamine); + } + + /// + /// Defines the text provided on examine. + /// Changes depending on the amount of hunger the target has. + /// + private void OnExamine(Entity entity, ref ExaminedEvent args) + { + var identity = Identity.Entity(entity, EntityManager); + + if (!_hungerQuery.TryComp(entity, out var hungerComp) + || !entity.Comp.Descriptions.TryGetValue(_hunger.GetHungerThreshold(hungerComp), out var locId)) + { + // Use a fallback message if the entity has no HungerComponent + // or is missing a description for the current threshold + locId = entity.Comp.NoHungerDescription; + } + + var msg = Loc.GetString(locId, ("entity", identity)); + args.PushMarkup(msg); + } +} diff --git a/Resources/Locale/en-US/animals/udder/udder-system.ftl b/Resources/Locale/en-US/animals/udder/udder-system.ftl index 959a4fef591..8479ae08bff 100644 --- a/Resources/Locale/en-US/animals/udder/udder-system.ftl +++ b/Resources/Locale/en-US/animals/udder/udder-system.ftl @@ -5,9 +5,3 @@ udder-system-success = You fill {THE($target)} with {$amount}u from the udder. udder-system-dry = The udder is dry. udder-system-verb-milk = Milk - -udder-system-examine-overfed = {CAPITALIZE(SUBJECT($entity))} looks stuffed! -udder-system-examine-okay = {CAPITALIZE(SUBJECT($entity))} looks content. -udder-system-examine-hungry = {CAPITALIZE(SUBJECT($entity))} looks hungry. -udder-system-examine-starved = {CAPITALIZE(SUBJECT($entity))} looks starved! -udder-system-examine-none = {CAPITALIZE(SUBJECT($entity))} seems not to get hungry. diff --git a/Resources/Locale/en-US/nutrition/components/examineable-hunger-component.ftl b/Resources/Locale/en-US/nutrition/components/examineable-hunger-component.ftl new file mode 100644 index 00000000000..d8d99639089 --- /dev/null +++ b/Resources/Locale/en-US/nutrition/components/examineable-hunger-component.ftl @@ -0,0 +1,5 @@ +examineable-hunger-component-examine-overfed = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} stuffed! +examineable-hunger-component-examine-okay = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} content. +examineable-hunger-component-examine-peckish = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} hungry. +examineable-hunger-component-examine-starving = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "look", "looks")} starved! +examineable-hunger-component-examine-none = {CAPITALIZE(SUBJECT($entity))} {CONJUGATE-BASIC($entity, "seem", "seems")} not to get hungry. diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index e71902db6b0..a33529cc48c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -229,6 +229,7 @@ - type: EggLayer eggSpawn: - id: FoodEgg + - type: ExamineableHunger - type: ReplacementAccent accent: chicken - type: SentienceTarget @@ -663,6 +664,7 @@ - type: EggLayer eggSpawn: - id: FoodEgg + - type: ExamineableHunger - type: ReplacementAccent accent: duck - type: SentienceTarget @@ -828,6 +830,7 @@ reagentId: Milk quantityPerUpdate: 25 growthDelay: 30 + - type: ExamineableHunger - type: Butcherable spawned: - id: FoodMeat @@ -984,6 +987,7 @@ reagentId: MilkGoat quantityPerUpdate: 25 growthDelay: 20 + - type: ExamineableHunger - type: Wooly - type: Food solution: wool From 9042827d45ac9255ea9797957f6f3b6f79856651 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 15 Feb 2025 04:30:47 +0000 Subject: [PATCH 040/365] Automatic changelog update --- Resources/Changelog/Changelog.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index a8f0fd717af..1c74b7f64c8 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Ilya246 - changes: - - message: Steel cost of conveyor belt assemblies halved. - type: Tweak - id: 7456 - time: '2024-09-29T15:18:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32444 - author: CuteMoonGod changes: - message: Fixed execution system showing character name instead of their identity @@ -3898,3 +3891,10 @@ id: 7955 time: '2025-02-15T03:59:19.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/34695 +- author: Tayrtahn + changes: + - message: Chickens and ducks now report hunger levels when examined. + type: Add + id: 7956 + time: '2025-02-15T04:29:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/35164 From 44747278869ef8b084ee5c15e05f43a62d9f6fc0 Mon Sep 17 00:00:00 2001 From: Kyle Tyo <36606155+VerinSenpai@users.noreply.github.com> Date: Sat, 15 Feb 2025 01:07:15 -0500 Subject: [PATCH 041/365] Fix air devices ignoring settings after power cycle (#34887) * add powered variable to vent components * add checks for powered to vent systems also corrected onpowerchanged methods to update powered arg. * removed powered from components * Use ApcPowerReceieverComponent for power state. * removed unneeded code from OnPowerChanged * document what enabled is used for in components * only you can prevent oopsie daisies. * add check for powered in OnGasVentPumpUpdated * apcPowerReceiverComponent BEGONE * CODE RED EVERYTHINGS ON FIRE wait we're fine now. --- .../Piping/Unary/Components/GasVentPumpComponent.cs | 6 +++++- .../Unary/Components/GasVentScrubberComponent.cs | 6 +++++- .../Piping/Unary/EntitySystems/GasVentPumpSystem.cs | 11 +++++++---- .../Unary/EntitySystems/GasVentScrubberSystem.cs | 8 ++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs b/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs index 25b15f0ed55..a9aa40611dc 100644 --- a/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs +++ b/Content.Server/Atmos/Piping/Unary/Components/GasVentPumpComponent.cs @@ -11,8 +11,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components [RegisterComponent] public sealed partial class GasVentPumpComponent : Component { + /// + /// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered. + /// By default, all air vents start enabled, whether linked to an alarm or not. + /// [ViewVariables(VVAccess.ReadWrite)] - public bool Enabled { get; set; } = false; + public bool Enabled { get; set; } = true; [ViewVariables] public bool IsDirty { get; set; } = false; diff --git a/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs b/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs index b2143283f73..4a9437bc1fc 100644 --- a/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs +++ b/Content.Server/Atmos/Piping/Unary/Components/GasVentScrubberComponent.cs @@ -9,8 +9,12 @@ namespace Content.Server.Atmos.Piping.Unary.Components [Access(typeof(GasVentScrubberSystem))] public sealed partial class GasVentScrubberComponent : Component { + /// + /// Identifies if the device is enabled by an air alarm. Does not indicate if the device is powered. + /// By default, all air scrubbers start enabled, whether linked to an alarm or not. + /// [DataField] - public bool Enabled { get; set; } = false; + public bool Enabled { get; set; } = true; [DataField] public bool IsDirty { get; set; } = false; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index c58d6eb14bc..93f7dcf1110 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -9,6 +9,8 @@ using Content.Server.DeviceNetwork.Systems; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; @@ -43,6 +45,7 @@ public sealed class GasVentPumpSystem : EntitySystem [Dependency] private readonly SharedToolSystem _toolSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; public override void Initialize() { base.Initialize(); @@ -66,9 +69,10 @@ private void OnGasVentPumpUpdated(EntityUid uid, GasVentPumpComponent vent, ref { //Bingo waz here if (_weldable.IsWelded(uid)) - { return; - } + + if (!_powerReceiverSystem.IsPowered(uid)) + return; var nodeName = vent.PumpDirection switch { @@ -210,7 +214,6 @@ private void OnAtmosAlarm(EntityUid uid, GasVentPumpComponent component, AtmosAl private void OnPowerChanged(EntityUid uid, GasVentPumpComponent component, ref PowerChangedEvent args) { - component.Enabled = args.Powered; UpdateState(uid, component); } @@ -318,7 +321,7 @@ private void UpdateState(EntityUid uid, GasVentPumpComponent vent, AppearanceCom _ambientSoundSystem.SetAmbience(uid, false); _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Welded, appearance); } - else if (!vent.Enabled) + else if (!_powerReceiverSystem.IsPowered(uid) || !vent.Enabled) { _ambientSoundSystem.SetAmbience(uid, false); _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Off, appearance); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index 02075353981..38d75701d73 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -10,6 +10,7 @@ using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping.Unary.Visuals; @@ -37,6 +38,7 @@ public sealed class GasVentScrubberSystem : EntitySystem [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly WeldableSystem _weldable = default!; + [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!; public override void Initialize() { @@ -58,6 +60,9 @@ private void OnVentScrubberUpdated(EntityUid uid, GasVentScrubberComponent scrub var timeDelta = args.dt; + if (!_powerReceiverSystem.IsPowered(uid)) + return; + if (!scrubber.Enabled || !_nodeContainer.TryGetNode(uid, scrubber.OutletName, out PipeNode? outlet)) return; @@ -141,7 +146,6 @@ private void OnAtmosAlarm(EntityUid uid, GasVentScrubberComponent component, Atm private void OnPowerChanged(EntityUid uid, GasVentScrubberComponent component, ref PowerChangedEvent args) { - component.Enabled = args.Powered; UpdateState(uid, component); } @@ -225,7 +229,7 @@ private void UpdateState(EntityUid uid, GasVentScrubberComponent scrubber, _ambientSoundSystem.SetAmbience(uid, false); _appearance.SetData(uid, ScrubberVisuals.State, ScrubberState.Welded, appearance); } - else if (!scrubber.Enabled) + else if (!_powerReceiverSystem.IsPowered(uid) || !scrubber.Enabled) { _ambientSoundSystem.SetAmbience(uid, false); _appearance.SetData(uid, ScrubberVisuals.State, ScrubberState.Off, appearance); From 97d6111614582f8e86352cc85ab8cdd2d888bdda Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 15 Feb 2025 06:08:21 +0000 Subject: [PATCH 042/365] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 1c74b7f64c8..7423194135c 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: CuteMoonGod - changes: - - message: Fixed execution system showing character name instead of their identity - type: Fix - id: 7457 - time: '2024-09-29T22:36:47.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/32536 - author: drakewill-CRL changes: - message: Fix error in gas exchange processing order. @@ -3898,3 +3891,11 @@ id: 7956 time: '2025-02-15T04:29:41.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/35164 +- author: VerinSenpai + changes: + - message: Air alarm devices now return to their previous settings after a power + cycle. + type: Fix + id: 7957 + time: '2025-02-15T06:07:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/34887 From 71b5133a5395b3ebe0d2af31055d6dcaa65a1b78 Mon Sep 17 00:00:00 2001 From: Simon <63975668+Simyon264@users.noreply.github.com> Date: Sat, 15 Feb 2025 10:01:46 +0100 Subject: [PATCH 043/365] Fix colornetwork command not checking for correct permissions (#35180) Fix colornetwork command not checking for correct permissions. What is shell.IsClient even?? --- Content.Server/Sandbox/Commands/ColorNetworkCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs b/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs index 1fc207058d8..0a778f58803 100644 --- a/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs +++ b/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs @@ -19,7 +19,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) { var sandboxManager = _entManager.System(); var adminManager = IoCManager.Resolve(); - if (shell.IsClient && (!sandboxManager.IsSandboxEnabled && !adminManager.HasAdminFlag(shell.Player!, AdminFlags.Mapping))) + if (shell.IsClient || (!sandboxManager.IsSandboxEnabled && !adminManager.HasAdminFlag(shell.Player!, AdminFlags.Mapping))) { shell.WriteError(Loc.GetString("cmd-colornetwork-no-access")); } From 0cb11241d79c8e51f5a1391c1fc49bc063144ccc Mon Sep 17 00:00:00 2001 From: keronshb <54602815+keronshb@users.noreply.github.com> Date: Sat, 15 Feb 2025 11:17:29 -0500 Subject: [PATCH 044/365] Store Refund - Add more disable scenerios & time to disable refund. (#34671) * Puts disable refund logic into a helper method. Removes action check. Disables refund on action use. * Adds check if refund is disabled for the store already * Adds a way to disable refunds based on time * Checks if the ent is on the starting map and the end time is below the current time before disabling refund * Replaces instances of component.RefundAllowed = false with DisableRefund for more consistency and easier tracking * Adds methods to handle inhand and shooting events * Removes gamestates --------- Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> --- Content.Server/Store/StoreRefundComponent.cs | 21 +++++++- .../Store/Systems/StoreSystem.Refund.cs | 49 ++++++++++++++++--- .../Store/Systems/StoreSystem.Ui.cs | 5 +- Content.Server/Store/Systems/StoreSystem.cs | 2 + 4 files changed, 67 insertions(+), 10 deletions(-) diff --git a/Content.Server/Store/StoreRefundComponent.cs b/Content.Server/Store/StoreRefundComponent.cs index 1a6b17c5ead..df35afdf53f 100644 --- a/Content.Server/Store/StoreRefundComponent.cs +++ b/Content.Server/Store/StoreRefundComponent.cs @@ -2,12 +2,31 @@ namespace Content.Server.Store.Components; +// TODO: Refund on a per-item/action level. +// Requires a refund button next to each purchase (disabled/invis by default) +// Interactions with ActionUpgrades would need to be modified to reset all upgrade progress and return the original action purchase to the store. + /// /// Keeps track of entities bought from stores for refunds, especially useful if entities get deleted before they can be refunded. /// [RegisterComponent, Access(typeof(StoreSystem))] public sealed partial class StoreRefundComponent : Component { - [ViewVariables, DataField] + /// + /// The store this entity was bought from + /// + [DataField] public EntityUid? StoreEntity; + + /// + /// The time this entity was bought + /// + [DataField] + public TimeSpan? BoughtTime; + + /// + /// How long until this entity disables refund purchase? + /// + [DataField] + public TimeSpan DisableTime = TimeSpan.FromSeconds(300); } diff --git a/Content.Server/Store/Systems/StoreSystem.Refund.cs b/Content.Server/Store/Systems/StoreSystem.Refund.cs index 04bd585ffcf..e9d801f9e17 100644 --- a/Content.Server/Store/Systems/StoreSystem.Refund.cs +++ b/Content.Server/Store/Systems/StoreSystem.Refund.cs @@ -1,5 +1,8 @@ using Content.Server.Store.Components; +using Content.Shared.Actions.Events; +using Content.Shared.Interaction.Events; using Content.Shared.Store.Components; +using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Containers; namespace Content.Server.Store.Systems; @@ -12,22 +15,39 @@ private void InitializeRefund() SubscribeLocalEvent(OnRefundTerminating); SubscribeLocalEvent(OnEntityRemoved); SubscribeLocalEvent(OnEntityInserted); + SubscribeLocalEvent(OnActionPerformed); + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnShootAttempt); + // TODO: Handle guardian refund disabling when guardians support refunds. } - private void OnEntityRemoved(EntityUid uid, StoreRefundComponent component, EntRemovedFromContainerMessage args) + private void OnEntityRemoved(Entity ent, ref EntRemovedFromContainerMessage args) { - if (component.StoreEntity == null || _actions.TryGetActionData(uid, out _, false) || !TryComp(component.StoreEntity.Value, out var storeComp)) - return; + CheckDisableRefund(ent); + } - DisableRefund(component.StoreEntity.Value, storeComp); + private void OnEntityInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + CheckDisableRefund(ent); } - private void OnEntityInserted(EntityUid uid, StoreRefundComponent component, EntInsertedIntoContainerMessage args) + private void OnActionPerformed(Entity ent, ref ActionPerformedEvent args) { - if (component.StoreEntity == null || _actions.TryGetActionData(uid, out _) || !TryComp(component.StoreEntity.Value, out var storeComp)) + CheckDisableRefund(ent); + } + + private void OnUseInHand(Entity ent, ref UseInHandEvent args) + { + args.Handled = true; + CheckDisableRefund(ent); + } + + private void OnShootAttempt(Entity ent, ref AttemptShootEvent args) + { + if (args.Cancelled) return; - DisableRefund(component.StoreEntity.Value, storeComp); + CheckDisableRefund(ent); } private void OnStoreTerminating(Entity ent, ref EntityTerminatingEvent args) @@ -52,4 +72,19 @@ private void OnRefundTerminating(Entity ent, ref EntityTer var ev = new RefundEntityDeletedEvent(ent); RaiseLocalEvent(ent.Comp.StoreEntity.Value, ref ev); } + + private void CheckDisableRefund(Entity ent) + { + var component = ent.Comp; + + if (component.StoreEntity == null || !TryComp(component.StoreEntity.Value, out var storeComp) || !storeComp.RefundAllowed) + return; + + var endTime = component.BoughtTime + component.DisableTime; + + if (IsOnStartingMap(component.StoreEntity.Value, storeComp) && _timing.CurTime < endTime) + return; + + DisableRefund(component.StoreEntity.Value, storeComp); + } } diff --git a/Content.Server/Store/Systems/StoreSystem.Ui.cs b/Content.Server/Store/Systems/StoreSystem.Ui.cs index 5af6ce1c975..3f4ccf696df 100644 --- a/Content.Server/Store/Systems/StoreSystem.Ui.cs +++ b/Content.Server/Store/Systems/StoreSystem.Ui.cs @@ -164,7 +164,7 @@ private void OnBuyRequest(EntityUid uid, StoreComponent component, StoreBuyListi } if (!IsOnStartingMap(uid, component)) - component.RefundAllowed = false; + DisableRefund(uid, component); //subtract the cash foreach (var (currency, amount) in cost) @@ -332,7 +332,7 @@ private void OnRequestRefund(EntityUid uid, StoreComponent component, StoreReque if (!IsOnStartingMap(uid, component)) { - component.RefundAllowed = false; + DisableRefund(uid, component); UpdateUserInterface(buyer, uid, component); } @@ -376,6 +376,7 @@ private void HandleRefundComp(EntityUid uid, StoreComponent component, EntityUid component.BoughtEntities.Add(purchase); var refundComp = EnsureComp(purchase); refundComp.StoreEntity = uid; + refundComp.BoughtTime = _timing.CurTime; } private bool IsOnStartingMap(EntityUid store, StoreComponent component) diff --git a/Content.Server/Store/Systems/StoreSystem.cs b/Content.Server/Store/Systems/StoreSystem.cs index a57895364dc..0625ced0876 100644 --- a/Content.Server/Store/Systems/StoreSystem.cs +++ b/Content.Server/Store/Systems/StoreSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; using System.Linq; +using Robust.Shared.Timing; using Content.Shared.Mind; namespace Content.Server.Store.Systems; @@ -22,6 +23,7 @@ public sealed partial class StoreSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { From d798d4d3f567ee5311105950f2cd5a59d6c9fcf2 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Sun, 16 Feb 2025 06:52:47 +1100 Subject: [PATCH 045/365] Update StaticFieldValidationTest (#34287) --- .../Tests/Linter/StaticFieldValidationTest.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs index 0632fe1347c..90bf82e8f10 100644 --- a/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs +++ b/Content.IntegrationTests/Tests/Linter/StaticFieldValidationTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Content.Shared.Tag; +using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Reflection; using Robust.Shared.Serialization.Manager.Attributes; @@ -29,7 +30,9 @@ public async Task TestStaticFieldValidation() Assert.That(protoMan.ValidateStaticFields(typeof(StringValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayValid), protos), Is.Empty); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayValid), protos), Is.Empty); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListValid), protos), Is.Empty); @@ -39,7 +42,9 @@ public async Task TestStaticFieldValidation() Assert.That(protoMan.ValidateStaticFields(typeof(StringInvalid), protos), Has.Count.EqualTo(1)); Assert.That(protoMan.ValidateStaticFields(typeof(StringArrayInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdInvalid), protos), Has.Count.EqualTo(1)); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTInvalid), protos), Has.Count.EqualTo(1)); Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); + Assert.That(protoMan.ValidateStaticFields(typeof(EntProtoIdTArrayInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdTestInvalid), protos), Has.Count.EqualTo(1)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdArrayInvalid), protos), Has.Count.EqualTo(2)); Assert.That(protoMan.ValidateStaticFields(typeof(ProtoIdListInvalid), protos), Has.Count.EqualTo(2)); @@ -88,24 +93,48 @@ private sealed class EntProtoIdValid public static EntProtoId Tag = "StaticFieldTestEnt"; } + [Reflect(false)] + private sealed class EntProtoIdTValid + { + public static EntProtoId Tag = "StaticFieldTestEnt"; + } + [Reflect(false)] private sealed class EntProtoIdInvalid { public static EntProtoId Tag = string.Empty; } + [Reflect(false)] + private sealed class EntProtoIdTInvalid + { + public static EntProtoId Tag = string.Empty; + } + [Reflect(false)] private sealed class EntProtoIdArrayValid { public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"]; } + [Reflect(false)] + private sealed class EntProtoIdTArrayValid + { + public static EntProtoId[] Tag = ["StaticFieldTestEnt", "StaticFieldTestEnt"]; + } + [Reflect(false)] private sealed class EntProtoIdArrayInvalid { public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty]; } + [Reflect(false)] + private sealed class EntProtoIdTArrayInvalid + { + public static EntProtoId[] Tag = [string.Empty, "StaticFieldTestEnt", string.Empty]; + } + [Reflect(false)] private sealed class ProtoIdTestValid { From 831dbef591d45f606f1e0f063304622306ebb5e8 Mon Sep 17 00:00:00 2001 From: Kyle Tyo <36606155+VerinSenpai@users.noreply.github.com> Date: Sat, 15 Feb 2025 21:01:58 -0500 Subject: [PATCH 046/365] move a colon to the localization string (#35192) * move the colon to the localization string * remove a redundancy * beck suggested this per how its done elsewhere. * comply with requested changes. --- .../CriminalRecords/CriminalRecordsConsoleWindow.xaml | 4 +--- .../en-US/medical/components/crew-monitoring-component.ftl | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml index d36718cf08b..179304a9785 100644 --- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml +++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml @@ -58,9 +58,7 @@ StyleClasses="LabelBig" /> -