From a2f99cc69e4c86595c5cff8f4db33885e57bef27 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:23:11 +1000 Subject: [PATCH 001/242] VGRoid support (#27659) * Dungeon spawn support for grid spawns * Recursive dungeons working * Mask approach working * zack * More work * Fix recursive dungeons * Heap of work * weh * the cud * rar * Job * weh * weh * weh * Master merges * orch * weh * vgroid most of the work * Tweaks * Tweaks * weh * do do do do do do * Basic layout * Ore spawning working * Big breaking changes * Mob gen working * weh * Finalising * emo * More finalising * reverty * Reduce distance --- .../Pathfinding/PathfindingSystem.Breadth.cs | 123 ++ .../NPC/Pathfinding/PathfindingSystem.Line.cs | 74 + .../Pathfinding/PathfindingSystem.Simple.cs | 154 ++ .../Pathfinding/PathfindingSystem.Splines.cs | 180 +++ .../Pathfinding/PathfindingSystem.Widen.cs | 89 ++ .../NPC/Systems/NPCSteeringSystem.Context.cs | 15 + .../Procedural/DungeonJob.PostGen.cs | 1258 ----------------- .../Procedural/DungeonJob.PostGenBiome.cs | 138 -- Content.Server/Procedural/DungeonJob.cs | 192 --- .../DungeonJob/DungeonJob.DunGenExterior.cs | 58 + .../DungeonJob/DungeonJob.DunGenFill.cs | 50 + .../DungeonJob.DunGenNoise.cs} | 53 +- .../DungeonJob.DunGenNoiseDistance.cs | 112 ++ .../DungeonJob.DunGenPrefab.cs} | 85 +- .../DungeonJob.DunGenReplaceTile.cs | 60 + .../DungeonJob/DungeonJob.MobDunGen.cs | 58 + .../DungeonJob/DungeonJob.OreDunGen.cs | 149 ++ .../DungeonJob/DungeonJob.PostGen.cs | 134 ++ .../DungeonJob.PostGenAutoCabling.cs | 162 +++ .../DungeonJob/DungeonJob.PostGenBiome.cs | 67 + .../DungeonJob.PostGenBiomeMarkerLayer.cs | 105 ++ .../DungeonJob.PostGenBoundaryWall.cs | 113 ++ .../DungeonJob.PostGenCornerClutter.cs | 56 + .../DungeonJob/DungeonJob.PostGenCorridor.cs | 116 ++ .../DungeonJob.PostGenCorridorClutter.cs} | 9 +- ...DungeonJob.PostGenCorridorDecalSkirting.cs | 124 ++ .../DungeonJob.PostGenDungeonConnector.cs | 6 + .../DungeonJob.PostGenDungeonEntrance.cs | 114 ++ .../DungeonJob.PostGenEntranceFlank.cs | 58 + .../DungeonJob.PostGenExternalWindow.cs | 138 ++ .../DungeonJob.PostGenInternalWindow.cs | 108 ++ .../DungeonJob/DungeonJob.PostGenJunction.cs | 144 ++ .../DungeonJob.PostGenMiddleConnection.cs | 147 ++ .../DungeonJob.PostGenRoomEntrance.cs | 48 + ...ungeonJob.PostGenSplineDungeonConnector.cs | 147 ++ .../DungeonJob/DungeonJob.PostGenWallMount.cs | 56 + .../DungeonJob.PostGenWorm.cs} | 22 +- .../Procedural/DungeonJob/DungeonJob.cs | 309 ++++ .../Procedural/DungeonSystem.Commands.cs | 2 + .../Procedural/DungeonSystem.Rooms.cs | 19 +- Content.Server/Procedural/DungeonSystem.cs | 13 +- Content.Server/Procedural/RoomFillSystem.cs | 1 + .../Salvage/SpawnSalvageMissionJob.cs | 4 +- .../Shuttles/Components/GridSpawnComponent.cs | 81 +- .../Systems/ShuttleSystem.GridFill.cs | 159 ++- .../Shuttles/Systems/ShuttleSystem.cs | 8 +- .../Components/EntityRemapComponent.cs | 13 + .../DunGenEuclideanSquaredDistance.cs | 10 + .../Procedural/Distance/DunGenSquareBump.cs | 10 + .../Procedural/Distance/IDunGenDistance.cs | 14 + Content.Shared/Procedural/Dungeon.cs | 66 +- .../Procedural/DungeonConfigPrototype.cs | 46 +- Content.Shared/Procedural/DungeonData.cs | 105 ++ .../DungeonGenerators/ExteriorDunGen.cs | 13 + .../DungeonGenerators/FillGridDunGen.cs | 10 + .../Procedural/DungeonGenerators/IDunGen.cs | 7 - .../DungeonGenerators/NoiseDistanceDunGen.cs | 18 + .../DungeonGenerators/NoiseDunGen.cs | 7 +- .../DungeonGenerators/PrefabDunGen.cs | 28 +- .../DungeonGenerators/PrototypeDunGen.cs | 13 + .../DungeonGenerators/ReplaceTileDunGen.cs | 30 + .../Procedural/DungeonLayers/MobsDunGen.cs | 21 + .../Procedural/DungeonLayers/OreDunGen.cs | 42 + Content.Shared/Procedural/DungeonRoom.cs | 1 + Content.Shared/Procedural/IDunGenLayer.cs | 7 + .../PostGeneration/AutoCablingDunGen.cs | 10 + .../PostGeneration/AutoCablingPostGen.cs | 12 - .../{BiomePostGen.cs => BiomeDunGen.cs} | 3 +- ...erPostGen.cs => BiomeMarkerLayerDunGen.cs} | 4 +- .../PostGeneration/BoundaryWallDunGen.cs | 23 + .../PostGeneration/BoundaryWallPostGen.cs | 33 - .../PostGeneration/CornerClutterDunGen.cs | 14 + .../PostGeneration/CornerClutterPostGen.cs | 18 - ...terPostGen.cs => CorridorClutterDunGen.cs} | 2 +- ...tGen.cs => CorridorDecalSkirtingDunGen.cs} | 14 +- .../{CorridorPostGen.cs => CorridorDunGen.cs} | 12 +- .../PostGeneration/DungeonEntranceDunGen.cs | 18 + .../PostGeneration/DungeonEntrancePostGen.cs | 28 - .../PostGeneration/EntranceFlankDunGen.cs | 11 + .../PostGeneration/EntranceFlankPostGen.cs | 16 - .../PostGeneration/ExternalWindowDunGen.cs | 11 + .../PostGeneration/ExternalWindowPostGen.cs | 22 - .../Procedural/PostGeneration/IPostDunGen.cs | 10 - .../PostGeneration/InternalWindowDunGen.cs | 11 + .../PostGeneration/InternalWindowPostGen.cs | 22 - .../PostGeneration/JunctionDunGen.cs | 18 + .../PostGeneration/JunctionPostGen.cs | 28 - .../PostGeneration/MiddleConnectionDunGen.cs | 19 + .../PostGeneration/MiddleConnectionPostGen.cs | 39 - .../PostGeneration/RoomEntranceDunGen.cs | 11 + .../PostGeneration/RoomEntrancePostGen.cs | 22 - .../SplineDungeonConnectorDunGen.cs | 19 + .../PostGeneration/WallMountDunGen.cs | 13 + .../PostGeneration/WallMountPostGen.cs | 23 - ...rridorPostGen.cs => WormCorridorDunGen.cs} | 9 +- .../Salvage/SharedSalvageSystem.Magnet.cs | 6 +- .../Shuttles/Systems/SharedShuttleSystem.cs | 2 +- Content.Shared/Storage/EntitySpawnEntry.cs | 13 + .../Prototypes/Entities/Stations/base.yml | 15 +- .../Entities/Structures/Walls/asteroid.yml | 342 +++-- .../Prototypes/Procedural/Magnet/asteroid.yml | 71 +- .../Prototypes/Procedural/dungeon_configs.yml | 591 ++++---- Resources/Prototypes/Procedural/vgroid.yml | 191 +++ 103 files changed, 4903 insertions(+), 2602 deletions(-) create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs create mode 100644 Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs delete mode 100644 Content.Server/Procedural/DungeonJob.PostGen.cs delete mode 100644 Content.Server/Procedural/DungeonJob.PostGenBiome.cs delete mode 100644 Content.Server/Procedural/DungeonJob.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs rename Content.Server/Procedural/{DungeonJob.NoiseDunGen.cs => DungeonJob/DungeonJob.DunGenNoise.cs} (73%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs rename Content.Server/Procedural/{DungeonJob.PrefabDunGen.cs => DungeonJob/DungeonJob.DunGenPrefab.cs} (82%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs rename Content.Server/Procedural/{DungeonJob.CorridorClutterPost.cs => DungeonJob/DungeonJob.PostGenCorridorClutter.cs} (83%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs rename Content.Server/Procedural/{DungeonJob.WormPost.cs => DungeonJob/DungeonJob.PostGenWorm.cs} (88%) create mode 100644 Content.Server/Procedural/DungeonJob/DungeonJob.cs create mode 100644 Content.Shared/Procedural/Components/EntityRemapComponent.cs create mode 100644 Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs create mode 100644 Content.Shared/Procedural/Distance/DunGenSquareBump.cs create mode 100644 Content.Shared/Procedural/Distance/IDunGenDistance.cs create mode 100644 Content.Shared/Procedural/DungeonData.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs delete mode 100644 Content.Shared/Procedural/DungeonGenerators/IDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs create mode 100644 Content.Shared/Procedural/DungeonLayers/OreDunGen.cs create mode 100644 Content.Shared/Procedural/IDunGenLayer.cs create mode 100644 Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs rename Content.Shared/Procedural/PostGeneration/{BiomePostGen.cs => BiomeDunGen.cs} (78%) rename Content.Shared/Procedural/PostGeneration/{BiomeMarkerLayerPostGen.cs => BiomeMarkerLayerDunGen.cs} (73%) create mode 100644 Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs rename Content.Shared/Procedural/PostGeneration/{CorridorClutterPostGen.cs => CorridorClutterDunGen.cs} (85%) rename Content.Shared/Procedural/PostGeneration/{CorridorDecalSkirtingPostGen.cs => CorridorDecalSkirtingDunGen.cs} (72%) rename Content.Shared/Procedural/PostGeneration/{CorridorPostGen.cs => CorridorDunGen.cs} (73%) create mode 100644 Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/IPostDunGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs create mode 100644 Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs delete mode 100644 Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs rename Content.Shared/Procedural/PostGeneration/{WormCorridorPostGen.cs => WormCorridorDunGen.cs} (73%) create mode 100644 Resources/Prototypes/Procedural/vgroid.yml diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs new file mode 100644 index 000000000000..ee8eaa9ad1a1 --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Breadth.cs @@ -0,0 +1,123 @@ +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + /* + * Handle BFS searches from Start->End. Doesn't consider NPC pathfinding. + */ + + /// + /// Pathfinding args for a 1-many path. + /// + public record struct BreadthPathArgs() + { + public Vector2i Start; + public List Ends; + + public bool Diagonals = false; + + public Func? TileCost; + + public int Limit = 10000; + } + + /// + /// Gets a BFS path from start to any end. Can also supply an optional tile-cost for tiles. + /// + public SimplePathResult GetBreadthPath(BreadthPathArgs args) + { + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + var frontier = new PriorityQueue(); + + costSoFar[args.Start] = 0f; + frontier.Enqueue(args.Start, 0f); + var count = 0; + + while (frontier.TryDequeue(out var node, out _) && count < args.Limit) + { + count++; + + if (args.Ends.Contains(node)) + { + // Found target + var path = ReconstructPath(node, cameFrom); + + return new SimplePathResult() + { + CameFrom = cameFrom, + Path = path, + }; + } + + var gCost = costSoFar[node]; + + if (args.Diagonals) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = node + new Vector2i(x, y); + var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + { + continue; + } + + // f = g + h + // gScore is distance to the start node + // hScore is distance to the end node + var gScore = gCost + neighborCost; + + // Slower to get here so just ignore it. + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + { + continue; + } + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + // pFactor is tie-breaker where the fscore is otherwise equal. + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties + // There's other ways to do it but future consideration + // The closer the fScore is to the actual distance then the better the pathfinder will be + // (i.e. somewhere between 1 and infinite) + // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now. + frontier.Enqueue(neighbor, gScore); + } + } + } + else + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = node + new Vector2i(x, y); + var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + continue; + + var gScore = gCost + neighborCost; + + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + continue; + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + + frontier.Enqueue(neighbor, gScore); + } + } + } + } + + return SimplePathResult.NoPath; + } +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs new file mode 100644 index 000000000000..479d5ad77f6b --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs @@ -0,0 +1,74 @@ +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + public void GridCast(Vector2i start, Vector2i end, Vector2iCallback callback) + { + // https://gist.github.com/Pyr3z/46884d67641094d6cf353358566db566 + // declare all locals at the top so it's obvious how big the footprint is + int dx, dy, xinc, yinc, side, i, error; + + // starting cell is always returned + if (!callback(start)) + return; + + xinc = (end.X < start.X) ? -1 : 1; + yinc = (end.Y < start.Y) ? -1 : 1; + dx = xinc * (end.X - start.X); + dy = yinc * (end.Y - start.Y); + var ax = start.X; + var ay = start.Y; + + if (dx == dy) // Handle perfect diagonals + { + // I include this "optimization" for more aesthetic reasons, actually. + // While Bresenham's Line can handle perfect diagonals just fine, it adds + // additional cells to the line that make it not a perfect diagonal + // anymore. So, while this branch is ~twice as fast as the next branch, + // the real reason it is here is for style. + + // Also, there *is* the reason of performance. If used for cell-based + // raycasts, for example, then perfect diagonals will check half as many + // cells. + + while (dx --> 0) + { + ax += xinc; + ay += yinc; + if (!callback(new Vector2i(ax, ay))) + return; + } + + return; + } + + // Handle all other lines + + side = -1 * ((dx == 0 ? yinc : xinc) - 1); + + i = dx + dy; + error = dx - dy; + + dx *= 2; + dy *= 2; + + while (i --> 0) + { + if (error > 0 || error == side) + { + ax += xinc; + error -= dy; + } + else + { + ay += yinc; + error += dx; + } + + if (!callback(new Vector2i(ax, ay))) + return; + } + } + + public delegate bool Vector2iCallback(Vector2i index); +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs new file mode 100644 index 000000000000..7afd3d78df1f --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs @@ -0,0 +1,154 @@ +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + /// + /// Pathfinding args for a 1-1 path. + /// + public record struct SimplePathArgs() + { + public Vector2i Start; + public Vector2i End; + + public bool Diagonals = false; + + public int Limit = 10000; + + /// + /// Custom tile-costs if applicable. + /// + public Func? TileCost; + } + + public record struct SimplePathResult + { + public static SimplePathResult NoPath = new(); + + public List Path; + public Dictionary CameFrom; + } + + /// + /// Gets simple A* path from start to end. Can also supply an optional tile-cost for tiles. + /// + public SimplePathResult GetPath(SimplePathArgs args) + { + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + var frontier = new PriorityQueue(); + + costSoFar[args.Start] = 0f; + frontier.Enqueue(args.Start, 0f); + var count = 0; + + while (frontier.TryDequeue(out var node, out _) && count < args.Limit) + { + count++; + + if (node == args.End) + { + // Found target + var path = ReconstructPath(args.End, cameFrom); + + return new SimplePathResult() + { + CameFrom = cameFrom, + Path = path, + }; + } + + var gCost = costSoFar[node]; + + if (args.Diagonals) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = node + new Vector2i(x, y); + var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + { + continue; + } + + // f = g + h + // gScore is distance to the start node + // hScore is distance to the end node + var gScore = gCost + neighborCost; + + // Slower to get here so just ignore it. + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + { + continue; + } + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + // pFactor is tie-breaker where the fscore is otherwise equal. + // See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties + // There's other ways to do it but future consideration + // The closer the fScore is to the actual distance then the better the pathfinder will be + // (i.e. somewhere between 1 and infinite) + // Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now. + var hScore = OctileDistance(args.End, neighbor) * (1.0f + 1.0f / 1000.0f); + var fScore = gScore + hScore; + frontier.Enqueue(neighbor, fScore); + } + } + } + else + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = node + new Vector2i(x, y); + var neighborCost = ManhattanDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f; + + if (neighborCost.Equals(0f)) + continue; + + var gScore = gCost + neighborCost; + + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + continue; + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + + // Still use octile even for manhattan distance. + var hScore = OctileDistance(args.End, neighbor) * 1.001f; + var fScore = gScore + hScore; + frontier.Enqueue(neighbor, fScore); + } + } + } + } + + return SimplePathResult.NoPath; + } + + private List ReconstructPath(Vector2i end, Dictionary cameFrom) + { + var path = new List() + { + end, + }; + var node = end; + + while (cameFrom.TryGetValue(node, out var source)) + { + path.Add(source); + node = source; + } + + path.Reverse(); + + return path; + } +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs new file mode 100644 index 000000000000..9979755f995e --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Splines.cs @@ -0,0 +1,180 @@ +using Robust.Shared.Collections; +using Robust.Shared.Random; + +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + public record struct SimplifyPathArgs + { + public Vector2i Start; + public Vector2i End; + public List Path; + } + + public record struct SplinePathResult() + { + public static SplinePathResult NoPath = new(); + + public List Points = new(); + + public List Path = new(); + public Dictionary CameFrom; + } + + public record struct SplinePathArgs(SimplePathArgs Args) + { + public SimplePathArgs Args = Args; + + public float MaxRatio = 0.25f; + + /// + /// Minimum distance between subdivisions. + /// + public int Distance = 20; + } + + /// + /// Gets a spline path from start to end. + /// + public SplinePathResult GetSplinePath(SplinePathArgs args, Random random) + { + var start = args.Args.Start; + var end = args.Args.End; + + var path = new List(); + + var pairs = new ValueList<(Vector2i Start, Vector2i End)> { (start, end) }; + var subdivided = true; + + // Sub-divide recursively + while (subdivided) + { + // Sometimes we might inadvertantly get 2 nodes too close together so better to just check each one as it comes up instead. + var i = 0; + subdivided = false; + + while (i < pairs.Count) + { + var pointA = pairs[i].Start; + var pointB = pairs[i].End; + var vector = pointB - pointA; + + var halfway = vector / 2f; + + // Finding the point + var adj = halfway.Length(); + + // Should we even subdivide. + if (adj <= args.Distance) + { + // Just check the next entry no double skip. + i++; + continue; + } + + subdivided = true; + var opposite = args.MaxRatio * adj; + var hypotenuse = MathF.Sqrt(MathF.Pow(adj, 2) + MathF.Pow(opposite, 2)); + + // Okay so essentially we have 2 points and no poly + // We add 2 other points to form a diamond and want some point halfway between randomly offset. + var angle = new Angle(MathF.Atan(opposite / adj)); + var pointAPerp = pointA + angle.RotateVec(halfway).Normalized() * hypotenuse; + var pointBPerp = pointA + (-angle).RotateVec(halfway).Normalized() * hypotenuse; + + var perpLine = pointBPerp - pointAPerp; + var perpHalfway = perpLine.Length() / 2f; + + var splinePoint = (pointAPerp + perpLine.Normalized() * random.NextFloat(-args.MaxRatio, args.MaxRatio) * perpHalfway).Floored(); + + // We essentially take (A, B) and turn it into (A, C) & (C, B) + pairs[i] = (pointA, splinePoint); + pairs.Insert(i + 1, (splinePoint, pointB)); + + i+= 2; + } + } + + var spline = new ValueList(pairs.Count - 1) + { + start + }; + + foreach (var pair in pairs) + { + spline.Add(pair.End); + } + + // Now we need to pathfind between each node on the spline. + + // TODO: Add rotation version or straight-line version for pathfinder config + // Move the worm pathfinder to here I think. + var cameFrom = new Dictionary(); + + // TODO: Need to get rid of the branch bullshit. + var points = new List(); + + for (var i = 0; i < spline.Count - 1; i++) + { + var point = spline[i]; + var target = spline[i + 1]; + points.Add(point); + var aStarArgs = args.Args with { Start = point, End = target }; + + var aStarResult = GetPath(aStarArgs); + + if (aStarResult == SimplePathResult.NoPath) + return SplinePathResult.NoPath; + + path.AddRange(aStarResult.Path[0..]); + + foreach (var a in aStarResult.CameFrom) + { + cameFrom[a.Key] = a.Value; + } + } + + points.Add(spline[^1]); + + var simple = SimplifyPath(new SimplifyPathArgs() + { + Start = args.Args.Start, + End = args.Args.End, + Path = path, + }); + + return new SplinePathResult() + { + Path = simple, + CameFrom = cameFrom, + Points = points, + }; + } + + /// + /// Does a simpler pathfinder over the nodes to prune unnecessary branches. + /// + public List SimplifyPath(SimplifyPathArgs args) + { + var nodes = new HashSet(args.Path); + + var result = GetBreadthPath(new BreadthPathArgs() + { + Start = args.Start, + Ends = new List() + { + args.End, + }, + TileCost = node => + { + if (!nodes.Contains(node)) + return 0f; + + return 1f; + } + }); + + return result.Path; + } +} diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs new file mode 100644 index 000000000000..f7bcd019f5fb --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Widen.cs @@ -0,0 +1,89 @@ +using System.Numerics; +using Robust.Shared.Random; + +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + /// + /// Widens the path by the specified amount. + /// + public HashSet GetWiden(WidenArgs args, Random random) + { + var tiles = new HashSet(args.Path.Count * 2); + var variance = (args.MaxWiden - args.MinWiden) / 2f + args.MinWiden; + var counter = 0; + + foreach (var tile in args.Path) + { + counter++; + + if (counter != args.TileSkip) + continue; + + counter = 0; + + var center = new Vector2(tile.X + 0.5f, tile.Y + 0.5f); + + if (args.Square) + { + for (var x = -variance; x <= variance; x++) + { + for (var y = -variance; y <= variance; y++) + { + var neighbor = center + new Vector2(x, y); + + tiles.Add(neighbor.Floored()); + } + } + } + else + { + for (var x = -variance; x <= variance; x++) + { + for (var y = -variance; y <= variance; y++) + { + var offset = new Vector2(x, y); + + if (offset.Length() > variance) + continue; + + var neighbor = center + offset; + + tiles.Add(neighbor.Floored()); + } + } + } + + variance += random.NextFloat(-args.Variance * args.TileSkip, args.Variance * args.TileSkip); + variance = Math.Clamp(variance, args.MinWiden, args.MaxWiden); + } + + return tiles; + } + + public record struct WidenArgs() + { + public bool Square = false; + + /// + /// How many tiles to skip between iterations., 1-in-n + /// + public int TileSkip = 3; + + /// + /// Maximum amount to vary per tile. + /// + public float Variance = 0.25f; + + /// + /// Minimum width. + /// + public float MinWiden = 2f; + + + public float MaxWiden = 7f; + + public List Path; + } +} diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs index 5f871a6ecfa0..e0bcb97a112c 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs @@ -142,6 +142,13 @@ private bool TrySeek( // Grab the target position, either the next path node or our end goal.. var targetCoordinates = GetTargetCoordinates(steering); + + if (!targetCoordinates.IsValid(EntityManager)) + { + steering.Status = SteeringStatus.NoPath; + return false; + } + var needsPath = false; // If the next node is invalid then get new ones @@ -243,6 +250,14 @@ private bool TrySeek( // Alright just adjust slightly and grab the next node so we don't stop moving for a tick. // TODO: If it's the last node just grab the target instead. targetCoordinates = GetTargetCoordinates(steering); + + if (!targetCoordinates.IsValid(EntityManager)) + { + SetDirection(mover, steering, Vector2.Zero); + steering.Status = SteeringStatus.NoPath; + return false; + } + targetMap = targetCoordinates.ToMap(EntityManager, _transform); // Can't make it again. diff --git a/Content.Server/Procedural/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob.PostGen.cs deleted file mode 100644 index cb9e64f04e25..000000000000 --- a/Content.Server/Procedural/DungeonJob.PostGen.cs +++ /dev/null @@ -1,1258 +0,0 @@ -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using Content.Server.NodeContainer; -using Content.Shared.Doors.Components; -using Content.Shared.Maps; -using Content.Shared.Physics; -using Content.Shared.Procedural; -using Content.Shared.Procedural.PostGeneration; -using Content.Shared.Storage; -using Content.Shared.Tag; -using Robust.Shared.Collections; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.Procedural; - -public sealed partial class DungeonJob -{ - /* - * Run after the main dungeon generation - */ - - private static readonly ProtoId WallTag = "Wall"; - - private bool HasWall(MapGridComponent grid, Vector2i tile) - { - var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); - - while (anchored.MoveNext(out var uid)) - { - if (_tag.HasTag(uid.Value, WallTag)) - return true; - } - - return false; - } - - private async Task PostGen(AutoCablingPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - // There's a lot of ways you could do this. - // For now we'll just connect every LV cable in the dungeon. - var cableTiles = new HashSet(); - var allTiles = new HashSet(dungeon.CorridorTiles); - allTiles.UnionWith(dungeon.RoomTiles); - allTiles.UnionWith(dungeon.RoomExteriorTiles); - allTiles.UnionWith(dungeon.CorridorExteriorTiles); - var nodeQuery = _entManager.GetEntityQuery(); - - // Gather existing nodes - foreach (var tile in allTiles) - { - var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); - - while (anchored.MoveNext(out var anc)) - { - if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || - !nodeContainer.Nodes.ContainsKey("power")) - { - continue; - } - - cableTiles.Add(tile); - break; - } - } - - // Iterating them all might be expensive. - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - - var startNodes = new List(cableTiles); - random.Shuffle(startNodes); - var start = startNodes[0]; - var remaining = new HashSet(startNodes); - var frontier = new PriorityQueue(); - frontier.Enqueue(start, 0f); - var cameFrom = new Dictionary(); - var costSoFar = new Dictionary(); - var lastDirection = new Dictionary(); - costSoFar[start] = 0f; - lastDirection[start] = Direction.Invalid; - - while (remaining.Count > 0) - { - if (frontier.Count == 0) - { - var newStart = remaining.First(); - frontier.Enqueue(newStart, 0f); - lastDirection[newStart] = Direction.Invalid; - } - - var node = frontier.Dequeue(); - - if (remaining.Remove(node)) - { - var weh = node; - - while (cameFrom.TryGetValue(weh, out var receiver)) - { - cableTiles.Add(weh); - weh = receiver; - - if (weh == start) - break; - } - } - - if (!grid.TryGetTileRef(node, out var tileRef) || tileRef.Tile.IsEmpty) - { - continue; - } - - for (var i = 0; i < 4; i++) - { - var dir = (Direction) (i * 2); - - var neighbor = node + dir.ToIntVec(); - var tileCost = 1f; - - // Prefer straight lines. - if (lastDirection[node] != dir) - { - tileCost *= 1.1f; - } - - if (cableTiles.Contains(neighbor)) - { - tileCost *= 0.1f; - } - - // Prefer tiles without walls on them - if (HasWall(grid, neighbor)) - { - tileCost *= 20f; - } - - var gScore = costSoFar[node] + tileCost; - - if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) - { - continue; - } - - cameFrom[neighbor] = node; - costSoFar[neighbor] = gScore; - lastDirection[neighbor] = dir; - frontier.Enqueue(neighbor, gScore); - } - } - - foreach (var tile in cableTiles) - { - var anchored = grid.GetAnchoredEntitiesEnumerator(tile); - var found = false; - - while (anchored.MoveNext(out var anc)) - { - if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || - !nodeContainer.Nodes.ContainsKey("power")) - { - continue; - } - - found = true; - break; - } - - if (found) - continue; - - _entManager.SpawnEntity(gen.Entity, _grid.GridTileToLocal(tile)); - } - } - - private async Task PostGen(BoundaryWallPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var tileDef = _tileDefManager[gen.Tile]; - var tiles = new List<(Vector2i Index, Tile Tile)>(dungeon.RoomExteriorTiles.Count); - - // Spawn wall outline - // - Tiles first - foreach (var neighbor in dungeon.RoomExteriorTiles) - { - DebugTools.Assert(!dungeon.RoomTiles.Contains(neighbor)); - - if (dungeon.Entrances.Contains(neighbor)) - continue; - - if (!_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - } - - foreach (var index in dungeon.CorridorExteriorTiles) - { - if (dungeon.RoomTiles.Contains(index)) - continue; - - if (!_anchorable.TileFree(grid, index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random))); - } - - grid.SetTiles(tiles); - - // Double iteration coz we bulk set tiles for speed. - for (var i = 0; i < tiles.Count; i++) - { - var index = tiles[i]; - if (!_anchorable.TileFree(grid, index.Index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - // If no cardinal neighbors in dungeon then we're a corner. - var isCorner = false; - - if (gen.CornerWall != null) - { - isCorner = true; - - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - if (x != 0 && y != 0) - { - continue; - } - - var neighbor = new Vector2i(index.Index.X + x, index.Index.Y + y); - - if (dungeon.RoomTiles.Contains(neighbor) || dungeon.CorridorTiles.Contains(neighbor)) - { - isCorner = false; - break; - } - } - - if (!isCorner) - break; - } - - if (isCorner) - _entManager.SpawnEntity(gen.CornerWall, grid.GridTileToLocal(index.Index)); - } - - if (!isCorner) - _entManager.SpawnEntity(gen.Wall, grid.GridTileToLocal(index.Index)); - - if (i % 20 == 0) - { - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } - - private async Task PostGen(CornerClutterPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var physicsQuery = _entManager.GetEntityQuery(); - - foreach (var tile in dungeon.CorridorTiles) - { - var enumerator = _grid.GetAnchoredEntitiesEnumerator(tile); - var blocked = false; - - while (enumerator.MoveNext(out var ent)) - { - // TODO: TileFree - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.CanCollide || - !physics.Hard) - { - continue; - } - - blocked = true; - break; - } - - if (blocked) - continue; - - // If at least 2 adjacent tiles are blocked consider it a corner - for (var i = 0; i < 4; i++) - { - var dir = (Direction) (i * 2); - blocked = HasWall(grid, tile + dir.ToIntVec()); - - if (!blocked) - continue; - - var nextDir = (Direction) ((i + 1) * 2 % 8); - blocked = HasWall(grid, tile + nextDir.ToIntVec()); - - if (!blocked) - continue; - - if (random.Prob(gen.Chance)) - { - var coords = _grid.GridTileToLocal(tile); - var protos = EntitySpawnCollection.GetSpawns(gen.Contents, random); - _entManager.SpawnEntities(coords, protos); - } - - break; - } - } - } - - private async Task PostGen(CorridorDecalSkirtingPostGen decks, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var directions = new ValueList(4); - var pocketDirections = new ValueList(4); - var doorQuery = _entManager.GetEntityQuery(); - var physicsQuery = _entManager.GetEntityQuery(); - var offset = -_grid.TileSizeHalfVector; - var color = decks.Color; - - foreach (var tile in dungeon.CorridorTiles) - { - DebugTools.Assert(!dungeon.RoomTiles.Contains(tile)); - directions.Clear(); - - // Do cardinals 1 step - // Do corners the other step - for (var i = 0; i < 4; i++) - { - var dir = (DirectionFlag) Math.Pow(2, i); - var neighbor = tile + dir.AsDir().ToIntVec(); - - var anc = _grid.GetAnchoredEntitiesEnumerator(neighbor); - - while (anc.MoveNext(out var ent)) - { - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.CanCollide || - !physics.Hard || - doorQuery.HasComponent(ent.Value)) - { - continue; - } - - directions.Add(dir); - break; - } - } - - // Pockets - if (directions.Count == 0) - { - pocketDirections.Clear(); - - for (var i = 1; i < 5; i++) - { - var dir = (Direction) (i * 2 - 1); - var neighbor = tile + dir.ToIntVec(); - - var anc = _grid.GetAnchoredEntitiesEnumerator(neighbor); - - while (anc.MoveNext(out var ent)) - { - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.CanCollide || - !physics.Hard || - doorQuery.HasComponent(ent.Value)) - { - continue; - } - - pocketDirections.Add(dir); - break; - } - } - - if (pocketDirections.Count == 1) - { - if (decks.PocketDecals.TryGetValue(pocketDirections[0], out var cDir)) - { - // Decals not being centered biting my ass again - var gridPos = _grid.GridTileToLocal(tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out _, color: color); - } - } - - continue; - } - - if (directions.Count == 1) - { - if (decks.CardinalDecals.TryGetValue(directions[0], out var cDir)) - { - // Decals not being centered biting my ass again - var gridPos = _grid.GridTileToLocal(tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out _, color: color); - } - - continue; - } - - // Corners - if (directions.Count == 2) - { - // Auehghegueugegegeheh help me - var dirFlag = directions[0] | directions[1]; - - if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir)) - { - var gridPos = _grid.GridTileToLocal(tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out _, color: color); - } - } - } - } - - private async Task PostGen(DungeonEntrancePostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var rooms = new List(dungeon.Rooms); - var roomTiles = new List(); - var tileDef = _tileDefManager[gen.Tile]; - - for (var i = 0; i < gen.Count; i++) - { - var roomIndex = random.Next(rooms.Count); - var room = rooms[roomIndex]; - - // Move out 3 tiles in a direction away from center of the room - // If none of those intersect another tile it's probably external - // TODO: Maybe need to take top half of furthest rooms in case there's interior exits? - roomTiles.AddRange(room.Exterior); - random.Shuffle(roomTiles); - - foreach (var tile in roomTiles) - { - var isValid = false; - - // Check if one side is dungeon and the other side is nothing. - for (var j = 0; j < 4; j++) - { - var dir = (Direction) (j * 2); - var oppositeDir = dir.GetOpposite(); - var dirVec = tile + dir.ToIntVec(); - var oppositeDirVec = tile + oppositeDir.ToIntVec(); - - if (!dungeon.RoomTiles.Contains(dirVec)) - { - continue; - } - - if (dungeon.RoomTiles.Contains(oppositeDirVec) || - dungeon.RoomExteriorTiles.Contains(oppositeDirVec) || - dungeon.CorridorExteriorTiles.Contains(oppositeDirVec) || - dungeon.CorridorTiles.Contains(oppositeDirVec)) - { - continue; - } - - // Check if exterior spot free. - if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - continue; - } - - // Check if interior spot free (no guarantees on exterior but ClearDoor should handle it) - if (!_anchorable.TileFree(_grid, dirVec, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - continue; - } - - // Valid pick! - isValid = true; - - // Entrance wew - grid.SetTile(tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - ClearDoor(dungeon, grid, tile); - var gridCoords = grid.GridTileToLocal(tile); - // Need to offset the spawn to avoid spawning in the room. - - _entManager.SpawnEntities(gridCoords, gen.Entities); - - // Clear out any biome tiles nearby to avoid blocking it - foreach (var nearTile in grid.GetTilesIntersecting(new Circle(gridCoords.Position, 1.5f), false)) - { - if (dungeon.RoomTiles.Contains(nearTile.GridIndices) || - dungeon.RoomExteriorTiles.Contains(nearTile.GridIndices) || - dungeon.CorridorTiles.Contains(nearTile.GridIndices) || - dungeon.CorridorExteriorTiles.Contains(nearTile.GridIndices)) - { - continue; - } - - grid.SetTile(nearTile.GridIndices, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));; - } - - break; - } - - if (isValid) - break; - } - - roomTiles.Clear(); - } - } - - private async Task PostGen(ExternalWindowPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - // Iterate every tile with N chance to spawn windows on that wall per cardinal dir. - var chance = 0.25 / 3f; - - var allExterior = new HashSet(dungeon.CorridorExteriorTiles); - allExterior.UnionWith(dungeon.RoomExteriorTiles); - var validTiles = allExterior.ToList(); - random.Shuffle(validTiles); - - var tiles = new List<(Vector2i, Tile)>(); - var tileDef = _tileDefManager[gen.Tile]; - var count = Math.Floor(validTiles.Count * chance); - var index = 0; - var takenTiles = new HashSet(); - - // There's a bunch of shit here but tl;dr - // - don't spawn over cap - // - Check if we have 3 tiles in a row that aren't corners and aren't obstructed - foreach (var tile in validTiles) - { - if (index > count) - break; - - // Room tile / already used. - if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask) || - takenTiles.Contains(tile)) - { - continue; - } - - // Check we're not on a corner - for (var i = 0; i < 2; i++) - { - var dir = (Direction) (i * 2); - var dirVec = dir.ToIntVec(); - var isValid = true; - - // Check 1 beyond either side to ensure it's not a corner. - for (var j = -1; j < 4; j++) - { - var neighbor = tile + dirVec * j; - - if (!allExterior.Contains(neighbor) || - takenTiles.Contains(neighbor) || - !_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - - // Also check perpendicular that it is free - foreach (var k in new [] {2, 6}) - { - var perp = (Direction) ((i * 2 + k) % 8); - var perpVec = perp.ToIntVec(); - var perpTile = tile + perpVec; - - if (allExterior.Contains(perpTile) || - takenTiles.Contains(neighbor) || - !_anchorable.TileFree(_grid, perpTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - } - - if (!isValid) - break; - } - - if (!isValid) - continue; - - for (var j = 0; j < 3; j++) - { - var neighbor = tile + dirVec * j; - - tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - index++; - takenTiles.Add(neighbor); - } - } - } - - grid.SetTiles(tiles); - index = 0; - - foreach (var tile in tiles) - { - var gridPos = grid.GridTileToLocal(tile.Item1); - - index += gen.Entities.Count; - _entManager.SpawnEntities(gridPos, gen.Entities); - - if (index > 20) - { - index -= 20; - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } - - /* - * You may be wondering why these are different. - * It's because for internals we want to force it as it looks nicer and not leave it up to chance. - */ - - // TODO: Can probably combine these a bit, their differences are in really annoying to pull out spots. - - private async Task PostGen(InternalWindowPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - // Iterate every room and check if there's a gap beyond it that leads to another room within N tiles - // If so then consider windows - var minDistance = 4; - var maxDistance = 6; - var tileDef = _tileDefManager[gen.Tile]; - - foreach (var room in dungeon.Rooms) - { - var validTiles = new List(); - - for (var i = 0; i < 4; i++) - { - var dir = (DirectionFlag) Math.Pow(2, i); - var dirVec = dir.AsDir().ToIntVec(); - - foreach (var tile in room.Tiles) - { - var tileAngle = ((Vector2) tile + grid.TileSizeHalfVector - room.Center).ToAngle(); - var roundedAngle = Math.Round(tileAngle.Theta / (Math.PI / 2)) * (Math.PI / 2); - - var tileVec = (Vector2i) new Angle(roundedAngle).ToVec().Rounded(); - - if (!tileVec.Equals(dirVec)) - continue; - - var valid = false; - - for (var j = 1; j < maxDistance; j++) - { - var edgeNeighbor = tile + dirVec * j; - - if (dungeon.RoomTiles.Contains(edgeNeighbor)) - { - if (j < minDistance) - { - valid = false; - } - else - { - valid = true; - } - - break; - } - } - - if (!valid) - continue; - - var windowTile = tile + dirVec; - - if (!_anchorable.TileFree(grid, windowTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - validTiles.Add(windowTile); - } - - validTiles.Sort((x, y) => ((Vector2) x + grid.TileSizeHalfVector - room.Center).LengthSquared().CompareTo((y + grid.TileSizeHalfVector - room.Center).LengthSquared)); - - for (var j = 0; j < Math.Min(validTiles.Count, 3); j++) - { - var tile = validTiles[j]; - var gridPos = grid.GridTileToLocal(tile); - grid.SetTile(tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - - _entManager.SpawnEntities(gridPos, gen.Entities); - } - - if (validTiles.Count > 0) - { - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - - validTiles.Clear(); - } - } - } - - /// - /// Simply places tiles / entities on the entrances to rooms. - /// - private async Task PostGen(RoomEntrancePostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var setTiles = new List<(Vector2i, Tile)>(); - var tileDef = _tileDefManager[gen.Tile]; - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - } - } - - grid.SetTiles(setTiles); - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - _entManager.SpawnEntities(grid.GridTileToLocal(entrance), gen.Entities); - } - } - } - - /// - /// Generates corridor connections between entrances to all the rooms. - /// - private async Task PostGen(CorridorPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - var entrances = new List(dungeon.Rooms.Count); - - // Grab entrances - foreach (var room in dungeon.Rooms) - { - entrances.AddRange(room.Entrances); - } - - var edges = _dungeon.MinimumSpanningTree(entrances, random); - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - - // TODO: Add in say 1/3 of edges back in to add some cyclic to it. - - var expansion = gen.Width - 2; - // Okay so tl;dr is that we don't want to cut close to rooms as it might go from 3 width to 2 width suddenly - // So we will add a buffer range around each room to deter pathfinding there unless necessary - var deterredTiles = new HashSet(); - - if (expansion >= 1) - { - foreach (var tile in dungeon.RoomExteriorTiles) - { - for (var x = -expansion; x <= expansion; x++) - { - for (var y = -expansion; y <= expansion; y++) - { - var neighbor = new Vector2(tile.X + x, tile.Y + y).Floored(); - - if (dungeon.RoomTiles.Contains(neighbor) || - dungeon.RoomExteriorTiles.Contains(neighbor) || - entrances.Contains(neighbor)) - { - continue; - } - - deterredTiles.Add(neighbor); - } - } - } - } - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - // Just so we can still actually get in to the entrance we won't deter from a tile away from it. - var normal = (entrance + grid.TileSizeHalfVector - room.Center).ToWorldAngle().GetCardinalDir().ToIntVec(); - deterredTiles.Remove(entrance + normal); - } - } - - var excludedTiles = new HashSet(dungeon.RoomExteriorTiles); - excludedTiles.UnionWith(dungeon.RoomTiles); - var corridorTiles = new HashSet(); - - _dungeon.GetCorridorNodes(corridorTiles, edges, gen.PathLimit, excludedTiles, tile => - { - var mod = 1f; - - if (corridorTiles.Contains(tile)) - { - mod *= 0.1f; - } - - if (deterredTiles.Contains(tile)) - { - mod *= 2f; - } - - return mod; - }); - - WidenCorridor(dungeon, gen.Width, corridorTiles); - - var setTiles = new List<(Vector2i, Tile)>(); - var tileDef = _prototype.Index(gen.Tile); - - foreach (var tile in corridorTiles) - { - setTiles.Add((tile, _tile.GetVariantTile(tileDef, random))); - } - - grid.SetTiles(setTiles); - dungeon.CorridorTiles.UnionWith(corridorTiles); - BuildCorridorExterior(dungeon); - } - - private void BuildCorridorExterior(Dungeon dungeon) - { - var exterior = dungeon.CorridorExteriorTiles; - - // Just ignore entrances or whatever for now. - foreach (var tile in dungeon.CorridorTiles) - { - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - var neighbor = new Vector2i(tile.X + x, tile.Y + y); - - if (dungeon.CorridorTiles.Contains(neighbor) || - dungeon.RoomExteriorTiles.Contains(neighbor) || - dungeon.RoomTiles.Contains(neighbor) || - dungeon.Entrances.Contains(neighbor)) - { - continue; - } - - exterior.Add(neighbor); - } - } - } - } - - private void WidenCorridor(Dungeon dungeon, float width, ICollection corridorTiles) - { - var expansion = width - 2; - - // Widen the path - if (expansion >= 1) - { - var toAdd = new ValueList(); - - foreach (var node in corridorTiles) - { - // Uhhh not sure on the cleanest way to do this but tl;dr we don't want to hug - // exterior walls and make the path smaller. - - for (var x = -expansion; x <= expansion; x++) - { - for (var y = -expansion; y <= expansion; y++) - { - var neighbor = new Vector2(node.X + x, node.Y + y).Floored(); - - // Diagonals still matter here. - if (dungeon.RoomTiles.Contains(neighbor) || - dungeon.RoomExteriorTiles.Contains(neighbor)) - { - // Try - - continue; - } - - toAdd.Add(neighbor); - } - } - } - - foreach (var node in toAdd) - { - corridorTiles.Add(node); - } - } - } - - private async Task PostGen(EntranceFlankPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var tiles = new List<(Vector2i Index, Tile)>(); - var tileDef = _tileDefManager[gen.Tile]; - var spawnPositions = new ValueList(dungeon.Rooms.Count); - - foreach (var room in dungeon.Rooms) - { - foreach (var entrance in room.Entrances) - { - for (var i = 0; i < 8; i++) - { - var dir = (Direction) i; - var neighbor = entrance + dir.ToIntVec(); - - if (!dungeon.RoomExteriorTiles.Contains(neighbor)) - continue; - - tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); - spawnPositions.Add(neighbor); - } - } - } - - grid.SetTiles(tiles); - - foreach (var entrance in spawnPositions) - { - _entManager.SpawnEntities(_grid.GridTileToLocal(entrance), gen.Entities); - } - } - - private async Task PostGen(JunctionPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var tileDef = _tileDefManager[gen.Tile]; - - // N-wide junctions - foreach (var tile in dungeon.CorridorTiles) - { - if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - // Check each direction: - // - Check if immediate neighbors are free - // - Check if the neighbors beyond that are not free - // - Then check either side if they're slightly more free - var exteriorWidth = (int) Math.Floor(gen.Width / 2f); - var width = (int) Math.Ceiling(gen.Width / 2f); - - for (var i = 0; i < 2; i++) - { - var isValid = true; - var neighborDir = (Direction) (i * 2); - var neighborVec = neighborDir.ToIntVec(); - - for (var j = -width; j <= width; j++) - { - if (j == 0) - continue; - - var neighbor = tile + neighborVec * j; - - // If it's an end tile then check it's occupied. - if (j == -width || - j == width) - { - if (!HasWall(grid, neighbor)) - { - isValid = false; - break; - } - - continue; - } - - // If we're not at the end tile then check it + perpendicular are free. - if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - - var perp1 = tile + neighborVec * j + ((Direction) ((i * 2 + 2) % 8)).ToIntVec(); - var perp2 = tile + neighborVec * j + ((Direction) ((i * 2 + 6) % 8)).ToIntVec(); - - if (!_anchorable.TileFree(_grid, perp1, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - - if (!_anchorable.TileFree(_grid, perp2, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - isValid = false; - break; - } - } - - if (!isValid) - continue; - - // Check corners to see if either side opens up (if it's just a 1x wide corridor do nothing, needs to be a funnel. - foreach (var j in new [] {-exteriorWidth, exteriorWidth}) - { - var freeCount = 0; - - // Need at least 3 of 4 free - for (var k = 0; k < 4; k++) - { - var cornerDir = (Direction) (k * 2 + 1); - var cornerVec = cornerDir.ToIntVec(); - var cornerNeighbor = tile + neighborVec * j + cornerVec; - - if (_anchorable.TileFree(_grid, cornerNeighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - { - freeCount++; - } - } - - if (freeCount < gen.Width) - continue; - - // Valid! - isValid = true; - - for (var x = -width + 1; x < width; x++) - { - var weh = tile + neighborDir.ToIntVec() * x; - grid.SetTile(weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - - var coords = grid.GridTileToLocal(weh); - _entManager.SpawnEntities(coords, gen.Entities); - } - - break; - } - - if (isValid) - { - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - - break; - } - } - } - - private async Task PostGen(MiddleConnectionPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - // TODO: Need a minimal spanning tree version tbh - - // Grab all of the room bounds - // Then, work out connections between them - var roomBorders = new Dictionary>(dungeon.Rooms.Count); - - foreach (var room in dungeon.Rooms) - { - var roomEdges = new HashSet(); - - foreach (var index in room.Tiles) - { - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - // Cardinals only - if (x != 0 && y != 0 || - x == 0 && y == 0) - { - continue; - } - - var neighbor = new Vector2i(index.X + x, index.Y + y); - - if (dungeon.RoomTiles.Contains(neighbor)) - continue; - - if (!_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - roomEdges.Add(neighbor); - } - } - } - - roomBorders.Add(room, roomEdges); - } - - // Do pathfind from first room to work out graph. - // TODO: Optional loops - - var roomConnections = new Dictionary>(); - var frontier = new Queue(); - frontier.Enqueue(dungeon.Rooms.First()); - var tileDef = _tileDefManager[gen.Tile]; - - foreach (var (room, border) in roomBorders) - { - var conns = roomConnections.GetOrNew(room); - - foreach (var (otherRoom, otherBorders) in roomBorders) - { - if (room.Equals(otherRoom) || - conns.Contains(otherRoom)) - { - continue; - } - - var flipp = new HashSet(border); - flipp.IntersectWith(otherBorders); - - if (flipp.Count == 0 || - gen.OverlapCount != -1 && flipp.Count != gen.OverlapCount) - continue; - - var center = Vector2.Zero; - - foreach (var node in flipp) - { - center += (Vector2) node + grid.TileSizeHalfVector; - } - - center /= flipp.Count; - // Weight airlocks towards center more. - var nodeDistances = new List<(Vector2i Node, float Distance)>(flipp.Count); - - foreach (var node in flipp) - { - nodeDistances.Add((node, ((Vector2) node + grid.TileSizeHalfVector - center).LengthSquared())); - } - - nodeDistances.Sort((x, y) => x.Distance.CompareTo(y.Distance)); - - var width = gen.Count; - - for (var i = 0; i < nodeDistances.Count; i++) - { - var node = nodeDistances[i].Node; - var gridPos = grid.GridTileToLocal(node); - if (!_anchorable.TileFree(grid, node, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - width--; - grid.SetTile(node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - - if (gen.EdgeEntities != null && nodeDistances.Count - i <= 2) - { - _entManager.SpawnEntities(gridPos, gen.EdgeEntities); - } - else - { - // Iterate neighbors and check for blockers, if so bulldoze - ClearDoor(dungeon, grid, node); - - _entManager.SpawnEntities(gridPos, gen.Entities); - } - - if (width == 0) - break; - } - - conns.Add(otherRoom); - var otherConns = roomConnections.GetOrNew(otherRoom); - otherConns.Add(room); - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } - - /// - /// Removes any unwanted obstacles around a door tile. - /// - private void ClearDoor(Dungeon dungeon, MapGridComponent grid, Vector2i indices, bool strict = false) - { - var flags = strict - ? LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.StaticSundries - : LookupFlags.Dynamic | LookupFlags.Static; - var physicsQuery = _entManager.GetEntityQuery(); - - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - if (x != 0 && y != 0) - continue; - - var neighbor = new Vector2i(indices.X + x, indices.Y + y); - - if (!dungeon.RoomTiles.Contains(neighbor)) - continue; - - // Shrink by 0.01 to avoid polygon overlap from neighboring tiles. - foreach (var ent in _lookup.GetEntitiesIntersecting(_gridUid, new Box2(neighbor * grid.TileSize, (neighbor + 1) * grid.TileSize).Enlarged(-0.1f), flags)) - { - if (!physicsQuery.TryGetComponent(ent, out var physics) || - !physics.Hard || - (DungeonSystem.CollisionMask & physics.CollisionLayer) == 0x0 && - (DungeonSystem.CollisionLayer & physics.CollisionMask) == 0x0) - { - continue; - } - - _entManager.DeleteEntity(ent); - } - } - } - } - - private async Task PostGen(WallMountPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) - { - var tileDef = _tileDefManager[gen.Tile]; - var checkedTiles = new HashSet(); - var allExterior = new HashSet(dungeon.CorridorExteriorTiles); - allExterior.UnionWith(dungeon.RoomExteriorTiles); - var count = 0; - - foreach (var neighbor in allExterior) - { - // Occupado - if (dungeon.RoomTiles.Contains(neighbor) || checkedTiles.Contains(neighbor) || !_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) - continue; - - if (!random.Prob(gen.Prob) || !checkedTiles.Add(neighbor)) - continue; - - grid.SetTile(neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - var gridPos = grid.GridTileToLocal(neighbor); - var protoNames = EntitySpawnCollection.GetSpawns(gen.Spawns, random); - - _entManager.SpawnEntities(gridPos, protoNames); - count += protoNames.Count; - - if (count > 20) - { - count -= 20; - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - return; - } - } - } -} diff --git a/Content.Server/Procedural/DungeonJob.PostGenBiome.cs b/Content.Server/Procedural/DungeonJob.PostGenBiome.cs deleted file mode 100644 index 4d3f573f4d41..000000000000 --- a/Content.Server/Procedural/DungeonJob.PostGenBiome.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Threading.Tasks; -using Content.Server.Parallax; -using Content.Shared.Parallax.Biomes; -using Content.Shared.Parallax.Biomes.Markers; -using Content.Shared.Procedural; -using Content.Shared.Procedural.PostGeneration; -using Content.Shared.Random.Helpers; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Utility; - -namespace Content.Server.Procedural; - -public sealed partial class DungeonJob -{ - /* - * Handles PostGen code for marker layers + biomes. - */ - - private async Task PostGen(BiomePostGen postGen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - if (_entManager.TryGetComponent(gridUid, out BiomeComponent? biomeComp)) - return; - - biomeComp = _entManager.AddComponent(gridUid); - var biomeSystem = _entManager.System(); - biomeSystem.SetTemplate(gridUid, biomeComp, _prototype.Index(postGen.BiomeTemplate)); - var seed = random.Next(); - var xformQuery = _entManager.GetEntityQuery(); - - foreach (var node in dungeon.RoomTiles) - { - // Need to set per-tile to override data. - if (biomeSystem.TryGetTile(node, biomeComp.Layers, seed, grid, out var tile)) - { - _maps.SetTile(gridUid, grid, node, tile.Value); - } - - if (biomeSystem.TryGetDecals(node, biomeComp.Layers, seed, grid, out var decals)) - { - foreach (var decal in decals) - { - _decals.TryAddDecal(decal.ID, new EntityCoordinates(gridUid, decal.Position), out _); - } - } - - if (biomeSystem.TryGetEntity(node, biomeComp, grid, out var entityProto)) - { - var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(gridUid, node + grid.TileSizeHalfVector)); - var xform = xformQuery.Get(ent); - - if (!xform.Comp.Anchored) - { - _transform.AnchorEntity(ent, xform); - } - - // TODO: Engine bug with SpawnAtPosition - DebugTools.Assert(xform.Comp.Anchored); - } - - await SuspendIfOutOfTime(); - ValidateResume(); - } - - biomeComp.Enabled = false; - } - - private async Task PostGen(BiomeMarkerLayerPostGen postGen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) - { - if (!_entManager.TryGetComponent(gridUid, out BiomeComponent? biomeComp)) - return; - - var biomeSystem = _entManager.System(); - var weightedRandom = _prototype.Index(postGen.MarkerTemplate); - var xformQuery = _entManager.GetEntityQuery(); - var templates = new Dictionary(); - - for (var i = 0; i < postGen.Count; i++) - { - var template = weightedRandom.Pick(random); - var count = templates.GetOrNew(template); - count++; - templates[template] = count; - } - - foreach (var (template, count) in templates) - { - var markerTemplate = _prototype.Index(template); - - var bounds = new Box2i(); - - foreach (var tile in dungeon.RoomTiles) - { - bounds = bounds.UnionTile(tile); - } - - await SuspendIfOutOfTime(); - ValidateResume(); - - biomeSystem.GetMarkerNodes(gridUid, biomeComp, grid, markerTemplate, true, bounds, count, - random, out var spawnSet, out var existing, false); - - await SuspendIfOutOfTime(); - ValidateResume(); - - foreach (var ent in existing) - { - _entManager.DeleteEntity(ent); - } - - await SuspendIfOutOfTime(); - ValidateResume(); - - foreach (var (node, mask) in spawnSet) - { - string? proto; - - if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto)) - { - proto = maskedProto; - } - else - { - proto = markerTemplate.Prototype; - } - - var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(gridUid, node + grid.TileSizeHalfVector)); - var xform = xformQuery.Get(ent); - - if (!xform.Comp.Anchored) - _transform.AnchorEntity(ent, xform); - - await SuspendIfOutOfTime(); - ValidateResume(); - } - } - } -} diff --git a/Content.Server/Procedural/DungeonJob.cs b/Content.Server/Procedural/DungeonJob.cs deleted file mode 100644 index bf2822ff4237..000000000000 --- a/Content.Server/Procedural/DungeonJob.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Content.Server.Construction; -using Robust.Shared.CPUJob.JobQueues; -using Content.Server.Decals; -using Content.Shared.Construction.EntitySystems; -using Content.Shared.Maps; -using Content.Shared.Procedural; -using Content.Shared.Procedural.DungeonGenerators; -using Content.Shared.Procedural.PostGeneration; -using Content.Shared.Tag; -using Robust.Server.Physics; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Utility; - -namespace Content.Server.Procedural; - -public sealed partial class DungeonJob : Job -{ - private readonly IEntityManager _entManager; - private readonly IMapManager _mapManager; - private readonly IPrototypeManager _prototype; - private readonly ITileDefinitionManager _tileDefManager; - - private readonly AnchorableSystem _anchorable; - private readonly DecalSystem _decals; - private readonly DungeonSystem _dungeon; - private readonly EntityLookupSystem _lookup; - private readonly TagSystem _tag; - private readonly TileSystem _tile; - private readonly SharedMapSystem _maps; - private readonly SharedTransformSystem _transform; - - private readonly DungeonConfigPrototype _gen; - private readonly int _seed; - private readonly Vector2i _position; - - private readonly MapGridComponent _grid; - private readonly EntityUid _gridUid; - - private readonly ISawmill _sawmill; - - public DungeonJob( - ISawmill sawmill, - double maxTime, - IEntityManager entManager, - IMapManager mapManager, - IPrototypeManager prototype, - ITileDefinitionManager tileDefManager, - AnchorableSystem anchorable, - DecalSystem decals, - DungeonSystem dungeon, - EntityLookupSystem lookup, - TagSystem tag, - TileSystem tile, - SharedTransformSystem transform, - DungeonConfigPrototype gen, - MapGridComponent grid, - EntityUid gridUid, - int seed, - Vector2i position, - CancellationToken cancellation = default) : base(maxTime, cancellation) - { - _sawmill = sawmill; - _entManager = entManager; - _mapManager = mapManager; - _prototype = prototype; - _tileDefManager = tileDefManager; - - _anchorable = anchorable; - _decals = decals; - _dungeon = dungeon; - _lookup = lookup; - _tag = tag; - _tile = tile; - _maps = _entManager.System(); - _transform = transform; - - _gen = gen; - _grid = grid; - _gridUid = gridUid; - _seed = seed; - _position = position; - } - - protected override async Task Process() - { - Dungeon dungeon; - _sawmill.Info($"Generating dungeon {_gen.ID} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}"); - _grid.CanSplit = false; - - switch (_gen.Generator) - { - case NoiseDunGen noise: - dungeon = await GenerateNoiseDungeon(noise, _gridUid, _grid, _seed); - break; - case PrefabDunGen prefab: - dungeon = await GeneratePrefabDungeon(prefab, _gridUid, _grid, _seed); - DebugTools.Assert(dungeon.RoomExteriorTiles.Count > 0); - break; - default: - throw new NotImplementedException(); - } - - DebugTools.Assert(dungeon.RoomTiles.Count > 0); - - // To make it slightly more deterministic treat this RNG as separate ig. - var random = new Random(_seed); - - foreach (var post in _gen.PostGeneration) - { - _sawmill.Debug($"Doing postgen {post.GetType()} for {_gen.ID} with seed {_seed}"); - - switch (post) - { - case AutoCablingPostGen cabling: - await PostGen(cabling, dungeon, _gridUid, _grid, random); - break; - case BiomePostGen biome: - await PostGen(biome, dungeon, _gridUid, _grid, random); - break; - case BoundaryWallPostGen boundary: - await PostGen(boundary, dungeon, _gridUid, _grid, random); - break; - case CornerClutterPostGen clutter: - await PostGen(clutter, dungeon, _gridUid, _grid, random); - break; - case CorridorClutterPostGen corClutter: - await PostGen(corClutter, dungeon, _gridUid, _grid, random); - break; - case CorridorPostGen cordor: - await PostGen(cordor, dungeon, _gridUid, _grid, random); - break; - case CorridorDecalSkirtingPostGen decks: - await PostGen(decks, dungeon, _gridUid, _grid, random); - break; - case EntranceFlankPostGen flank: - await PostGen(flank, dungeon, _gridUid, _grid, random); - break; - case JunctionPostGen junc: - await PostGen(junc, dungeon, _gridUid, _grid, random); - break; - case MiddleConnectionPostGen dordor: - await PostGen(dordor, dungeon, _gridUid, _grid, random); - break; - case DungeonEntrancePostGen entrance: - await PostGen(entrance, dungeon, _gridUid, _grid, random); - break; - case ExternalWindowPostGen externalWindow: - await PostGen(externalWindow, dungeon, _gridUid, _grid, random); - break; - case InternalWindowPostGen internalWindow: - await PostGen(internalWindow, dungeon, _gridUid, _grid, random); - break; - case BiomeMarkerLayerPostGen markerPost: - await PostGen(markerPost, dungeon, _gridUid, _grid, random); - break; - case RoomEntrancePostGen rEntrance: - await PostGen(rEntrance, dungeon, _gridUid, _grid, random); - break; - case WallMountPostGen wall: - await PostGen(wall, dungeon, _gridUid, _grid, random); - break; - case WormCorridorPostGen worm: - await PostGen(worm, dungeon, _gridUid, _grid, random); - break; - default: - throw new NotImplementedException(); - } - - await SuspendIfOutOfTime(); - - if (!ValidateResume()) - break; - } - - // Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way. - _grid.CanSplit = true; - _entManager.System().CheckSplits(_gridUid); - return dungeon; - } - - private bool ValidateResume() - { - if (_entManager.Deleted(_gridUid)) - return false; - - return true; - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs new file mode 100644 index 000000000000..acffd057fad0 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; +using Robust.Shared.Collections; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet reservedTiles, Random random) + { + DebugTools.Assert(_grid.ChunkCount > 0); + + var aabb = new Box2i(_grid.LocalAABB.BottomLeft.Floored(), _grid.LocalAABB.TopRight.Floored()); + var angle = random.NextAngle(); + + var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f); + + var startTile = new Vector2i(0, (int) distance).Rotate(angle); + + Vector2i? dungeonSpawn = null; + var pathfinder = _entManager.System(); + + // Gridcast + pathfinder.GridCast(startTile, position, tile => + { + if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) || + tileRef.Tile.IsSpace(_tileDefManager)) + { + return true; + } + + dungeonSpawn = tile; + return false; + }); + + if (dungeonSpawn == null) + { + return new List() + { + Dungeon.Empty + }; + } + + var config = _prototype.Index(dungen.Proto); + var nextSeed = random.Next(); + var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Data, config.Layers, reservedTiles, nextSeed, new Random(nextSeed)); + + return dungeons; + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs new file mode 100644 index 000000000000..5a0d77c6151f --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenFill.cs @@ -0,0 +1,50 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task GenerateFillDunGen(DungeonData data, HashSet reservedTiles) + { + if (!data.Entities.TryGetValue(DungeonDataKey.Fill, out var fillEnt)) + { + LogDataError(typeof(FillGridDunGen)); + return Dungeon.Empty; + } + + var roomTiles = new HashSet(); + var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid); + + while (tiles.MoveNext(out var tileRef)) + { + var tile = tileRef.Value.GridIndices; + + if (reservedTiles.Contains(tile)) + continue; + + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile); + _entManager.SpawnEntity(fillEnt, gridPos); + + roomTiles.Add(tile); + + await SuspendDungeon(); + if (!ValidateResume()) + break; + } + + var dungeon = new Dungeon(); + var room = new DungeonRoom(roomTiles, Vector2.Zero, Box2i.Empty, new HashSet()); + dungeon.AddRoom(room); + + return dungeon; + } +} diff --git a/Content.Server/Procedural/DungeonJob.NoiseDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoise.cs similarity index 73% rename from Content.Server/Procedural/DungeonJob.NoiseDunGen.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoise.cs index 73c3386ead54..b2526ec17d1b 100644 --- a/Content.Server/Procedural/DungeonJob.NoiseDunGen.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoise.cs @@ -4,19 +4,25 @@ using Content.Shared.Procedural; using Content.Shared.Procedural.DungeonGenerators; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { - private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid gridUid, MapGridComponent grid, - int seed) + /// + /// + /// + private async Task GenerateNoiseDunGen( + Vector2i position, + NoiseDunGen dungen, + HashSet reservedTiles, + int seed, + Random random) { - var rand = new Random(seed); var tiles = new List<(Vector2i, Tile)>(); + var matrix = Matrix3Helpers.CreateTranslation(position); foreach (var layer in dungen.Layers) { @@ -30,7 +36,7 @@ private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid g var frontier = new Queue(); var rooms = new List(); var tileCount = 0; - var tileCap = rand.NextGaussian(dungen.TileCap, dungen.CapStd); + var tileCap = random.NextGaussian(dungen.TileCap, dungen.CapStd); var visited = new HashSet(); while (iterations > 0 && tileCount < tileCap) @@ -39,22 +45,22 @@ private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid g iterations--; // Get a random exterior tile to start floodfilling from. - var edge = rand.Next(4); + var edge = random.Next(4); Vector2i seedTile; switch (edge) { case 0: - seedTile = new Vector2i(rand.Next(area.Left - 2, area.Right + 1), area.Bottom - 2); + seedTile = new Vector2i(random.Next(area.Left - 2, area.Right + 1), area.Bottom - 2); break; case 1: - seedTile = new Vector2i(area.Right + 1, rand.Next(area.Bottom - 2, area.Top + 1)); + seedTile = new Vector2i(area.Right + 1, random.Next(area.Bottom - 2, area.Top + 1)); break; case 2: - seedTile = new Vector2i(rand.Next(area.Left - 2, area.Right + 1), area.Top + 1); + seedTile = new Vector2i(random.Next(area.Left - 2, area.Right + 1), area.Top + 1); break; case 3: - seedTile = new Vector2i(area.Left - 2, rand.Next(area.Bottom - 2, area.Top + 1)); + seedTile = new Vector2i(area.Left - 2, random.Next(area.Bottom - 2, area.Top + 1)); break; default: throw new ArgumentOutOfRangeException(); @@ -80,14 +86,20 @@ private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid g if (value < layer.Threshold) continue; - roomArea = roomArea.UnionTile(node); foundNoise = true; noiseFill = true; + + // Still want the tile to gen as normal but can't do anything with it. + if (reservedTiles.Contains(node)) + break; + + roomArea = roomArea.UnionTile(node); var tileDef = _tileDefManager[layer.Tile]; - var variant = _tile.PickVariant((ContentTileDefinition) tileDef, rand); + var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random); + var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored(); - tiles.Add((node, new Tile(tileDef.TileId, variant: variant))); - roomTiles.Add(node); + tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); + roomTiles.Add(adjusted); tileCount++; break; } @@ -123,7 +135,7 @@ private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid g foreach (var tile in roomTiles) { - center += tile + grid.TileSizeHalfVector; + center += tile + _grid.TileSizeHalfVector; } center /= roomTiles.Count; @@ -132,15 +144,8 @@ private async Task GenerateNoiseDungeon(NoiseDunGen dungen, EntityUid g ValidateResume(); } - grid.SetTiles(tiles); - + _maps.SetTiles(_gridUid, _grid, tiles); var dungeon = new Dungeon(rooms); - - foreach (var tile in tiles) - { - dungeon.RoomTiles.Add(tile.Item1); - } - return dungeon; } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs new file mode 100644 index 000000000000..f1808ec90cd2 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs @@ -0,0 +1,112 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.Distance; +using Content.Shared.Procedural.DungeonGenerators; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /* + * See https://www.redblobgames.com/maps/terrain-from-noise/#islands + * Really it's just blending from the original noise (which may occupy the entire area) + * with some other shape to confine it into a bounds more naturally. + * https://old.reddit.com/r/proceduralgeneration/comments/kaen7h/new_video_on_procedural_island_noise_generation/gfjmgen/ also has more variations + */ + + /// + /// + /// + private async Task GenerateNoiseDistanceDunGen( + Vector2i position, + NoiseDistanceDunGen dungen, + HashSet reservedTiles, + int seed, + Random random) + { + var tiles = new List<(Vector2i, Tile)>(); + var matrix = Matrix3Helpers.CreateTranslation(position); + + foreach (var layer in dungen.Layers) + { + layer.Noise.SetSeed(seed); + } + + // First we have to find a seed tile, then floodfill from there until we get to noise + // at which point we floodfill the entire noise. + var area = Box2i.FromDimensions(-dungen.Size / 2, dungen.Size); + var roomTiles = new HashSet(); + var width = (float) area.Width; + var height = (float) area.Height; + + for (var x = area.Left; x <= area.Right; x++) + { + for (var y = area.Bottom; y <= area.Top; y++) + { + var node = new Vector2i(x, y); + + foreach (var layer in dungen.Layers) + { + var value = layer.Noise.GetNoise(node.X, node.Y); + + if (dungen.DistanceConfig != null) + { + // Need to get dx - dx in a range from -1 -> 1 + var dx = 2 * x / width; + var dy = 2 * y / height; + + var distance = GetDistance(dx, dy, dungen.DistanceConfig); + + value = MathHelper.Lerp(value, 1f - distance, dungen.DistanceConfig.BlendWeight); + } + + if (value < layer.Threshold) + continue; + + var tileDef = _tileDefManager[layer.Tile]; + var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random); + var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored(); + + // Do this down here because noise has a much higher chance of failing than reserved tiles. + if (reservedTiles.Contains(adjusted)) + { + break; + } + + tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); + roomTiles.Add(adjusted); + break; + } + } + + await SuspendDungeon(); + } + + var room = new DungeonRoom(roomTiles, area.Center, area, new HashSet()); + + _maps.SetTiles(_gridUid, _grid, tiles); + var dungeon = new Dungeon(new List() + { + room, + }); + + await SuspendDungeon(); + return dungeon; + } + + private float GetDistance(float dx, float dy, IDunGenDistance distance) + { + switch (distance) + { + case DunGenEuclideanSquaredDistance: + return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2)); + case DunGenSquareBump: + return 1f - (1f - dx * dx) * (1f - dy * dy); + default: + throw new ArgumentOutOfRangeException(); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs similarity index 82% rename from Content.Server/Procedural/DungeonJob.PrefabDunGen.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs index a19f7e4701d9..33bbeba4b53c 100644 --- a/Content.Server/Procedural/DungeonJob.PrefabDunGen.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs @@ -1,25 +1,33 @@ using System.Numerics; using System.Threading.Tasks; -using Content.Shared.Decals; using Content.Shared.Procedural; using Content.Shared.Procedural.DungeonGenerators; +using Content.Shared.Whitelist; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { - private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid gridUid, MapGridComponent grid, int seed) + /// + /// + /// + private async Task GeneratePrefabDunGen(Vector2i position, DungeonData data, PrefabDunGen prefab, HashSet reservedTiles, Random random) { - var random = new Random(seed); + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.Whitelists.TryGetValue(DungeonDataKey.Rooms, out var roomWhitelist)) + { + LogDataError(typeof(PrefabDunGen)); + return Dungeon.Empty; + } + var preset = prefab.Presets[random.Next(prefab.Presets.Count)]; - var gen = _prototype.Index(preset); + var gen = _prototype.Index(preset); - var dungeonRotation = _dungeon.GetDungeonRotation(seed); - var dungeonTransform = Matrix3Helpers.CreateTransform(_position, dungeonRotation); + var dungeonRotation = _dungeon.GetDungeonRotation(random.Next()); + var dungeonTransform = Matrix3Helpers.CreateTransform(position, dungeonRotation); var roomPackProtos = new Dictionary>(); foreach (var pack in _prototype.EnumeratePrototypes()) @@ -42,12 +50,15 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid { var whitelisted = false; - foreach (var tag in prefab.RoomWhitelist) + if (roomWhitelist?.Tags != null) { - if (proto.Tags.Contains(tag)) + foreach (var tag in roomWhitelist.Tags) { - whitelisted = true; - break; + if (proto.Tags.Contains(tag)) + { + whitelisted = true; + break; + } } } @@ -182,12 +193,16 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid { for (var y = roomSize.Bottom; y < roomSize.Top; y++) { - var index = Vector2.Transform(new Vector2(x, y) + grid.TileSizeHalfVector - packCenter, matty).Floored(); - tiles.Add((index, new Tile(_tileDefManager["FloorPlanetGrass"].TileId))); + var index = Vector2.Transform(new Vector2(x, y) + _grid.TileSizeHalfVector - packCenter, matty).Floored(); + + if (reservedTiles.Contains(index)) + continue; + + tiles.Add((index, new Tile(_tileDefManager[tileProto].TileId))); } } - grid.SetTiles(tiles); + _maps.SetTiles(_gridUid, _grid, tiles); tiles.Clear(); _sawmill.Error($"Unable to find room variant for {roomDimensions}, leaving empty."); continue; @@ -215,12 +230,12 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform); // The expensive bit yippy. - _dungeon.SpawnRoom(gridUid, grid, dungeonMatty, room); + _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles); - var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize; + var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize; var roomTiles = new HashSet(room.Size.X * room.Size.Y); var exterior = new HashSet(room.Size.X * 2 + room.Size.Y * 2); - var tileOffset = -roomCenter + grid.TileSizeHalfVector; + var tileOffset = -roomCenter + _grid.TileSizeHalfVector; Box2i? mapBounds = null; for (var x = -1; x <= room.Size.X; x++) @@ -232,8 +247,12 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid continue; } - var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty); - exterior.Add(tilePos.Floored()); + var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty).Floored(); + + if (reservedTiles.Contains(tilePos)) + continue; + + exterior.Add(tilePos); } } @@ -249,38 +268,36 @@ private async Task GeneratePrefabDungeon(PrefabDunGen prefab, EntityUid roomTiles.Add(tileIndex); mapBounds = mapBounds?.Union(tileIndex) ?? new Box2i(tileIndex, tileIndex); - center += tilePos + grid.TileSizeHalfVector; + center += tilePos + _grid.TileSizeHalfVector; } } center /= roomTiles.Count; - dungeon.Rooms.Add(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior)); + dungeon.AddRoom(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior)); + + await SuspendDungeon(); - await SuspendIfOutOfTime(); - ValidateResume(); + if (!ValidateResume()) + return Dungeon.Empty; } } // Calculate center and do entrances var dungeonCenter = Vector2.Zero; - foreach (var room in dungeon.Rooms) - { - dungeon.RoomTiles.UnionWith(room.Tiles); - dungeon.RoomExteriorTiles.UnionWith(room.Exterior); - } - foreach (var room in dungeon.Rooms) { dungeonCenter += room.Center; - SetDungeonEntrance(dungeon, room, random); + SetDungeonEntrance(dungeon, room, reservedTiles, random); } + dungeon.Rebuild(); + return dungeon; } - private void SetDungeonEntrance(Dungeon dungeon, DungeonRoom room, Random random) + private void SetDungeonEntrance(Dungeon dungeon, DungeonRoom room, HashSet reservedTiles, Random random) { // TODO: Move to dungeonsystem. @@ -323,8 +340,10 @@ private void SetDungeonEntrance(Dungeon dungeon, DungeonRoom room, Random random continue; } + if (reservedTiles.Contains(entrancePos)) + continue; + room.Entrances.Add(entrancePos); - dungeon.Entrances.Add(entrancePos); break; } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs new file mode 100644 index 000000000000..6b36d101095c --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs @@ -0,0 +1,60 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task GenerateTileReplacementDunGen(ReplaceTileDunGen gen, DungeonData data, HashSet reservedTiles, Random random) + { + var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid); + var replacements = new List<(Vector2i Index, Tile Tile)>(); + var reserved = new HashSet(); + + while (tiles.MoveNext(out var tileRef)) + { + var node = tileRef.Value.GridIndices; + + if (reservedTiles.Contains(node)) + continue; + + foreach (var layer in gen.Layers) + { + var value = layer.Noise.GetNoise(node.X, node.Y); + + if (value < layer.Threshold) + continue; + + Tile tile; + + if (random.Prob(gen.VariantWeight)) + { + tile = _tileDefManager.GetVariantTile(_prototype.Index(layer.Tile), random); + } + else + { + tile = new Tile(_prototype.Index(layer.Tile).TileId); + } + + replacements.Add((node, tile)); + reserved.Add(node); + break; + } + + await SuspendDungeon(); + } + + _maps.SetTiles(_gridUid, _grid, replacements); + return new Dungeon(new List() + { + new DungeonRoom(reserved, _position, Box2i.Empty, new HashSet()), + }); + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs new file mode 100644 index 000000000000..150849d2c519 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.MobDunGen.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Content.Server.Ghost.Roles.Components; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using Content.Shared.Physics; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonLayers; +using Content.Shared.Storage; +using Robust.Shared.Collections; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + private async Task PostGen( + MobsDunGen gen, + Dungeon dungeon, + Random random) + { + var availableRooms = new ValueList(); + availableRooms.AddRange(dungeon.Rooms); + var availableTiles = new ValueList(dungeon.AllTiles); + + var entities = EntitySpawnCollection.GetSpawns(gen.Groups, random); + var count = random.Next(gen.MinCount, gen.MaxCount + 1); + var npcs = _entManager.System(); + + for (var i = 0; i < count; i++) + { + while (availableTiles.Count > 0) + { + var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count)); + + if (!_anchorable.TileFree(_grid, tile, (int) CollisionGroup.MachineLayer, + (int) CollisionGroup.MachineLayer)) + { + continue; + } + + foreach (var ent in entities) + { + var uid = _entManager.SpawnAtPosition(ent, _maps.GridTileToLocal(_gridUid, _grid, tile)); + _entManager.RemoveComponent(uid); + _entManager.RemoveComponent(uid); + npcs.SleepNPC(uid); + } + + break; + } + + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs new file mode 100644 index 000000000000..e89c1d7e470b --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.OreDunGen.cs @@ -0,0 +1,149 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.Components; +using Content.Shared.Procedural.DungeonLayers; +using Robust.Shared.Collections; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen( + OreDunGen gen, + Dungeon dungeon, + Random random) + { + // Doesn't use dungeon data because layers and we don't need top-down support at the moment. + + var emptyTiles = false; + var replaceEntities = new Dictionary(); + var availableTiles = new List(); + + foreach (var node in dungeon.AllTiles) + { + // Empty tile, skip if relevant. + if (!emptyTiles && (!_maps.TryGetTile(_grid, node, out var tile) || tile.IsEmpty)) + continue; + + // Check if it's a valid spawn, if so then use it. + var enumerator = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, node); + var found = false; + + // We use existing entities as a mark to spawn in place + // OR + // We check for any existing entities to see if we can spawn there. + while (enumerator.MoveNext(out var uid)) + { + // We can't replace so just stop here. + if (gen.Replacement == null) + break; + + var prototype = _entManager.GetComponent(uid.Value).EntityPrototype; + + if (prototype?.ID == gen.Replacement) + { + replaceEntities[node] = uid.Value; + found = true; + break; + } + } + + if (!found) + continue; + + // Add it to valid nodes. + availableTiles.Add(node); + + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + + var remapping = new Dictionary(); + + // TODO: Move this to engine + if (_prototype.TryIndex(gen.Entity, out var proto) && + proto.Components.TryGetComponent("EntityRemap", out var comps)) + { + var remappingComp = (EntityRemapComponent) comps; + remapping = remappingComp.Mask; + } + + var frontier = new ValueList(32); + + // Iterate the group counts and pathfind out each group. + for (var i = 0; i < gen.Count; i++) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1); + + // While we have remaining tiles keep iterating + while (groupSize >= 0 && availableTiles.Count > 0) + { + var startNode = random.PickAndTake(availableTiles); + frontier.Clear(); + frontier.Add(startNode); + + // This essentially may lead to a vein being split in multiple areas but the count matters more than position. + while (frontier.Count > 0 && groupSize >= 0) + { + // Need to pick a random index so we don't just get straight lines of ores. + var frontierIndex = random.Next(frontier.Count); + var node = frontier[frontierIndex]; + frontier.RemoveSwap(frontierIndex); + availableTiles.Remove(node); + + // Add neighbors if they're valid, worst case we add no more and pick another random seed tile. + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = new Vector2i(node.X + x, node.Y + y); + + if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor)) + continue; + + frontier.Add(neighbor); + } + } + + var prototype = gen.Entity; + + if (replaceEntities.TryGetValue(node, out var existingEnt)) + { + var existingProto = _entManager.GetComponent(existingEnt).EntityPrototype; + _entManager.DeleteEntity(existingEnt); + + if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped)) + { + prototype = remapped; + } + } + + // Tile valid salad so add it. + _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node)); + + groupSize--; + } + } + + if (groupSize > 0) + { + _sawmill.Warning($"Found remaining group size for ore veins!"); + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs new file mode 100644 index 000000000000..b1c83346d872 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGen.cs @@ -0,0 +1,134 @@ +using System.Numerics; +using Content.Shared.Procedural; +using Robust.Shared.Collections; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Components; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /* + * Run after the main dungeon generation + */ + + private bool HasWall(Vector2i tile) + { + var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); + + while (anchored.MoveNext(out var uid)) + { + if (_tags.HasTag(uid.Value, "Wall")) + return true; + } + + return false; + } + + private void BuildCorridorExterior(Dungeon dungeon) + { + var exterior = dungeon.CorridorExteriorTiles; + + // Just ignore entrances or whatever for now. + foreach (var tile in dungeon.CorridorTiles) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = new Vector2i(tile.X + x, tile.Y + y); + + if (dungeon.CorridorTiles.Contains(neighbor) || + dungeon.RoomExteriorTiles.Contains(neighbor) || + dungeon.RoomTiles.Contains(neighbor) || + dungeon.Entrances.Contains(neighbor)) + { + continue; + } + + exterior.Add(neighbor); + } + } + } + } + + private void WidenCorridor(Dungeon dungeon, float width, ICollection corridorTiles) + { + var expansion = width - 2; + + // Widen the path + if (expansion >= 1) + { + var toAdd = new ValueList(); + + foreach (var node in corridorTiles) + { + // Uhhh not sure on the cleanest way to do this but tl;dr we don't want to hug + // exterior walls and make the path smaller. + + for (var x = -expansion; x <= expansion; x++) + { + for (var y = -expansion; y <= expansion; y++) + { + var neighbor = new Vector2(node.X + x, node.Y + y).Floored(); + + // Diagonals still matter here. + if (dungeon.RoomTiles.Contains(neighbor) || + dungeon.RoomExteriorTiles.Contains(neighbor)) + { + // Try + + continue; + } + + toAdd.Add(neighbor); + } + } + } + + foreach (var node in toAdd) + { + corridorTiles.Add(node); + } + } + } + + /// + /// Removes any unwanted obstacles around a door tile. + /// + private void ClearDoor(Dungeon dungeon, MapGridComponent grid, Vector2i indices, bool strict = false) + { + var flags = strict + ? LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.StaticSundries + : LookupFlags.Dynamic | LookupFlags.Static; + + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + continue; + + var neighbor = new Vector2i(indices.X + x, indices.Y + y); + + if (!dungeon.RoomTiles.Contains(neighbor)) + continue; + + // Shrink by 0.01 to avoid polygon overlap from neighboring tiles. + // TODO: Uhh entityset re-usage. + foreach (var ent in _lookup.GetEntitiesIntersecting(_gridUid, new Box2(neighbor * grid.TileSize, (neighbor + 1) * grid.TileSize).Enlarged(-0.1f), flags)) + { + if (!_physicsQuery.TryGetComponent(ent, out var physics) || + !physics.Hard || + (DungeonSystem.CollisionMask & physics.CollisionLayer) == 0x0 && + (DungeonSystem.CollisionLayer & physics.CollisionMask) == 0x0) + { + continue; + } + + _entManager.DeleteEntity(ent); + } + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs new file mode 100644 index 000000000000..aaea23ddd566 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenAutoCabling.cs @@ -0,0 +1,162 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Server.NodeContainer; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(AutoCablingDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Entities.TryGetValue(DungeonDataKey.Cabling, out var ent)) + { + LogDataError(typeof(AutoCablingDunGen)); + return; + } + + // There's a lot of ways you could do this. + // For now we'll just connect every LV cable in the dungeon. + var cableTiles = new HashSet(); + var allTiles = new HashSet(dungeon.CorridorTiles); + allTiles.UnionWith(dungeon.RoomTiles); + allTiles.UnionWith(dungeon.RoomExteriorTiles); + allTiles.UnionWith(dungeon.CorridorExteriorTiles); + var nodeQuery = _entManager.GetEntityQuery(); + + // Gather existing nodes + foreach (var tile in allTiles) + { + var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); + + while (anchored.MoveNext(out var anc)) + { + if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || + !nodeContainer.Nodes.ContainsKey("power")) + { + continue; + } + + cableTiles.Add(tile); + break; + } + } + + // Iterating them all might be expensive. + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + var startNodes = new List(cableTiles); + random.Shuffle(startNodes); + var start = startNodes[0]; + var remaining = new HashSet(startNodes); + var frontier = new PriorityQueue(); + frontier.Enqueue(start, 0f); + var cameFrom = new Dictionary(); + var costSoFar = new Dictionary(); + var lastDirection = new Dictionary(); + costSoFar[start] = 0f; + lastDirection[start] = Direction.Invalid; + + while (remaining.Count > 0) + { + if (frontier.Count == 0) + { + var newStart = remaining.First(); + frontier.Enqueue(newStart, 0f); + lastDirection[newStart] = Direction.Invalid; + } + + var node = frontier.Dequeue(); + + if (remaining.Remove(node)) + { + var weh = node; + + while (cameFrom.TryGetValue(weh, out var receiver)) + { + cableTiles.Add(weh); + weh = receiver; + + if (weh == start) + break; + } + } + + if (!_maps.TryGetTileRef(_gridUid, _grid, node, out var tileRef) || tileRef.Tile.IsEmpty) + { + continue; + } + + for (var i = 0; i < 4; i++) + { + var dir = (Direction) (i * 2); + + var neighbor = node + dir.ToIntVec(); + var tileCost = 1f; + + // Prefer straight lines. + if (lastDirection[node] != dir) + { + tileCost *= 1.1f; + } + + if (cableTiles.Contains(neighbor)) + { + tileCost *= 0.1f; + } + + // Prefer tiles without walls on them + if (HasWall(neighbor)) + { + tileCost *= 20f; + } + + var gScore = costSoFar[node] + tileCost; + + if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue) + { + continue; + } + + cameFrom[neighbor] = node; + costSoFar[neighbor] = gScore; + lastDirection[neighbor] = dir; + frontier.Enqueue(neighbor, gScore); + } + } + + foreach (var tile in cableTiles) + { + if (reservedTiles.Contains(tile)) + continue; + + var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile); + var found = false; + + while (anchored.MoveNext(out var anc)) + { + if (!nodeQuery.TryGetComponent(anc, out var nodeContainer) || + !nodeContainer.Nodes.ContainsKey("power")) + { + continue; + } + + found = true; + break; + } + + if (found) + continue; + + _entManager.SpawnEntity(ent, _maps.GridTileToLocal(_gridUid, _grid, tile)); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs new file mode 100644 index 000000000000..65f6d2d14f95 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiome.cs @@ -0,0 +1,67 @@ +using System.Threading.Tasks; +using Content.Server.Parallax; +using Content.Shared.Parallax.Biomes; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(BiomeDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (_entManager.TryGetComponent(_gridUid, out BiomeComponent? biomeComp)) + return; + + biomeComp = _entManager.AddComponent(_gridUid); + var biomeSystem = _entManager.System(); + biomeSystem.SetTemplate(_gridUid, biomeComp, _prototype.Index(dunGen.BiomeTemplate)); + var seed = random.Next(); + var xformQuery = _entManager.GetEntityQuery(); + + foreach (var node in dungeon.RoomTiles) + { + if (reservedTiles.Contains(node)) + continue; + + // Need to set per-tile to override data. + if (biomeSystem.TryGetTile(node, biomeComp.Layers, seed, _grid, out var tile)) + { + _maps.SetTile(_gridUid, _grid, node, tile.Value); + } + + if (biomeSystem.TryGetDecals(node, biomeComp.Layers, seed, _grid, out var decals)) + { + foreach (var decal in decals) + { + _decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _); + } + } + + if (biomeSystem.TryGetEntity(node, biomeComp, _grid, out var entityProto)) + { + var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector)); + var xform = xformQuery.Get(ent); + + if (!xform.Comp.Anchored) + { + _transform.AnchorEntity(ent, xform); + } + + // TODO: Engine bug with SpawnAtPosition + DebugTools.Assert(xform.Comp.Anchored); + } + + await SuspendDungeon(); + if (!ValidateResume()) + return; + } + + biomeComp.Enabled = false; + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs new file mode 100644 index 000000000000..fb0eaa01573e --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBiomeMarkerLayer.cs @@ -0,0 +1,105 @@ +using System.Threading.Tasks; +using Content.Server.Parallax; +using Content.Shared.Parallax.Biomes; +using Content.Shared.Parallax.Biomes.Markers; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Random.Helpers; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(BiomeMarkerLayerDunGen dunGen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + // If we're adding biome then disable it and just use for markers. + if (_entManager.EnsureComponent(_gridUid, out BiomeComponent biomeComp)) + { + biomeComp.Enabled = false; + } + + var biomeSystem = _entManager.System(); + var weightedRandom = _prototype.Index(dunGen.MarkerTemplate); + var xformQuery = _entManager.GetEntityQuery(); + var templates = new Dictionary(); + + for (var i = 0; i < dunGen.Count; i++) + { + var template = weightedRandom.Pick(random); + var count = templates.GetOrNew(template); + count++; + templates[template] = count; + } + + foreach (var (template, count) in templates) + { + var markerTemplate = _prototype.Index(template); + + var bounds = new Box2i(); + + foreach (var tile in dungeon.RoomTiles) + { + bounds = bounds.UnionTile(tile); + } + + await SuspendDungeon(); + if (!ValidateResume()) + return; + + biomeSystem.GetMarkerNodes(_gridUid, biomeComp, _grid, markerTemplate, true, bounds, count, + random, out var spawnSet, out var existing, false); + + await SuspendDungeon(); + if (!ValidateResume()) + return; + + var checkTile = reservedTiles.Count > 0; + + foreach (var ent in existing) + { + if (checkTile && reservedTiles.Contains(_maps.LocalToTile(_gridUid, _grid, _xformQuery.GetComponent(ent).Coordinates))) + { + continue; + } + + _entManager.DeleteEntity(ent); + + await SuspendDungeon(); + if (!ValidateResume()) + return; + } + + foreach (var (node, mask) in spawnSet) + { + if (reservedTiles.Contains(node)) + continue; + + string? proto; + + if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto)) + { + proto = maskedProto; + } + else + { + proto = markerTemplate.Prototype; + } + + var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector)); + var xform = xformQuery.Get(ent); + + if (!xform.Comp.Anchored) + _transform.AnchorEntity(ent, xform); + + await SuspendDungeon(); + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs new file mode 100644 index 000000000000..84697a56bc7c --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenBoundaryWall.cs @@ -0,0 +1,113 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(BoundaryWallDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var protoTileDef) || + !data.Entities.TryGetValue(DungeonDataKey.Walls, out var wall)) + { + _sawmill.Error($"Error finding dungeon data for {nameof(gen)}"); + return; + } + + var tileDef = _tileDefManager[protoTileDef]; + var tiles = new List<(Vector2i Index, Tile Tile)>(dungeon.RoomExteriorTiles.Count); + + if (!data.Entities.TryGetValue(DungeonDataKey.CornerWalls, out var cornerWall)) + { + cornerWall = wall; + } + + if (cornerWall == default) + { + cornerWall = wall; + } + + // Spawn wall outline + // - Tiles first + foreach (var neighbor in dungeon.RoomExteriorTiles) + { + DebugTools.Assert(!dungeon.RoomTiles.Contains(neighbor)); + + if (dungeon.Entrances.Contains(neighbor)) + continue; + + if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + } + + foreach (var index in dungeon.CorridorExteriorTiles) + { + if (dungeon.RoomTiles.Contains(index)) + continue; + + if (!_anchorable.TileFree(_grid, index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random))); + } + + _maps.SetTiles(_gridUid, _grid, tiles); + + // Double iteration coz we bulk set tiles for speed. + for (var i = 0; i < tiles.Count; i++) + { + var index = tiles[i]; + + if (!_anchorable.TileFree(_grid, index.Index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + // If no cardinal neighbors in dungeon then we're a corner. + var isCorner = true; + + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x != 0 && y != 0) + { + continue; + } + + var neighbor = new Vector2i(index.Index.X + x, index.Index.Y + y); + + if (dungeon.RoomTiles.Contains(neighbor) || dungeon.CorridorTiles.Contains(neighbor)) + { + isCorner = false; + break; + } + } + + if (!isCorner) + break; + } + + if (isCorner) + _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); + + if (!isCorner) + _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); + + if (i % 20 == 0) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs new file mode 100644 index 000000000000..f7858298504b --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCornerClutter.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(CornerClutterDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.SpawnGroups.TryGetValue(DungeonDataKey.CornerClutter, out var corner)) + { + _sawmill.Error(Environment.StackTrace); + return; + } + + foreach (var tile in dungeon.CorridorTiles) + { + var blocked = _anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask); + + if (blocked) + continue; + + // If at least 2 adjacent tiles are blocked consider it a corner + for (var i = 0; i < 4; i++) + { + var dir = (Direction) (i * 2); + blocked = HasWall(tile + dir.ToIntVec()); + + if (!blocked) + continue; + + var nextDir = (Direction) ((i + 1) * 2 % 8); + blocked = HasWall(tile + nextDir.ToIntVec()); + + if (!blocked) + continue; + + if (random.Prob(gen.Chance)) + { + var coords = _maps.GridTileToLocal(_gridUid, _grid, tile); + var protos = EntitySpawnCollection.GetSpawns(_prototype.Index(corner).Entries, random); + _entManager.SpawnEntities(coords, protos); + } + + break; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs new file mode 100644 index 000000000000..8ea79ffe54fd --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridor.cs @@ -0,0 +1,116 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(CorridorDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto)) + { + LogDataError(typeof(CorridorDunGen)); + return; + } + + var entrances = new List(dungeon.Rooms.Count); + + // Grab entrances + foreach (var room in dungeon.Rooms) + { + entrances.AddRange(room.Entrances); + } + + var edges = _dungeon.MinimumSpanningTree(entrances, random); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + // TODO: Add in say 1/3 of edges back in to add some cyclic to it. + + var expansion = gen.Width - 2; + // Okay so tl;dr is that we don't want to cut close to rooms as it might go from 3 width to 2 width suddenly + // So we will add a buffer range around each room to deter pathfinding there unless necessary + var deterredTiles = new HashSet(); + + if (expansion >= 1) + { + foreach (var tile in dungeon.RoomExteriorTiles) + { + for (var x = -expansion; x <= expansion; x++) + { + for (var y = -expansion; y <= expansion; y++) + { + var neighbor = new Vector2(tile.X + x, tile.Y + y).Floored(); + + if (dungeon.RoomTiles.Contains(neighbor) || + dungeon.RoomExteriorTiles.Contains(neighbor) || + entrances.Contains(neighbor)) + { + continue; + } + + deterredTiles.Add(neighbor); + } + } + } + } + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + // Just so we can still actually get in to the entrance we won't deter from a tile away from it. + var normal = (entrance + _grid.TileSizeHalfVector - room.Center).ToWorldAngle().GetCardinalDir().ToIntVec(); + deterredTiles.Remove(entrance + normal); + } + } + + var excludedTiles = new HashSet(dungeon.RoomExteriorTiles); + excludedTiles.UnionWith(dungeon.RoomTiles); + var corridorTiles = new HashSet(); + + _dungeon.GetCorridorNodes(corridorTiles, edges, gen.PathLimit, excludedTiles, tile => + { + var mod = 1f; + + if (corridorTiles.Contains(tile)) + { + mod *= 0.1f; + } + + if (deterredTiles.Contains(tile)) + { + mod *= 2f; + } + + return mod; + }); + + WidenCorridor(dungeon, gen.Width, corridorTiles); + + var setTiles = new List<(Vector2i, Tile)>(); + var tileDef = (ContentTileDefinition) _tileDefManager[tileProto]; + + foreach (var tile in corridorTiles) + { + if (reservedTiles.Contains(tile)) + continue; + + setTiles.Add((tile, _tile.GetVariantTile(tileDef, random))); + } + + _maps.SetTiles(_gridUid, _grid, setTiles); + dungeon.CorridorTiles.UnionWith(corridorTiles); + dungeon.RefreshAllTiles(); + BuildCorridorExterior(dungeon); + } +} diff --git a/Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorClutter.cs similarity index 83% rename from Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorClutter.cs index 8099157cc508..cb7c4b210c86 100644 --- a/Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorClutter.cs @@ -2,16 +2,17 @@ using Content.Shared.Procedural; using Content.Shared.Procedural.PostGeneration; using Content.Shared.Storage; -using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Random; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { - private async Task PostGen(CorridorClutterPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, - Random random) + /// + /// + /// + private async Task PostGen(CorridorClutterDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) { var physicsQuery = _entManager.GetEntityQuery(); var count = (int) Math.Ceiling(dungeon.CorridorTiles.Count * gen.Chance); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs new file mode 100644 index 000000000000..3b516c3fa8a6 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenCorridorDecalSkirting.cs @@ -0,0 +1,124 @@ +using System.Threading.Tasks; +using Content.Shared.Doors.Components; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Collections; +using Robust.Shared.Physics.Components; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(CorridorDecalSkirtingDunGen decks, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Colors.TryGetValue(DungeonDataKey.Decals, out var color)) + { + _sawmill.Error(Environment.StackTrace); + } + + var directions = new ValueList(4); + var pocketDirections = new ValueList(4); + var doorQuery = _entManager.GetEntityQuery(); + var physicsQuery = _entManager.GetEntityQuery(); + var offset = -_grid.TileSizeHalfVector; + + foreach (var tile in dungeon.CorridorTiles) + { + DebugTools.Assert(!dungeon.RoomTiles.Contains(tile)); + directions.Clear(); + + // Do cardinals 1 step + // Do corners the other step + for (var i = 0; i < 4; i++) + { + var dir = (DirectionFlag) Math.Pow(2, i); + var neighbor = tile + dir.AsDir().ToIntVec(); + + var anc = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, neighbor); + + while (anc.MoveNext(out var ent)) + { + if (!physicsQuery.TryGetComponent(ent, out var physics) || + !physics.CanCollide || + !physics.Hard || + doorQuery.HasComponent(ent.Value)) + { + continue; + } + + directions.Add(dir); + break; + } + } + + // Pockets + if (directions.Count == 0) + { + pocketDirections.Clear(); + + for (var i = 1; i < 5; i++) + { + var dir = (Direction) (i * 2 - 1); + var neighbor = tile + dir.ToIntVec(); + + var anc = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, neighbor); + + while (anc.MoveNext(out var ent)) + { + if (!physicsQuery.TryGetComponent(ent, out var physics) || + !physics.CanCollide || + !physics.Hard || + doorQuery.HasComponent(ent.Value)) + { + continue; + } + + pocketDirections.Add(dir); + break; + } + } + + if (pocketDirections.Count == 1) + { + if (decks.PocketDecals.TryGetValue(pocketDirections[0], out var cDir)) + { + // Decals not being centered biting my ass again + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); + _decals.TryAddDecal(cDir, gridPos, out _, color: color); + } + } + + continue; + } + + if (directions.Count == 1) + { + if (decks.CardinalDecals.TryGetValue(directions[0], out var cDir)) + { + // Decals not being centered biting my ass again + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); + _decals.TryAddDecal(cDir, gridPos, out _, color: color); + } + + continue; + } + + // Corners + if (directions.Count == 2) + { + // Auehghegueugegegeheh help me + var dirFlag = directions[0] | directions[1]; + + if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir)) + { + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); + _decals.TryAddDecal(cDir, gridPos, out _, color: color); + } + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs new file mode 100644 index 000000000000..917b1ffc9caf --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonConnector.cs @@ -0,0 +1,6 @@ +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs new file mode 100644 index 000000000000..abc52f07c6c9 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenDungeonEntrance.cs @@ -0,0 +1,114 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(DungeonEntranceDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Entrance, out var entrance)) + { + LogDataError(typeof(DungeonEntranceDunGen)); + return; + } + + var rooms = new List(dungeon.Rooms); + var roomTiles = new List(); + var tileDef = (ContentTileDefinition) _tileDefManager[tileProto]; + + for (var i = 0; i < gen.Count; i++) + { + var roomIndex = random.Next(rooms.Count); + var room = rooms[roomIndex]; + + // Move out 3 tiles in a direction away from center of the room + // If none of those intersect another tile it's probably external + // TODO: Maybe need to take top half of furthest rooms in case there's interior exits? + roomTiles.AddRange(room.Exterior); + random.Shuffle(roomTiles); + + foreach (var tile in roomTiles) + { + var isValid = false; + + // Check if one side is dungeon and the other side is nothing. + for (var j = 0; j < 4; j++) + { + var dir = (Direction) (j * 2); + var oppositeDir = dir.GetOpposite(); + var dirVec = tile + dir.ToIntVec(); + var oppositeDirVec = tile + oppositeDir.ToIntVec(); + + if (!dungeon.RoomTiles.Contains(dirVec)) + { + continue; + } + + if (dungeon.RoomTiles.Contains(oppositeDirVec) || + dungeon.RoomExteriorTiles.Contains(oppositeDirVec) || + dungeon.CorridorExteriorTiles.Contains(oppositeDirVec) || + dungeon.CorridorTiles.Contains(oppositeDirVec)) + { + continue; + } + + // Check if exterior spot free. + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + continue; + } + + // Check if interior spot free (no guarantees on exterior but ClearDoor should handle it) + if (!_anchorable.TileFree(_grid, dirVec, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + continue; + } + + // Valid pick! + isValid = true; + + // Entrance wew + _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random)); + ClearDoor(dungeon, _grid, tile); + var gridCoords = _maps.GridTileToLocal(_gridUid, _grid, tile); + // Need to offset the spawn to avoid spawning in the room. + + foreach (var ent in EntitySpawnCollection.GetSpawns(_prototype.Index(entrance).Entries, random)) + { + _entManager.SpawnAtPosition(ent, gridCoords); + } + + // Clear out any biome tiles nearby to avoid blocking it + foreach (var nearTile in _maps.GetLocalTilesIntersecting(_gridUid, _grid, new Circle(gridCoords.Position, 1.5f), false)) + { + if (dungeon.RoomTiles.Contains(nearTile.GridIndices) || + dungeon.RoomExteriorTiles.Contains(nearTile.GridIndices) || + dungeon.CorridorTiles.Contains(nearTile.GridIndices) || + dungeon.CorridorExteriorTiles.Contains(nearTile.GridIndices)) + { + continue; + } + + _maps.SetTile(_gridUid, _grid, nearTile.GridIndices, _tile.GetVariantTile(tileDef, random)); + } + + break; + } + + if (isValid) + break; + } + + roomTiles.Clear(); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs new file mode 100644 index 000000000000..3a1c7a377938 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenEntranceFlank.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Collections; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(EntranceFlankDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.EntranceFlank, out var flankProto)) + { + _sawmill.Error($"Unable to get dungeon data for {nameof(gen)}"); + return; + } + + var tiles = new List<(Vector2i Index, Tile)>(); + var tileDef = _tileDefManager[tileProto]; + var spawnPositions = new ValueList(dungeon.Rooms.Count); + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + for (var i = 0; i < 8; i++) + { + var dir = (Direction) i; + var neighbor = entrance + dir.ToIntVec(); + + if (!dungeon.RoomExteriorTiles.Contains(neighbor)) + continue; + + if (reservedTiles.Contains(neighbor)) + continue; + + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + spawnPositions.Add(neighbor); + } + } + } + + _maps.SetTiles(_gridUid, _grid, tiles); + var entGroup = _prototype.Index(flankProto); + + foreach (var entrance in spawnPositions) + { + _entManager.SpawnEntities(_maps.GridTileToLocal(_gridUid, _grid, entrance), EntitySpawnCollection.GetSpawns(entGroup.Entries, random)); + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs new file mode 100644 index 000000000000..9a1b44ec91be --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenExternalWindow.cs @@ -0,0 +1,138 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + // (Comment refers to internal & external). + + /* + * You may be wondering why these are different. + * It's because for internals we want to force it as it looks nicer and not leave it up to chance. + */ + + // TODO: Can probably combine these a bit, their differences are in really annoying to pull out spots. + + /// + /// + /// + private async Task PostGen(ExternalWindowDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Window, out var windowGroup)) + { + _sawmill.Error($"Unable to get dungeon data for {nameof(gen)}"); + return; + } + + // Iterate every tile with N chance to spawn windows on that wall per cardinal dir. + var chance = 0.25 / 3f; + + var allExterior = new HashSet(dungeon.CorridorExteriorTiles); + allExterior.UnionWith(dungeon.RoomExteriorTiles); + var validTiles = allExterior.ToList(); + random.Shuffle(validTiles); + + var tiles = new List<(Vector2i, Tile)>(); + var tileDef = _tileDefManager[tileProto]; + var count = Math.Floor(validTiles.Count * chance); + var index = 0; + var takenTiles = new HashSet(); + + // There's a bunch of shit here but tl;dr + // - don't spawn over cap + // - Check if we have 3 tiles in a row that aren't corners and aren't obstructed + foreach (var tile in validTiles) + { + if (index > count) + break; + + // Room tile / already used. + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask) || + takenTiles.Contains(tile)) + { + continue; + } + + // Check we're not on a corner + for (var i = 0; i < 2; i++) + { + var dir = (Direction) (i * 2); + var dirVec = dir.ToIntVec(); + var isValid = true; + + // Check 1 beyond either side to ensure it's not a corner. + for (var j = -1; j < 4; j++) + { + var neighbor = tile + dirVec * j; + + if (!allExterior.Contains(neighbor) || + takenTiles.Contains(neighbor) || + !_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + + // Also check perpendicular that it is free + foreach (var k in new [] {2, 6}) + { + var perp = (Direction) ((i * 2 + k) % 8); + var perpVec = perp.ToIntVec(); + var perpTile = tile + perpVec; + + if (allExterior.Contains(perpTile) || + takenTiles.Contains(neighbor) || + !_anchorable.TileFree(_grid, perpTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + } + + if (!isValid) + break; + } + + if (!isValid) + continue; + + for (var j = 0; j < 3; j++) + { + var neighbor = tile + dirVec * j; + + if (reservedTiles.Contains(neighbor)) + continue; + + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + index++; + takenTiles.Add(neighbor); + } + } + } + + _maps.SetTiles(_gridUid, _grid, tiles); + index = 0; + var spawnEntry = _prototype.Index(windowGroup); + + foreach (var tile in tiles) + { + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1); + + index += spawnEntry.Entries.Count; + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(spawnEntry.Entries, random)); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs new file mode 100644 index 000000000000..d3b8c6d2f5dd --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenInternalWindow.cs @@ -0,0 +1,108 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(InternalWindowDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Window, out var windowGroup)) + { + _sawmill.Error($"Unable to find dungeon data keys for {nameof(gen)}"); + return; + } + + // Iterate every room and check if there's a gap beyond it that leads to another room within N tiles + // If so then consider windows + var minDistance = 4; + var maxDistance = 6; + var tileDef = _tileDefManager[tileProto]; + var window = _prototype.Index(windowGroup); + + foreach (var room in dungeon.Rooms) + { + var validTiles = new List(); + + for (var i = 0; i < 4; i++) + { + var dir = (DirectionFlag) Math.Pow(2, i); + var dirVec = dir.AsDir().ToIntVec(); + + foreach (var tile in room.Tiles) + { + var tileAngle = (tile + _grid.TileSizeHalfVector - room.Center).ToAngle(); + var roundedAngle = Math.Round(tileAngle.Theta / (Math.PI / 2)) * (Math.PI / 2); + + var tileVec = (Vector2i) new Angle(roundedAngle).ToVec().Rounded(); + + if (!tileVec.Equals(dirVec)) + continue; + + var valid = false; + + for (var j = 1; j < maxDistance; j++) + { + var edgeNeighbor = tile + dirVec * j; + + if (dungeon.RoomTiles.Contains(edgeNeighbor)) + { + if (j < minDistance) + { + valid = false; + } + else + { + valid = true; + } + + break; + } + } + + if (!valid) + continue; + + var windowTile = tile + dirVec; + + if (reservedTiles.Contains(windowTile)) + continue; + + if (!_anchorable.TileFree(_grid, windowTile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + validTiles.Add(windowTile); + } + + validTiles.Sort((x, y) => (x + _grid.TileSizeHalfVector - room.Center).LengthSquared().CompareTo((y + _grid.TileSizeHalfVector - room.Center).LengthSquared())); + + for (var j = 0; j < Math.Min(validTiles.Count, 3); j++) + { + var tile = validTiles[j]; + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile); + _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); + + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(window.Entries, random)); + } + + if (validTiles.Count > 0) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + + validTiles.Clear(); + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs new file mode 100644 index 000000000000..700406eb894d --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenJunction.cs @@ -0,0 +1,144 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Map.Components; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(JunctionDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Junction, out var junctionProto)) + { + _sawmill.Error($"Dungeon data keys are missing for {nameof(gen)}"); + return; + } + + var tileDef = _tileDefManager[tileProto]; + var entranceGroup = _prototype.Index(junctionProto); + + // N-wide junctions + foreach (var tile in dungeon.CorridorTiles) + { + if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + // Check each direction: + // - Check if immediate neighbors are free + // - Check if the neighbors beyond that are not free + // - Then check either side if they're slightly more free + var exteriorWidth = (int) Math.Floor(gen.Width / 2f); + var width = (int) Math.Ceiling(gen.Width / 2f); + + for (var i = 0; i < 2; i++) + { + var isValid = true; + var neighborDir = (Direction) (i * 2); + var neighborVec = neighborDir.ToIntVec(); + + for (var j = -width; j <= width; j++) + { + if (j == 0) + continue; + + var neighbor = tile + neighborVec * j; + + // If it's an end tile then check it's occupied. + if (j == -width || + j == width) + { + if (!HasWall(neighbor)) + { + isValid = false; + break; + } + + continue; + } + + // If we're not at the end tile then check it + perpendicular are free. + if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + + var perp1 = tile + neighborVec * j + ((Direction) ((i * 2 + 2) % 8)).ToIntVec(); + var perp2 = tile + neighborVec * j + ((Direction) ((i * 2 + 6) % 8)).ToIntVec(); + + if (!_anchorable.TileFree(_grid, perp1, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + + if (!_anchorable.TileFree(_grid, perp2, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + isValid = false; + break; + } + } + + if (!isValid) + continue; + + // Check corners to see if either side opens up (if it's just a 1x wide corridor do nothing, needs to be a funnel. + foreach (var j in new [] {-exteriorWidth, exteriorWidth}) + { + var freeCount = 0; + + // Need at least 3 of 4 free + for (var k = 0; k < 4; k++) + { + var cornerDir = (Direction) (k * 2 + 1); + var cornerVec = cornerDir.ToIntVec(); + var cornerNeighbor = tile + neighborVec * j + cornerVec; + + if (_anchorable.TileFree(_grid, cornerNeighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + { + freeCount++; + } + } + + if (freeCount < gen.Width) + continue; + + // Valid! + isValid = true; + + for (var x = -width + 1; x < width; x++) + { + var weh = tile + neighborDir.ToIntVec() * x; + + if (reservedTiles.Contains(weh)) + continue; + + _maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); + + var coords = _maps.GridTileToLocal(_gridUid, _grid, weh); + _entManager.SpawnEntities(coords, EntitySpawnCollection.GetSpawns(entranceGroup.Entries, random)); + } + + break; + } + + if (isValid) + { + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + + break; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs new file mode 100644 index 000000000000..15d0f6342327 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenMiddleConnection.cs @@ -0,0 +1,147 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Utility; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(MiddleConnectionDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Entrance, out var entranceProto) || + !_prototype.TryIndex(entranceProto, out var entrance)) + { + _sawmill.Error($"Tried to run {nameof(MiddleConnectionDunGen)} without any dungeon data set which is unsupported"); + return; + } + + data.SpawnGroups.TryGetValue(DungeonDataKey.EntranceFlank, out var flankProto); + _prototype.TryIndex(flankProto, out var flank); + + // Grab all of the room bounds + // Then, work out connections between them + var roomBorders = new Dictionary>(dungeon.Rooms.Count); + + foreach (var room in dungeon.Rooms) + { + var roomEdges = new HashSet(); + + foreach (var index in room.Tiles) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + // Cardinals only + if (x != 0 && y != 0 || + x == 0 && y == 0) + { + continue; + } + + var neighbor = new Vector2i(index.X + x, index.Y + y); + + if (dungeon.RoomTiles.Contains(neighbor)) + continue; + + if (!_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + roomEdges.Add(neighbor); + } + } + } + + roomBorders.Add(room, roomEdges); + } + + // Do pathfind from first room to work out graph. + // TODO: Optional loops + + var roomConnections = new Dictionary>(); + var tileDef = _tileDefManager[tileProto]; + + foreach (var (room, border) in roomBorders) + { + var conns = roomConnections.GetOrNew(room); + + foreach (var (otherRoom, otherBorders) in roomBorders) + { + if (room.Equals(otherRoom) || + conns.Contains(otherRoom)) + { + continue; + } + + var flipp = new HashSet(border); + flipp.IntersectWith(otherBorders); + + if (flipp.Count == 0 || + gen.OverlapCount != -1 && flipp.Count != gen.OverlapCount) + continue; + + var center = Vector2.Zero; + + foreach (var node in flipp) + { + center += node + _grid.TileSizeHalfVector; + } + + center /= flipp.Count; + // Weight airlocks towards center more. + var nodeDistances = new List<(Vector2i Node, float Distance)>(flipp.Count); + + foreach (var node in flipp) + { + nodeDistances.Add((node, (node + _grid.TileSizeHalfVector - center).LengthSquared())); + } + + nodeDistances.Sort((x, y) => x.Distance.CompareTo(y.Distance)); + + var width = gen.Count; + + for (var i = 0; i < nodeDistances.Count; i++) + { + var node = nodeDistances[i].Node; + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, node); + if (!_anchorable.TileFree(_grid, node, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + width--; + _maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); + + if (flank != null && nodeDistances.Count - i <= 2) + { + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(flank.Entries, random)); + } + else + { + // Iterate neighbors and check for blockers, if so bulldoze + ClearDoor(dungeon, _grid, node); + + _entManager.SpawnEntities(gridPos, EntitySpawnCollection.GetSpawns(entrance.Entries, random)); + } + + if (width == 0) + break; + } + + conns.Add(otherRoom); + var otherConns = roomConnections.GetOrNew(otherRoom); + otherConns.Add(room); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs new file mode 100644 index 000000000000..09d223e86cf5 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenRoomEntrance.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Map; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(RoomEntranceDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || + !data.SpawnGroups.TryGetValue(DungeonDataKey.Entrance, out var entranceProtos) || + !_prototype.TryIndex(entranceProtos, out var entranceIn)) + { + LogDataError(typeof(RoomEntranceDunGen)); + return; + } + + var setTiles = new List<(Vector2i, Tile)>(); + var tileDef = _tileDefManager[tileProto]; + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); + } + } + + _maps.SetTiles(_gridUid, _grid, setTiles); + + foreach (var room in dungeon.Rooms) + { + foreach (var entrance in room.Entrances) + { + _entManager.SpawnEntities( + _maps.GridTileToLocal(_gridUid, _grid, entrance), + EntitySpawnCollection.GetSpawns(entranceIn.Entries, random)); + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs new file mode 100644 index 000000000000..8fe2f36665f9 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenSplineDungeonConnector.cs @@ -0,0 +1,147 @@ +using System.Numerics; +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen( + SplineDungeonConnectorDunGen gen, + DungeonData data, + List dungeons, + HashSet reservedTiles, + Random random) + { + // TODO: The path itself use the tile + // Widen it randomly (probably for each tile offset it by some changing amount). + + // NOOP + if (dungeons.Count <= 1) + return Dungeon.Empty; + + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var fallback) || + !data.Tiles.TryGetValue(DungeonDataKey.WidenTile, out var widen)) + { + LogDataError(typeof(SplineDungeonConnectorDunGen)); + return Dungeon.Empty; + } + + var nodes = new List(); + + foreach (var dungeon in dungeons) + { + foreach (var room in dungeon.Rooms) + { + if (room.Entrances.Count == 0) + continue; + + nodes.Add(room.Entrances[0]); + break; + } + } + + var tree = _dungeon.MinimumSpanningTree(nodes, random); + await SuspendDungeon(); + + if (!ValidateResume()) + return Dungeon.Empty; + + var tiles = new List<(Vector2i Index, Tile Tile)>(); + var pathfinding = _entManager.System(); + var allTiles = new HashSet(); + var fallbackTile = new Tile(_prototype.Index(fallback).TileId); + + foreach (var pair in tree) + { + var path = pathfinding.GetSplinePath(new PathfindingSystem.SplinePathArgs() + { + Distance = gen.DivisionDistance, + MaxRatio = gen.VarianceMax, + Args = new PathfindingSystem.SimplePathArgs() + { + Start = pair.Start, + End = pair.End, + TileCost = node => + { + // We want these to get prioritised internally and into space if it's a space dungeon. + if (_maps.TryGetTile(_grid, node, out var tile) && !tile.IsEmpty) + return 1f; + + return 5f; + } + }, + }, + random); + + // Welp + if (path.Path.Count == 0) + { + _sawmill.Error($"Unable to connect spline dungeon path for {_entManager.ToPrettyString(_gridUid)} between {pair.Start} and {pair.End}"); + continue; + } + + await SuspendDungeon(); + + if (!ValidateResume()) + return Dungeon.Empty; + + var wide = pathfinding.GetWiden(new PathfindingSystem.WidenArgs() + { + Path = path.Path, + }, + random); + + tiles.Clear(); + allTiles.EnsureCapacity(allTiles.Count + wide.Count); + + foreach (var node in wide) + { + if (reservedTiles.Contains(node)) + continue; + + allTiles.Add(node); + Tile tile; + + if (random.Prob(0.9f)) + { + tile = new Tile(_prototype.Index(widen).TileId); + } + else + { + tile = _tileDefManager.GetVariantTile(widen, random); + } + + tiles.Add((node, tile)); + } + + _maps.SetTiles(_gridUid, _grid, tiles); + tiles.Clear(); + allTiles.EnsureCapacity(allTiles.Count + path.Path.Count); + + foreach (var node in path.Path) + { + if (reservedTiles.Contains(node)) + continue; + + allTiles.Add(node); + tiles.Add((node, fallbackTile)); + } + + _maps.SetTiles(_gridUid, _grid, tiles); + } + + var dungy = new Dungeon(); + var dungyRoom = new DungeonRoom(allTiles, Vector2.Zero, Box2i.Empty, new HashSet()); + dungy.AddRoom(dungyRoom); + + return dungy; + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs new file mode 100644 index 000000000000..afc7608d64af --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWallMount.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Content.Shared.Procedural; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Storage; +using Robust.Shared.Random; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob +{ + /// + /// + /// + private async Task PostGen(WallMountDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto)) + { + _sawmill.Error($"Tried to run {nameof(WallMountDunGen)} without any dungeon data set which is unsupported"); + return; + } + + var tileDef = _prototype.Index(tileProto); + data.SpawnGroups.TryGetValue(DungeonDataKey.WallMounts, out var spawnProto); + + var checkedTiles = new HashSet(); + var allExterior = new HashSet(dungeon.CorridorExteriorTiles); + allExterior.UnionWith(dungeon.RoomExteriorTiles); + var count = 0; + + foreach (var neighbor in allExterior) + { + // Occupado + if (dungeon.RoomTiles.Contains(neighbor) || checkedTiles.Contains(neighbor) || !_anchorable.TileFree(_grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) + continue; + + if (!random.Prob(gen.Prob) || !checkedTiles.Add(neighbor)) + continue; + + _maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random)); + var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor); + var protoNames = EntitySpawnCollection.GetSpawns(_prototype.Index(spawnProto).Entries, random); + + _entManager.SpawnEntities(gridPos, protoNames); + count += protoNames.Count; + + if (count > 20) + { + count -= 20; + await SuspendDungeon(); + + if (!ValidateResume()) + return; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob.WormPost.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWorm.cs similarity index 88% rename from Content.Server/Procedural/DungeonJob.WormPost.cs rename to Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWorm.cs index 5d2271cae659..6fd00e548248 100644 --- a/Content.Server/Procedural/DungeonJob.WormPost.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.PostGenWorm.cs @@ -1,23 +1,27 @@ using System.Linq; -using System.Numerics; using System.Threading.Tasks; using Content.Shared.Procedural; using Content.Shared.Procedural.PostGeneration; using Robust.Shared.Collections; using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Random; using Robust.Shared.Utility; -namespace Content.Server.Procedural; +namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { /// - /// Tries to connect rooms via worm-like corridors. + /// /// - private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random) + private async Task PostGen(WormCorridorDunGen gen, DungeonData data, Dungeon dungeon, HashSet reservedTiles, Random random) { + if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || !_prototype.TryIndex(tileProto, out var tileDef)) + { + _sawmill.Error($"Tried to run {nameof(WormCorridorDunGen)} without any dungeon data set which is unsupported"); + return; + } + var networks = new List<(Vector2i Start, HashSet Network)>(); // List of places to start from. @@ -32,7 +36,7 @@ private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid g networks.Add((entrance, network)); // Point away from the room to start with. - startAngles.Add(entrance, (entrance + grid.TileSizeHalfVector - room.Center).ToAngle()); + startAngles.Add(entrance, (entrance + _grid.TileSizeHalfVector - room.Center).ToAngle()); } } @@ -46,7 +50,7 @@ private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid g // Find a random network to worm from. var startIndex = (i % networks.Count); var startPos = networks[startIndex].Start; - var position = startPos + grid.TileSizeHalfVector; + var position = startPos + _grid.TileSizeHalfVector; var remainingLength = gen.Length; worm.Clear(); @@ -108,7 +112,7 @@ private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid g costSoFar[startNode] = 0f; var count = 0; - await SuspendIfOutOfTime(); + await SuspendDungeon(); if (!ValidateResume()) return; @@ -174,9 +178,9 @@ private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid g WidenCorridor(dungeon, gen.Width, main.Network); dungeon.CorridorTiles.UnionWith(main.Network); BuildCorridorExterior(dungeon); + dungeon.RefreshAllTiles(); var tiles = new List<(Vector2i Index, Tile Tile)>(); - var tileDef = _prototype.Index(gen.Tile); foreach (var tile in dungeon.CorridorTiles) { diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.cs new file mode 100644 index 000000000000..1468a80902cb --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.cs @@ -0,0 +1,309 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.Decals; +using Content.Server.NPC.Components; +using Content.Server.NPC.HTN; +using Content.Server.NPC.Systems; +using Content.Shared.Construction.EntitySystems; +using Content.Shared.Maps; +using Content.Shared.Procedural; +using Content.Shared.Procedural.DungeonGenerators; +using Content.Shared.Procedural.DungeonLayers; +using Content.Shared.Procedural.PostGeneration; +using Content.Shared.Tag; +using JetBrains.Annotations; +using Robust.Server.Physics; +using Robust.Shared.CPUJob.JobQueues; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; +using IDunGenLayer = Content.Shared.Procedural.IDunGenLayer; + +namespace Content.Server.Procedural.DungeonJob; + +public sealed partial class DungeonJob : Job> +{ + public bool TimeSlice = true; + + private readonly IEntityManager _entManager; + private readonly IPrototypeManager _prototype; + private readonly ITileDefinitionManager _tileDefManager; + + private readonly AnchorableSystem _anchorable; + private readonly DecalSystem _decals; + private readonly DungeonSystem _dungeon; + private readonly EntityLookupSystem _lookup; + private readonly TagSystem _tags; + private readonly TileSystem _tile; + private readonly SharedMapSystem _maps; + private readonly SharedTransformSystem _transform; + + private EntityQuery _physicsQuery; + private EntityQuery _xformQuery; + + private readonly DungeonConfigPrototype _gen; + private readonly int _seed; + private readonly Vector2i _position; + + private readonly EntityUid _gridUid; + private readonly MapGridComponent _grid; + + private readonly ISawmill _sawmill; + + public DungeonJob( + ISawmill sawmill, + double maxTime, + IEntityManager entManager, + IPrototypeManager prototype, + ITileDefinitionManager tileDefManager, + AnchorableSystem anchorable, + DecalSystem decals, + DungeonSystem dungeon, + EntityLookupSystem lookup, + TileSystem tile, + SharedTransformSystem transform, + DungeonConfigPrototype gen, + MapGridComponent grid, + EntityUid gridUid, + int seed, + Vector2i position, + CancellationToken cancellation = default) : base(maxTime, cancellation) + { + _sawmill = sawmill; + _entManager = entManager; + _prototype = prototype; + _tileDefManager = tileDefManager; + + _anchorable = anchorable; + _decals = decals; + _dungeon = dungeon; + _lookup = lookup; + _tile = tile; + _tags = _entManager.System(); + _maps = _entManager.System(); + _transform = transform; + + _physicsQuery = _entManager.GetEntityQuery(); + _xformQuery = _entManager.GetEntityQuery(); + + _gen = gen; + _grid = grid; + _gridUid = gridUid; + _seed = seed; + _position = position; + } + + /// + /// Gets the relevant dungeon, running recursively as relevant. + /// + /// Should we reserve tiles even if the config doesn't specify. + private async Task> GetDungeons( + Vector2i position, + DungeonConfigPrototype config, + DungeonData data, + List layers, + HashSet reservedTiles, + int seed, + Random random) + { + var dungeons = new List(); + var count = random.Next(config.MinCount, config.MaxCount + 1); + + for (var i = 0; i < count; i++) + { + position += random.NextPolarVector2(config.MinOffset, config.MaxOffset).Floored(); + + foreach (var layer in layers) + { + await RunLayer(dungeons, data, position, layer, reservedTiles, seed, random); + + if (config.ReserveTiles) + { + foreach (var dungeon in dungeons) + { + reservedTiles.UnionWith(dungeon.AllTiles); + } + } + + await SuspendDungeon(); + if (!ValidateResume()) + return new List(); + } + } + + return dungeons; + } + + protected override async Task?> Process() + { + _sawmill.Info($"Generating dungeon {_gen.ID} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}"); + _grid.CanSplit = false; + var random = new Random(_seed); + var position = (_position + random.NextPolarVector2(_gen.MinOffset, _gen.MaxOffset)).Floored(); + + // Tiles we can no longer generate on due to being reserved elsewhere. + var reservedTiles = new HashSet(); + + var dungeons = await GetDungeons(position, _gen, _gen.Data, _gen.Layers, reservedTiles, _seed, random); + // To make it slightly more deterministic treat this RNG as separate ig. + + // Post-processing after finishing loading. + + // Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way. + _grid.CanSplit = true; + _entManager.System().CheckSplits(_gridUid); + var npcSystem = _entManager.System(); + var npcs = new HashSet>(); + + _lookup.GetChildEntities(_gridUid, npcs); + + foreach (var npc in npcs) + { + npcSystem.WakeNPC(npc.Owner, npc.Comp); + } + + return dungeons; + } + + private async Task RunLayer( + List dungeons, + DungeonData data, + Vector2i position, + IDunGenLayer layer, + HashSet reservedTiles, + int seed, + Random random) + { + _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen.ID} with seed {_seed}"); + + // If there's a way to just call the methods directly for the love of god tell me. + // Some of these don't care about reservedtiles because they only operate on dungeon tiles (which should + // never be reserved) + + // Some may or may not return dungeons. + // It's clamplicated but yeah procgen layering moment I'll take constructive feedback. + + switch (layer) + { + case AutoCablingDunGen cabling: + await PostGen(cabling, data, dungeons[^1], reservedTiles, random); + break; + case BiomeMarkerLayerDunGen markerPost: + await PostGen(markerPost, data, dungeons[^1], reservedTiles, random); + break; + case BiomeDunGen biome: + await PostGen(biome, data, dungeons[^1], reservedTiles, random); + break; + case BoundaryWallDunGen boundary: + await PostGen(boundary, data, dungeons[^1], reservedTiles, random); + break; + case CornerClutterDunGen clutter: + await PostGen(clutter, data, dungeons[^1], reservedTiles, random); + break; + case CorridorClutterDunGen corClutter: + await PostGen(corClutter, data, dungeons[^1], reservedTiles, random); + break; + case CorridorDunGen cordor: + await PostGen(cordor, data, dungeons[^1], reservedTiles, random); + break; + case CorridorDecalSkirtingDunGen decks: + await PostGen(decks, data, dungeons[^1], reservedTiles, random); + break; + case EntranceFlankDunGen flank: + await PostGen(flank, data, dungeons[^1], reservedTiles, random); + break; + case ExteriorDunGen exterior: + dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random)); + break; + case FillGridDunGen fill: + dungeons.Add(await GenerateFillDunGen(data, reservedTiles)); + break; + case JunctionDunGen junc: + await PostGen(junc, data, dungeons[^1], reservedTiles, random); + break; + case MiddleConnectionDunGen dordor: + await PostGen(dordor, data, dungeons[^1], reservedTiles, random); + break; + case DungeonEntranceDunGen entrance: + await PostGen(entrance, data, dungeons[^1], reservedTiles, random); + break; + case ExternalWindowDunGen externalWindow: + await PostGen(externalWindow, data, dungeons[^1], reservedTiles, random); + break; + case InternalWindowDunGen internalWindow: + await PostGen(internalWindow, data, dungeons[^1], reservedTiles, random); + break; + case MobsDunGen mob: + await PostGen(mob, dungeons[^1], random); + break; + case NoiseDistanceDunGen distance: + dungeons.Add(await GenerateNoiseDistanceDunGen(position, distance, reservedTiles, seed, random)); + break; + case NoiseDunGen noise: + dungeons.Add(await GenerateNoiseDunGen(position, noise, reservedTiles, seed, random)); + break; + case OreDunGen ore: + await PostGen(ore, dungeons[^1], random); + break; + case PrefabDunGen prefab: + dungeons.Add(await GeneratePrefabDunGen(position, data, prefab, reservedTiles, random)); + break; + case PrototypeDunGen prototypo: + var groupConfig = _prototype.Index(prototypo.Proto); + position = (position + random.NextPolarVector2(groupConfig.MinOffset, groupConfig.MaxOffset)).Floored(); + + var dataCopy = groupConfig.Data.Clone(); + dataCopy.Apply(data); + + dungeons.AddRange(await GetDungeons(position, groupConfig, dataCopy, groupConfig.Layers, reservedTiles, seed, random)); + break; + case ReplaceTileDunGen replace: + dungeons.Add(await GenerateTileReplacementDunGen(replace, data, reservedTiles, random)); + break; + case RoomEntranceDunGen rEntrance: + await PostGen(rEntrance, data, dungeons[^1], reservedTiles, random); + break; + case SplineDungeonConnectorDunGen spline: + dungeons.Add(await PostGen(spline, data, dungeons, reservedTiles, random)); + break; + case WallMountDunGen wall: + await PostGen(wall, data, dungeons[^1], reservedTiles, random); + break; + case WormCorridorDunGen worm: + await PostGen(worm, data, dungeons[^1], reservedTiles, random); + break; + default: + throw new NotImplementedException(); + } + } + + private void LogDataError(Type type) + { + _sawmill.Error($"Unable to find dungeon data keys for {type}"); + } + + [Pure] + private bool ValidateResume() + { + if (_entManager.Deleted(_gridUid)) + { + return false; + } + + return true; + } + + /// + /// Wrapper around + /// + private async Task SuspendDungeon() + { + if (!TimeSlice) + return; + + await SuspendIfOutOfTime(); + } +} diff --git a/Content.Server/Procedural/DungeonSystem.Commands.cs b/Content.Server/Procedural/DungeonSystem.Commands.cs index d783eb60c63e..51a6a57bbebe 100644 --- a/Content.Server/Procedural/DungeonSystem.Commands.cs +++ b/Content.Server/Procedural/DungeonSystem.Commands.cs @@ -51,6 +51,8 @@ private async void GenerateDungeon(IConsoleShell shell, string argstr, string[] dungeonUid = EntityManager.CreateEntityUninitialized(null, new EntityCoordinates(dungeonUid, position)); dungeonGrid = EntityManager.AddComponent(dungeonUid); EntityManager.InitializeAndStartEntity(dungeonUid, mapId); + // If we created a grid (e.g. space dungen) then offset it so we don't double-apply positions + position = Vector2i.Zero; } int seed; diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index ddd4a4732f8e..8a1606c48891 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -64,6 +64,7 @@ public void SpawnRoom( Vector2i origin, DungeonRoomPrototype room, Random random, + HashSet? reservedTiles, bool clearExisting = false, bool rotation = false) { @@ -78,7 +79,7 @@ public void SpawnRoom( var roomTransform = Matrix3Helpers.CreateTransform((Vector2) room.Size / 2f, roomRotation); var finalTransform = Matrix3x2.Multiply(roomTransform, originTransform); - SpawnRoom(gridUid, grid, finalTransform, room, clearExisting); + SpawnRoom(gridUid, grid, finalTransform, room, reservedTiles, clearExisting); } public Angle GetRoomRotation(DungeonRoomPrototype room, Random random) @@ -103,6 +104,7 @@ public void SpawnRoom( MapGridComponent grid, Matrix3x2 roomTransform, DungeonRoomPrototype room, + HashSet? reservedTiles = null, bool clearExisting = false) { // Ensure the underlying template exists. @@ -150,6 +152,10 @@ public void SpawnRoom( var tilePos = Vector2.Transform(indices + tileOffset, roomTransform); var rounded = tilePos.Floored(); + + if (!clearExisting && reservedTiles?.Contains(rounded) == true) + continue; + _tiles.Add((rounded, tileRef.Tile)); } } @@ -165,6 +171,10 @@ public void SpawnRoom( { var templateXform = _xformQuery.GetComponent(templateEnt); var childPos = Vector2.Transform(templateXform.LocalPosition - roomCenter, roomTransform); + + if (!clearExisting && reservedTiles?.Contains(childPos.Floored()) == true) + continue; + var childRot = templateXform.LocalRotation + finalRoomRotation; var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID; @@ -192,8 +202,11 @@ public void SpawnRoom( // Offset by 0.5 because decals are offset from bot-left corner // So we convert it to center of tile then convert it back again after transform. // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles. - var position = Vector2.Transform(decal.Coordinates + Vector2Helpers.Half - roomCenter, roomTransform); - position -= Vector2Helpers.Half; + var position = Vector2.Transform(decal.Coordinates + grid.TileSizeHalfVector - roomCenter, roomTransform); + position -= grid.TileSizeHalfVector; + + if (!clearExisting && reservedTiles?.Contains(position.Floored()) == true) + continue; // Umm uhh I love decals so uhhhh idk what to do about this var angle = (decal.Angle + finalRoomRotation).Reduced(); diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 36009896a2cb..b73e843fffdb 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Procedural; using Content.Shared.Tag; using Robust.Server.GameObjects; +using Robust.Shared.Collections; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Map; @@ -49,7 +50,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem public const int CollisionLayer = (int) CollisionGroup.Impassable; private readonly JobQueue _dungeonJobQueue = new(DungeonJobTime); - private readonly Dictionary _dungeonJobs = new(); + private readonly Dictionary _dungeonJobs = new(); [ValidatePrototypeId] public const string FallbackTileId = "FloorSteel"; @@ -190,18 +191,16 @@ public void GenerateDungeon(DungeonConfigPrototype gen, int seed) { var cancelToken = new CancellationTokenSource(); - var job = new DungeonJob( + var job = new DungeonJob.DungeonJob( Log, DungeonJobTime, EntityManager, - _mapManager, _prototype, _tileDefManager, _anchorable, _decals, this, _lookup, - _tag, _tile, _transform, gen, @@ -215,7 +214,7 @@ public void GenerateDungeon(DungeonConfigPrototype gen, _dungeonJobQueue.EnqueueJob(job); } - public async Task GenerateDungeonAsync( + public async Task> GenerateDungeonAsync( DungeonConfigPrototype gen, EntityUid gridUid, MapGridComponent grid, @@ -223,18 +222,16 @@ public async Task GenerateDungeonAsync( int seed) { var cancelToken = new CancellationTokenSource(); - var job = new DungeonJob( + var job = new DungeonJob.DungeonJob( Log, DungeonJobTime, EntityManager, - _mapManager, _prototype, _tileDefManager, _anchorable, _decals, this, _lookup, - _tag, _tile, _transform, gen, diff --git a/Content.Server/Procedural/RoomFillSystem.cs b/Content.Server/Procedural/RoomFillSystem.cs index 20ffa98586d9..b539cc9780ef 100644 --- a/Content.Server/Procedural/RoomFillSystem.cs +++ b/Content.Server/Procedural/RoomFillSystem.cs @@ -35,6 +35,7 @@ private void OnRoomFillMapInit(EntityUid uid, RoomFillComponent component, MapIn _maps.LocalToTile(xform.GridUid.Value, mapGrid, xform.Coordinates), room, random, + null, clearExisting: component.ClearExisting, rotation: component.Rotation); } diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index ce844e57a13e..e9318792b726 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -176,9 +176,11 @@ protected override async Task Process() dungeonOffset = dungeonRotation.RotateVec(dungeonOffset); var dungeonMod = _prototypeManager.Index(mission.Dungeon); var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto); - var dungeon = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset, + var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i) dungeonOffset, _missionParams.Seed)); + var dungeon = dungeons.First(); + // Aborty if (dungeon.Rooms.Count == 0) { diff --git a/Content.Server/Shuttles/Components/GridSpawnComponent.cs b/Content.Server/Shuttles/Components/GridSpawnComponent.cs index 5f0fa7dd6244..d8144354b8e7 100644 --- a/Content.Server/Shuttles/Components/GridSpawnComponent.cs +++ b/Content.Server/Shuttles/Components/GridSpawnComponent.cs @@ -1,4 +1,6 @@ using Content.Server.Shuttles.Systems; +using Content.Shared.Dataset; +using Content.Shared.Procedural; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -14,39 +16,92 @@ public sealed partial class GridSpawnComponent : Component /// Dictionary of groups where each group will have entries selected. /// String is just an identifier to make yaml easier. /// - [DataField(required: true)] public Dictionary Groups = new(); + [DataField(required: true)] public Dictionary Groups = new(); } -[DataRecord] -public record struct GridSpawnGroup +public interface IGridSpawnGroup { - public List Paths = new(); - public int MinCount = 1; - public int MaxCount = 1; + /// + /// Minimum distance to spawn away from the station. + /// + public float MinimumDistance { get; } + + /// + public ProtoId? NameDataset { get; } + + /// + int MinCount { get; set; } + + /// + int MaxCount { get; set; } /// /// Components to be added to any spawned grids. /// - public ComponentRegistry AddComponents = new(); + public ComponentRegistry AddComponents { get; set; } /// /// Hide the IFF label of the grid. /// - public bool Hide = false; + public bool Hide { get; set; } /// /// Should we set the metadata name of a grid. Useful for admin purposes. /// - public bool NameGrid = false; + public bool NameGrid { get; set; } /// /// Should we add this to the station's grids (if possible / relevant). /// - public bool StationGrid = true; + public bool StationGrid { get; set; } +} + +[DataRecord] +public sealed class DungeonSpawnGroup : IGridSpawnGroup +{ + /// + /// Prototypes we can choose from to spawn. + /// + public List> Protos = new(); + + /// + public float MinimumDistance { get; } + + /// + public ProtoId? NameDataset { get; } + + /// + public int MinCount { get; set; } = 1; + + /// + public int MaxCount { get; set; } = 1; + + /// + public ComponentRegistry AddComponents { get; set; } = new(); + + /// + public bool Hide { get; set; } = false; + + /// + public bool NameGrid { get; set; } = false; + + /// + public bool StationGrid { get; set; } = false; +} + +[DataRecord] +public sealed class GridSpawnGroup : IGridSpawnGroup +{ + public List Paths = new(); - public GridSpawnGroup() - { - } + public float MinimumDistance { get; } + public ProtoId? NameDataset { get; } + public int MinCount { get; set; } = 1; + public int MaxCount { get; set; } = 1; + public ComponentRegistry AddComponents { get; set; } = new(); + public bool Hide { get; set; } = false; + public bool NameGrid { get; set; } = true; + public bool StationGrid { get; set; } = true; } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index 853548add37c..b4fcccd80557 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -1,9 +1,14 @@ +using System.Numerics; using Content.Server.Shuttles.Components; using Content.Server.Station.Components; using Content.Server.Station.Events; using Content.Shared.Cargo.Components; using Content.Shared.CCVar; +using Content.Shared.Procedural; +using Content.Shared.Salvage; using Content.Shared.Shuttles.Components; +using Robust.Shared.Collections; +using Robust.Shared.Map; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -80,6 +85,76 @@ private void CargoSpawn(EntityUid uid, StationCargoShuttleComponent component) _mapManager.DeleteMap(mapId); } + private bool TryDungeonSpawn(EntityUid targetGrid, EntityUid stationUid, MapId mapId, DungeonSpawnGroup group, out EntityUid spawned) + { + spawned = EntityUid.Invalid; + var dungeonProtoId = _random.Pick(group.Protos); + + if (!_protoManager.TryIndex(dungeonProtoId, out var dungeonProto)) + { + return false; + } + + var spawnCoords = new EntityCoordinates(targetGrid, Vector2.Zero); + + if (group.MinimumDistance > 0f) + { + spawnCoords = spawnCoords.Offset(_random.NextVector2(group.MinimumDistance, group.MinimumDistance * 1.5f)); + } + + var spawnMapCoords = _transform.ToMapCoordinates(spawnCoords); + var spawnedGrid = _mapManager.CreateGridEntity(mapId); + + _transform.SetMapCoordinates(spawnedGrid, spawnMapCoords); + _dungeon.GenerateDungeon(dungeonProto, spawnedGrid.Owner, spawnedGrid.Comp, Vector2i.Zero, _random.Next()); + + spawned = spawnedGrid.Owner; + return true; + } + + private bool TryGridSpawn(EntityUid targetGrid, EntityUid stationUid, MapId mapId, GridSpawnGroup group, out EntityUid spawned) + { + spawned = EntityUid.Invalid; + + if (group.Paths.Count == 0) + { + Log.Error($"Found no paths for GridSpawn"); + return false; + } + + var paths = new ValueList(); + + // Round-robin so we try to avoid dupes where possible. + if (paths.Count == 0) + { + paths.AddRange(group.Paths); + _random.Shuffle(paths); + } + + var path = paths[^1]; + paths.RemoveAt(paths.Count - 1); + + if (_loader.TryLoad(mapId, path.ToString(), out var ent) && ent.Count == 1) + { + if (TryComp(ent[0], out var shuttle)) + { + TryFTLProximity(ent[0], targetGrid); + } + + if (group.NameGrid) + { + var name = path.FilenameWithoutExtension; + _metadata.SetEntityName(ent[0], name); + } + + spawned = ent[0]; + return true; + } + + Log.Error($"Error loading gridspawn for {ToPrettyString(stationUid)} / {path}"); + return false; + } + private void GridSpawns(EntityUid uid, GridSpawnComponent component) { if (!_cfg.GetCVar(CCVars.GridFill)) @@ -97,81 +172,49 @@ private void GridSpawns(EntityUid uid, GridSpawnComponent component) // Spawn on a dummy map and try to FTL if possible, otherwise dump it. var mapId = _mapManager.CreateMap(); - var valid = true; - var paths = new List(); foreach (var group in component.Groups.Values) { - if (group.Paths.Count == 0) - { - Log.Error($"Found no paths for GridSpawn"); - continue; - } - - var count = _random.Next(group.MinCount, group.MaxCount); - paths.Clear(); + var count = _random.Next(group.MinCount, group.MaxCount + 1); for (var i = 0; i < count; i++) { - // Round-robin so we try to avoid dupes where possible. - if (paths.Count == 0) - { - paths.AddRange(group.Paths); - _random.Shuffle(paths); - } - - var path = paths[^1]; - paths.RemoveAt(paths.Count - 1); + EntityUid spawned; - if (_loader.TryLoad(mapId, path.ToString(), out var ent) && ent.Count == 1) + switch (group) { - if (TryComp(ent[0], out var shuttle)) - { - TryFTLProximity(ent[0], targetGrid.Value); - } - else - { - valid = false; - } - - if (group.Hide) - { - var iffComp = EnsureComp(ent[0]); - iffComp.Flags |= IFFFlags.HideLabel; - Dirty(ent[0], iffComp); - } - - if (group.StationGrid) - { - _station.AddGridToStation(uid, ent[0]); - } - - if (group.NameGrid) - { - var name = path.FilenameWithoutExtension; - _metadata.SetEntityName(ent[0], name); - } - - foreach (var compReg in group.AddComponents.Values) - { - var compType = compReg.Component.GetType(); + case DungeonSpawnGroup dungeon: + if (!TryDungeonSpawn(targetGrid.Value, uid, mapId, dungeon, out spawned)) + continue; - if (HasComp(ent[0], compType)) + break; + case GridSpawnGroup grid: + if (!TryGridSpawn(targetGrid.Value, uid, mapId, grid, out spawned)) continue; - var comp = _factory.GetComponent(compType); - AddComp(ent[0], comp, true); - } + break; + default: + throw new NotImplementedException(); + } + + if (_protoManager.TryIndex(group.NameDataset, out var dataset)) + { + _metadata.SetEntityName(spawned, SharedSalvageSystem.GetFTLName(dataset, _random.Next())); } - else + + if (group.Hide) { - valid = false; + var iffComp = EnsureComp(spawned); + iffComp.Flags |= IFFFlags.HideLabel; + Dirty(spawned, iffComp); } - if (!valid) + if (group.StationGrid) { - Log.Error($"Error loading gridspawn for {ToPrettyString(uid)} / {path}"); + _station.AddGridToStation(uid, spawned); } + + EntityManager.AddComponents(spawned, group.AddComponents); } } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index b8f216db7370..85703389e9de 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Systems; using Content.Server.Doors.Systems; using Content.Server.Parallax; +using Content.Server.Procedural; using Content.Server.Shuttles.Components; using Content.Server.Station.Systems; using Content.Server.Stunnable; @@ -20,6 +21,7 @@ using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; +using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -28,15 +30,18 @@ namespace Content.Server.Shuttles.Systems; [UsedImplicitly] public sealed partial class ShuttleSystem : SharedShuttleSystem { + [Dependency] private readonly IAdminLogManager _logger = default!; [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BiomeSystem _biomes = default!; [Dependency] private readonly BodySystem _bobby = default!; [Dependency] private readonly DockingSystem _dockSystem = default!; + [Dependency] private readonly DungeonSystem _dungeon = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; @@ -52,7 +57,6 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; - [Dependency] private readonly IAdminLogManager _logger = default!; public const float TileMassMultiplier = 0.5f; diff --git a/Content.Shared/Procedural/Components/EntityRemapComponent.cs b/Content.Shared/Procedural/Components/EntityRemapComponent.cs new file mode 100644 index 000000000000..3d7199743af7 --- /dev/null +++ b/Content.Shared/Procedural/Components/EntityRemapComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.Components; + +/// +/// Indicates this entity prototype should be re-mapped to another +/// +[RegisterComponent] +public sealed partial class EntityRemapComponent : Component +{ + [DataField(required: true)] + public Dictionary Mask = new(); +} diff --git a/Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs b/Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs new file mode 100644 index 000000000000..617304729e21 --- /dev/null +++ b/Content.Shared/Procedural/Distance/DunGenEuclideanSquaredDistance.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.Distance; + +/// +/// Produces a rounder shape useful for more natural areas. +/// +public sealed partial class DunGenEuclideanSquaredDistance : IDunGenDistance +{ + [DataField] + public float BlendWeight { get; set; } = 0.50f; +} diff --git a/Content.Shared/Procedural/Distance/DunGenSquareBump.cs b/Content.Shared/Procedural/Distance/DunGenSquareBump.cs new file mode 100644 index 000000000000..48b0c4bcb7ee --- /dev/null +++ b/Content.Shared/Procedural/Distance/DunGenSquareBump.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.Distance; + +/// +/// Produces a squarish-shape that's better for filling in most of the area. +/// +public sealed partial class DunGenSquareBump : IDunGenDistance +{ + [DataField] + public float BlendWeight { get; set; } = 0.50f; +} diff --git a/Content.Shared/Procedural/Distance/IDunGenDistance.cs b/Content.Shared/Procedural/Distance/IDunGenDistance.cs new file mode 100644 index 000000000000..b1071a14e349 --- /dev/null +++ b/Content.Shared/Procedural/Distance/IDunGenDistance.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.Procedural.Distance; + +/// +/// Used if you want to limit the distance noise is generated by some arbitrary config +/// +[ImplicitDataDefinitionForInheritors] +public partial interface IDunGenDistance +{ + /// + /// How much to blend between the original noise value and the adjusted one. + /// + float BlendWeight { get; } +} + diff --git a/Content.Shared/Procedural/Dungeon.cs b/Content.Shared/Procedural/Dungeon.cs index aecfef2c782d..0d290b67905e 100644 --- a/Content.Shared/Procedural/Dungeon.cs +++ b/Content.Shared/Procedural/Dungeon.cs @@ -1,8 +1,16 @@ namespace Content.Shared.Procedural; +/// +/// Procedurally generated dungeon data. +/// public sealed class Dungeon { - public readonly List Rooms; + public static Dungeon Empty = new Dungeon(); + + private List _rooms; + private HashSet _allTiles = new(); + + public IReadOnlyList Rooms => _rooms; /// /// Hashset of the tiles across all rooms. @@ -17,18 +25,64 @@ public sealed class Dungeon public readonly HashSet Entrances = new(); - public Dungeon() + public IReadOnlySet AllTiles => _allTiles; + + public Dungeon() : this(new List()) { - Rooms = new List(); } public Dungeon(List rooms) { - Rooms = rooms; + // This reftype is mine now. + _rooms = rooms; + + foreach (var room in _rooms) + { + InternalAddRoom(room); + } + + RefreshAllTiles(); + } + + public void RefreshAllTiles() + { + _allTiles.Clear(); + _allTiles.UnionWith(RoomTiles); + _allTiles.UnionWith(RoomExteriorTiles); + _allTiles.UnionWith(CorridorTiles); + _allTiles.UnionWith(CorridorExteriorTiles); + _allTiles.UnionWith(Entrances); + } + + public void Rebuild() + { + _allTiles.Clear(); + + RoomTiles.Clear(); + RoomExteriorTiles.Clear(); + Entrances.Clear(); - foreach (var room in Rooms) + foreach (var room in _rooms) { - Entrances.UnionWith(room.Entrances); + InternalAddRoom(room, false); } + + RefreshAllTiles(); + } + + public void AddRoom(DungeonRoom room) + { + _rooms.Add(room); + InternalAddRoom(room); + } + + private void InternalAddRoom(DungeonRoom room, bool refreshAll = true) + { + Entrances.UnionWith(room.Entrances); + RoomTiles.UnionWith(room.Tiles); + RoomExteriorTiles.UnionWith(room.Exterior); + + if (refreshAll) + RefreshAllTiles(); } } diff --git a/Content.Shared/Procedural/DungeonConfigPrototype.cs b/Content.Shared/Procedural/DungeonConfigPrototype.cs index 07a7000d6374..d0d8e0ff12d7 100644 --- a/Content.Shared/Procedural/DungeonConfigPrototype.cs +++ b/Content.Shared/Procedural/DungeonConfigPrototype.cs @@ -1,21 +1,53 @@ -using Content.Shared.Procedural.DungeonGenerators; using Content.Shared.Procedural.PostGeneration; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural; -[Prototype("dungeonConfig")] +[Prototype] public sealed partial class DungeonConfigPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; - [DataField("generator", required: true)] - public IDunGen Generator = default!; + /// + /// + /// + [DataField] + public DungeonData Data = DungeonData.Empty; + + /// + /// The secret sauce, procedural generation layers that get run. + /// + [DataField(required: true)] + public List Layers = new(); + + /// + /// Should we reserve the tiles generated by this config so no other dungeons can spawn on it within the same job? + /// + [DataField] + public bool ReserveTiles; + + /// + /// Minimum times to run the config. + /// + [DataField] + public int MinCount = 1; + + /// + /// Maximum times to run the config. + /// + [DataField] + public int MaxCount = 1; + + /// + /// Minimum amount we can offset the dungeon by. + /// + [DataField] + public int MinOffset; /// - /// Ran after the main dungeon is created. + /// Maximum amount we can offset the dungeon by. /// - [DataField("postGeneration")] - public List PostGeneration = new(); + [DataField] + public int MaxOffset; } diff --git a/Content.Shared/Procedural/DungeonData.cs b/Content.Shared/Procedural/DungeonData.cs new file mode 100644 index 000000000000..58ec96678614 --- /dev/null +++ b/Content.Shared/Procedural/DungeonData.cs @@ -0,0 +1,105 @@ +using System.Linq; +using Content.Shared.Maps; +using Content.Shared.Storage; +using Content.Shared.Whitelist; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Procedural; + +/// +/// Used to set dungeon values for all layers. +/// +/// +/// This lets us share data between different dungeon configs without having to repeat entire configs. +/// +[DataRecord] +public sealed class DungeonData +{ + // I hate this but it also significantly reduces yaml bloat if we add like 10 variations on the same set of layers + // e.g. science rooms, engi rooms, cargo rooms all under PlanetBase for example. + // without having to do weird nesting. It also means we don't need to copy-paste the same prototype across several layers + // The alternative is doing like, + // 2 layer prototype, 1 layer with the specified data, 3 layer prototype, 2 layers with specified data, etc. + // As long as we just keep the code clean over time it won't be bad to maintain. + + public static DungeonData Empty = new(); + + public Dictionary Colors = new(); + public Dictionary Entities = new(); + public Dictionary> SpawnGroups = new(); + public Dictionary> Tiles = new(); + public Dictionary Whitelists = new(); + + /// + /// Applies the specified data to this data. + /// + public void Apply(DungeonData data) + { + // Copy-paste moment. + foreach (var color in data.Colors) + { + Colors[color.Key] = color.Value; + } + + foreach (var color in data.Entities) + { + Entities[color.Key] = color.Value; + } + + foreach (var color in data.SpawnGroups) + { + SpawnGroups[color.Key] = color.Value; + } + + foreach (var color in data.Tiles) + { + Tiles[color.Key] = color.Value; + } + + foreach (var color in data.Whitelists) + { + Whitelists[color.Key] = color.Value; + } + } + + public DungeonData Clone() + { + return new DungeonData + { + // Only shallow clones but won't matter for DungeonJob purposes. + Colors = Colors.ShallowClone(), + Entities = Entities.ShallowClone(), + SpawnGroups = SpawnGroups.ShallowClone(), + Tiles = Tiles.ShallowClone(), + Whitelists = Whitelists.ShallowClone(), + }; + } +} + +public enum DungeonDataKey : byte +{ + // Colors + Decals, + + // Entities + Cabling, + CornerWalls, + Fill, + Junction, + Walls, + + // SpawnGroups + CornerClutter, + Entrance, + EntranceFlank, + WallMounts, + Window, + + // Tiles + FallbackTile, + WidenTile, + + // Whitelists + Rooms, +} diff --git a/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs new file mode 100644 index 000000000000..e9a5181f8d07 --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Generates the specified config on an exterior tile of the attached dungeon. +/// Useful if you're using or otherwise want a dungeon on the outside of a grid. +/// +public sealed partial class ExteriorDunGen : IDunGenLayer +{ + [DataField(required: true)] + public ProtoId Proto; +} diff --git a/Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs new file mode 100644 index 000000000000..368ec5cc3e45 --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/FillGridDunGen.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Fills unreserved tiles with the specified entity prototype. +/// +/// +/// DungeonData keys are: +/// - Fill +/// +public sealed partial class FillGridDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/DungeonGenerators/IDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/IDunGen.cs deleted file mode 100644 index 5aa82f1596f7..000000000000 --- a/Content.Shared/Procedural/DungeonGenerators/IDunGen.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Shared.Procedural.DungeonGenerators; - -[ImplicitDataDefinitionForInheritors] -public partial interface IDunGen -{ - -} diff --git a/Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs new file mode 100644 index 000000000000..0dfb3daef847 --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/NoiseDistanceDunGen.cs @@ -0,0 +1,18 @@ +using Content.Shared.Procedural.Distance; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Like except with maximum dimensions +/// +public sealed partial class NoiseDistanceDunGen : IDunGenLayer +{ + [DataField] + public IDunGenDistance? DistanceConfig; + + [DataField] + public Vector2i Size; + + [DataField(required: true)] + public List Layers = new(); +} diff --git a/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs index 3ea0d989a2a2..56d63bec8f59 100644 --- a/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs +++ b/Content.Shared/Procedural/DungeonGenerators/NoiseDunGen.cs @@ -1,15 +1,12 @@ -using Content.Shared.Maps; +using Content.Shared.Procedural.Distance; using Robust.Shared.Noise; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Shared.Procedural.DungeonGenerators; /// /// Generates dungeon flooring based on the specified noise. /// -public sealed partial class NoiseDunGen : IDunGen +public sealed partial class NoiseDunGen : IDunGenLayer { /* * Floodfills out from 0 until it finds a valid tile. diff --git a/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs index ef61fff4b045..aeb24d014482 100644 --- a/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs +++ b/Content.Shared/Procedural/DungeonGenerators/PrefabDunGen.cs @@ -1,30 +1,20 @@ -using Content.Shared.Maps; -using Content.Shared.Tag; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.DungeonGenerators; /// /// Places rooms in pre-selected pack layouts. Chooses rooms from the specified whitelist. /// -public sealed partial class PrefabDunGen : IDunGen +/// +/// DungeonData keys are: +/// - FallbackTile +/// - Rooms +/// +public sealed partial class PrefabDunGen : IDunGenLayer { - /// - /// Rooms need to match any of these tags - /// - [DataField("roomWhitelist", customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List RoomWhitelist = new(); - /// /// Room pack presets we can use for this prefab. /// - [DataField("presets", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List Presets = new(); - - /// - /// Fallback tile. - /// - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; + [DataField(required: true)] + public List> Presets = new(); } diff --git a/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs new file mode 100644 index 000000000000..346c60a6cb58 --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Runs another . +/// Used for storing data on 1 system. +/// +public sealed partial class PrototypeDunGen : IDunGenLayer +{ + [DataField(required: true)] + public ProtoId Proto; +} diff --git a/Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs new file mode 100644 index 000000000000..64b76b4cccc7 --- /dev/null +++ b/Content.Shared/Procedural/DungeonGenerators/ReplaceTileDunGen.cs @@ -0,0 +1,30 @@ +using Content.Shared.Maps; +using Robust.Shared.Noise; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonGenerators; + +/// +/// Replaces existing tiles if they're not empty. +/// +public sealed partial class ReplaceTileDunGen : IDunGenLayer +{ + /// + /// Chance for a non-variant tile to be used, in case they're too noisy. + /// + [DataField] + public float VariantWeight = 0.1f; + + [DataField(required: true)] + public List Layers = new(); +} + +[DataRecord] +public record struct ReplaceTileLayer +{ + public ProtoId Tile; + + public float Threshold; + + public FastNoiseLite Noise; +} diff --git a/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs b/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs new file mode 100644 index 000000000000..30b502efe073 --- /dev/null +++ b/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs @@ -0,0 +1,21 @@ +using Content.Shared.Storage; + +namespace Content.Shared.Procedural.DungeonLayers; + + +/// +/// Spawns mobs inside of the dungeon randomly. +/// +public sealed partial class MobsDunGen : IDunGenLayer +{ + // Counts separate to config to avoid some duplication. + + [DataField] + public int MinCount = 1; + + [DataField] + public int MaxCount = 1; + + [DataField(required: true)] + public List Groups = new(); +} diff --git a/Content.Shared/Procedural/DungeonLayers/OreDunGen.cs b/Content.Shared/Procedural/DungeonLayers/OreDunGen.cs new file mode 100644 index 000000000000..31bf367d0e45 --- /dev/null +++ b/Content.Shared/Procedural/DungeonLayers/OreDunGen.cs @@ -0,0 +1,42 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.DungeonLayers; + +/// +/// Generates veins inside of the specified dungeon. +/// +/// +/// Generates on top of existing entities for sanity reasons moreso than performance. +/// +public sealed partial class OreDunGen : IDunGenLayer +{ + /// + /// If the vein generation should occur on top of existing entities what are we replacing. + /// + [DataField] + public EntProtoId? Replacement; + + /// + /// Entity to spawn. + /// + [DataField(required: true)] + public EntProtoId Entity; + + /// + /// Maximum amount of group spawns + /// + [DataField] + public int Count = 10; + + /// + /// Minimum entities to spawn in one group. + /// + [DataField] + public int MinGroupSize = 1; + + /// + /// Maximum entities to spawn in one group. + /// + [DataField] + public int MaxGroupSize = 1; +} diff --git a/Content.Shared/Procedural/DungeonRoom.cs b/Content.Shared/Procedural/DungeonRoom.cs index 4802949d2f38..0c6af8f23db0 100644 --- a/Content.Shared/Procedural/DungeonRoom.cs +++ b/Content.Shared/Procedural/DungeonRoom.cs @@ -2,6 +2,7 @@ namespace Content.Shared.Procedural; +// TODO: Cache center and bounds and shit and don't make the caller deal with it. public sealed record DungeonRoom(HashSet Tiles, Vector2 Center, Box2i Bounds, HashSet Exterior) { public readonly List Entrances = new(); diff --git a/Content.Shared/Procedural/IDunGenLayer.cs b/Content.Shared/Procedural/IDunGenLayer.cs new file mode 100644 index 000000000000..a4e8045af1cd --- /dev/null +++ b/Content.Shared/Procedural/IDunGenLayer.cs @@ -0,0 +1,7 @@ +namespace Content.Shared.Procedural; + +[ImplicitDataDefinitionForInheritors] +public partial interface IDunGenLayer +{ + +} diff --git a/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs b/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs new file mode 100644 index 000000000000..5afad7edb18f --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Runs cables throughout the dungeon. +/// +/// +/// DungeonData keys are: +/// - Cabling +/// +public sealed partial class AutoCablingDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs b/Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs deleted file mode 100644 index 8278352b036a..000000000000 --- a/Content.Shared/Procedural/PostGeneration/AutoCablingPostGen.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Runs cables throughout the dungeon. -/// -public sealed partial class AutoCablingPostGen : IPostDunGen -{ - [DataField] - public EntProtoId Entity = "CableApcExtension"; -} diff --git a/Content.Shared/Procedural/PostGeneration/BiomePostGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs similarity index 78% rename from Content.Shared/Procedural/PostGeneration/BiomePostGen.cs rename to Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs index d02de241355e..833cf2dec769 100644 --- a/Content.Shared/Procedural/PostGeneration/BiomePostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs @@ -1,5 +1,4 @@ using Content.Shared.Parallax.Biomes; -using Content.Shared.Procedural.PostGeneration; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.PostGeneration; @@ -8,7 +7,7 @@ namespace Content.Shared.Procedural.PostGeneration; /// Generates a biome on top of valid tiles, then removes the biome when done. /// Only works if no existing biome is present. /// -public sealed partial class BiomePostGen : IPostDunGen +public sealed partial class BiomeDunGen : IDunGenLayer { [DataField(required: true)] public ProtoId BiomeTemplate; diff --git a/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerPostGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs similarity index 73% rename from Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerPostGen.cs rename to Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs index dc64febe7b05..af5d7c5d8f9a 100644 --- a/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs @@ -1,5 +1,3 @@ -using Content.Shared.Parallax.Biomes.Markers; -using Content.Shared.Procedural.PostGeneration; using Content.Shared.Random; using Robust.Shared.Prototypes; @@ -8,7 +6,7 @@ namespace Content.Shared.Procedural.PostGeneration; /// /// Spawns the specified marker layer on top of the dungeon rooms. /// -public sealed partial class BiomeMarkerLayerPostGen : IPostDunGen +public sealed partial class BiomeMarkerLayerDunGen : IDunGenLayer { /// /// How many times to spawn marker layers; can duplicate. diff --git a/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs b/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs new file mode 100644 index 000000000000..4151527f8a07 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs @@ -0,0 +1,23 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Iterates room edges and places the relevant tiles and walls on any free indices. +/// +/// +/// Dungeon data keys are: +/// - CornerWalls (Optional) +/// - FallbackTile +/// - Walls +/// +public sealed partial class BoundaryWallDunGen : IDunGenLayer +{ + [DataField] + public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms; +} + +[Flags] +public enum BoundaryWallFlags : byte +{ + Rooms = 1 << 0, + Corridors = 1 << 1, +} diff --git a/Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs b/Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs deleted file mode 100644 index 390ff42feea8..000000000000 --- a/Content.Shared/Procedural/PostGeneration/BoundaryWallPostGen.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Iterates room edges and places the relevant tiles and walls on any free indices. -/// -public sealed partial class BoundaryWallPostGen : IPostDunGen -{ - [DataField] - public ProtoId Tile = "FloorSteel"; - - [DataField] - public EntProtoId Wall = "WallSolid"; - - /// - /// Walls to use in corners if applicable. - /// - [DataField] - public string? CornerWall; - - [DataField] - public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms; -} - -[Flags] -public enum BoundaryWallFlags : byte -{ - Rooms = 1 << 0, - Corridors = 1 << 1, -} diff --git a/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs b/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs new file mode 100644 index 000000000000..2a904281c803 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs @@ -0,0 +1,14 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns entities inside corners. +/// +/// +/// Dungeon data keys are: +/// - CornerClutter +/// +public sealed partial class CornerClutterDunGen : IDunGenLayer +{ + [DataField] + public float Chance = 0.50f; +} diff --git a/Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs b/Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs deleted file mode 100644 index a16c7f9ab3f3..000000000000 --- a/Content.Shared/Procedural/PostGeneration/CornerClutterPostGen.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Storage; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Spawns entities inside corners. -/// -public sealed partial class CornerClutterPostGen : IPostDunGen -{ - [DataField] - public float Chance = 0.50f; - - /// - /// The default starting bulbs - /// - [DataField(required: true)] - public List Contents = new(); -} diff --git a/Content.Shared/Procedural/PostGeneration/CorridorClutterPostGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs similarity index 85% rename from Content.Shared/Procedural/PostGeneration/CorridorClutterPostGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs index a8a74ba6ccb0..5b397b40dfc1 100644 --- a/Content.Shared/Procedural/PostGeneration/CorridorClutterPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs @@ -5,7 +5,7 @@ namespace Content.Shared.Procedural.PostGeneration; /// /// Adds entities randomly to the corridors. /// -public sealed partial class CorridorClutterPostGen : IPostDunGen +public sealed partial class CorridorClutterDunGen : IDunGenLayer { [DataField] public float Chance = 0.05f; diff --git a/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingPostGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs similarity index 72% rename from Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingPostGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs index 4b139a8be651..e60904365555 100644 --- a/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs @@ -7,29 +7,23 @@ namespace Content.Shared.Procedural.PostGeneration; /// /// Applies decal skirting to corridors. /// -public sealed partial class CorridorDecalSkirtingPostGen : IPostDunGen +public sealed partial class CorridorDecalSkirtingDunGen : IDunGenLayer { - /// - /// Color to apply to decals. - /// - [DataField("color")] - public Color? Color; - /// /// Decal where 1 edge is found. /// - [DataField("cardinalDecals")] + [DataField] public Dictionary CardinalDecals = new(); /// /// Decal where 1 corner edge is found. /// - [DataField("pocketDecals")] + [DataField] public Dictionary PocketDecals = new(); /// /// Decal where 2 or 3 edges are found. /// - [DataField("cornerDecals")] + [DataField] public Dictionary CornerDecals = new(); } diff --git a/Content.Shared/Procedural/PostGeneration/CorridorPostGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs similarity index 73% rename from Content.Shared/Procedural/PostGeneration/CorridorPostGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs index 705ae99dcef4..6d75cd9cb2bd 100644 --- a/Content.Shared/Procedural/PostGeneration/CorridorPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs @@ -1,12 +1,13 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; - namespace Content.Shared.Procedural.PostGeneration; /// /// Connects room entrances via corridor segments. /// -public sealed partial class CorridorPostGen : IPostDunGen +/// +/// Dungeon data keys are: +/// - FallbackTile +/// +public sealed partial class CorridorDunGen : IDunGenLayer { /// /// How far we're allowed to generate a corridor before calling it. @@ -17,9 +18,6 @@ public sealed partial class CorridorPostGen : IPostDunGen [DataField] public int PathLimit = 2048; - [DataField] - public ProtoId Tile = "FloorSteel"; - /// /// How wide to make the corridor. /// diff --git a/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs b/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs new file mode 100644 index 000000000000..40cc95f5fc90 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs @@ -0,0 +1,18 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Selects [count] rooms and places external doors to them. +/// +/// +/// Dungeon data keys are: +/// - Entrance +/// - FallbackTile +/// +public sealed partial class DungeonEntranceDunGen : IDunGenLayer +{ + /// + /// How many rooms we place doors on. + /// + [DataField] + public int Count = 1; +} diff --git a/Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs b/Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs deleted file mode 100644 index 3398b5131797..000000000000 --- a/Content.Shared/Procedural/PostGeneration/DungeonEntrancePostGen.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Selects [count] rooms and places external doors to them. -/// -public sealed partial class DungeonEntrancePostGen : IPostDunGen -{ - /// - /// How many rooms we place doors on. - /// - [DataField("count")] - public int Count = 1; - - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs b/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs new file mode 100644 index 000000000000..27baa48ec628 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns entities on either side of an entrance. +/// +/// +/// Dungeon data keys are: +/// - FallbackTile +/// - +/// +public sealed partial class EntranceFlankDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs b/Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs deleted file mode 100644 index 96e9bd5d6d11..000000000000 --- a/Content.Shared/Procedural/PostGeneration/EntranceFlankPostGen.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Spawns entities on either side of an entrance. -/// -public sealed partial class EntranceFlankPostGen : IPostDunGen -{ - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("entities")] - public List Entities = new(); -} diff --git a/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs b/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs new file mode 100644 index 000000000000..0b29344b90b6 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// If external areas are found will try to generate windows. +/// +/// +/// Dungeon data keys are: +/// - EntranceFlank +/// - FallbackTile +/// +public sealed partial class ExternalWindowDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs b/Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs deleted file mode 100644 index d5580baeaaab..000000000000 --- a/Content.Shared/Procedural/PostGeneration/ExternalWindowPostGen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// If external areas are found will try to generate windows. -/// -public sealed partial class ExternalWindowPostGen : IPostDunGen -{ - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "Grille", - "Window", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/IPostDunGen.cs b/Content.Shared/Procedural/PostGeneration/IPostDunGen.cs deleted file mode 100644 index b55cab8e63e3..000000000000 --- a/Content.Shared/Procedural/PostGeneration/IPostDunGen.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Ran after generating dungeon rooms. Can be used for additional loot, contents, etc. -/// -[ImplicitDataDefinitionForInheritors] -public partial interface IPostDunGen -{ - -} diff --git a/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs b/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs new file mode 100644 index 000000000000..11b1c6a785a6 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// If internal areas are found will try to generate windows. +/// +/// +/// Dungeon data keys are: +/// - FallbackTile +/// - Window +/// +public sealed partial class InternalWindowDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs b/Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs deleted file mode 100644 index 4c6223eb92a0..000000000000 --- a/Content.Shared/Procedural/PostGeneration/InternalWindowPostGen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// If internal areas are found will try to generate windows. -/// -public sealed partial class InternalWindowPostGen : IPostDunGen -{ - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "Grille", - "Window", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs b/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs new file mode 100644 index 000000000000..899f27162168 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs @@ -0,0 +1,18 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Places the specified entities at junction areas. +/// +/// +/// Dungeon data keys are: +/// - Entrance +/// - FallbackTile +/// +public sealed partial class JunctionDunGen : IDunGenLayer +{ + /// + /// Width to check for junctions. + /// + [DataField] + public int Width = 3; +} diff --git a/Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs b/Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs deleted file mode 100644 index 5c4cf43b7f0b..000000000000 --- a/Content.Shared/Procedural/PostGeneration/JunctionPostGen.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Places the specified entities at junction areas. -/// -public sealed partial class JunctionPostGen : IPostDunGen -{ - /// - /// Width to check for junctions. - /// - [DataField("width")] - public int Width = 3; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass" - }; -} diff --git a/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs b/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs new file mode 100644 index 000000000000..a5758c149893 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs @@ -0,0 +1,19 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Places the specified entities on the middle connections between rooms +/// +public sealed partial class MiddleConnectionDunGen : IDunGenLayer +{ + /// + /// How much overlap there needs to be between 2 rooms exactly. + /// + [DataField] + public int OverlapCount = -1; + + /// + /// How many connections to spawn between rooms. + /// + [DataField] + public int Count = 1; +} diff --git a/Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs b/Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs deleted file mode 100644 index d29a65434c7a..000000000000 --- a/Content.Shared/Procedural/PostGeneration/MiddleConnectionPostGen.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Places the specified entities on the middle connections between rooms -/// -public sealed partial class MiddleConnectionPostGen : IPostDunGen -{ - /// - /// How much overlap there needs to be between 2 rooms exactly. - /// - [DataField("overlapCount")] - public int OverlapCount = -1; - - /// - /// How many connections to spawn between rooms. - /// - [DataField("count")] - public int Count = 1; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass" - }; - - /// - /// If overlap > 1 then what should spawn on the edges. - /// - [DataField("edgeEntities")] public List EdgeEntities = new(); -} diff --git a/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs b/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs new file mode 100644 index 000000000000..d3b5672dcb01 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Places tiles / entities onto room entrances. +/// +/// +/// DungeonData keys are: +/// - Entrance +/// - FallbackTile +/// +public sealed partial class RoomEntranceDunGen : IDunGenLayer; diff --git a/Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs b/Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs deleted file mode 100644 index 5fd78b0540ae..000000000000 --- a/Content.Shared/Procedural/PostGeneration/RoomEntrancePostGen.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Shared.Maps; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Places tiles / entities onto room entrances. -/// -public sealed partial class RoomEntrancePostGen : IPostDunGen -{ - [DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Entities = new() - { - "CableApcExtension", - "AirlockGlass", - }; - - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; -} diff --git a/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs b/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs new file mode 100644 index 000000000000..ec8349c671bd --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs @@ -0,0 +1,19 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Connects dungeons via points that get subdivided. +/// +public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer +{ + /// + /// Will divide the distance between the start and end points so that no subdivision is more than these metres away. + /// + [DataField] + public int DivisionDistance = 10; + + /// + /// How much each subdivision can vary from the middle. + /// + [DataField] + public float VarianceMax = 0.35f; +} diff --git a/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs b/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs new file mode 100644 index 000000000000..a5c790cb22fc --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs @@ -0,0 +1,13 @@ +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns on the boundary tiles of rooms. +/// +public sealed partial class WallMountDunGen : IDunGenLayer +{ + /// + /// Chance per free tile to spawn a wallmount. + /// + [DataField] + public double Prob = 0.1; +} diff --git a/Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs b/Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs deleted file mode 100644 index 1fbdedf56103..000000000000 --- a/Content.Shared/Procedural/PostGeneration/WallMountPostGen.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Maps; -using Content.Shared.Storage; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Procedural.PostGeneration; - -/// -/// Spawns on the boundary tiles of rooms. -/// -public sealed partial class WallMountPostGen : IPostDunGen -{ - [DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Tile = "FloorSteel"; - - [DataField("spawns")] - public List Spawns = new(); - - /// - /// Chance per free tile to spawn a wallmount. - /// - [DataField("prob")] - public double Prob = 0.1; -} diff --git a/Content.Shared/Procedural/PostGeneration/WormCorridorPostGen.cs b/Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs similarity index 73% rename from Content.Shared/Procedural/PostGeneration/WormCorridorPostGen.cs rename to Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs index c57d92ef9568..b71e845a7337 100644 --- a/Content.Shared/Procedural/PostGeneration/WormCorridorPostGen.cs +++ b/Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs @@ -1,14 +1,10 @@ -using Content.Shared.Maps; -using Content.Shared.Procedural.DungeonGenerators; -using Robust.Shared.Prototypes; - namespace Content.Shared.Procedural.PostGeneration; // Ime a worm /// /// Generates worm corridors. /// -public sealed partial class WormCorridorPostGen : IPostDunGen +public sealed partial class WormCorridorDunGen : IDunGenLayer { [DataField] public int PathLimit = 2048; @@ -31,9 +27,6 @@ public sealed partial class WormCorridorPostGen : IPostDunGen [DataField] public Angle MaxAngleChange = Angle.FromDegrees(45); - [DataField] - public ProtoId Tile = "FloorSteel"; - /// /// How wide to make the corridor. /// diff --git a/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs b/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs index 81390e5f65a0..62edb36db93a 100644 --- a/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs +++ b/Content.Shared/Salvage/SharedSalvageSystem.Magnet.cs @@ -32,14 +32,14 @@ public ISalvageMagnetOffering GetSalvageOffering(int seed) var layers = new Dictionary(); // If we ever add more random layers will need to Next on these. - foreach (var layer in configProto.PostGeneration) + foreach (var layer in configProto.Layers) { switch (layer) { - case BiomePostGen: + case BiomeDunGen: rand.Next(); break; - case BiomeMarkerLayerPostGen marker: + case BiomeMarkerLayerDunGen marker: for (var i = 0; i < marker.Count; i++) { var proto = _proto.Index(marker.MarkerTemplate).Pick(rand); diff --git a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs index a382e943ff94..db2cbaa138b8 100644 --- a/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs +++ b/Content.Shared/Shuttles/Systems/SharedShuttleSystem.cs @@ -18,7 +18,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem [Dependency] protected readonly SharedTransformSystem XformSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; - public const float FTLRange = 512f; + public const float FTLRange = 256f; public const float FTLBufferRange = 8f; private EntityQuery _gridQuery; diff --git a/Content.Shared/Storage/EntitySpawnEntry.cs b/Content.Shared/Storage/EntitySpawnEntry.cs index 792459c72f72..6e24681c2dbd 100644 --- a/Content.Shared/Storage/EntitySpawnEntry.cs +++ b/Content.Shared/Storage/EntitySpawnEntry.cs @@ -5,6 +5,19 @@ namespace Content.Shared.Storage; +/// +/// Prototype wrapper around +/// +[Prototype] +public sealed class EntitySpawnEntryPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = string.Empty; + + [DataField] + public List Entries = new(); +} + /// /// Dictates a list of items that can be spawned. /// diff --git a/Resources/Prototypes/Entities/Stations/base.yml b/Resources/Prototypes/Entities/Stations/base.yml index 8a0d6c400304..7b5809158834 100644 --- a/Resources/Prototypes/Entities/Stations/base.yml +++ b/Resources/Prototypes/Entities/Stations/base.yml @@ -46,17 +46,26 @@ path: /Maps/Shuttles/cargo.yml - type: GridSpawn groups: - trade: + vgroid: !type:DungeonSpawnGroup + minimumDistance: 1000 + nameDataset: names_borer + addComponents: + - type: Gravity + enabled: true + inherent: true + protos: + - VGRoid + trade: !type:GridSpawnGroup addComponents: - type: ProtectedGrid - type: TradeStation paths: - /Maps/Shuttles/trading_outpost.yml - mining: + mining: !type:GridSpawnGroup paths: - /Maps/Shuttles/mining.yml # Spawn last - ruins: + ruins: !type:GridSpawnGroup hide: true nameGrid: true minCount: 2 diff --git a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml index d131805bf51a..e14bf26e0dbd 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/asteroid.yml @@ -1,5 +1,7 @@ #TODO: Someone should probably move the ore vein prototypes into their own file, or otherwise split this up in some way. This should not be 1.5k lines long. +# Anyway +# See WallRock variants for the remappings. #Asteroid rock - type: entity @@ -639,21 +641,28 @@ description: An ore vein rich with coal. suffix: Coal components: - - type: OreVein - oreChance: 1.0 - currentOre: OreCoal - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_coal + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockCoal + WallRockBasalt: WallRockBasaltCoal + WallRockChromite: WallRockChromiteCoal + WallRockSand: WallRockSandCoal + WallRockSnow: WallRockSnowCoal + - type: OreVein + oreChance: 1.0 + currentOre: OreCoal + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_coal - type: entity id: WallRockGold @@ -661,21 +670,28 @@ description: An ore vein rich with gold. suffix: Gold components: - - type: OreVein - oreChance: 1.0 - currentOre: OreGold - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_gold + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockGold + WallRockBasalt: WallRockBasaltGold + WallRockChromite: WallRockChromiteGold + WallRockSand: WallRockSandGold + WallRockSnow: WallRockSnowGold + - type: OreVein + oreChance: 1.0 + currentOre: OreGold + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_gold - type: entity id: WallRockPlasma @@ -683,21 +699,28 @@ description: An ore vein rich with plasma. suffix: Plasma components: - - type: OreVein - oreChance: 1.0 - currentOre: OrePlasma - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_phoron + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockPlasma + WallRockBasalt: WallRockBasaltPlasma + WallRockChromite: WallRockChromitePlasma + WallRockSand: WallRockSandPlasma + WallRockSnow: WallRockSnowPlasma + - type: OreVein + oreChance: 1.0 + currentOre: OrePlasma + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_phoron - type: entity id: WallRockQuartz @@ -705,21 +728,28 @@ description: An ore vein rich with quartz. suffix: Quartz components: - - type: OreVein - oreChance: 1.0 - currentOre: OreSpaceQuartz - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_quartz + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockQuartz + WallRockBasalt: WallRockBasaltQuartz + WallRockChromite: WallRockChromiteQuartz + WallRockSand: WallRockSandQuartz + WallRockSnow: WallRockSnowQuartz + - type: OreVein + oreChance: 1.0 + currentOre: OreSpaceQuartz + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_quartz - type: entity id: WallRockSilver @@ -727,21 +757,28 @@ description: An ore vein rich with silver. suffix: Silver components: - - type: OreVein - oreChance: 1.0 - currentOre: OreSilver - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_silver + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockSilver + WallRockBasalt: WallRockBasaltSilver + WallRockChromite: WallRockChromiteSilver + WallRockSand: WallRockSandSilver + WallRockSnow: WallRockSnowSilver + - type: OreVein + oreChance: 1.0 + currentOre: OreSilver + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_silver # Yes I know it drops steel but we may get smelting at some point - type: entity @@ -750,6 +787,13 @@ description: An ore vein rich with iron. suffix: Iron components: + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockTin + WallRockBasalt: WallRockBasaltTin + WallRockChromite: WallRockChromiteTin + WallRockSand: WallRockSandTin + WallRockSnow: WallRockSnowTin - type: OreVein oreChance: 1.0 currentOre: OreSteel @@ -772,21 +816,28 @@ description: An ore vein rich with uranium. suffix: Uranium components: - - type: OreVein - oreChance: 1.0 - currentOre: OreUranium - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_uranium + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockUranium + WallRockBasalt: WallRockBasaltUranium + WallRockChromite: WallRockChromiteUranium + WallRockSand: WallRockSandUranium + WallRockSnow: WallRockSnowUranium + - type: OreVein + oreChance: 1.0 + currentOre: OreUranium + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_uranium - type: entity @@ -795,21 +846,28 @@ description: An ore vein rich with bananium. suffix: Bananium components: - - type: OreVein - oreChance: 1.0 - currentOre: OreBananium - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_bananium + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockBananium + WallRockBasalt: WallRockBasaltBananium + WallRockChromite: WallRockChromiteBananium + WallRockSand: WallRockSandBananium + WallRockSnow: WallRockSnowBananium + - type: OreVein + oreChance: 1.0 + currentOre: OreBananium + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_bananium - type: entity id: WallRockArtifactFragment @@ -817,21 +875,28 @@ description: A rock wall. What's that sticking out of it? suffix: Artifact Fragment components: - - type: OreVein - oreChance: 1.0 - currentOre: OreArtifactFragment - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_artifact_fragment + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockArtifactFragment + WallRockBasalt: WallRockBasaltArtifactFragment + WallRockChromite: WallRockChromiteArtifactFragment + WallRockSand: WallRockSandArtifactFragment + WallRockSnow: WallRockSnowArtifactFragment + - type: OreVein + oreChance: 1.0 + currentOre: OreArtifactFragment + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_artifact_fragment - type: entity id: WallRockSalt @@ -839,21 +904,28 @@ description: An ore vein rich with salt. suffix: Salt components: - - type: OreVein - oreChance: 1.0 - currentOre: OreSalt - - type: Sprite - layers: - - state: rock - - map: [ "enum.EdgeLayer.South" ] - state: rock_south - - map: [ "enum.EdgeLayer.East" ] - state: rock_east - - map: [ "enum.EdgeLayer.North" ] - state: rock_north - - map: [ "enum.EdgeLayer.West" ] - state: rock_west - - state: rock_salt + - type: EntityRemap + mask: + AsteroidRock: AsteroidRockSalt + WallRockBasalt: WallRockBasaltSalt + WallRockChromite: WallRockChromiteSalt + WallRockSand: WallRockSandSalt + WallRockSnow: WallRockSnowSalt + - type: OreVein + oreChance: 1.0 + currentOre: OreSalt + - type: Sprite + layers: + - state: rock + - map: [ "enum.EdgeLayer.South" ] + state: rock_south + - map: [ "enum.EdgeLayer.East" ] + state: rock_east + - map: [ "enum.EdgeLayer.North" ] + state: rock_north + - map: [ "enum.EdgeLayer.West" ] + state: rock_west + - state: rock_salt # Basalt variants - type: entity diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid.yml b/Resources/Prototypes/Procedural/Magnet/asteroid.yml index a21b709afada..c20b80af55bf 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid.yml @@ -15,7 +15,8 @@ - type: dungeonConfig id: BlobAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1500 capStd: 32 iterations: 3 @@ -28,22 +29,22 @@ fractalType: FBm octaves: 2 lacunarity: 2 - # Everything else - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre # Multiple smaller asteroids # This is a pain so we generate fewer tiles - type: dungeonConfig id: ClusterAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1000 capStd: 32 layers: @@ -55,21 +56,21 @@ fractalType: FBm octaves: 2 lacunarity: 2 - # Everything else - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre # Long and spindly, less smooth than blob - type: dungeonConfig id: SpindlyAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1500 capStd: 32 layers: @@ -82,20 +83,21 @@ octaves: 3 lacunarity: 2 cellularDistanceFunction: Euclidean - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre # Lots of holes in it - type: dungeonConfig id: SwissCheeseAsteroid # Floor generation - generator: !type:NoiseDunGen + layers: + - !type:NoiseDunGen tileCap: 1500 capStd: 32 layers: @@ -107,12 +109,11 @@ fractalType: FBm octaves: 2 lacunarity: 2 - # Everything else - postGeneration: - # Generate biome - - !type:BiomePostGen - biomeTemplate: Asteroid - # Generate ore veins - - !type:MarkerLayerPostGen - markerTemplate: AsteroidOre + # Generate biome + - !type:BiomeDunGen + biomeTemplate: Asteroid + + # Generate ore veins + - !type:BiomeMarkerLayerDunGen + markerTemplate: AsteroidOre diff --git a/Resources/Prototypes/Procedural/dungeon_configs.yml b/Resources/Prototypes/Procedural/dungeon_configs.yml index 3614e4e787fa..b55d5a9e6977 100644 --- a/Resources/Prototypes/Procedural/dungeon_configs.yml +++ b/Resources/Prototypes/Procedural/dungeon_configs.yml @@ -1,361 +1,284 @@ +# Base configs - type: dungeonConfig - id: Experiment - generator: !type:PrefabDunGen - roomWhitelist: - - SalvageExperiment + id: PlanetBase + layers: + - !type:PrefabDunGen presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:CorridorPostGen - width: 3 + - Bucket + - Wow + - SpaceShip + - Tall - - !type:DungeonEntrancePostGen - count: 2 + - !type:CorridorDunGen + width: 3 - - !type:RoomEntrancePostGen - entities: - - CableApcExtension - - AirlockGlass + - !type:DungeonEntranceDunGen + count: 2 - - !type:EntranceFlankPostGen - entities: - - Grille - - Window + - !type:RoomEntranceDunGen - - !type:ExternalWindowPostGen - entities: - - Grille - - Window + - !type:EntranceFlankDunGen - - !type:WallMountPostGen - spawns: - # Posters - - id: RandomPosterLegit - orGroup: content - - id: ExtinguisherCabinetFilled - prob: 0.2 - orGroup: content - - id: RandomPainting - prob: 0.05 - orGroup: content - - id: IntercomCommon - prob: 0.1 - orGroup: content + - !type:ExternalWindowDunGen - - !type:BoundaryWallPostGen - tile: FloorSteel - wall: WallSolid - cornerWall: WallReinforced + - !type:WallMountDunGen - - !type:JunctionPostGen - width: 1 + - !type:BoundaryWallDunGen - - !type:JunctionPostGen + - !type:JunctionDunGen + width: 1 - - !type:AutoCablingPostGen + - !type:JunctionDunGen - - !type:CornerClutterPostGen - contents: - - id: PottedPlantRandom - amount: 1 + - !type:AutoCablingDunGen - - !type:CorridorDecalSkirtingPostGen - color: "#D381C996" - cardinalDecals: - South: BrickTileWhiteLineS - East: BrickTileWhiteLineE - North: BrickTileWhiteLineN - West: BrickTileWhiteLineW - cornerDecals: - SouthEast: BrickTileWhiteCornerSe - SouthWest: BrickTileWhiteCornerSw - NorthEast: BrickTileWhiteCornerNe - NorthWest: BrickTileWhiteCornerNw - pocketDecals: - SouthWest: BrickTileWhiteInnerSw - SouthEast: BrickTileWhiteInnerSe - NorthWest: BrickTileWhiteInnerNw - NorthEast: BrickTileWhiteInnerNe + - !type:CornerClutterDunGen + - !type:CorridorDecalSkirtingDunGen + cardinalDecals: + South: BrickTileWhiteLineS + East: BrickTileWhiteLineE + North: BrickTileWhiteLineN + West: BrickTileWhiteLineW + cornerDecals: + SouthEast: BrickTileWhiteCornerSe + SouthWest: BrickTileWhiteCornerSw + NorthEast: BrickTileWhiteCornerNe + NorthWest: BrickTileWhiteCornerNw + pocketDecals: + SouthWest: BrickTileWhiteInnerSw + SouthEast: BrickTileWhiteInnerSe + NorthWest: BrickTileWhiteInnerNw + NorthEast: BrickTileWhiteInnerNe +# Setups - type: dungeonConfig - id: LavaBrig - generator: !type:PrefabDunGen - roomWhitelist: - - LavaBrig - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:CorridorPostGen - width: 3 - - - !type:DungeonEntrancePostGen - count: 2 - - - !type:RoomEntrancePostGen - entities: - - CableApcExtension - - AirlockSecurityGlassLocked - - - !type:EntranceFlankPostGen - entities: - - Grille - - Window - - - !type:ExternalWindowPostGen - entities: - - Grille - - Window - - - !type:WallMountPostGen - spawns: - # Posters - - id: RandomPosterLegit - orGroup: content - - id: ExtinguisherCabinetFilled - prob: 0.2 - orGroup: content - - id: RandomPainting - prob: 0.05 - orGroup: content - - id: IntercomCommon - prob: 0.1 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorSteel - wall: WallSolid - cornerWall: WallReinforced - - - !type:JunctionPostGen - width: 1 - - - !type:JunctionPostGen - - - !type:AutoCablingPostGen - - - !type:CornerClutterPostGen - contents: - - id: PottedPlantRandom - amount: 1 - - - !type:CorridorDecalSkirtingPostGen - color: "#DE3A3A96" - cardinalDecals: - South: BrickTileWhiteLineS - East: BrickTileWhiteLineE - North: BrickTileWhiteLineN - West: BrickTileWhiteLineW - cornerDecals: - SouthEast: BrickTileWhiteCornerSe - SouthWest: BrickTileWhiteCornerSw - NorthEast: BrickTileWhiteCornerNe - NorthWest: BrickTileWhiteCornerNw - pocketDecals: - SouthWest: BrickTileWhiteInnerSw - SouthEast: BrickTileWhiteInnerSe - NorthWest: BrickTileWhiteInnerNw - NorthEast: BrickTileWhiteInnerNe + id: Experiment + data: + colors: + Decals: "#D381C996" + entities: + Cabling: CableApcExtension + CornerWalls: WallReinforced + Walls: WallSolid + spawnGroups: + CornerClutter: BaseClutter + Entrance: BaseAirlock + EntranceFlank: BaseWindow + Junction: BaseAirlock + WallMounts: ScienceLabsWalls + Window: BaseWindow + tiles: + FallbackTile: FloorSteel + whitelists: + Rooms: + tags: + - SalvageExperiment + layers: + - !type:PrototypeDunGen + proto: PlanetBase - type: dungeonConfig - id: Mineshaft - generator: !type:PrefabDunGen - tile: FloorCaveDrought - roomWhitelist: - - Mineshaft + id: Haunted + data: + entities: + Walls: WallRock + tiles: + FallbackTile: FloorCaveDrought + whitelists: + Rooms: + tags: + - Mineshaft + layers: + - !type:PrefabDunGen presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - - !type:CorridorPostGen - tile: FloorCaveDrought - width: 3 - - - !type:DungeonEntrancePostGen - count: 5 - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:RoomEntrancePostGen - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:EntranceFlankPostGen - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:ExternalWindowPostGen - tile: FloorCaveDrought - entities: - - RandomWoodenWall - - - !type:WallMountPostGen - tile: FloorCaveDrought - spawns: - # Ore - - id: WallRockSalt - prob: 0.6 - orGroup: content - - id: WallRockCoal - prob: 0.6 - orGroup: content - - id: WallRockTin - prob: 0.4 - orGroup: content - - id: WallMining - prob: 0.8 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorCaveDrought - wall: WallRock - cornerWall: WallRock + - Bucket + - Wow + - SpaceShip + - Tall - - !type:AutoCablingPostGen - entity: Catwalk + - !type:WormCorridorDunGen + width: 3 - - !type:JunctionPostGen - tile: FloorCaveDrought - width: 3 - entities: - - RandomWoodenSupport + - !type:CorridorClutterDunGen + contents: + - id: FloraStalagmite1 + - id: FloraStalagmite2 + - id: FloraStalagmite3 + - id: FloraStalagmite4 + - id: FloraStalagmite5 + - id: FloraStalagmite6 - - !type:CornerClutterPostGen - contents: - - id: RandomStalagmiteOrCrystal - amount: 1 + - !type:BoundaryWallDunGen - type: dungeonConfig - id: SnowyLabs - generator: !type:PrefabDunGen - roomWhitelist: - - SnowyLabs - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:CorridorPostGen - width: 3 - - - !type:DungeonEntrancePostGen - count: 2 - - - !type:RoomEntrancePostGen - entities: - - CableApcExtension - - AirlockFreezerHydroponicsLocked - - - !type:EntranceFlankPostGen - entities: - - Grille - - Window - - - !type:ExternalWindowPostGen - entities: - - Grille - - Window - - - !type:WallMountPostGen - spawns: - # Posters - - id: RandomPosterLegit - orGroup: content - - id: ExtinguisherCabinetFilled - prob: 0.2 - orGroup: content - - id: RandomPainting - prob: 0.05 - orGroup: content - - id: IntercomScience - prob: 0.1 - orGroup: content - - - !type:BoundaryWallPostGen - tile: FloorSteel - wall: WallSilver - cornerWall: WallSilver - - - !type:JunctionPostGen - width: 1 - entities: - - AirlockGlass - - - !type:JunctionPostGen - entities: - - AirlockGlass - - - !type:AutoCablingPostGen - - - !type:CornerClutterPostGen - contents: - - id: PottedPlantRandom - amount: 1 - - - !type:CorridorDecalSkirtingPostGen - color: "#4cc7aa96" - cardinalDecals: - South: BrickTileWhiteLineS - East: BrickTileWhiteLineE - North: BrickTileWhiteLineN - West: BrickTileWhiteLineW - cornerDecals: - SouthEast: BrickTileWhiteCornerSe - SouthWest: BrickTileWhiteCornerSw - NorthEast: BrickTileWhiteCornerNe - NorthWest: BrickTileWhiteCornerNw - pocketDecals: - SouthWest: BrickTileWhiteInnerSw - SouthEast: BrickTileWhiteInnerSe - NorthWest: BrickTileWhiteInnerNw - NorthEast: BrickTileWhiteInnerNe - -# todo: Add a biome dungeon generator -# Add corridor first gens that place rooms on top -# Add a worm corridor gen (place subsequent corridors somewhere randomly along the path) -# Place room entrances on ends of corridors touching a tile -# Remove all room tiles from corridors -# Fix paths up and try to reconnect all corridor tiles -# Add a postgen step to spread rooms out, though it shouldn't spread into corridor exteriors + id: LavaBrig + data: + colors: + Decals: "#DE3A3A96" + entities: + Cabling: CableApcExtension + CornerWalls: WallReinforced + Walls: WallSolid + spawnGroups: + CornerClutter: BaseClutter + Entrance: LavaBrigEntrance + EntranceFlank: BaseWindow + Junction: BaseAirlock + WallMounts: ScienceLabsWalls + Window: BaseWindow + whitelists: + Rooms: + tags: + - LavaBrig + layers: + - !type:PrototypeDunGen + proto: PlanetBase - type: dungeonConfig - id: Haunted - generator: !type:PrefabDunGen - tile: FloorCaveDrought - roomWhitelist: - - Mineshaft - presets: - - Bucket - - Wow - - SpaceShip - - Tall - postGeneration: - - !type:WormCorridorPostGen - width: 3 - tile: FloorCaveDrought - - - !type:CorridorClutterPostGen - contents: - - id: FloraStalagmite1 - - id: FloraStalagmite2 - - id: FloraStalagmite3 - - id: FloraStalagmite4 - - id: FloraStalagmite5 - - id: FloraStalagmite6 + id: Mineshaft + data: + entities: + Cabling: Catwalk + spawnGroups: + CornerClutter: MineshaftClutter + Entrance: BaseWoodWall + EntranceFlank: BaseWoodWall + Junction: BaseWoodSupport + Window: BaseWoodWall + tiles: + FallbackTile: FloorCaveDrought + whitelists: + Rooms: + tags: + - Mineshaft + layers: + - !type:PrototypeDunGen + proto: PlanetBase - - !type:BoundaryWallPostGen - tile: FloorCaveDrought - wall: WallRock +- type: dungeonConfig + id: SnowyLabs + data: + colors: + Decals: "#4cc7aa96" + entities: + Cabling: CableApcExtension + CornerWalls: WallSilver + Walls: WallSilver + spawnGroups: + CornerClutter: BaseClutter + Entrance: SnowyLabsEntrance + EntranceFlank: BaseWindow + Junction: BaseAirlock + WallMounts: SnowyLabsWalls + Window: BaseWindow + tiles: + FallbackTile: FloorSteel + whitelists: + Rooms: + tags: + - SnowyLabs + layers: + - !type:PrototypeDunGen + proto: PlanetBase + +# Spawn groups +# Basic +- type: entitySpawnEntry + id: BaseClutter + entries: + - id: PottedPlantRandom + amount: 1 + +- type: entitySpawnEntry + id: BaseAirlock + entries: + - id: CableApcExtension + - id: AirlockGlass + +- type: entitySpawnEntry + id: BaseWindow + entries: + - id: Grille + - id: Window + +# Lava brig +- type: entitySpawnEntry + id: LavaBrigEntrance + entries: + - id: CableApcExtension + - id: AirlockSecurityGlassLocked + +# Mineshaft +- type: entitySpawnEntry + id: BaseWoodWall + entries: + - id: RandomWoodenWall + +- type: entitySpawnEntry + id: BaseWoodSupport + entries: + - id: RandomWoodenSupport + +- type: entitySpawnEntry + id: MineshaftClutter + entries: + - id: RandomStalagmiteOrCrystal + amount: 1 + +- type: entitySpawnEntry + id: MineshaftWalls + entries: + # Ore + - id: WallRockSalt + prob: 0.6 + orGroup: content + - id: WallRockCoal + prob: 0.6 + orGroup: content + - id: WallRockTin + prob: 0.4 + orGroup: content + - id: WallMining + prob: 0.8 + orGroup: content + +# Science lab +- type: entitySpawnEntry + id: ScienceLabsWalls + entries: + # Posters + - id: RandomPosterLegit + orGroup: content + - id: ExtinguisherCabinetFilled + prob: 0.2 + orGroup: content + - id: RandomPainting + prob: 0.05 + orGroup: content + - id: IntercomCommon + prob: 0.1 + orGroup: content + +# Snowy labs +- type: entitySpawnEntry + id: SnowyLabsEntrance + entries: + - id: CableApcExtension + - id: AirlockFreezerHydroponicsLocked + +- type: entitySpawnEntry + id: SnowyLabsWalls + entries: + # Posters + - id: RandomPosterLegit + orGroup: content + - id: ExtinguisherCabinetFilled + prob: 0.2 + orGroup: content + - id: RandomPainting + prob: 0.05 + orGroup: content + - id: IntercomScience + prob: 0.1 + orGroup: content diff --git a/Resources/Prototypes/Procedural/vgroid.yml b/Resources/Prototypes/Procedural/vgroid.yml new file mode 100644 index 000000000000..49e956e73f5b --- /dev/null +++ b/Resources/Prototypes/Procedural/vgroid.yml @@ -0,0 +1,191 @@ +# Okay so my general thought is this: +# 1. Generate the large mass +# 2. Generate smaller masses offset +# 3. Generate N normal dungeons around the larger mass, preferably near the border +# 4. Generate large paths / small paths around the place +# 5. Spawn ores + fill the rest and the normal stuff + +# If you want mobs they needed to be added at specific steps due to how dungeons work at the moment. + +- type: dungeonConfig + id: VGRoid + layers: + - !type:PrototypeDunGen + proto: VGRoidBlob + - !type:PrototypeDunGen + proto: VGRoidExterior + - !type:PrototypeDunGen + proto: VGRoidSmaller + - !type:PrototypeDunGen + proto: VGRoidSmallPaths + # Fill + - !type:PrototypeDunGen + proto: VGRoidFill + # Ores + - !type:OreDunGen + replacement: IronRock + entity: IronRockIron + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockCoal + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockQuartz + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockSalt + count: 50 + minGroupSize: 20 + maxGroupSize: 30 + - !type:OreDunGen + replacement: IronRock + entity: IronRockGold + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockSilver + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockPlasma + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockUranium + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockBananium + count: 50 + minGroupSize: 10 + maxGroupSize: 20 + - !type:OreDunGen + replacement: IronRock + entity: IronRockArtifactFragment + count: 50 + minGroupSize: 2 + maxGroupSize: 4 + +# Configs +- type: dungeonConfig + id: VGRoidBlob + layers: + - !type:NoiseDistanceDunGen + size: 272, 272 + distanceConfig: !type:DunGenEuclideanSquaredDistance + blendWeight: 0.80 + layers: + - tile: FloorAsteroidSand + threshold: 0.50 + noise: + frequency: 0.010 + noiseType: OpenSimplex2 + fractalType: FBm + octaves: 5 + lacunarity: 2 + gain: 0.5 + +- type: dungeonConfig + id: VGRoidSmaller + minOffset: 40 + maxOffset: 60 + layers: + - !type:NoiseDistanceDunGen + size: 150, 150 + distanceConfig: !type:DunGenEuclideanSquaredDistance + layers: + - tile: FloorAsteroidSand + threshold: 0.50 + noise: + frequency: 0.080 + noiseType: OpenSimplex2 + fractalType: FBm + octaves: 5 + lacunarity: 1.5 + gain: 0.5 + +- type: dungeonConfig + id: VGRoidExterior + reserveTiles: true + data: + tiles: + FallbackTile: PlatingAsteroid + WidenTile: FloorAsteroidSand + layers: + - !type:PrototypeDunGen + proto: VGRoidExteriorDungeons + - !type:SplineDungeonConnectorDunGen + +- type: dungeonConfig + id: VGRoidExteriorDungeons + reserveTiles: true + minCount: 2 + maxCount: 3 + layers: + - !type:ExteriorDunGen + proto: Experiment + - !type:MobsDunGen + minCount: 5 + maxCount: 8 + groups: + - id: MobXeno + amount: 1 + +#- type: dungeonConfig +# id: VGRoidInteriorDungeons +# minCount: 3 +# maxCount: 5 +# # Just randomly spawn these in bounds, doesn't really matter if they go out. + +- type: dungeonConfig + id: VGRoidSmallPaths + reserveTiles: true + layers: + - !type:ReplaceTileDunGen + layers: + - tile: FloorAsteroidSand + threshold: 0.75 + noise: + frequency: 0.040 + noiseType: OpenSimplex2 + fractalType: Ridged + lacunarity: 1.5 + octaves: 2 + gain: 2.0 + # Mobs + # If you want exterior dungeon mobs add them under the prototype. + - !type:MobsDunGen + minCount: 20 + maxCount: 30 + groups: + - id: MobXeno + amount: 1 + +#- type: dungeonConfig +# id: VGRoidOres + +# Fill with rocks. +- type: dungeonConfig + id: VGRoidFill + data: + entities: + Fill: IronRock + layers: + - !type:FillGridDunGen From a981f99b066cf6bf11990ca155c41a44777a15f6 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:14:39 +0300 Subject: [PATCH 002/242] New anomaly behaviour: Invisibility (#29120) * invisible anomaly * good luck --- Resources/Locale/en-US/anomaly/anomaly.ftl | 1 + Resources/Prototypes/Anomaly/behaviours.yml | 33 +++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Resources/Locale/en-US/anomaly/anomaly.ftl b/Resources/Locale/en-US/anomaly/anomaly.ftl index da5882fa62f0..c8d099777d40 100644 --- a/Resources/Locale/en-US/anomaly/anomaly.ftl +++ b/Resources/Locale/en-US/anomaly/anomaly.ftl @@ -89,6 +89,7 @@ anomaly-behavior-rapid = The frequency of the pulsation is much higher, but its anomaly-behavior-reflect = A protective coating was detected. anomaly-behavior-nonsensivity = A weak reaction to particles was detected. anomaly-behavior-sensivity = Amplified reaction to particles was detected. +anomaly-behavior-invisibility = Light wave distortion has been detected. anomaly-behavior-secret = Interference detected. Some data cannot be read anomaly-behavior-inconstancy = [color=crimson]Impermanence has been detected. Particle types can change over time.[/color] anomaly-behavior-fast = [color=crimson]The pulsation frequency is strongly increased.[/color] diff --git a/Resources/Prototypes/Anomaly/behaviours.yml b/Resources/Prototypes/Anomaly/behaviours.yml index dea1ddb69c36..924a4a75004b 100644 --- a/Resources/Prototypes/Anomaly/behaviours.yml +++ b/Resources/Prototypes/Anomaly/behaviours.yml @@ -20,10 +20,12 @@ InconstancyParticle: 0.5 FullUnknown: 0.5 Jumping: 0.3 + Invisibility: 0.5 #Complex FastUnknown: 0.2 JumpingUnknown: 0.1 InconstancyParticleUnknown: 0.1 + InvisibilityJumping: 0.1 # Easy x0.5 point production @@ -153,6 +155,17 @@ randomStartSecretMin: 4 randomStartSecretMax: 6 +- type: anomalyBehavior + id: Invisibility + earnPointModifier: 1.6 + description: anomaly-behavior-invisibility + components: + - type: Stealth + maxVisibility: 1.2 + - type: StealthOnMove + passiveVisibilityRate: -0.37 + movementVisibilityRate: 0.20 + # Complex Effects - type: anomalyBehavior @@ -170,7 +183,6 @@ randomStartSecretMin: 3 randomStartSecretMax: 5 - - type: anomalyBehavior id: FastUnknown earnPointModifier: 1.9 @@ -191,4 +203,21 @@ prob: 0.5 - type: SecretDataAnomaly randomStartSecretMin: 3 - randomStartSecretMax: 5 \ No newline at end of file + randomStartSecretMax: 5 + +- type: anomalyBehavior + id: InvisibilityJumping + earnPointModifier: 1.95 + description: anomaly-behavior-invisibility + components: + - type: ChaoticJump + jumpMinInterval: 15 + jumpMaxInterval: 25 + rangeMin: 1 + rangeMax: 1 + effect: PuddleSparkle + - type: Stealth + maxVisibility: 1.2 + - type: StealthOnMove + passiveVisibilityRate: -0.37 + movementVisibilityRate: 0.20 \ No newline at end of file From 833320a34a5c724770ce72424472b108fe1e6f11 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 15:15:47 +0000 Subject: [PATCH 003/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index e1a72faa15e9..f19099f899a5 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: MACMAN2003 - changes: - - message: Nuclear operatives now only need 20 players to be readied up again instead - of 35. - type: Tweak - id: 6364 - time: '2024-04-17T03:19:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27036 - author: Bellwether changes: - message: The nun hood now appears in the Chaplain's loadout. @@ -3826,3 +3818,10 @@ id: 6863 time: '2024-07-03T05:27:33.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/28117 +- author: TheShuEd + changes: + - message: anomalies have the ability to gain invisibility behavior + type: Add + id: 6864 + time: '2024-07-03T15:14:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29120 From ac87f7a9777c919b2b743e0d880b2cad3cbc2cd3 Mon Sep 17 00:00:00 2001 From: Alex Pavlenko Date: Wed, 3 Jul 2024 18:37:40 +0300 Subject: [PATCH 004/242] feat: allow developers to customize vscode settings, closes #29285 (#29294) --- .vscode/settings.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0e0d3ae890cd..000000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "omnisharp.analyzeOpenDocumentsOnly": true, - "dotnet.defaultSolution": "SpaceStation14.sln" -} From 54c659f4aaabbaebca012a099a00530b08a56fa4 Mon Sep 17 00:00:00 2001 From: ArkiveDev <95712736+ArkiveDev@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:59:29 -0400 Subject: [PATCH 005/242] Allow construction of rotated railings (#29687) * Remove southRotation from railing structures * Curly Braces --- .../Construction/Graphs/structures/railing.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml index f050c65c42c4..1772bddea0bb 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/railing.yml @@ -6,32 +6,28 @@ edges: - to: railing completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 1 doAfter: 2 - to: railingCorner completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 2 doAfter: 2.5 - to: railingCornerSmall completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 1 doAfter: 2 - to: railingRound completed: - - !type:SnapToGrid - southRotation: true + - !type:SnapToGrid { } steps: - material: MetalRod amount: 2 From 5c1aa578ef991a2d3327321b18dbb04180402f90 Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 3 Jul 2024 17:00:36 +0000 Subject: [PATCH 006/242] 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 f19099f899a5..51396e8a1843 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Bellwether - changes: - - message: The nun hood now appears in the Chaplain's loadout. - type: Tweak - id: 6365 - time: '2024-04-17T03:36:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27025 - author: iNV3RT3D & metalgearsloth changes: - message: Added a jukebox. @@ -3825,3 +3818,10 @@ id: 6864 time: '2024-07-03T15:14:39.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29120 +- author: ArkiveDev + changes: + - message: Railings can now be constructed in all 4 orientations. + type: Fix + id: 6865 + time: '2024-07-03T16:59:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29687 From 5198c87597c496d21e503a567e7ffb75051b519a Mon Sep 17 00:00:00 2001 From: Interrobang01 <113810873+Interrobang01@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:43:42 -0700 Subject: [PATCH 007/242] improved wrench description (#29700) Lefty latchy, righty removey --- Resources/Prototypes/Entities/Objects/Tools/tools.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index 7930482960ff..36e629c21c11 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -92,7 +92,7 @@ name: wrench parent: BaseItem id: Wrench - description: 'A common tool for assembly and disassembly. Remember: righty tighty, lefty loosey.' + description: 'A common tool for assembly and disassembly. Remember: lefty latchy, righty removey.' components: - type: EmitSoundOnLand sound: From e612ccda92edf052053b17a2c2f2ee08bdad08e9 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 3 Jul 2024 21:13:49 -0400 Subject: [PATCH 008/242] add apc power draw to stat value command (#29701) add apc stat value --- .../UserInterface/StatValuesCommand.cs | 46 ++++++++++++++++++- .../en-US/commands/stat-values-command.ftl | 5 ++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Content.Server/UserInterface/StatValuesCommand.cs b/Content.Server/UserInterface/StatValuesCommand.cs index f0c4f531d018..cb599f7b09f7 100644 --- a/Content.Server/UserInterface/StatValuesCommand.cs +++ b/Content.Server/UserInterface/StatValuesCommand.cs @@ -4,6 +4,7 @@ using Content.Server.Cargo.Systems; using Content.Server.EUI; using Content.Server.Item; +using Content.Server.Power.Components; using Content.Shared.Administration; using Content.Shared.Item; using Content.Shared.Materials; @@ -56,6 +57,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) case "itemsize": message = GetItem(); break; + case "drawrate": + message = GetDrawRateMessage(); + break; default: shell.WriteError(Loc.GetString("stat-values-invalid", ("arg", args[0]))); return; @@ -70,7 +74,7 @@ public CompletionResult GetCompletion(IConsoleShell shell, string[] args) { if (args.Length == 1) { - return CompletionResult.FromOptions(new[] { "cargosell", "lathesell", "melee" }); + return CompletionResult.FromOptions(new[] { "cargosell", "lathesell", "melee", "itemsize", "drawrate" }); } return CompletionResult.Empty; @@ -250,4 +254,44 @@ private StatValuesEuiMessage GetLatheMessage() return state; } + + private StatValuesEuiMessage GetDrawRateMessage() + { + var values = new List(); + var powerName = _factory.GetComponentName(typeof(ApcPowerReceiverComponent)); + + foreach (var proto in _proto.EnumeratePrototypes()) + { + if (proto.Abstract || + !proto.Components.TryGetValue(powerName, + out var powerConsumer)) + { + continue; + } + + var comp = (ApcPowerReceiverComponent) powerConsumer.Component; + + if (comp.Load == 0) + continue; + + values.Add(new[] + { + proto.ID, + comp.Load.ToString(CultureInfo.InvariantCulture), + }); + } + + var state = new StatValuesEuiMessage + { + Title = Loc.GetString("stat-drawrate-values"), + Headers = new List + { + Loc.GetString("stat-drawrate-id"), + Loc.GetString("stat-drawrate-rate"), + }, + Values = values, + }; + + return state; + } } diff --git a/Resources/Locale/en-US/commands/stat-values-command.ftl b/Resources/Locale/en-US/commands/stat-values-command.ftl index 99c6bd194e3c..67a211adabe5 100644 --- a/Resources/Locale/en-US/commands/stat-values-command.ftl +++ b/Resources/Locale/en-US/commands/stat-values-command.ftl @@ -18,3 +18,8 @@ stat-lathe-sell = Sell price stat-item-values = Item sizes stat-item-id = ID stat-item-price = Size + +# Draw Rate +stat-drawrate-values = APC draw rate +stat-drawrate-id = ID +stat-drawrate-rate = Draw Rate (W) From 9120f5fada1a2f3f9714ff64245b80161b0e691e Mon Sep 17 00:00:00 2001 From: Rinary <72972221+Rinary1@users.noreply.github.com> Date: Thu, 4 Jul 2024 04:25:25 +0300 Subject: [PATCH 009/242] Dynamic Radial Menus (#29678) * fix * Clean Some Code * Some Commentaries * Update Content.Client/UserInterface/Controls/RadialContainer.cs * Update Content.Client/UserInterface/Controls/RadialContainer.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- Content.Client/UserInterface/Controls/RadialContainer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Content.Client/UserInterface/Controls/RadialContainer.cs b/Content.Client/UserInterface/Controls/RadialContainer.cs index be263d12772e..be9b8817a065 100644 --- a/Content.Client/UserInterface/Controls/RadialContainer.cs +++ b/Content.Client/UserInterface/Controls/RadialContainer.cs @@ -67,11 +67,18 @@ public RadialContainer() { } - + protected override void Draw(DrawingHandleScreen handle) { + + const float baseRadius = 100f; + const float radiusIncrement = 5f; + var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible); var childCount = children.Count(); + + // Add padding from the center at higher child counts so they don't overlap. + Radius = baseRadius + (childCount * radiusIncrement); // Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements var arc = AngularRange.Y - AngularRange.X; From 143151f2846979702cc23cd26e848c66aaa6f8a1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 01:26:31 +0000 Subject: [PATCH 010/242] 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 51396e8a1843..074bfc3492b2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: iNV3RT3D & metalgearsloth - changes: - - message: Added a jukebox. - type: Add - id: 6366 - time: '2024-04-17T09:27:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26736 - author: Vermidia changes: - message: Pirate Accent and Mobster accent will respect capitalization better now @@ -3825,3 +3818,10 @@ id: 6865 time: '2024-07-03T16:59:29.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29687 +- author: Rinary + changes: + - message: Fixed radial menus overlapping where there's many icons. + type: Fix + id: 6866 + time: '2024-07-04T01:25:25.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29678 From 48ae8ce0a88d0ecfcb48d22cfa103405ddbd7d63 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:29:07 -0700 Subject: [PATCH 011/242] Fixes objects changing physics behavior after being pulled (#29694) * Fixes pull rotation logic * cleaner condition * even less code * I CHANGED MY MIND * first one * second one --------- Co-authored-by: plykiya --- Content.Shared/Movement/Pulling/Systems/PullingSystem.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index edc8ad516172..f563440af048 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -310,7 +310,7 @@ public bool IsPulling(EntityUid puller, PullerComponent? component = null) private void OnReleasePulledObject(ICommonSession? session) { - if (session?.AttachedEntity is not {Valid: true} player) + if (session?.AttachedEntity is not { Valid: true } player) { return; } @@ -447,6 +447,9 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, pullerComp.Pulling = pullableUid; pullableComp.Puller = pullerUid; + // store the pulled entity's physics FixedRotation setting in case we change it + pullableComp.PrevFixedRotation = pullablePhysics.FixedRotation; + // joint state handling will manage its own state if (!_timing.ApplyingState) { @@ -465,8 +468,6 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, _physics.SetFixedRotation(pullableUid, pullableComp.FixedRotationOnPull, body: pullablePhysics); } - pullableComp.PrevFixedRotation = pullablePhysics.FixedRotation; - // Messaging var message = new PullStartedMessage(pullerUid, pullableUid); _modifierSystem.RefreshMovementSpeedModifiers(pullerUid); From 406cf7adcdf604e2ba4e11a899a223024aee767c Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 01:30:13 +0000 Subject: [PATCH 012/242] 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 074bfc3492b2..fa2e53f8b63b 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Vermidia - changes: - - message: Pirate Accent and Mobster accent will respect capitalization better now - type: Fix - id: 6367 - time: '2024-04-17T10:04:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26644 - author: metalgearsloth changes: - message: Fix lobby character preview not updating upon changing characters. @@ -3825,3 +3818,10 @@ id: 6866 time: '2024-07-04T01:25:25.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29678 +- author: Plykiya + changes: + - message: An object's physics properly returns to normal after being pulled. + type: Fix + id: 6867 + time: '2024-07-04T01:29:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29694 From 62fcb6bd96802b2540ecee245f2797c9e7ff52f4 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:51:46 -0700 Subject: [PATCH 013/242] Makes portable flashers destructable (#29564) Makes portable flashers destructible Co-authored-by: plykiya --- Resources/Prototypes/Entities/Objects/Weapons/security.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 74a42c07742c..24bc21c43679 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -180,7 +180,7 @@ - type: entity name: portable flasher - parent: BaseStructure + parent: BaseMachine id: PortableFlasher description: An ultrabright flashbulb with a proximity trigger, useful for making an area security-only. components: @@ -196,14 +196,11 @@ !type:PhysShapeCircle radius: 2 repeating: true - - type: Anchorable - type: Sprite sprite: Objects/Weapons/pflash.rsi layers: - state: "off" map: ["enum.ProximityTriggerVisualLayers.Base"] - - type: InteractionOutline - - type: Physics - type: Fixtures fixtures: fix1: From 0d80021433564b2ce6ab2bc06d887be457a95d34 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 01:52:52 +0000 Subject: [PATCH 014/242] 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 fa2e53f8b63b..4089a09eaf27 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Fix lobby character preview not updating upon changing characters. - type: Fix - id: 6368 - time: '2024-04-17T10:06:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27043 - author: Beck Thompson changes: - message: You now must equip gloved weapons (E.g boxing gloves) to use them. @@ -3825,3 +3818,10 @@ id: 6867 time: '2024-07-04T01:29:07.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29694 +- author: Plykiya + changes: + - message: You can now destroy portable flashers. + type: Fix + id: 6868 + time: '2024-07-04T01:51:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29564 From 223ade9b3fe00a2bfbfbd0e79394a88b407fe7df Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Thu, 4 Jul 2024 04:01:03 +0200 Subject: [PATCH 015/242] Starting gear for vox crewmembers (#29685) * tank harness * weh * Suit Storage Whitelist * Revert "Suit Storage Whitelist" This reverts commit b1f503573c2936642a2d7627c4852153ec71ce79. * suit storage filter * vox spawn gear * weh --- .../en-US/preferences/loadout-groups.ftl | 5 + .../Entities/Clothing/OuterClothing/vests.yml | 12 +++ .../Entities/Clothing/base_clothing.yml | 9 ++ .../Entities/Objects/Tools/gas_tanks.yml | 3 + .../Loadouts/Miscellaneous/survival.yml | 72 +++++++++++++ .../Prototypes/Loadouts/loadout_groups.yml | 47 +++++++++ .../Prototypes/Loadouts/role_loadouts.yml | 96 ++++++++++++++++-- Resources/Prototypes/tags.yml | 3 + .../equipped-OUTERCLOTHING-vox.png | Bin 0 -> 661 bytes .../equipped-OUTERCLOTHING.png | Bin 0 -> 800 bytes .../Vests/tankharness.rsi/icon.png | Bin 0 -> 307 bytes .../Vests/tankharness.rsi/inhand-left.png | Bin 0 -> 265 bytes .../Vests/tankharness.rsi/inhand-right.png | Bin 0 -> 289 bytes .../Vests/tankharness.rsi/meta.json | 30 ++++++ 14 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING-vox.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json diff --git a/Resources/Locale/en-US/preferences/loadout-groups.ftl b/Resources/Locale/en-US/preferences/loadout-groups.ftl index 1c28509b2d43..28785e305c89 100644 --- a/Resources/Locale/en-US/preferences/loadout-groups.ftl +++ b/Resources/Locale/en-US/preferences/loadout-groups.ftl @@ -1,3 +1,6 @@ +# Errors +loadout-group-species-restriction = This item is not available for your current species. + # Miscellaneous loadout-group-trinkets = Trinkets loadout-group-glasses = Glasses @@ -9,6 +12,8 @@ loadout-group-survival-clown = Clown Survival Box loadout-group-survival-medical = Medical Survival Box loadout-group-survival-security = Security Survival Box loadout-group-survival-syndicate = Github is forcing me to write text that is literally twice-impossible for the player to ever see, send help +loadout-group-breath-tool = Species-dependent breath tools +loadout-group-tank-harness = Species-specific survival equipment # Command loadout-group-captain-head = Captain head diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml index 74b6ec74fb55..1fd46e8e7633 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml @@ -78,3 +78,15 @@ sprite: Clothing/OuterClothing/Vests/vest.rsi - type: Clothing sprite: Clothing/OuterClothing/Vests/vest.rsi + +#Tank Harness +- type: entity + parent: [ClothingOuterBase, AllowSuitStorageClothingGasTanks] + id: ClothingOuterVestTank + name: tank harness + description: A simple harness that can hold a gas tank. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Vests/tankharness.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Vests/tankharness.rsi diff --git a/Resources/Prototypes/Entities/Clothing/base_clothing.yml b/Resources/Prototypes/Entities/Clothing/base_clothing.yml index 55bc2fd3e6a2..a96ca2d23c8c 100644 --- a/Resources/Prototypes/Entities/Clothing/base_clothing.yml +++ b/Resources/Prototypes/Entities/Clothing/base_clothing.yml @@ -25,6 +25,15 @@ components: - type: AllowSuitStorage +- type: entity + abstract: true + id: AllowSuitStorageClothingGasTanks + components: + - type: AllowSuitStorage + whitelist: + tags: + - GasTank + # for clothing that has a single item slot to insert and alt click out. # inheritors add a whitelisted slot named item - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml index 53423e84a4fe..b825647ac13e 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/gas_tanks.yml @@ -9,6 +9,9 @@ - type: Item size: Normal sprite: Objects/Tanks/generic.rsi + - type: Tag + tags: + - GasTank - type: Clothing quickEquip: false sprite: Objects/Tanks/generic.rsi diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml b/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml index 5a9f6a0d07a2..7b2cb5d6ff30 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/survival.yml @@ -19,6 +19,13 @@ - Moth - Reptilian +- type: loadoutEffectGroup + id: EffectSpeciesVox + effects: + - !type:SpeciesLoadoutEffect + species: + - Vox + # Basic - type: loadout id: EmergencyOxygen @@ -180,3 +187,68 @@ storage: back: - BoxSurvivalSyndicateNitrogen + +# Pre-equipped species gear + +# Full Tank Equipped +- type: loadout + id: LoadoutSpeciesEVANitrogen + equipment: GearEVANitrogen + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearEVANitrogen + equipment: + suitstorage: NitrogenTankFilled + +# Tank Harness +- type: loadout + id: LoadoutTankHarness + equipment: GearTankHarness + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearTankHarness + equipment: + outerClothing: ClothingOuterVestTank + +# Breaths Tool On Face +- type: loadout + id: LoadoutSpeciesBreathTool + equipment: GearSpeciesBreathTool + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearSpeciesBreathTool + equipment: + mask: ClothingMaskBreath + +- type: loadout + id: LoadoutSpeciesBreathToolMedical + equipment: GearSpeciesBreathToolMedical + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearSpeciesBreathToolMedical + equipment: + mask: ClothingMaskBreathMedical + +- type: loadout + id: LoadoutSpeciesBreathToolSecurity + equipment: GearSpeciesBreathToolSecurity + effects: + - !type:GroupLoadoutEffect + proto: EffectSpeciesVox + +- type: startingGear + id: GearSpeciesBreathToolSecurity + equipment: + mask: ClothingMaskGasSecurity diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index 57d2e5827d99..b1d267cc00c9 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -35,13 +35,23 @@ - GlassesJamjar - GlassesJensen +- type: loadoutGroup + id: GroupTankHarness + name: loadout-group-tank-harness + minLimit: 1 + hidden: true + loadouts: + - LoadoutTankHarness + - type: loadoutGroup id: Survival name: loadout-group-survival-basic + minLimit: 3 hidden: true loadouts: - EmergencyNitrogen - EmergencyOxygen + - LoadoutSpeciesEVANitrogen # Command - type: loadoutGroup @@ -408,10 +418,12 @@ - type: loadoutGroup id: SurvivalClown name: loadout-group-survival-clown + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenClown - EmergencyOxygenClown + - LoadoutSpeciesEVANitrogen - type: loadoutGroup id: MimeHead @@ -729,10 +741,12 @@ - type: loadoutGroup id: SurvivalExtended name: loadout-group-survival-extended + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenExtended - EmergencyOxygenExtended + - LoadoutSpeciesEVANitrogen # Science - type: loadoutGroup @@ -1007,10 +1021,12 @@ - type: loadoutGroup id: SurvivalSecurity name: loadout-group-survival-security + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenSecurity - EmergencyOxygenSecurity + - LoadoutSpeciesEVANitrogen # Medical - type: loadoutGroup @@ -1187,10 +1203,12 @@ - type: loadoutGroup id: SurvivalMedical name: loadout-group-survival-medical + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenMedical - EmergencyOxygenMedical + - LoadoutSpeciesEVANitrogen # Wildcards - type: loadoutGroup @@ -1220,7 +1238,36 @@ - type: loadoutGroup id: SurvivalSyndicate name: loadout-group-survival-syndicate + minLimit: 2 hidden: true loadouts: - EmergencyNitrogenSyndicate - EmergencyOxygenSyndicate + - LoadoutSpeciesEVANitrogen + +- type: loadoutGroup + id: GroupSpeciesBreathTool + name: loadout-group-breath-tool + minLimit: 1 + maxLimit: 1 + hidden: true + loadouts: + - LoadoutSpeciesBreathTool + +- type: loadoutGroup + id: GroupSpeciesBreathToolMedical + name: loadout-group-breath-tool + minLimit: 1 + maxLimit: 1 + hidden: true + loadouts: + - LoadoutSpeciesBreathToolMedical + +- type: loadoutGroup + id: GroupSpeciesBreathToolSecurity + name: loadout-group-breath-tool + minLimit: 1 + maxLimit: 1 + hidden: true + loadouts: + - LoadoutSpeciesBreathToolSecurity diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 4f27c6949bcf..bb30cc182a61 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -9,11 +9,12 @@ - CaptainOuterClothing - Survival - Trinkets - + - GroupSpeciesBreathTool - type: roleLoadout id: JobHeadOfPersonnel groups: + - GroupTankHarness - HoPHead - HoPNeck - HoPJumpsuit @@ -22,11 +23,13 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool # Civilian - type: roleLoadout id: JobPassenger groups: + - GroupTankHarness - PassengerJumpsuit - CommonBackpack - PassengerFace @@ -36,10 +39,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobBartender groups: + - GroupTankHarness - BartenderHead - BartenderJumpsuit - CommonBackpack @@ -47,19 +52,23 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobServiceWorker groups: + - GroupTankHarness - BartenderJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobChef groups: + - GroupTankHarness - ChefHead - ChefMask - ChefJumpsuit @@ -68,29 +77,35 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobLibrarian groups: + - GroupTankHarness - LibrarianJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobLawyer groups: + - GroupTankHarness - LawyerNeck - LawyerJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobChaplain groups: + - GroupTankHarness - ChaplainHead - ChaplainMask - ChaplainNeck @@ -100,10 +115,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobJanitor groups: + - GroupTankHarness - JanitorHead - JanitorJumpsuit - JanitorGloves @@ -113,10 +130,12 @@ - Survival - Trinkets - JanitorPlunger + - GroupSpeciesBreathTool - type: roleLoadout id: JobBotanist groups: + - GroupTankHarness - BotanistHead - BotanistJumpsuit - BotanistBackpack @@ -124,10 +143,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobClown groups: + - GroupTankHarness - ClownHead - ClownJumpsuit - ClownBackpack @@ -140,6 +161,7 @@ - type: roleLoadout id: JobMime groups: + - GroupTankHarness - MimeHead - MimeMask - MimeJumpsuit @@ -152,6 +174,7 @@ - type: roleLoadout id: JobMusician groups: + - GroupTankHarness - MusicianJumpsuit - CommonBackpack - MusicianOuterClothing @@ -159,11 +182,13 @@ - Survival - Trinkets - Instruments + - GroupSpeciesBreathTool # Cargo - type: roleLoadout id: JobQuartermaster groups: + - GroupTankHarness - QuartermasterHead - QuartermasterNeck - QuartermasterJumpsuit @@ -173,10 +198,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobCargoTechnician groups: + - GroupTankHarness - CargoTechnicianHead - CargoTechnicianJumpsuit - CargoTechnicianBackpack @@ -185,21 +212,25 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobSalvageSpecialist groups: + - GroupTankHarness - SalvageSpecialistBackpack - SalvageSpecialistOuterClothing - SalvageSpecialistShoes - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool # Engineering - type: roleLoadout id: JobChiefEngineer groups: + - GroupTankHarness - ChiefEngineerHead - ChiefEngineerJumpsuit - StationEngineerBackpack @@ -208,18 +239,22 @@ - ChiefEngineerShoes - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobTechnicalAssistant groups: + - GroupTankHarness - TechnicalAssistantJumpsuit - StationEngineerBackpack - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobStationEngineer groups: + - GroupTankHarness - StationEngineerHead - StationEngineerJumpsuit - StationEngineerBackpack @@ -228,21 +263,25 @@ - StationEngineerID - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobAtmosphericTechnician groups: + - GroupTankHarness - AtmosphericTechnicianJumpsuit - AtmosphericTechnicianBackpack - AtmosphericTechnicianOuterClothing - AtmosphericTechnicianShoes - SurvivalExtended - Trinkets + - GroupSpeciesBreathTool # Science - type: roleLoadout id: JobResearchDirector groups: + - GroupTankHarness - ResearchDirectorHead - ResearchDirectorNeck - ResearchDirectorJumpsuit @@ -253,10 +292,12 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobScientist groups: + - GroupTankHarness - ScientistHead - ScientistNeck - ScientistJumpsuit @@ -268,15 +309,18 @@ - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobResearchAssistant groups: + - GroupTankHarness - ResearchAssistantJumpsuit - ScientistBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool # Security - type: roleLoadout @@ -291,6 +335,7 @@ - SecurityShoes - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobWarden @@ -303,6 +348,7 @@ - SecurityShoes - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobSecurityOfficer @@ -316,10 +362,12 @@ - SecurityBelt - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobDetective groups: + - GroupTankHarness - DetectiveHead - DetectiveNeck - DetectiveJumpsuit @@ -328,6 +376,7 @@ - SecurityShoes - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity - type: roleLoadout id: JobSecurityCadet @@ -336,11 +385,13 @@ - SecurityBackpack - SurvivalSecurity - Trinkets + - GroupSpeciesBreathToolSecurity # Medical - type: roleLoadout id: JobChiefMedicalOfficer groups: + - GroupTankHarness - ChiefMedicalOfficerHead - MedicalMask - ChiefMedicalOfficerJumpsuit @@ -352,10 +403,12 @@ - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobMedicalDoctor groups: + - GroupTankHarness - MedicalDoctorHead - MedicalMask - MedicalDoctorJumpsuit @@ -367,19 +420,23 @@ - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobMedicalIntern groups: + - GroupTankHarness - MedicalInternJumpsuit - MedicalBackpack - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobChemist groups: + - GroupTankHarness - MedicalMask - ChemistJumpsuit - MedicalGloves @@ -388,10 +445,12 @@ - MedicalShoes - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical - type: roleLoadout id: JobParamedic groups: + - GroupTankHarness - ParamedicHead - MedicalMask - ParamedicJumpsuit @@ -402,71 +461,92 @@ - Glasses - SurvivalMedical - Trinkets + - GroupSpeciesBreathToolMedical # Wildcards - type: roleLoadout id: JobZookeeper groups: + - GroupTankHarness - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobReporter groups: + - GroupTankHarness - ReporterJumpsuit - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobPsychologist groups: + - GroupTankHarness - MedicalBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool - type: roleLoadout id: JobBoxer groups: + - GroupTankHarness - BoxerJumpsuit - BoxerGloves - CommonBackpack - Glasses - Survival - Trinkets + - GroupSpeciesBreathTool -# These loadouts will be used without player configuration, thus they must be designed to work without manual selection +# These loadouts are used for non-crew spawns, like off-station antags and event mobs +# They will be used without player configuration, thus they will only ever apply what is forced by MinLimit - type: roleLoadout - id: LoadoutSurvivalStandard + id: RoleSurvivalStandard groups: - Survival + - GroupSpeciesBreathTool + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalClown + id: RoleSurvivalClown groups: - SurvivalClown + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalExtended + id: RoleSurvivalExtended groups: - SurvivalExtended + - GroupSpeciesBreathTool + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalMedical + id: RoleSurvivalMedical groups: - SurvivalMedical + - GroupSpeciesBreathToolMedical + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalSecurity + id: RoleSurvivalSecurity groups: - SurvivalSecurity + - GroupSpeciesBreathToolSecurity + - GroupTankHarness - type: roleLoadout - id: LoadoutSurvivalSyndicate + id: RoleSurvivalSyndicate groups: - SurvivalSyndicate + - GroupSpeciesBreathTool + - GroupTankHarness diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index ca593e8631c3..bc12ea0af215 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -623,6 +623,9 @@ - type: Tag id: GasScrubber +- type: Tag + id: GasTank + - type: Tag id: GasVent diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING-vox.png b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING-vox.png new file mode 100644 index 0000000000000000000000000000000000000000..712a04c614c73d17caba2e57a5a37243c076d601 GIT binary patch literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5he4R}c>anM1_mZ~ zPZ!6KinzBkee(_(2(->!;hGc}5abu&kaW6Dx*$vMP}3&;MIxt~ScO*34|UJv5b&Sl zurz%om)X4e-+SLKh|)9@QeZ?vjYcfL^7B0}?(u${TC?lSOdmB*JF#C9=L zI^glglVOI#?XljC1@`K5A3(W!%y2Obs# z9Di)s%JA_;a*f$+OV;T==koTyEt|dTz>XyhkDLSM@8Se%dF6e?Be&#^{qlWpy}2In zEV!Gez4uM;3yZD|QX6D_>P@6p{;bivT6&o;^qANYZT<2WGYf0pS4mjqGU^)KeE&W3 zsC}6BWRoMwG4&#UC3dd-Q+}v;HS3Rbbvu{jm`g6+;tHP*mOORcr{a0Jjm4t@Cz4Wa zsJAySm5pdKOsC=eqsS&y-UkYfjDaezf-dEyX93&xa)#NT~c+ zt@L45?zR2vQ{O)|Ivm9RCH8O9#t)A^3voDj_^E&1`r0&NqW|*M8?~<*E}9r|SNp*7 ziYGCg4eRzu@wLBNdf71N%jRpZey`f~UH4VC{WRY*={Czn1za!QWK5rKvueHl^s51x z^X~q3y}efBxYxl`^GK9+=J;JYD@<);T3K0RU1rCO7~9 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..51708c386a685b7888d7b399a57ccb90911d637c GIT binary patch literal 800 zcmV+*1K<3KP)pH(n&-?RCr$Pn!RosK@i86God30Vj0&4MU*HhB1I|E&_#Ze6nPRC zR*Jkvlp=nCl&+Bl)WLEN2y|dBJFCoTy&Q0}=d%s|uY`_WX6NI#vwOgeQfMFm0w4ea zAOHd&00PgGKtURINGy+_jC7{w%e=2Lt|$=9-AxmeYc_b5F~?P7)G?};b>&Cf%@fg zx$vS?$`t?wNudAS+^pE4ar#_(HxL7uPNyc=`FyVGM@MRSeXXcZv{;S}Yc89n=6+x7!uzl%&kI72fu_#1>>h00ck)1V8`; zKw$3)tbA*``tdyu`t_&Nod;mYL&3N)iGL05JP{{=XAfZC78LP-00@8p2!H?xfB*=j zB_RF_oBp?_S^zSTR40&keLzxRS?UACzXiAuf*fHG?)$X#edX-OV0Q4nkH=E`K?W!T z_{D_^c0aW;PeC_$H|X3U*nTqrHtN<5c*eMK8ryJf``Qve8?+4t2M6X1S>E2Rl&ttV zlxAL*J@4ulp8>W49k}W0C$r+iG)3x--+Zy}l%db{d_*h;3{?awZ`}^zJqd$B*D_yYRuoKg<^wi|V zYndCCH8;tmSSnvQ-s9BNv_OE-m0dYRCR?)ce5lS6tCpB z&v~AU4DxC}*Dg>N8+^(2{fv@^juSTL&ntc1Xmonhn{Vqf_Z6u_tO1eFH_Psi=JWp_ zb^rTO{_V2+q`4Gd>{@r!a`|6|P|>TO&e^|ZXJ80;$GCHcX~l|F9x5O`p00i_>zopr E0Ozi1fB*mh literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-right.png b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..56e13c98e6323484999554f404ac082a4a2e279c GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=FFaiwLn`LHy=mCjY#`A3kn!l0 zg*QCDn29@l616Z*ziDE}$kci0XwFv82@Pw^{5Gxmw|=9+`U)xEbU~nQ5IFF!^8MAw z)%U9Jo_d;XKWA%I^}CD5UzV5`N9@YE9rk`yV=IEt{G9_L`V}UPH$T1D@*_5>3CU`(8HrZRYxMnHt0*kX`>b zJYW7f|91Xy`v+^T?pjxF-ObP0l+XkK!YO<) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json new file mode 100644 index 000000000000..80cd5e0bd883 --- /dev/null +++ b/Resources/Textures/Clothing/OuterClothing/Vests/tankharness.rsi/meta.json @@ -0,0 +1,30 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Modified from tgstation vest sprite at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e by Errant for Space Station 14", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-OUTERCLOTHING-vox", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} From 3e3e050aafb93daa1eb017ee06b5e2a15fb3d315 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:29:26 -0400 Subject: [PATCH 016/242] Make all nukies humans (#29693) --- .../GameTicking/Rules/AntagLoadProfileRuleSystem.cs | 12 ++++++++++-- .../Components/AntagLoadProfileRuleCOmponent.cs | 12 +++++++++++- Resources/Prototypes/GameRules/roundstart.yml | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs index fd3fb6cd655e..b93904c685f8 100644 --- a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs @@ -30,10 +30,18 @@ private void OnSelectEntity(Entity ent, ref Antag var profile = args.Session != null ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile : HumanoidCharacterProfile.RandomWithSpecies(); - if (profile?.Species is not {} speciesId || !_proto.TryIndex(speciesId, out var species)) + + SpeciesPrototype? species; + if (ent.Comp.SpeciesOverride != null) + { + species = _proto.Index(ent.Comp.SpeciesOverride.Value); + } + else if (profile?.Species is not { } speciesId || !_proto.TryIndex(speciesId, out species)) + { species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); + } args.Entity = Spawn(species.Prototype); - _humanoid.LoadProfile(args.Entity.Value, profile); + _humanoid.LoadProfile(args.Entity.Value, profile?.WithSpecies(species.ID)); } } diff --git a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs index 5e58fd14fc04..0816902ad43b 100644 --- a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs +++ b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs @@ -1,7 +1,17 @@ +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Prototypes; + namespace Content.Server.GameTicking.Rules.Components; /// /// Makes this rules antags spawn a humanoid, either from the player's profile or a random one. /// [RegisterComponent] -public sealed partial class AntagLoadProfileRuleComponent : Component; +public sealed partial class AntagLoadProfileRuleComponent : Component +{ + /// + /// If specified, the profile loaded will be made into this species. + /// + [DataField] + public ProtoId? SpeciesOverride; +} diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index a7b749a35f9f..da198a25d098 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -81,6 +81,7 @@ - type: RuleGrids - type: AntagSelection - type: AntagLoadProfileRule + speciesOverride: Human - type: entity parent: BaseNukeopsRule From 97d39e0c2892bba94ea52c38004693d8e9bad1ee Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 02:30:32 +0000 Subject: [PATCH 017/242] 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 4089a09eaf27..4a0b1b1a53f3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Beck Thompson - changes: - - message: You now must equip gloved weapons (E.g boxing gloves) to use them. - type: Fix - id: 6369 - time: '2024-04-17T10:10:04.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26762 - author: slarticodefast changes: - message: The gas analyzer now tells you the volume of scanned objects. Tanks inside @@ -3825,3 +3818,10 @@ id: 6868 time: '2024-07-04T01:51:46.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29564 +- author: EmoGarbage404 + changes: + - message: All nuclear operatives are now humans. + type: Tweak + id: 6869 + time: '2024-07-04T02:29:26.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29693 From 2988ac383924fd428823ee7125241cd27aaf8fb3 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:11:01 +1000 Subject: [PATCH 018/242] Make vox roundstart (#29704) * Make vox roundstart I believe all the issues are fixed. * Click detection bandaid --- Content.Client/Clickable/ClickableComponent.cs | 7 +++++-- Resources/Prototypes/Species/vox.yml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Content.Client/Clickable/ClickableComponent.cs b/Content.Client/Clickable/ClickableComponent.cs index 6f75df46830e..987473ca46cd 100644 --- a/Content.Client/Clickable/ClickableComponent.cs +++ b/Content.Client/Clickable/ClickableComponent.cs @@ -48,7 +48,7 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero; // First we get `localPos`, the clicked location in the sprite-coordinate frame. - var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); + var entityXform = Matrix3Helpers.CreateInverseTransform(spritePos, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping); var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix); // Check explicitly defined click-able bounds @@ -58,8 +58,11 @@ public bool CheckClick(SpriteComponent sprite, TransformComponent transform, Ent // Next check each individual sprite layer using automatically computed click maps. foreach (var spriteLayer in sprite.AllLayers) { - if (!spriteLayer.Visible || spriteLayer is not Layer layer) + // TODO: Move this to a system and also use SpriteSystem.IsVisible instead. + if (!spriteLayer.Visible || spriteLayer is not Layer layer || layer.CopyToShaderParameters != null) + { continue; + } // Check the layer's texture, if it has one if (layer.Texture != null) diff --git a/Resources/Prototypes/Species/vox.yml b/Resources/Prototypes/Species/vox.yml index e3fdb2bf08f0..7419f3f277e9 100644 --- a/Resources/Prototypes/Species/vox.yml +++ b/Resources/Prototypes/Species/vox.yml @@ -1,7 +1,7 @@ - type: species id: Vox name: species-name-vox - roundStart: false # sad + roundStart: true prototype: MobVox sprites: MobVoxSprites markingLimits: MobVoxMarkingLimits From acf186490ca0864b4169f01888ac78eb7ba12695 Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 07:12:08 +0000 Subject: [PATCH 019/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4a0b1b1a53f3..06c545ef630a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: slarticodefast - changes: - - message: The gas analyzer now tells you the volume of scanned objects. Tanks inside - canisters now show up as well. - type: Add - - message: The gas analyzer now shows the amount of moles inside the scanned pipe - element instead of the whole pipe network. - type: Tweak - id: 6370 - time: '2024-04-17T17:42:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25720 - author: deltanedas changes: - message: The Chameleon Projector has been added to the uplink for 7 TC, it lets @@ -3825,3 +3814,10 @@ id: 6869 time: '2024-07-04T02:29:26.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29693 +- author: metalgearsloth + changes: + - message: Made vox roundstart. + type: Tweak + id: 6870 + time: '2024-07-04T07:11:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29704 From 1471bd5b53090634f0ed6ef4f08fb54dbc640ced Mon Sep 17 00:00:00 2001 From: JIPDawg <51352440+JIPDawg@users.noreply.github.com> Date: Thu, 4 Jul 2024 02:17:47 -0500 Subject: [PATCH 020/242] Added Health Analyzer to basic treatment module. (#29696) Removed dropped, added Health Analyzer to Basic Treatment Module Co-authored-by: JIP --- .../Entities/Objects/Specific/Robotics/borg_modules.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 74e91a768cdd..1566a84e52da 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -345,12 +345,12 @@ - state: icon-treatment - type: ItemBorgModule items: + - HandheldHealthAnalyzerUnpowered - Brutepack10Lingering - Ointment10Lingering - Gauze10Lingering - Bloodpack10Lingering - Syringe - - Dropper - type: entity id: BorgModuleDefibrillator From bc7907728c8c4104390f4ad355f36be7157ee8bf Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 07:18:53 +0000 Subject: [PATCH 021/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 06c545ef630a..e41c5f4389db 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: deltanedas - changes: - - message: The Chameleon Projector has been added to the uplink for 7 TC, it lets - you disguise as anything (within reason). - type: Add - id: 6371 - time: '2024-04-17T21:48:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26691 - author: ShadowCommander changes: - message: Fixed PDA and ID card name, job, and access not getting set on some jobs. @@ -3821,3 +3813,11 @@ id: 6870 time: '2024-07-04T07:11:02.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29704 +- author: JIPDawg + changes: + - message: Changed the basic treatment module to include a Health Analyzer and removed + the dropper. + type: Tweak + id: 6871 + time: '2024-07-04T07:17:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29696 From 8d015f5c9ff60107dccdf35fa48e1558728ff269 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 4 Jul 2024 10:02:43 +0200 Subject: [PATCH 022/242] Fix animation looping bugs. (#29457) Summary of the problem is in the corresponding engine commit: https://github.com/space-wizards/RobustToolbox/commit/a4ea5a462092c93cca941b073d080e284d73c2a6 This commit requires engine master right now. I think #29144 is probably the most severe one, but I touched Jittering and RotatingLight too since they seemed sus too. Fixes #29144 Co-authored-by: metalgearsloth --- Content.Client/Jittering/JitteringSystem.cs | 3 +++ Content.Client/Light/EntitySystems/LightBehaviorSystem.cs | 3 +++ Content.Client/Light/EntitySystems/RotatingLightSystem.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Content.Client/Jittering/JitteringSystem.cs b/Content.Client/Jittering/JitteringSystem.cs index 185bd490d3b1..0c11a1396355 100644 --- a/Content.Client/Jittering/JitteringSystem.cs +++ b/Content.Client/Jittering/JitteringSystem.cs @@ -48,6 +48,9 @@ private void OnAnimationCompleted(EntityUid uid, JitteringComponent jittering, A if(args.Key != _jitterAnimationKey) return; + if (!args.Finished) + return; + if (TryComp(uid, out AnimationPlayerComponent? animationPlayer) && TryComp(uid, out SpriteComponent? sprite)) _animationPlayer.Play(uid, animationPlayer, GetAnimation(jittering, sprite), _jitterAnimationKey); diff --git a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs index 11f69165cf65..ca19d8522c56 100644 --- a/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs +++ b/Content.Client/Light/EntitySystems/LightBehaviorSystem.cs @@ -19,6 +19,9 @@ public override void Initialize() private void OnBehaviorAnimationCompleted(EntityUid uid, LightBehaviourComponent component, AnimationCompletedEvent args) { + if (!args.Finished) + return; + var container = component.Animations.FirstOrDefault(x => x.FullKey == args.Key); if (container == null) diff --git a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs index 842c13dedfe9..5c2c4e4c875b 100644 --- a/Content.Client/Light/EntitySystems/RotatingLightSystem.cs +++ b/Content.Client/Light/EntitySystems/RotatingLightSystem.cs @@ -69,6 +69,9 @@ private void OnAfterAutoHandleState(EntityUid uid, RotatingLightComponent comp, private void OnAnimationComplete(EntityUid uid, RotatingLightComponent comp, AnimationCompletedEvent args) { + if (!args.Finished) + return; + PlayAnimation(uid, comp); } From 2cf42bf7a41ad27c37034151ca6e53852106cfff Mon Sep 17 00:00:00 2001 From: PJBot Date: Thu, 4 Jul 2024 08:03:49 +0000 Subject: [PATCH 023/242] 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 e41c5f4389db..8d5076c356a4 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: ShadowCommander - changes: - - message: Fixed PDA and ID card name, job, and access not getting set on some jobs. - type: Fix - id: 6372 - time: '2024-04-17T22:16:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27062 - author: Krunk changes: - message: Head of Security and Warden now have armored and unarmored variants of @@ -3821,3 +3814,10 @@ id: 6871 time: '2024-07-04T07:17:47.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29696 +- author: PJB3005 + changes: + - message: Fixed flashlights and similar permanently getting stuck blinking. + type: Fix + id: 6872 + time: '2024-07-04T08:02:43.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29457 From fb9c03186c557905d6be41bfbf8e782aa24a6c7f Mon Sep 17 00:00:00 2001 From: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:01:31 +0000 Subject: [PATCH 024/242] fix(marathon): Fixing more issues (#29411) --- Resources/Maps/marathon.yml | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/Resources/Maps/marathon.yml b/Resources/Maps/marathon.yml index 7657d84e1703..0a5da0bd3165 100644 --- a/Resources/Maps/marathon.yml +++ b/Resources/Maps/marathon.yml @@ -11089,7 +11089,7 @@ entities: pos: -20.5,-5.5 parent: 30 - type: Door - secondsUntilStateChange: -295.55695 + secondsUntilStateChange: -537.51984 state: Opening - type: DeviceLinkSource lastSignals: @@ -54789,12 +54789,6 @@ entities: parent: 30 - proto: DisposalBend entities: - - uid: 6965 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -12.5,-9.5 - parent: 30 - uid: 7196 components: - type: Transform @@ -55856,6 +55850,12 @@ entities: - type: Transform pos: -21.5,-18.5 parent: 30 + - uid: 8689 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -11.5,-9.5 + parent: 30 - uid: 11018 components: - type: Transform @@ -55876,6 +55876,12 @@ entities: - type: Transform pos: 4.5,-3.5 parent: 30 + - uid: 11447 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -10.5,-9.5 + parent: 30 - uid: 12678 components: - type: Transform @@ -58721,12 +58727,6 @@ entities: rot: -1.5707963267948966 rad pos: -9.5,-9.5 parent: 30 - - uid: 14440 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -10.5,-9.5 - parent: 30 - uid: 14442 components: - type: Transform @@ -60147,6 +60147,12 @@ entities: parent: 30 - proto: DisposalYJunction entities: + - uid: 6965 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -12.5,-9.5 + parent: 30 - uid: 14077 components: - type: Transform @@ -64472,14 +64478,14 @@ entities: - type: Transform pos: 26.5,-26.5 parent: 30 -- proto: GasMinerNitrogen +- proto: GasMinerNitrogenStationLarge entities: - - uid: 8689 + - uid: 11446 components: - type: Transform pos: 26.5,-22.5 parent: 30 -- proto: GasMinerOxygen +- proto: GasMinerOxygenStationLarge entities: - uid: 8690 components: From 96ff998d1870fc4963c6697c58ec431a557e6c9f Mon Sep 17 00:00:00 2001 From: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:02:02 +0000 Subject: [PATCH 025/242] fix(packed): Fix a couple of issues (#29422) * fix(packed): Fix a couple of issues * Name the signal button for bar --- Resources/Maps/packed.yml | 50 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Resources/Maps/packed.yml b/Resources/Maps/packed.yml index e41fdcd0eb58..6954fe7892de 100644 --- a/Resources/Maps/packed.yml +++ b/Resources/Maps/packed.yml @@ -33890,11 +33890,6 @@ entities: - type: Transform pos: 28.5,-40.5 parent: 2 - - uid: 5262 - components: - - type: Transform - pos: 28.5,-41.5 - parent: 2 - proto: CrateEngineeringCableBulk entities: - uid: 782 @@ -65602,25 +65597,6 @@ entities: - Pressed: Toggle 4503: - Pressed: Toggle - - uid: 1151 - components: - - type: MetaData - name: Bar Shutters - - type: Transform - pos: 40.15871,-2.7952938 - parent: 2 - - type: DeviceLinkSource - linkedPorts: - 1154: - - Pressed: Toggle - 913: - - Pressed: Toggle - 6512: - - Pressed: Toggle - 1658: - - Pressed: Toggle - 419: - - Pressed: Toggle - uid: 1471 components: - type: MetaData @@ -65750,6 +65726,28 @@ entities: - Pressed: Toggle 12824: - Pressed: Toggle +- proto: SignalButtonDirectional + entities: + - uid: 543 + components: + - type: MetaData + name: Bar Shutters + - type: Transform + rot: 3.141592653589793 rad + pos: 40.5,-8.5 + parent: 2 + - type: DeviceLinkSource + linkedPorts: + 419: + - Pressed: Toggle + 1658: + - Pressed: Toggle + 6512: + - Pressed: Toggle + 913: + - Pressed: Toggle + 1154: + - Pressed: Toggle - proto: SignAnomaly entities: - uid: 7376 @@ -81933,10 +81931,10 @@ entities: parent: 2 - proto: WindoorKitchenHydroponicsLocked entities: - - uid: 543 + - uid: 1151 components: - type: Transform - rot: -1.5707963267948966 rad + rot: 1.5707963267948966 rad pos: 40.5,-7.5 parent: 2 - uid: 5055 From 38cd8ad6a142f1046d675911de4eeb40a8568a07 Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:55:00 +0300 Subject: [PATCH 026/242] Fix Vox clothing in character creation menu (#29709) Update vox.yml --- Resources/Prototypes/Entities/Mobs/Species/vox.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index ec8035563b72..4d5239e50c56 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -112,4 +112,16 @@ species: Vox - type: Body prototype: Vox + - type: Inventory + speciesId: vox + displacements: + jumpsuit: + layer: + sprite: Mobs/Species/Vox/displacement.rsi + state: jumpsuit + copyToShaderParameters: + # Value required, provide a dummy. Gets overridden when applied. + layerKey: dummy + parameterTexture: displacementMap + parameterUV: displacementUV From bc7c1217130efb7106dd7b60052ed970d57bbb1a Mon Sep 17 00:00:00 2001 From: jmcb Date: Thu, 4 Jul 2024 19:30:33 +0100 Subject: [PATCH 027/242] Fix typo in pineapple pizza description (#29714) --- .../Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml index 3004dc4cb2b0..f822fb7c2da8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pizza.yml @@ -401,7 +401,7 @@ name: Hawaiian pizza parent: FoodPizzaBase id: FoodPizzaPineapple - description: Makes people burst into tears. Tears of joy or sadness depends on the persons fondness for pineapple. + description: Makes people burst into tears. Tears of joy or sadness depends on the person's fondness for pineapple. components: - type: FlavorProfile flavors: From 8e05e1cfed591534b97647be351784a407daf14e Mon Sep 17 00:00:00 2001 From: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:02:53 +0000 Subject: [PATCH 028/242] Move checking code for BlacklistedRange to the right place in sqlite (#29389) --- Content.Server/Database/ServerDbSqlite.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Content.Server/Database/ServerDbSqlite.cs b/Content.Server/Database/ServerDbSqlite.cs index ce6f97a11710..204d9fca4fb5 100644 --- a/Content.Server/Database/ServerDbSqlite.cs +++ b/Content.Server/Database/ServerDbSqlite.cs @@ -131,6 +131,10 @@ private static async Task> GetAllBans( if (exemptFlags is { } exempt) { + // Any flag to bypass BlacklistedRange bans. + if (exempt != ServerBanExemptFlags.None) + exempt |= ServerBanExemptFlags.BlacklistedRange; + query = query.Where(b => (b.ExemptFlags & exempt) == 0); } @@ -144,15 +148,12 @@ private static bool BanMatches(ServerBan ban, ServerBanExemptFlags? exemptFlags, bool newPlayer) { - // Any flag to bypass BlacklistedRange bans. - var exemptFromBlacklistedRange = exemptFlags != null && exemptFlags.Value != ServerBanExemptFlags.None; - if (!exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP) && address != null && ban.Address is not null && address.IsInSubnet(ban.Address.ToTuple().Value) && (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) || - newPlayer && !exemptFromBlacklistedRange)) + newPlayer)) { return true; } From 8adcb379649a1bc79b352d3023a27ee2e7772d48 Mon Sep 17 00:00:00 2001 From: Jezithyr Date: Fri, 5 Jul 2024 00:59:16 -0700 Subject: [PATCH 029/242] Re-enabling nukie species(except vox), added antag species blacklisting * Revert "Make all Nukies humans (#29693)" This reverts commit 3e3e050aafb93daa1eb017ee06b5e2a15fb3d315. * Implemented species blacklist * Re-enabled all species as Nukies except for Vox because loadouts don't support breathing alternative gases yet. --- .../GameTicking/Rules/AntagLoadProfileRuleSystem.cs | 12 +++++++----- .../Components/AntagLoadProfileRuleCOmponent.cs | 8 +++++++- Resources/Prototypes/GameRules/roundstart.yml | 5 +++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs index b93904c685f8..3527e2a11c2b 100644 --- a/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/AntagLoadProfileRuleSystem.cs @@ -31,14 +31,16 @@ private void OnSelectEntity(Entity ent, ref Antag ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile : HumanoidCharacterProfile.RandomWithSpecies(); - SpeciesPrototype? species; - if (ent.Comp.SpeciesOverride != null) + + if (profile?.Species is not { } speciesId || !_proto.TryIndex(speciesId, out var species)) { - species = _proto.Index(ent.Comp.SpeciesOverride.Value); + species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); } - else if (profile?.Species is not { } speciesId || !_proto.TryIndex(speciesId, out species)) + + if (ent.Comp.SpeciesOverride != null + && (ent.Comp.SpeciesOverrideBlacklist?.Contains(new ProtoId(species.ID)) ?? false)) { - species = _proto.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); + species = _proto.Index(ent.Comp.SpeciesOverride.Value); } args.Entity = Spawn(species.Prototype); diff --git a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs index 0816902ad43b..3a4cb5fc75ea 100644 --- a/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs +++ b/Content.Server/GameTicking/Rules/Components/AntagLoadProfileRuleCOmponent.cs @@ -10,8 +10,14 @@ namespace Content.Server.GameTicking.Rules.Components; public sealed partial class AntagLoadProfileRuleComponent : Component { /// - /// If specified, the profile loaded will be made into this species. + /// If specified, the profile loaded will be made into this species if the chosen species matches the blacklist. /// [DataField] public ProtoId? SpeciesOverride; + + /// + /// List of species that trigger the override + /// + [DataField] + public HashSet>? SpeciesOverrideBlacklist; } diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index da198a25d098..d5cd6a3f9788 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -82,6 +82,11 @@ - type: AntagSelection - type: AntagLoadProfileRule speciesOverride: Human + speciesOverrideBlacklist: + #Species that do not work with nukies should be included in this list. + #Once the issues are fixed the species should be removed from this list to be enabled. + #Balance concerns are not a valid reason to disable a species, except for high-impact Nukie-specific exploits. + - Vox - type: entity parent: BaseNukeopsRule From ead0c375f64a726874d76b903ac003d2f13023d1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Fri, 5 Jul 2024 08:00:24 +0000 Subject: [PATCH 030/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 8d5076c356a4..4d0bb277f8e7 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: Krunk - changes: - - message: Head of Security and Warden now have armored and unarmored variants of - their winter coats and the unarmored variants are available in the uniform printer. - type: Tweak - - message: Head of Security's and Warden's winter coats now provide resistances - equal to their non-winter counterparts. - type: Tweak - id: 6373 - time: '2024-04-18T00:08:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/24865 - author: icekot8 changes: - message: "\u0421argo request console now reports when a request is approved" @@ -3821,3 +3810,11 @@ id: 6872 time: '2024-07-04T08:02:43.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29457 +- author: Jezithyr + changes: + - message: All species except for Vox can now be played as Nukies. (Vox will be + enabled when load out code supports them) + type: Tweak + id: 6873 + time: '2024-07-05T07:59:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29707 From b0ae7d57255117752e633df8687763921a0369c0 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:01:39 +0200 Subject: [PATCH 031/242] Add summary comments for ElectrifiedComponent fields (#29733) add summaries for ElectrifiedComponent --- .../Components/ElectrifiedComponent.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs index 4ef07a0cca8b..5755e98091b5 100644 --- a/Content.Server/Electrocution/Components/ElectrifiedComponent.cs +++ b/Content.Server/Electrocution/Components/ElectrifiedComponent.cs @@ -11,33 +11,63 @@ public sealed partial class ElectrifiedComponent : Component [DataField("enabled")] public bool Enabled = true; + /// + /// Should player get damage on collide + /// [DataField("onBump")] public bool OnBump = true; + /// + /// Should player get damage on attack + /// [DataField("onAttacked")] public bool OnAttacked = true; + /// + /// When true - disables power if a window is present in the same tile + /// [DataField("noWindowInTile")] public bool NoWindowInTile = false; + /// + /// Should player get damage on interact with empty hand + /// [DataField("onHandInteract")] public bool OnHandInteract = true; + /// + /// Should player get damage on interact while holding an object in their hand + /// [DataField("onInteractUsing")] public bool OnInteractUsing = true; + /// + /// Indicates if the entity requires power to function + /// [DataField("requirePower")] public bool RequirePower = true; + /// + /// Indicates if the entity uses APC power + /// [DataField("usesApcPower")] public bool UsesApcPower = false; + /// + /// Identifier for the high voltage node. + /// [DataField("highVoltageNode")] public string? HighVoltageNode; + /// + /// Identifier for the medium voltage node. + /// [DataField("mediumVoltageNode")] public string? MediumVoltageNode; + /// + /// Identifier for the low voltage node. + /// [DataField("lowVoltageNode")] public string? LowVoltageNode; @@ -69,7 +99,7 @@ public sealed partial class ElectrifiedComponent : Component public float ShockDamage = 7.5f; /// - /// Shock time, in seconds. + /// Shock time, in seconds. /// [DataField("shockTime")] public float ShockTime = 8f; From 11034a098626980e1990b0a962fd23eb6bb825f0 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Fri, 5 Jul 2024 07:25:23 -0700 Subject: [PATCH 032/242] Fix showing the inventory button on entities without any inventory slots (#29728) --- .../UserInterface/Systems/Inventory/InventoryUIController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs index fb7477991708..5d7a775104cb 100644 --- a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs +++ b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs @@ -132,6 +132,9 @@ private void UpdateInventoryHotbar(InventorySlotsComponent? clientInv) if (clientInv == null) { _inventoryHotbar?.ClearButtons(); + if (_inventoryButton != null) + _inventoryButton.Visible = false; + return; } @@ -409,6 +412,8 @@ private void UnloadSlots() { slotGroup.ClearButtons(); } + + UpdateInventoryHotbar(null); } private void SpriteUpdated(SlotSpriteUpdate update) From 24c47c805d60c2a7d846682db4d87255152c4f2d Mon Sep 17 00:00:00 2001 From: AndreyCamper Date: Sat, 6 Jul 2024 00:37:29 +0300 Subject: [PATCH 033/242] Fire Axe resprite 45 degrees v3 (#28866) Rotating Fire Axe 45 deg --- .../Weapons/Melee/fireaxeflaming.rsi/icon.png | Bin 334 -> 331 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Textures/Objects/Weapons/Melee/fireaxeflaming.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/fireaxeflaming.rsi/icon.png index 58e2f14942656a01fa6681f72aa4b1902b538631..ef62ecf5f49264955cbfe263c2fb69eb24687721 100644 GIT binary patch delta 315 zcmX@dbed^`WIZzj1B1(wu46!ou{g-xiDBJ2nU_EgOS+@4BLl<6e(pbstU$g>fKP~P zm4MZM#VAcp%|(kARaRF1|NlQKDk?NI^hCe<#~Z;<_SpgzGFLrM22wmFL4LtN1u(!M zbpPBrpg3oNM`SSr1K&XqX53LfG5al0bcUykV~B3n3R3)nl8P%NLbFk|AUu<0dg3;yLO;*=8yE*wy3hZVT z9E|<(K#l2w3yV+7f-Z@U3g+Vy1#gQu&X%Q~loCIF*B Be$M~^ delta 318 zcmV-E0m1&u0?q=E8Gi-<0047(dh`GQ00DDSM?wIu&K&6g009U|L_t(oh3%8E3W7i! z#y`b1u%#gyTzv!a363Ub1KZl&!sc3g1mVC3XzK~WRS=|bXov=vg7&VZ%)u);P?!5n zC;tz>?>_zqNF53WW$4&&FEIKPnn;=s1puKXwJMoSzzLV}Nl30017>;^%xY9+hWqfHX~o z%)`YKm*lr*QVGEgkY$-762S8RYvL^!l7hbj?`340`#~tgKP>-P{#QLujat!qXux&d z)}NzB2*C{d+BaboMIeMQ&+}So0sufMWjrXQtv8^2Levdgy}3jpkx1UW0Copq4=@Y+ QlmGw#07*qoM6N<$g0?Y>%>V!Z From 84ff5e334b521ad7266ababadfddaecd486f2ab9 Mon Sep 17 00:00:00 2001 From: end <72604018+laok233@users.noreply.github.com> Date: Fri, 5 Jul 2024 23:52:27 +0200 Subject: [PATCH 034/242] nuke biochem (#29751) Nuke biomchem again --- Resources/ServerInfo/Guidebook/Science/Science.xml | 2 +- Resources/ServerInfo/Guidebook/Science/Technologies.xml | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Resources/ServerInfo/Guidebook/Science/Science.xml b/Resources/ServerInfo/Guidebook/Science/Science.xml index 6e0183fc5f8c..e3de7738c7e6 100644 --- a/Resources/ServerInfo/Guidebook/Science/Science.xml +++ b/Resources/ServerInfo/Guidebook/Science/Science.xml @@ -22,7 +22,7 @@ Each technology costs [color=#a4885c]Research Points[/color] and unlocks recipes [textlink="Click here to see a list of technologies." link="Technologies"]. ## Disciplines -Technologies are spread over 5 different Disciplines: +Technologies are spread over 4 different Disciplines: diff --git a/Resources/ServerInfo/Guidebook/Science/Technologies.xml b/Resources/ServerInfo/Guidebook/Science/Technologies.xml index 7f0feaca4208..ef89c80269a5 100644 --- a/Resources/ServerInfo/Guidebook/Science/Technologies.xml +++ b/Resources/ServerInfo/Guidebook/Science/Technologies.xml @@ -8,9 +8,6 @@ The different technologies and their respective discipline are listed below. ## Industrial -## Biochemical - - ## Arsenal From 4c5c6a84dce709d0bf623aff5edf90cff8bac29f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 6 Jul 2024 13:51:55 +1000 Subject: [PATCH 035/242] Shuttle map button tweaks (#29757) - Avoids adding pending objects we can never show so the list should fill much faster. --- Content.Client/Shuttles/UI/MapScreen.xaml.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Content.Client/Shuttles/UI/MapScreen.xaml.cs b/Content.Client/Shuttles/UI/MapScreen.xaml.cs index 10800b8c5f78..489dbc8c90c8 100644 --- a/Content.Client/Shuttles/UI/MapScreen.xaml.cs +++ b/Content.Client/Shuttles/UI/MapScreen.xaml.cs @@ -261,7 +261,7 @@ private void RebuildMapObjects() ourMap = shuttleXform.MapID; } - while (mapComps.MoveNext(out var mapComp, out var mapXform, out var mapMetadata)) + while (mapComps.MoveNext(out var mapUid, out var mapComp, out var mapXform, out var mapMetadata)) { if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value)) { @@ -327,8 +327,10 @@ private void RebuildMapObjects() { AddMapObject(mapComp.MapId, gridObj); } - else if (!_shuttles.IsBeaconMap(_mapManager.GetMapEntityId(mapComp.MapId)) && (iffComp == null || - (iffComp.Flags & IFFFlags.Hide) == 0x0)) + // If we can show it then add it to pending. + else if (!_shuttles.IsBeaconMap(mapUid) && (iffComp == null || + (iffComp.Flags & IFFFlags.Hide) == 0x0) && + !gridObj.HideButton) { _pendingMapObjects.Add((mapComp.MapId, gridObj)); } @@ -336,11 +338,17 @@ private void RebuildMapObjects() foreach (var (beacon, _) in _shuttles.GetExclusions(mapComp.MapId, _exclusions)) { + if (beacon.HideButton) + continue; + _pendingMapObjects.Add((mapComp.MapId, beacon)); } foreach (var (beacon, _) in _shuttles.GetBeacons(mapComp.MapId, _beacons)) { + if (beacon.HideButton) + continue; + _pendingMapObjects.Add((mapComp.MapId, beacon)); } @@ -425,9 +433,6 @@ private void AddMapObject(MapId mapId, IMapObject mapObj) var existing = _mapObjects.GetOrNew(mapId); existing.Add(mapObj); - if (mapObj.HideButton) - return; - var gridContents = _mapHeadings[mapId]; var gridButton = new Button() From 12edad89e83448958d8e8cc68a00f1034f1ae3c4 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 6 Jul 2024 03:53:02 +0000 Subject: [PATCH 036/242] 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 4d0bb277f8e7..d57e1cde11b3 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: icekot8 - changes: - - message: "\u0421argo request console now reports when a request is approved" - type: Add - id: 6374 - time: '2024-04-18T00:32:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27038 - author: Bellwether changes: - message: Midround zombie outbreaks are less common and spread more slowly. @@ -3818,3 +3811,10 @@ id: 6873 time: '2024-07-05T07:59:16.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29707 +- author: metalgearsloth + changes: + - message: Shuttle map buttons will show up faster. + type: Tweak + id: 6874 + time: '2024-07-06T03:51:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29757 From 7b99d1f851d3a3be179d5307a72b213a8971af9d Mon Sep 17 00:00:00 2001 From: themias <89101928+themias@users.noreply.github.com> Date: Fri, 5 Jul 2024 23:55:58 -0400 Subject: [PATCH 037/242] Don't allow toggling internals while asleep (#29753) --- Content.Server/Body/Systems/InternalsSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Body/Systems/InternalsSystem.cs b/Content.Server/Body/Systems/InternalsSystem.cs index 922d48f13ed2..d6ece39d590a 100644 --- a/Content.Server/Body/Systems/InternalsSystem.cs +++ b/Content.Server/Body/Systems/InternalsSystem.cs @@ -100,7 +100,7 @@ public void ToggleInternals( // Toggle off if they're on if (AreInternalsWorking(internals)) { - if (force || user == uid) + if (force) { DisconnectTank(internals); return; From 7ae3e353ea36dd01247589e0bfccb68dbaf937fa Mon Sep 17 00:00:00 2001 From: Killerqu00 <47712032+Killerqu00@users.noreply.github.com> Date: Sat, 6 Jul 2024 06:01:51 +0200 Subject: [PATCH 038/242] You no longer get deleted when cuffed and buckled (#29718) fix --- Content.Shared/Buckle/SharedBuckleSystem.Strap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs b/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs index eb23aa973b4b..bfb0cd9cd6fd 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Strap.cs @@ -57,7 +57,7 @@ private void StrapRemoveAll(EntityUid uid, StrapComponent strapComp) { foreach (var entity in strapComp.BuckledEntities.ToArray()) { - TryUnbuckle(entity, entity, true); + Unbuckle(entity, entity); } } From 34f36665a6bd866b2edaac6887c7edcfd443b8d8 Mon Sep 17 00:00:00 2001 From: MFMessage <22904993+MFMessage@users.noreply.github.com> Date: Fri, 5 Jul 2024 23:04:59 -0500 Subject: [PATCH 039/242] Fixes a minor typo for the base gingerbread body part (#29717) "Fixes a minor typo in the gingerbread bodypart prototype" --- Resources/Prototypes/Body/Parts/gingerbread.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Body/Parts/gingerbread.yml b/Resources/Prototypes/Body/Parts/gingerbread.yml index 661835ab843b..f95e66145be1 100644 --- a/Resources/Prototypes/Body/Parts/gingerbread.yml +++ b/Resources/Prototypes/Body/Parts/gingerbread.yml @@ -1,7 +1,7 @@ - type: entity id: PartGingerbread parent: [BaseItem, BasePart] - name: "gingerbead body part" + name: "gingerbread body part" abstract: true components: - type: Extractable From b418338af77c994d43af3b8fd015de5dee91aaa3 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Sat, 6 Jul 2024 06:17:32 +0200 Subject: [PATCH 040/242] Phoronman 1984 (#29747) No more. --- .../Textures/Mobs/Species/lungs_phoronman.png | Bin 233 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Resources/Textures/Mobs/Species/lungs_phoronman.png diff --git a/Resources/Textures/Mobs/Species/lungs_phoronman.png b/Resources/Textures/Mobs/Species/lungs_phoronman.png deleted file mode 100644 index 1c6d2dc11a8c8d12b1bc8cd3ffe9c0ba180c2896..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvxd5LK*8>L*e17=l^{KZ<5>D;* zICQ4$;-gJZFSTEvW;|z=^+vxBe=Y)5GL{7S1v5B2yO9RuRC>BNhGMavWE-LEPKf4Vddn=#9_PU;Xy{8 zV+%PLr(FnQXM27jV26Y8i36Goj} Date: Sat, 6 Jul 2024 12:33:20 -0500 Subject: [PATCH 041/242] Added cryosleep UnitSpawner and UnitSpawnerLate to the map Origin (#29761) Added cryosleep UnitSpawner and UnitSpawnerLate Co-authored-by: JIP --- Resources/Maps/origin.yml | 1124 +------------------------------------ 1 file changed, 18 insertions(+), 1106 deletions(-) diff --git a/Resources/Maps/origin.yml b/Resources/Maps/origin.yml index 7dd4d4386f1a..380447782fb7 100644 --- a/Resources/Maps/origin.yml +++ b/Resources/Maps/origin.yml @@ -12115,26 +12115,17 @@ entities: rot: 3.141592653589793 rad pos: -44.5,11.5 parent: 2 - - type: DeviceLinkSink - links: - - 24384 - uid: 116 components: - type: Transform pos: -51.5,12.5 parent: 2 - - type: DeviceLinkSink - links: - - 24382 - uid: 117 components: - type: Transform rot: 3.141592653589793 rad pos: -52.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24383 - uid: 118 components: - type: Transform @@ -12143,26 +12134,18 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 1 - links: - - 24386 - uid: 119 components: - type: Transform rot: -1.5707963267948966 rad pos: -17.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 24387 - uid: 120 components: - type: Transform rot: -1.5707963267948966 rad pos: -13.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 24389 - uid: 121 components: - type: Transform @@ -12389,7 +12372,7 @@ entities: pos: 19.5,16.5 parent: 2 - type: Door - secondsUntilStateChange: -25677.166 + secondsUntilStateChange: -25890.535 state: Opening - uid: 156 components: @@ -13799,17 +13782,11 @@ entities: - type: Transform pos: 26.5,15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24448 - uid: 390 components: - type: Transform pos: 24.5,15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24448 - uid: 391 components: - type: Transform @@ -13843,9 +13820,6 @@ entities: - type: Transform pos: 25.5,15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24448 - uid: 397 components: - type: Transform @@ -14090,25 +14064,16 @@ entities: - type: Transform pos: 40.5,-41.5 parent: 2 - - type: DeviceLinkSink - links: - - 24461 - uid: 440 components: - type: Transform pos: 40.5,-42.5 parent: 2 - - type: DeviceLinkSink - links: - - 24461 - uid: 441 components: - type: Transform pos: 40.5,-43.5 parent: 2 - - type: DeviceLinkSink - links: - - 24461 - uid: 442 components: - type: Transform @@ -14206,7 +14171,7 @@ entities: pos: 51.5,-3.5 parent: 2 - type: Door - secondsUntilStateChange: -24490.984 + secondsUntilStateChange: -24704.354 state: Opening - uid: 458 components: @@ -14215,7 +14180,7 @@ entities: pos: 52.5,-3.5 parent: 2 - type: Door - secondsUntilStateChange: -24489.768 + secondsUntilStateChange: -24703.137 state: Opening - uid: 459 components: @@ -14372,25 +14337,16 @@ entities: - type: Transform pos: -21.5,-12.5 parent: 2 - - type: DeviceLinkSink - links: - - 24459 - uid: 487 components: - type: Transform pos: -21.5,-11.5 parent: 2 - - type: DeviceLinkSink - links: - - 24459 - uid: 488 components: - type: Transform pos: -21.5,20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24449 - uid: 489 components: - type: Transform @@ -14468,9 +14424,6 @@ entities: - type: Transform pos: -21.5,21.5 parent: 2 - - type: DeviceLinkSink - links: - - 24449 - uid: 504 components: - type: Transform @@ -14778,9 +14731,6 @@ entities: - type: Transform pos: 0.5,-5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24152 - uid: 557 components: - type: MetaData @@ -14788,9 +14738,6 @@ entities: - type: Transform pos: 6.5,-7.5 parent: 2 - - type: DeviceLinkSink - links: - - 24152 - uid: 558 components: - type: Transform @@ -15239,9 +15186,6 @@ entities: rot: 1.5707963267948966 rad pos: 1.5,-8.5 parent: 2 - - type: DeviceLinkSink - links: - - 24152 - proto: AirlockMaintJanitorLocked entities: - uid: 629 @@ -15501,9 +15445,6 @@ entities: - type: Transform pos: -22.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 24387 - uid: 675 components: - type: Transform @@ -16300,9 +16241,6 @@ entities: - type: Transform pos: -25.5,45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24388 - uid: 796 components: - type: MetaData @@ -16310,9 +16248,6 @@ entities: - type: Transform pos: -23.5,38.5 parent: 2 - - type: DeviceLinkSink - links: - - 24385 - proto: AirlockVirologyGlass entities: - uid: 797 @@ -17234,8 +17169,6 @@ entities: parent: 2 - type: Apc hasAccess: True - lastExternalState: Good - lastChargeState: Full - uid: 955 components: - type: MetaData @@ -17339,8 +17272,6 @@ entities: parent: 2 - type: Apc hasAccess: True - lastExternalState: Good - lastChargeState: Full - proto: APCHighCapacity entities: - uid: 969 @@ -17489,8 +17420,6 @@ entities: parent: 2 - type: Apc hasAccess: True - lastExternalState: Good - lastChargeState: Full - uid: 989 components: - type: MetaData @@ -23008,49 +22937,31 @@ entities: - type: Transform pos: -78.5,-41.5 parent: 2 - - type: DeviceLinkSink - links: - - 24444 - uid: 2060 components: - type: Transform pos: -78.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24444 - uid: 2061 components: - type: Transform pos: -78.5,-42.5 parent: 2 - - type: DeviceLinkSink - links: - - 24444 - uid: 2062 components: - type: Transform pos: -74.5,-46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24443 - uid: 2063 components: - type: Transform pos: -74.5,-44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24443 - uid: 2064 components: - type: Transform pos: -74.5,-45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24443 - proto: BlastDoorBridge entities: - uid: 2065 @@ -23058,25 +22969,16 @@ entities: - type: Transform pos: 10.5,47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24425 - uid: 2066 components: - type: Transform pos: 10.5,49.5 parent: 2 - - type: DeviceLinkSink - links: - - 24425 - uid: 2067 components: - type: Transform pos: 10.5,48.5 parent: 2 - - type: DeviceLinkSink - links: - - 24425 - proto: BlastDoorExterior1 entities: - uid: 2068 @@ -23084,229 +22986,142 @@ entities: - type: Transform pos: 18.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 24401 - uid: 2069 components: - type: Transform pos: 47.5,-51.5 parent: 2 - - type: DeviceLinkSink - links: - - 24397 - uid: 2070 components: - type: Transform pos: 47.5,-52.5 parent: 2 - - type: DeviceLinkSink - links: - - 24397 - uid: 2071 components: - type: Transform pos: 47.5,-53.5 parent: 2 - - type: DeviceLinkSink - links: - - 24397 - uid: 2072 components: - type: Transform pos: 47.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 24397 - uid: 2073 components: - type: Transform pos: 52.5,-58.5 parent: 2 - - type: DeviceLinkSink - links: - - 24398 - uid: 2074 components: - type: Transform pos: 50.5,-62.5 parent: 2 - - type: DeviceLinkSink - links: - - 24431 - uid: 2075 components: - type: Transform rot: 1.5707963267948966 rad pos: 55.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 24400 - uid: 2076 components: - type: Transform rot: 1.5707963267948966 rad pos: 54.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 24400 - uid: 2077 components: - type: Transform pos: -45.5,-34.5 parent: 2 - - type: DeviceLinkSink - links: - - 24445 - uid: 2078 components: - type: Transform rot: -1.5707963267948966 rad pos: -40.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24404 - uid: 2079 components: - type: Transform rot: -1.5707963267948966 rad pos: -39.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24404 - uid: 2080 components: - type: Transform rot: -1.5707963267948966 rad pos: -38.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24404 - uid: 2081 components: - type: Transform rot: -1.5707963267948966 rad pos: -37.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24404 - uid: 2082 components: - type: Transform pos: -49.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 24406 - uid: 2083 components: - type: Transform pos: -49.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 24406 - uid: 2084 components: - type: Transform pos: 51.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24414 - - 24416 - - 24421 - uid: 2085 components: - type: Transform pos: 54.5,45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24412 - - 24414 - uid: 2086 components: - type: Transform pos: -52.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 24435 - uid: 2087 components: - type: Transform pos: -52.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 24399 - uid: 2088 components: - type: Transform pos: -53.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 24405 - uid: 2089 components: - type: Transform pos: -53.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 24405 - uid: 2090 components: - type: Transform pos: 53.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24408 - - 24417 - - 24424 - uid: 2091 components: - type: Transform pos: 55.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24412 - - 24415 - uid: 2092 components: - type: Transform pos: 50.5,47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24410 - uid: 2093 components: - type: Transform pos: 57.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24416 - uid: 2094 components: - type: Transform pos: 56.5,47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24407 - uid: 2095 components: - type: Transform @@ -23317,72 +23132,41 @@ entities: - type: Transform pos: 53.5,48.5 parent: 2 - - type: DeviceLinkSink - links: - - 24407 - - 24422 - - 24424 - uid: 2097 components: - type: Transform pos: 55.5,48.5 parent: 2 - - type: DeviceLinkSink - links: - - 24422 - uid: 2098 components: - type: Transform pos: 58.5,47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24408 - uid: 2099 components: - type: Transform pos: 52.5,45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24411 - - 24420 - uid: 2100 components: - type: Transform pos: 53.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24418 - - 24424 - uid: 2101 components: - type: Transform pos: 55.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24418 - - 24421 - uid: 2102 components: - type: Transform pos: 58.5,45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24419 - uid: 2103 components: - type: Transform pos: 57.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24411 - - 24413 - - 24422 - uid: 2104 components: - type: Transform @@ -23393,101 +23177,62 @@ entities: - type: Transform pos: 54.5,47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24415 - uid: 2106 components: - type: Transform pos: 52.5,47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24417 - uid: 2107 components: - type: Transform pos: 51.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24421 - - 24422 - uid: 2108 components: - type: Transform pos: 56.5,45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24413 - - 24419 - uid: 2109 components: - type: Transform pos: 50.5,45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24410 - - 24420 - uid: 2110 components: - type: Transform pos: -18.5,-96.5 parent: 2 - - type: DeviceLinkSink - links: - - 24427 - uid: 2111 components: - type: Transform pos: -18.5,-98.5 parent: 2 - - type: DeviceLinkSink - links: - - 24427 - uid: 2112 components: - type: Transform pos: -26.5,-96.5 parent: 2 - - type: DeviceLinkSink - links: - - 24428 - uid: 2113 components: - type: Transform pos: -26.5,-98.5 parent: 2 - - type: DeviceLinkSink - links: - - 24428 - uid: 2114 components: - type: Transform pos: 67.5,-39.5 parent: 2 - - type: DeviceLinkSink - links: - - 24396 - uid: 2115 components: - type: Transform rot: 3.141592653589793 rad pos: 72.5,-27.5 parent: 2 - - type: DeviceLinkSink - links: - - 24432 - uid: 2116 components: - type: Transform pos: -45.5,-35.5 parent: 2 - - type: DeviceLinkSink - links: - - 24445 - proto: BlastDoorExterior1Open entities: - uid: 2117 @@ -23496,162 +23241,100 @@ entities: rot: -1.5707963267948966 rad pos: 24.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2118 components: - type: Transform rot: -1.5707963267948966 rad pos: 23.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2119 components: - type: Transform rot: -1.5707963267948966 rad pos: 25.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2120 components: - type: Transform rot: -1.5707963267948966 rad pos: 26.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2121 components: - type: Transform rot: -1.5707963267948966 rad pos: 24.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2122 components: - type: Transform rot: -1.5707963267948966 rad pos: 23.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2123 components: - type: Transform rot: -1.5707963267948966 rad pos: 25.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2124 components: - type: Transform rot: -1.5707963267948966 rad pos: 26.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2125 components: - type: Transform rot: -1.5707963267948966 rad pos: 22.5,44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2126 components: - type: Transform rot: -1.5707963267948966 rad pos: 22.5,46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24426 - uid: 2127 components: - type: Transform pos: -8.5,-91.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2128 components: - type: Transform pos: -8.5,-92.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2129 components: - type: Transform pos: -8.5,-93.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2130 components: - type: Transform pos: -6.5,-91.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2131 components: - type: Transform pos: -6.5,-90.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2132 components: - type: Transform pos: -6.5,-92.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2133 components: - type: Transform pos: -6.5,-93.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - uid: 2134 components: - type: Transform pos: -8.5,-90.5 parent: 2 - - type: DeviceLinkSink - links: - - 24429 - - 24430 - proto: BlastDoorOpen entities: - uid: 2135 @@ -23659,86 +23342,56 @@ entities: - type: Transform pos: -56.5,-11.5 parent: 2 - - type: DeviceLinkSink - links: - - 24403 - uid: 2136 components: - type: Transform pos: -56.5,-12.5 parent: 2 - - type: DeviceLinkSink - links: - - 24403 - uid: 2137 components: - type: Transform pos: -56.5,-13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24403 - uid: 2138 components: - type: Transform pos: -56.5,-14.5 parent: 2 - - type: DeviceLinkSink - links: - - 24403 - uid: 2139 components: - type: Transform pos: -56.5,-15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24403 - uid: 2140 components: - type: Transform rot: -1.5707963267948966 rad pos: -64.5,-22.5 parent: 2 - - type: DeviceLinkSink - links: - - 24402 - uid: 2141 components: - type: Transform rot: -1.5707963267948966 rad pos: -65.5,-22.5 parent: 2 - - type: DeviceLinkSink - links: - - 24402 - uid: 2142 components: - type: Transform rot: -1.5707963267948966 rad pos: -66.5,-22.5 parent: 2 - - type: DeviceLinkSink - links: - - 24402 - uid: 2143 components: - type: Transform rot: -1.5707963267948966 rad pos: -67.5,-22.5 parent: 2 - - type: DeviceLinkSink - links: - - 24402 - uid: 2144 components: - type: Transform rot: -1.5707963267948966 rad pos: -68.5,-22.5 parent: 2 - - type: DeviceLinkSink - links: - - 24402 - proto: BlockGameArcadeComputerCircuitboard entities: - uid: 2145 @@ -23781,7 +23434,7 @@ entities: - type: Transform pos: -25.389751,-36.57607 parent: 2 -- proto: BookChefGaming +- proto: BookHowToCookForFortySpaceman entities: - uid: 2151 components: @@ -80698,906 +80351,592 @@ entities: rot: 1.5707963267948966 rad pos: 16.5,-55.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12862 components: - type: Transform pos: 18.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12863 components: - type: Transform rot: 1.5707963267948966 rad pos: 17.5,-55.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12864 components: - type: Transform rot: 1.5707963267948966 rad pos: 15.5,-55.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12865 components: - type: Transform pos: 18.5,-55.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12866 components: - type: Transform rot: -1.5707963267948966 rad pos: -12.5,-10.5 parent: 2 - - type: DeviceLinkSink - links: - - 26159 - uid: 12867 components: - type: Transform pos: 15.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12868 components: - type: Transform pos: 18.5,-57.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12869 components: - type: Transform rot: -1.5707963267948966 rad pos: 17.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12870 components: - type: Transform rot: -1.5707963267948966 rad pos: 16.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12871 components: - type: Transform rot: -1.5707963267948966 rad pos: 18.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - uid: 12872 components: - type: Transform rot: -1.5707963267948966 rad pos: -14.5,-10.5 parent: 2 - - type: DeviceLinkSink - links: - - 26159 - uid: 12873 components: - type: Transform rot: -1.5707963267948966 rad pos: -13.5,-10.5 parent: 2 - - type: DeviceLinkSink - links: - - 26159 - uid: 12874 components: - type: Transform rot: 3.141592653589793 rad pos: -11.5,-10.5 parent: 2 - - type: DeviceLinkSink - links: - - 26159 - uid: 12875 components: - type: Transform rot: 1.5707963267948966 rad pos: -25.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26147 - uid: 12876 components: - type: Transform rot: 1.5707963267948966 rad pos: -26.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26147 - uid: 12877 components: - type: Transform rot: 1.5707963267948966 rad pos: -27.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26147 - uid: 12878 components: - type: Transform rot: 1.5707963267948966 rad pos: -28.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26147 - uid: 12879 components: - type: Transform rot: 1.5707963267948966 rad pos: -30.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26147 - uid: 12880 components: - type: Transform rot: 1.5707963267948966 rad pos: -29.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26147 - uid: 12881 components: - type: Transform rot: 1.5707963267948966 rad pos: -35.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26151 - uid: 12882 components: - type: Transform rot: 1.5707963267948966 rad pos: -34.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26151 - uid: 12883 components: - type: Transform rot: 1.5707963267948966 rad pos: -36.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26151 - uid: 12884 components: - type: Transform rot: 1.5707963267948966 rad pos: -37.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26151 - uid: 12885 components: - type: Transform rot: 1.5707963267948966 rad pos: -38.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26151 - uid: 12886 components: - type: Transform rot: 1.5707963267948966 rad pos: -48.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12887 components: - type: Transform rot: 1.5707963267948966 rad pos: -49.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12888 components: - type: Transform rot: 1.5707963267948966 rad pos: -50.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12889 components: - type: Transform rot: 1.5707963267948966 rad pos: -51.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12890 components: - type: Transform rot: 1.5707963267948966 rad pos: -48.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12891 components: - type: Transform rot: 1.5707963267948966 rad pos: -49.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12892 components: - type: Transform rot: 1.5707963267948966 rad pos: -50.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12893 components: - type: Transform rot: 1.5707963267948966 rad pos: -51.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12894 components: - type: Transform rot: 1.5707963267948966 rad pos: -52.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12895 components: - type: Transform rot: 1.5707963267948966 rad pos: -53.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12896 components: - type: Transform rot: 1.5707963267948966 rad pos: -47.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12897 components: - type: Transform rot: 1.5707963267948966 rad pos: -46.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 26150 - uid: 12898 components: - type: Transform rot: 1.5707963267948966 rad pos: -47.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12899 components: - type: Transform rot: 1.5707963267948966 rad pos: -46.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12900 components: - type: Transform rot: 1.5707963267948966 rad pos: -53.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12901 components: - type: Transform rot: 1.5707963267948966 rad pos: -52.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26149 - uid: 12902 components: - type: Transform rot: 3.141592653589793 rad pos: -42.5,14.5 parent: 2 - - type: DeviceLinkSink - links: - - 26148 - uid: 12903 components: - type: Transform rot: 1.5707963267948966 rad pos: -51.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12904 components: - type: Transform rot: 1.5707963267948966 rad pos: -52.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12905 components: - type: Transform rot: 1.5707963267948966 rad pos: -50.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12906 components: - type: Transform rot: 1.5707963267948966 rad pos: -49.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12907 components: - type: Transform rot: 1.5707963267948966 rad pos: -48.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12908 components: - type: Transform rot: 1.5707963267948966 rad pos: -52.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12909 components: - type: Transform rot: 1.5707963267948966 rad pos: -51.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12910 components: - type: Transform rot: 1.5707963267948966 rad pos: -50.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12911 components: - type: Transform rot: 1.5707963267948966 rad pos: -49.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12912 components: - type: Transform rot: 1.5707963267948966 rad pos: -48.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12913 components: - type: Transform rot: 1.5707963267948966 rad pos: -53.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12914 components: - type: Transform rot: 1.5707963267948966 rad pos: -53.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12915 components: - type: Transform rot: 1.5707963267948966 rad pos: -47.5,34.5 parent: 2 - - type: DeviceLinkSink - links: - - 26154 - uid: 12916 components: - type: Transform rot: 1.5707963267948966 rad pos: -47.5,30.5 parent: 2 - - type: DeviceLinkSink - links: - - 26153 - uid: 12917 components: - type: Transform rot: -1.5707963267948966 rad pos: -12.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12918 components: - type: Transform pos: -10.5,28.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12919 components: - type: Transform rot: -1.5707963267948966 rad pos: -9.5,28.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12920 components: - type: Transform rot: 3.141592653589793 rad pos: -42.5,15.5 parent: 2 - - type: DeviceLinkSink - links: - - 26148 - uid: 12921 components: - type: Transform rot: 3.141592653589793 rad pos: -42.5,17.5 parent: 2 - - type: DeviceLinkSink - links: - - 26148 - uid: 12922 components: - type: Transform rot: 3.141592653589793 rad pos: -42.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 26148 - uid: 12923 components: - type: Transform pos: -47.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 26155 - uid: 12924 components: - type: Transform pos: -47.5,14.5 parent: 2 - - type: DeviceLinkSink - links: - - 26155 - uid: 12925 components: - type: Transform pos: -47.5,12.5 parent: 2 - - type: DeviceLinkSink - links: - - 26155 - uid: 12926 components: - type: Transform rot: 1.5707963267948966 rad pos: -8.5,22.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12927 components: - type: Transform rot: 1.5707963267948966 rad pos: -7.5,22.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12928 components: - type: Transform rot: 1.5707963267948966 rad pos: -6.5,22.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12929 components: - type: Transform rot: 1.5707963267948966 rad pos: 43.5,37.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12930 components: - type: Transform rot: 1.5707963267948966 rad pos: 44.5,37.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12931 components: - type: Transform rot: 1.5707963267948966 rad pos: 45.5,37.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12932 components: - type: Transform pos: 46.5,37.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12933 components: - type: Transform rot: 1.5707963267948966 rad pos: 46.5,36.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12934 components: - type: Transform rot: 1.5707963267948966 rad pos: 47.5,36.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12935 components: - type: Transform rot: 3.141592653589793 rad pos: 48.5,36.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12936 components: - type: Transform rot: 3.141592653589793 rad pos: 48.5,37.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12937 components: - type: Transform rot: 1.5707963267948966 rad pos: -9.5,22.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12938 components: - type: Transform pos: -9.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12939 components: - type: Transform pos: -9.5,26.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12940 components: - type: Transform pos: -9.5,25.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12941 components: - type: Transform pos: -9.5,24.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12942 components: - type: Transform pos: -9.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12943 components: - type: Transform rot: 3.141592653589793 rad pos: -10.5,22.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12944 components: - type: Transform rot: 1.5707963267948966 rad pos: -10.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12945 components: - type: Transform rot: -1.5707963267948966 rad pos: -10.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12946 components: - type: Transform rot: -1.5707963267948966 rad pos: -11.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 26152 - uid: 12947 components: - type: Transform rot: 3.141592653589793 rad pos: 48.5,38.5 parent: 2 - - type: DeviceLinkSink - links: - - 26156 - uid: 12948 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-99.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12949 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-98.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12950 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-100.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12951 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-101.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12952 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-102.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12953 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-103.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12954 components: - type: Transform rot: 3.141592653589793 rad pos: -37.5,-104.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12955 components: - type: Transform rot: -1.5707963267948966 rad pos: -37.5,-105.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12956 components: - type: Transform rot: -1.5707963267948966 rad pos: -36.5,-105.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12957 components: - type: Transform rot: 3.141592653589793 rad pos: -38.5,-105.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12958 components: - type: Transform rot: 1.5707963267948966 rad pos: -38.5,-104.5 parent: 2 - - type: DeviceLinkSink - links: - - 26157 - - 26158 - uid: 12959 components: - type: Transform rot: 3.141592653589793 rad pos: -11.5,-11.5 parent: 2 - - type: DeviceLinkSink - links: - - 26159 - uid: 12960 components: - type: Transform rot: 3.141592653589793 rad pos: -11.5,-12.5 parent: 2 - - type: DeviceLinkSink - links: - - 26159 - uid: 12961 components: - type: Transform rot: 3.141592653589793 rad pos: -42.5,16.5 parent: 2 - - type: DeviceLinkSink - links: - - 26148 - uid: 12962 components: - type: Transform @@ -81605,8 +80944,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 2 - links: - - 26160 - uid: 12963 components: - type: Transform @@ -81614,8 +80951,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 2 - links: - - 26160 - uid: 12964 components: - type: Transform @@ -81623,8 +80958,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 2 - links: - - 26160 - proto: ConveyorBeltAssembly entities: - uid: 12965 @@ -82507,22 +81840,12 @@ entities: - type: Transform pos: 13.5,-83.5 parent: 2 - - uid: 13036 - components: - - type: Transform - pos: 13.5,-84.5 - parent: 2 - uid: 13037 components: - type: Transform rot: 1.5707963267948966 rad pos: 17.5,-82.5 parent: 2 - - uid: 13038 - components: - - type: Transform - pos: 13.5,-82.5 - parent: 2 - uid: 13039 components: - type: Transform @@ -82552,6 +81875,20 @@ entities: rot: 1.5707963267948966 rad pos: -8.5,-66.5 parent: 2 +- proto: CryogenicSleepUnitSpawner + entities: + - uid: 13036 + components: + - type: Transform + pos: 13.5,-84.5 + parent: 2 +- proto: CryogenicSleepUnitSpawnerLateJoin + entities: + - uid: 13038 + components: + - type: Transform + pos: 13.5,-82.5 + parent: 2 - proto: CryoPod entities: - uid: 13044 @@ -140558,44 +139895,29 @@ entities: - type: Transform pos: -5.5,3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24456 - uid: 21473 components: - type: Transform pos: 38.5,-41.5 parent: 2 - - type: DeviceLinkSink - links: - - 24451 - uid: 21474 components: - type: Transform rot: 1.5707963267948966 rad pos: -20.5,-15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24452 - uid: 21475 components: - type: Transform rot: 1.5707963267948966 rad pos: 24.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24454 - uid: 21476 components: - type: Transform rot: -1.5707963267948966 rad pos: 16.5,-23.5 parent: 2 - - type: DeviceLinkSink - links: - - 24446 - uid: 21477 components: - type: Transform @@ -140604,43 +139926,29 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 2 - links: - - 24447 - uid: 21478 components: - type: Transform rot: 1.5707963267948966 rad pos: -20.5,-33.5 parent: 2 - - type: DeviceLinkSink - links: - - 24455 - uid: 21479 components: - type: Transform rot: 1.5707963267948966 rad pos: 34.5,-23.5 parent: 2 - - type: DeviceLinkSink - links: - - 24446 - uid: 21480 components: - type: Transform rot: 1.5707963267948966 rad pos: -20.5,22.5 parent: 2 - - type: DeviceLinkSink - links: - - 24453 - uid: 21481 components: - type: Transform pos: 0.5,-25.5 parent: 2 - - type: DeviceLinkSink - links: - - 24457 - proto: JetpackBlueFilled entities: - uid: 21482 @@ -142227,9 +141535,6 @@ entities: - type: Transform pos: 72.5,-28.5 parent: 2 - - type: DeviceLinkSink - links: - - 12764 - proto: MachineCentrifuge entities: - uid: 21605 @@ -146478,9 +145783,6 @@ entities: rot: -1.5707963267948966 rad pos: 5.5,-5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24450 - uid: 22311 components: - type: Transform @@ -149140,9 +148442,6 @@ entities: rot: 1.5707963267948966 rad pos: 1.5,-7.5 parent: 2 - - type: DeviceLinkSink - links: - - 24450 - uid: 22663 components: - type: Transform @@ -154237,9 +153536,6 @@ entities: rot: 1.5707963267948966 rad pos: 17.5,-55.5 parent: 2 - - type: DeviceLinkSink - links: - - 26146 - proto: ReinforcedPlasmaWindow entities: - uid: 23478 @@ -158447,18 +157743,12 @@ entities: rot: -1.5707963267948966 rad pos: 66.5,-45.5 parent: 2 - - type: DeviceLinkSink - links: - - 24395 - uid: 24237 components: - type: Transform rot: -1.5707963267948966 rad pos: 66.5,-46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24395 - proto: ShuttersNormalOpen entities: - uid: 24238 @@ -158468,8 +157758,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 1 - links: - - 24463 - uid: 24239 components: - type: Transform @@ -158477,8 +157765,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 1 - links: - - 24391 - uid: 24240 components: - type: Transform @@ -158486,8 +157772,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 4 - links: - - 24442 - uid: 24241 components: - type: Transform @@ -158495,17 +157779,12 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 4 - links: - - 24442 - uid: 24242 components: - type: Transform rot: -1.5707963267948966 rad pos: 15.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24243 components: - type: Transform @@ -158517,332 +157796,212 @@ entities: - type: Transform pos: -32.5,32.5 parent: 2 - - type: DeviceLinkSink - links: - - 24423 - uid: 24245 components: - type: Transform pos: 37.5,-0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24394 - uid: 24246 components: - type: Transform pos: -8.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24438 - uid: 24247 components: - type: Transform rot: -1.5707963267948966 rad pos: 15.5,11.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24248 components: - type: Transform rot: -1.5707963267948966 rad pos: 15.5,10.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24249 components: - type: Transform pos: 18.5,15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24250 components: - type: Transform pos: 17.5,15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24251 components: - type: Transform pos: -6.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24438 - uid: 24252 components: - type: Transform rot: 1.5707963267948966 rad pos: 48.5,6.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24253 components: - type: Transform rot: -1.5707963267948966 rad pos: 35.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24433 - uid: 24254 components: - type: Transform rot: -1.5707963267948966 rad pos: 15.5,12.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24255 components: - type: Transform pos: 61.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24256 components: - type: Transform rot: -1.5707963267948966 rad pos: 15.5,14.5 parent: 2 - - type: DeviceLinkSink - links: - - 24393 - uid: 24257 components: - type: Transform pos: 45.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24258 components: - type: Transform pos: 43.5,7.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24259 components: - type: Transform pos: 62.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24260 components: - type: Transform pos: 63.5,-56.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24261 components: - type: Transform pos: -30.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 24423 - uid: 24262 components: - type: Transform pos: 29.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24433 - uid: 24263 components: - type: Transform pos: 34.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24433 - uid: 24264 components: - type: Transform pos: -31.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 24423 - uid: 24265 components: - type: Transform rot: -1.5707963267948966 rad pos: 35.5,7.5 parent: 2 - - type: DeviceLinkSink - links: - - 24433 - uid: 24266 components: - type: Transform pos: -33.5,27.5 parent: 2 - - type: DeviceLinkSink - links: - - 24423 - uid: 24267 components: - type: Transform pos: -49.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24409 - uid: 24268 components: - type: Transform pos: -48.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24409 - uid: 24269 components: - type: Transform pos: -31.5,32.5 parent: 2 - - type: DeviceLinkSink - links: - - 24423 - uid: 24270 components: - type: Transform pos: 32.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24433 - uid: 24271 components: - type: Transform pos: 39.5,-0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24394 - uid: 24272 components: - type: Transform pos: -33.5,32.5 parent: 2 - - type: DeviceLinkSink - links: - - 24423 - uid: 24273 components: - type: Transform pos: -46.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24409 - uid: 24274 components: - type: Transform pos: -47.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24409 - uid: 24275 components: - type: Transform pos: 43.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24276 components: - type: Transform pos: 59.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24277 components: - type: Transform pos: 65.5,-54.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24278 components: - type: Transform rot: 1.5707963267948966 rad pos: 66.5,-51.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24279 components: - type: Transform rot: 1.5707963267948966 rad pos: 66.5,-52.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24280 components: - type: Transform rot: -1.5707963267948966 rad pos: 58.5,-51.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24281 components: - type: Transform rot: -1.5707963267948966 rad pos: 58.5,-52.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24282 components: - type: Transform pos: 61.5,-50.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24283 components: - type: Transform pos: 63.5,-50.5 parent: 2 - - type: DeviceLinkSink - links: - - 24434 - uid: 24284 components: - type: Transform @@ -158850,8 +158009,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 1 - links: - - 24463 - uid: 24285 components: - type: Transform @@ -158863,640 +158020,412 @@ entities: rot: -1.5707963267948966 rad pos: -33.5,-15.5 parent: 2 - - type: DeviceLinkSink - links: - - 24390 - uid: 24287 components: - type: Transform rot: -1.5707963267948966 rad pos: -33.5,-17.5 parent: 2 - - type: DeviceLinkSink - links: - - 24390 - uid: 24288 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24289 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,8.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24290 components: - type: Transform rot: -1.5707963267948966 rad pos: 7.5,7.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24291 components: - type: Transform pos: 2.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24292 components: - type: Transform pos: 1.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24293 components: - type: Transform pos: 0.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24294 components: - type: Transform pos: 5.5,11.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24295 components: - type: Transform pos: 4.5,11.5 parent: 2 - - type: DeviceLinkSink - links: - - 24436 - uid: 24296 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-46.5 parent: 2 - - type: DeviceLinkSink - links: - - 24391 - uid: 24297 components: - type: Transform pos: 34.5,18.5 parent: 2 - - type: DeviceLinkSink - links: - - 24437 - uid: 24298 components: - type: Transform pos: 35.5,18.5 parent: 2 - - type: DeviceLinkSink - links: - - 24437 - uid: 24299 components: - type: Transform pos: 36.5,18.5 parent: 2 - - type: DeviceLinkSink - links: - - 24437 - uid: 24300 components: - type: Transform pos: -7.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24438 - uid: 24301 components: - type: Transform rot: 1.5707963267948966 rad pos: 48.5,7.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24302 components: - type: Transform pos: 46.5,9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24303 components: - type: Transform rot: 1.5707963267948966 rad pos: 35.5,-1.5 parent: 2 - - type: DeviceLinkSink - links: - - 24394 - uid: 24304 components: - type: Transform rot: 1.5707963267948966 rad pos: 35.5,-2.5 parent: 2 - - type: DeviceLinkSink - links: - - 24394 - uid: 24305 components: - type: Transform rot: 1.5707963267948966 rad pos: 35.5,-4.5 parent: 2 - - type: DeviceLinkSink - links: - - 24394 - uid: 24306 components: - type: Transform rot: 1.5707963267948966 rad pos: 35.5,-5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24394 - uid: 24307 components: - type: Transform pos: 41.5,-0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24439 - uid: 24308 components: - type: Transform pos: 42.5,-0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24439 - uid: 24309 components: - type: Transform pos: 43.5,-0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24439 - uid: 24310 components: - type: Transform pos: 46.5,3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24392 - uid: 24311 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-47.5 parent: 2 - - type: DeviceLinkSink - links: - - 24391 - uid: 24312 components: - type: Transform rot: -1.5707963267948966 rad pos: 1.5,-48.5 parent: 2 - - type: DeviceLinkSink - links: - - 24391 - uid: 24313 components: - type: Transform pos: 3.5,-44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24391 - uid: 24314 components: - type: Transform pos: 4.5,-44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24391 - uid: 24315 components: - type: Transform pos: 5.5,-44.5 parent: 2 - - type: DeviceLinkSink - links: - - 24391 - uid: 24316 components: - type: Transform pos: 23.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24317 components: - type: Transform pos: 22.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24318 components: - type: Transform pos: 21.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24319 components: - type: Transform pos: 25.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24320 components: - type: Transform pos: 26.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24321 components: - type: Transform pos: 27.5,5.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24322 components: - type: Transform pos: 27.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24323 components: - type: Transform pos: 26.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24324 components: - type: Transform pos: 25.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24325 components: - type: Transform pos: 23.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24326 components: - type: Transform pos: 22.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24327 components: - type: Transform pos: 21.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24328 components: - type: Transform rot: 1.5707963267948966 rad pos: 19.5,3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24329 components: - type: Transform rot: 1.5707963267948966 rad pos: 19.5,2.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24330 components: - type: Transform rot: 1.5707963267948966 rad pos: 19.5,1.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24331 components: - type: Transform rot: 1.5707963267948966 rad pos: 19.5,0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24332 components: - type: Transform rot: 1.5707963267948966 rad pos: 19.5,-0.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24333 components: - type: Transform rot: 1.5707963267948966 rad pos: 19.5,-1.5 parent: 2 - - type: DeviceLinkSink - links: - - 24440 - uid: 24334 components: - type: Transform pos: 22.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24335 components: - type: Transform pos: 23.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24336 components: - type: Transform pos: 24.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24337 components: - type: Transform pos: 25.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24338 components: - type: Transform pos: 26.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24339 components: - type: Transform pos: 27.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24340 components: - type: Transform pos: 28.5,-20.5 parent: 2 - - type: DeviceLinkSink - links: - - 24441 - uid: 24341 components: - type: Transform pos: 4.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24458 - uid: 24342 components: - type: Transform pos: 1.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24458 - uid: 24343 components: - type: Transform pos: 3.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24458 - uid: 24344 components: - type: Transform pos: 2.5,-3.5 parent: 2 - - type: DeviceLinkSink - links: - - 24458 - uid: 24345 components: - type: Transform pos: 23.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24346 components: - type: Transform pos: 24.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24347 components: - type: Transform pos: 26.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24348 components: - type: Transform pos: 27.5,-40.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24349 components: - type: Transform rot: -1.5707963267948966 rad pos: 20.5,-36.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24350 components: - type: Transform rot: -1.5707963267948966 rad pos: 20.5,-35.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24351 components: - type: Transform rot: -1.5707963267948966 rad pos: 20.5,-34.5 parent: 2 - - type: DeviceLinkSink - links: - - 24460 - uid: 24352 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,-9.5 parent: 2 - - type: DeviceLinkSink - links: - - 24459 - uid: 24353 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,-10.5 parent: 2 - - type: DeviceLinkSink - links: - - 24459 - uid: 24354 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,-13.5 parent: 2 - - type: DeviceLinkSink - links: - - 24459 - uid: 24355 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,-14.5 parent: 2 - - type: DeviceLinkSink - links: - - 24459 - uid: 24356 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,24.5 parent: 2 - - type: DeviceLinkSink - links: - - 24449 - uid: 24357 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,23.5 parent: 2 - - type: DeviceLinkSink - links: - - 24449 - uid: 24358 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,19.5 parent: 2 - - type: DeviceLinkSink - links: - - 24449 - uid: 24359 components: - type: Transform rot: -1.5707963267948966 rad pos: -21.5,18.5 parent: 2 - - type: DeviceLinkSink - links: - - 24449 - uid: 24360 components: - type: Transform rot: -1.5707963267948966 rad pos: 37.5,-35.5 parent: 2 - - type: DeviceLinkSink - links: - - 24461 - uid: 24361 components: - type: Transform rot: -1.5707963267948966 rad pos: 37.5,-36.5 parent: 2 - - type: DeviceLinkSink - links: - - 24461 - proto: ShuttleConsoleCircuitboard entities: - uid: 24362 @@ -199573,8 +198502,6 @@ entities: parent: 2 - type: DeviceLinkSink invokeCounter: 1 - links: - - 24462 - proto: WindoorSecureArmoryLocked entities: - uid: 31248 @@ -199784,18 +198711,12 @@ entities: rot: 3.141592653589793 rad pos: 29.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 2261 - uid: 31280 components: - type: Transform rot: 1.5707963267948966 rad pos: 39.5,7.5 parent: 2 - - type: DeviceLinkSink - links: - - 2263 - uid: 31281 components: - type: Transform @@ -199814,18 +198735,12 @@ entities: rot: 3.141592653589793 rad pos: 32.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 2262 - uid: 31284 components: - type: Transform rot: 1.5707963267948966 rad pos: 39.5,4.5 parent: 2 - - type: DeviceLinkSink - links: - - 2264 - uid: 31285 components: - type: Transform @@ -199838,9 +198753,6 @@ entities: rot: 3.141592653589793 rad pos: 35.5,13.5 parent: 2 - - type: DeviceLinkSink - links: - - 2260 - uid: 31287 components: - type: Transform From fa6d6806d18de2d619311c99ae340c9029fc6cdd Mon Sep 17 00:00:00 2001 From: PJBot Date: Sat, 6 Jul 2024 17:34:30 +0000 Subject: [PATCH 042/242] 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 d57e1cde11b3..a2f77f3b5ae9 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Bellwether - changes: - - message: Midround zombie outbreaks are less common and spread more slowly. - type: Tweak - id: 6375 - time: '2024-04-18T01:06:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27060 - author: Flareguy changes: - message: Updated Remote Signaller sprites. @@ -3818,3 +3811,10 @@ id: 6874 time: '2024-07-06T03:51:55.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29757 +- author: JIPDawg + changes: + - message: Added Late join CryoSleepers to Origin. + type: Tweak + id: 6875 + time: '2024-07-06T17:33:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29761 From 60ef7848fb270117c0d89fea31d014307c9e5655 Mon Sep 17 00:00:00 2001 From: themias <89101928+themias@users.noreply.github.com> Date: Sat, 6 Jul 2024 23:49:55 -0400 Subject: [PATCH 043/242] Fix wielding while pulling (#29781) --- Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs index b31cc7557638..4a5894d89589 100644 --- a/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs +++ b/Content.Shared/Inventory/VirtualItem/SharedVirtualItemSystem.cs @@ -99,7 +99,7 @@ public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [No if (hand.HeldEntity is not { } held) continue; - if (held == blockingEnt || HasComp(held)) + if (held == blockingEnt) continue; if (!_handsSystem.TryDrop(user, hand)) From 0098d7171750e49097a96f48f817d55aa1a25650 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 13:50:12 +1000 Subject: [PATCH 044/242] Update Credits (#29784) Co-authored-by: PJBot --- Resources/Credits/GitHub.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index 1e41be2bb79d..f3d458e04689 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aeshus, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, ArkiveDev, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blueDev2, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, dffdff2423, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, Futuristic-OK, GalacticChimp, gbasood, Geekyhobo, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, Henry12116, HerCoyote23, hitomishirichan, Hmeister-real, HolySSSS, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Keer-Sar, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, MjrLandWhale, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, RamZ, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zonespace27, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aeshus, Aexxie, Afrokada, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlexUm418, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, ArchPigeon, Arendian, arimah, ArkiveDev, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, BellwetherLogic, BGare, bhenrich, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, blueDev2, Boaz1111, BobdaBiscuit, brainfood1183, Brandon-Huu, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, Callmore, CaptainSqrBeard, Carbonhell, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, Ciac32, clement-or, Clyybber, Cojoke-dot, ColdAutumnRain, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, deepdarkdepths, deepy, Delete69, deltanedas, DerbyX, DexlerXD, dffdff2423, diraven, Doctor-Cpu, DoctorBeard, DogZeroX, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, freeman2651, Froffy025, Fromoriss, FungiFellow, Futuristic-OK, GalacticChimp, gbasood, Geekyhobo, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, GNF54, Golinth, GoodWheatley, Gotimanga, graevy, GreyMario, gusxyz, Gyrandola, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, hitomishirichan, Hmeister-real, HoofedEar, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JIPDawg, JoeHammad1844, joelsgp, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, K-Dynamic, KaiShibaa, kalane15, kalanosh, Keer-Sar, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, Kukutis96513, kxvvv, Lamrr, LankLTE, laok233, lapatison, Leander-0, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, luckyshotpictures, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Mangohydra, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, MjrLandWhale, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, Nopey, notafet, notquitehadouken, noudoit, nuke-haus, NULL882, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, pigeonpeas, pissdemon, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Psychpsyo, psykzz, PuroSlavKing, PursuitInAshes, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, RamZ, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, RumiTiger, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, SethLafuente, ShadowCommander, Shadowtheprotogen546, shampunj, SignalWalker, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, snebl, Snowni, snowsignal, SonicHDC, SoulFN, SoulSloth, SpaceManiac, SpeltIncorrectyl, SphiraI, spoogemonster, ssdaniel24, Stealthbomber16, StrawberryMoses, superjj18, SweptWasTaken, Szunti, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, Terraspark4941, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UBlueberry, UKNOWH, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zonespace27, Zumorica, Zymem From 18df27d8e708117f64f50624c582bf0d6d0f5287 Mon Sep 17 00:00:00 2001 From: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Date: Sat, 6 Jul 2024 20:52:18 -0700 Subject: [PATCH 045/242] Spilling reagents on mutliple entities at once fix (#29763) * Fixed! * That could have been bad this is why I'm glad I check the diff xD --- .../Fluids/EntitySystems/PuddleSystem.Spillable.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs index 8cdae84a9362..e722108e147e 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.Spillable.cs @@ -70,13 +70,18 @@ private void SplashOnMeleeHit(Entity entity, ref MeleeHitEve return; args.Handled = true; + + // First update the hit count so anything that is not reactive wont count towards the total! + foreach (var hit in args.HitEntities) + { + if (!HasComp(hit)) + hitCount -= 1; + } + foreach (var hit in args.HitEntities) { if (!HasComp(hit)) - { - hitCount -= 1; // so we don't undershoot solution calculation for actual reactive entities continue; - } var splitSolution = _solutionContainerSystem.SplitSolution(soln.Value, totalSplit / hitCount); From d9be666e8ae8a35c929ab630a40e983bb0a334a1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 7 Jul 2024 03:53:24 +0000 Subject: [PATCH 046/242] 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 a2f77f3b5ae9..87dfa869fb72 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Flareguy - changes: - - message: Updated Remote Signaller sprites. - type: Tweak - id: 6376 - time: '2024-04-18T01:16:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27073 - author: Dutch-VanDerLinde changes: - message: Grey security jumpsuits are now available in the security officer loadout. @@ -3818,3 +3811,10 @@ id: 6875 time: '2024-07-06T17:33:20.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29761 +- author: Beck Thompson + changes: + - message: Splashing reagents on players will now apply the correct amounts. + type: Fix + id: 6876 + time: '2024-07-07T03:52:18.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29763 From 23887d5bd9444b81891d672463f1627aa62336cb Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sun, 7 Jul 2024 02:20:53 -0400 Subject: [PATCH 047/242] Improve buckling's interactions with standing state (#29741) --- .../Buckle/SharedBuckleSystem.Buckle.cs | 9 +++------ Content.Shared/Standing/StandingStateSystem.cs | 16 +++++++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 0ce8c992682b..0fbfd51d6985 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -196,7 +196,6 @@ protected void SetBuckledTo(Entity buckle, Entity buckle, Entity strap SetBuckledTo(buckle, strap!); Appearance.SetData(strap, StrapVisuals.State, true); Appearance.SetData(buckle, BuckleVisuals.Buckled, true); - Appearance.SetData(buckle, RotationVisuals.RotationState, RotationState.Horizontal); _rotationVisuals.SetHorizontalAngle(buckle.Owner, strap.Comp.Rotation); @@ -363,10 +361,10 @@ private void Buckle(Entity buckle, Entity strap switch (strap.Comp.Position) { case StrapPosition.Stand: - _standing.Stand(buckle); + _standing.Stand(buckle, force: true); break; case StrapPosition.Down: - _standing.Down(buckle, false, false); + _standing.Down(buckle, false, false, force: true); break; } @@ -458,10 +456,9 @@ private void Unbuckle(Entity buckle, Entity str _rotationVisuals.ResetHorizontalAngle(buckle.Owner); Appearance.SetData(strap, StrapVisuals.State, strap.Comp.BuckledEntities.Count != 0); Appearance.SetData(buckle, BuckleVisuals.Buckled, false); - Appearance.SetData(buckle, RotationVisuals.RotationState, RotationState.Vertical); if (HasComp(buckle) || _mobState.IsIncapacitated(buckle)) - _standing.Down(buckle); + _standing.Down(buckle, playSound: false); else _standing.Stand(buckle); diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index ed586e970dcd..8d9be9ab7769 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -25,7 +25,10 @@ public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) return !standingState.Standing; } - public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, + public bool Down(EntityUid uid, + bool playSound = true, + bool dropHeldItems = true, + bool force = false, StandingStateComponent? standingState = null, AppearanceComponent? appearance = null, HandsComponent? hands = null) @@ -49,11 +52,14 @@ public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true RaiseLocalEvent(uid, new DropHandItemsEvent(), false); } - var msg = new DownAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + if (!force) + { + var msg = new DownAttemptEvent(); + RaiseLocalEvent(uid, msg, false); - if (msg.Cancelled) - return false; + if (msg.Cancelled) + return false; + } standingState.Standing = false; Dirty(uid, standingState); From 336f264c7715dcd2cab25bb467c293c5d5461bce Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 7 Jul 2024 06:21:59 +0000 Subject: [PATCH 048/242] 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 87dfa869fb72..ef044e720130 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Grey security jumpsuits are now available in the security officer loadout. - type: Tweak - id: 6377 - time: '2024-04-18T01:22:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27023 - author: Tayrtahn changes: - message: Sodas and other fizzy drinks can be shaken with the verbs menu to build @@ -3818,3 +3811,11 @@ id: 6876 time: '2024-07-07T03:52:18.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29763 +- author: Tayrtahn + changes: + - message: Dead bodies will no longer remain standing after being unbuckled from + chairs. + type: Fix + id: 6877 + time: '2024-07-07T06:20:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29741 From c9c721be16f4764928bfc89cc28d70dd6fb1728b Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:05:09 +0200 Subject: [PATCH 049/242] Add supplybot moving states (#29795) * Add supplybot moving states * forgor --- .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 7 ++++++ .../Mobs/Silicon/Bots/supplybot.rsi/meta.json | 22 ++++++++++++++++++ .../Bots/supplybot.rsi/supplybot-moving.png | Bin 0 -> 2970 bytes .../Silicon/Bots/supplybot.rsi/supplybot.png | Bin 2970 -> 2718 bytes 4 files changed, 29 insertions(+) create mode 100644 Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/supplybot-moving.png diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 1686b723b59a..172ed66cc3a3 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -338,6 +338,13 @@ sprite: Mobs/Silicon/Bots/supplybot.rsi layers: - state: supplybot + - type: SpriteMovement + movementLayers: + movement: + state: supplybot-moving + noMovementLayers: + movement: + state: supplybot - type: GhostRole makeSentient: true name: ghost-role-information-supplybot-name diff --git a/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/meta.json b/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/meta.json index 6bb3e77cfeb0..f459a7caa634 100644 --- a/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/meta.json @@ -10,6 +10,28 @@ { "name": "supplybot", "directions": 4, + "delays": [ + [ + 0.3, + 0.1 + ], + [ + 0.3, + 0.1 + ], + [ + 0.3, + 0.1 + ], + [ + 0.3, + 0.1 + ] + ] + }, + { + "name": "supplybot-moving", + "directions": 4, "delays": [ [ 0.1, diff --git a/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/supplybot-moving.png b/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/supplybot-moving.png new file mode 100644 index 0000000000000000000000000000000000000000..cac976240bd3619027dfebb0597e3529251b5612 GIT binary patch literal 2970 zcmYjTc{tST7ypiRt|d!j&zeYRk|c`ASSzv%8H5tDm2CzS*|J?t2*XHMLYm@gvNXfB zPIj_2Vj48|j3qOfrr&hCfBgP<-}5~0Ip;m^`#hiXIiHLxwwA&IvH}1A2wPjh95@#B zYk~PVdxx=49{>pYSi?-6VleBa@i*mY@;$^YrXNj}{Cb;u#CfgLrBz_k{dq-Yw%Mwm zpoh$Ixu!;n_MZXkv)=c_z;an{g0&!9Jzh8a%CGwfaZM{;hu*vY!rH=RrC+Br%6G&q zHm}Yw5ANqt0Ij#%S{GR#G+f?5*56f6+mAQJAop5m+pGo4p>2%qoF$L}Q^~VZ75~>% zj+tD{v|?>l2bo~uK2f{`xJS_G3$An1YsWQ-GfX>THI}$6Okqzwo(5mb7PfnoPn_7+ ze+T+=;?1Q{pxWtuFc00ey-K0&OT_c_R#uOT7*O`Hzs;V+1xP>hGA=7q>{y#oiBddp z7NB$+;N0eGO#H_K4Ajs?TtIpt2%GB<0Q@<#&WmD1B+I7Grc+Hf{c->WjZ^UjP{T$b zVAFFrZU5)22Hf|~-hBY}Imkz1(TbieC(T`baPAgiMO3t;|IOn;+8*@_<@%{pd?Uls zodfMXOUMK$;C=EmkFuVSvR)QxYPg>6P-KBtC+Hyu3{2+v__QXo2IMr`BL*JOm~}z` zQu{>!@^u}SF+mR$zZn^L8a3s|$EDal1(W4cv_Dtao$r~l@kcSe>rP@Pz5r*SX@rPt z0h>fRyl${INIWEKYX)WS9uyH8KxKq#!?%wCy*XyfJEQOJcVf~@FXuD9i&W)ZQd0&` z|8unkoB~7@~7R$~&CzZ2d}_1}dJeC@Q-+`)i(*C`0dA4^>{b=o>vqulp<& zFA?@Rc{i-(6N8a&xkbo@$Erq~24pfS7lUjsNvDb-evWbtF2Xj$AmiiXF}G+6o20<7 zjooH)bsAPV-{uJo!#^?tIgqdiI$3nd9<4<6=$Nd7beyOluUiZmhyY4Dx;UBry689F z>~OB1$WU7aTvFOo&ZEuM?U1XC8vX1SVz0CWO+|-iX0k389tRC8+?db{?wMzB z_Yd9BjNPP|E)l&6e!ii#gW|J$&eZ9F?q*IBsBa z6q2L_ro<|6+zr{w?{WK`7fx#ACWHfAp9R4KoNreQ(HvX3qt-vBgq;aq;$2!^&Kp9r`L00V zwL*t~S#v)1kMlPR5 zHEx8cvscWGPy>~5xy-fYb>EjyUd56nyXZT}s9+XVf8z{5Y1j@?0aDJ(11g6_7HO|Z zSw7K1^t$;~!(`bR3CgX~Enn`33fn4FlKIT{pb0FS>82*{S6UCC+NZJOrrI>^CBFF`?wn>bf zyyGM1tba>^V}5jUTLzy@j)|~OxUZ6;>A0W?5rfbmkxVn>DzjLa^07!#CqXB9U{!O$ z(o!=s$Hk@5TKUWO{da%C)FpK=qLhUX8|l?}Sm34i<6*v%!Uwye z?{WOZirl1m-IPN4Tl@Xu^ou={*Ayv3Uqj5wI~tEA#(ix&M2L^@GiS$8ONR9SHg3Zu zcuongFJJli2qyxGn@s#hhFP=do%*@(`dyG}exD+C2p<=ZLbA~h6G;>N-qIF$p@tuH zCbD*tKgMCZY8T|>SH%j92PCF765SQcJMTixWfljq|3}WM;(^Y`@RPF}$ht;8t!=&n za*$n+@N-(|Rqj{GlU{Z~dWjMIWX}RU;B3q~7G}H)oGzp)Y-dM$y(WAXEN*wCTTaEF zSvw)Sw;x>s1D@Y?ru4Pc6A@$I$A8|Oom3<2BD?gHQd7skA2f`6pkf~IINzddi1#pfijvZQ+ODiNvz)Uw@5aE(&AkiMqujEJY1v{AL}2 zGVbsmzzQl?SbI2hvV8Sqn9tWO#Z=KbcR~f|&iC3Adg%7GHI;7Pi=V8nNl>QUn2B%c zZ#@^;Gff5Qp+-cm-eOVgr@eYGknJ(?nLVew`KAd%?$%zVEMT^-H=XgWzPP`%p!$8* z#!Qsw+`CoYJRj+$dDBu4UOj8iJ`Hyd7eq#Bt?amdw%OCd zU=B(p;1AqLyqf#uo}Iup)pQ#q-_w^24nfYBZ( zrqx&T{X+&^fxt2q?Fr`uJ}xWkURpY8DI2``6eylp-XTjEfiBz zh;Kp{sEK5X*qvz$$K2vQ!saAAtE##@A4Gb|ltL<^Xt9Ul10kjCV_h|l^Yil$)}LK2 zfp{!48H~5B+7{=A`kQ@rv5o@+jo6 zv0JTIR4u2gNb^@?!rWRo;O*XA3UTYt%AjB%#uA_qwLjlU=%7K1!p*PxZr*X%f|06q+KtBr~>(gS!0HeVj(D4HZ?O3|BJ_rH2V!KcPrY*|}$ z!8jDI)gY(1Sk{wmR@#aq&EDl;3OWSh3~=o1ZqiXO7YB3mPtj!Sj z?UCvdO=a82^Kl!e=Q=h*BA<-GQyhCf)qgb>1uw-d3{Wo;5Jm*~ZCo!7RUBbNVo?dk4MF{&0?2?|S7)Sa8y-aoEfyoNCsZ%wd)o@>o4=uE>mU86lH1o0|=H_V!9< zd2i9{R+1VSK^#F~1DrsJ{~ciTJ!%N+r~5-BKkH15=}dGMM{@(#=C-hx7rc}H2Z;~W AhX4Qo literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/supplybot.png b/Resources/Textures/Mobs/Silicon/Bots/supplybot.rsi/supplybot.png index cac976240bd3619027dfebb0597e3529251b5612..9a4895603b1cf1c2eb12d58a865531c200f1107d 100644 GIT binary patch literal 2718 zcmV;P3Ssq$P)c9KKK%u?0jvAVKESr3OUVJX(vh?rSy!d?JzB8V9?l$PeZ%~mmb1_fY2Cb{e zj4(a`lUCk@j1ztY?hJB@cUq><>#NeRs=XzP}{Z589z@? z=0w|7_V+w|mURV?1n9z7UIpYj%;hfqEt6U*5IX*~hZbmYagnlF^NP^e*hp<{ZFJ~2 zn`kb%P93e6`iiw&$_&tjuUrB{dt0gZ>J8M<*=%iS)UEf=h|Yie_)cpD;4_Zl9E!aT ze85-Oq6=S1Kw{eaCv016{LgKV=pdsHJU_?|%N{4+wvRyz@dY zVI*(+-dW1$^Pz&EE_~lcp%EBabOa@pTvZO(w;tO{tIH|+`;$u|2B3gA#D=h)mmoUu zJqZ{X7$_jJ%Yta1NF>Bp)jHqUeJw4|ou$=Plce6>UU7A>`2I`KVY!0_;} zXnadci`e74y%-~7{B`Ku){;{Wn;;*4Bm~v1Bz~wFV61D_2paLC5XB6L0-rqtxZoIB z*anSa*clKNK6?gm*RSlD*E;6G;x;svZ`X9WqTc{`Y`~ZAEH}$bq58?<{9ny*Ks|iEIa8py z#dUiBMKeDD2@t}z!D$CkB$HYbwxtvu_<;!$TdrIe3Q;Kqx5P`8#bkm+)cDF6aOBt$ zVN(@*$NV}O3yAV12xywCl-uQ1r(L z@?4ho?rfx{mL`F>mRY0mGg%tkZrn;YVumb8tU@3>`QaM<@E238JIb=IE_`JS=7rO)G-y;NEqjtW224B$%CaehFYA3#)&#$Qfdxl}6QgL_$y?P3OE z24V(c24V(c24V($8K~75zBs>}Sr>bboqd$$ zy9NB#Dc3L8j)U(hjX~UeX*6Zk|Hgq1gMi|ubL~)5K3dNSR9wGYJ3oBK62RKR=%dKQ z0+}R%SXLkFi+Hn1H?Kt?386ejdl9Qf$Y?~t<;EOnaRh_vvil1GV zp&~=zL_v+f{6mgW#hU;;zW*u?Cd=~_-H$g zj1nEXDLCaO$Zw_b9R%FGubJizr` zaQ$-aIQSw4K#Bd_EngQyzGdr1`sn364eV}p>~S3WzubcC;7iV$-#>DZj(y*F7m4u< z@~5x=VgY#e##Iwv5P+wKy=j6My@8$vGbb&y77w$kgwBMHIl2zbM4$Xy77ya05p7P`R>BiEOY3`t#$?goY@zdp8-n z@jY{dOfA3N(KfCo&$aWt6l%n`VgQs`X!pXa|BFztgL}tii$`GI%-!TKEMh_XgQaJ%rbL;feXBKH>WJGj&Y-}trM?fdOoh_Y+`?tB1yj(|Id0J#N-A;>k5c?VFRRT6nM@LBf*?3%wj9{{5&kGXRQa1I24 zg!^uv7HK!7lqm5dT>`SR$PK_!c=gx~T9*I^zvwD}I1pu$)PIAZGFPVB_@&Iy|LHpa Y0pR2nZ#wygegFUf07*qoM6N<$f_Dx)r~m)} literal 2970 zcmYjTc{tST7ypiRt|d!j&zeYRk|c`ASSzv%8H5tDm2CzS*|J?t2*XHMLYm@gvNXfB zPIj_2Vj48|j3qOfrr&hCfBgP<-}5~0Ip;m^`#hiXIiHLxwwA&IvH}1A2wPjh95@#B zYk~PVdxx=49{>pYSi?-6VleBa@i*mY@;$^YrXNj}{Cb;u#CfgLrBz_k{dq-Yw%Mwm zpoh$Ixu!;n_MZXkv)=c_z;an{g0&!9Jzh8a%CGwfaZM{;hu*vY!rH=RrC+Br%6G&q zHm}Yw5ANqt0Ij#%S{GR#G+f?5*56f6+mAQJAop5m+pGo4p>2%qoF$L}Q^~VZ75~>% zj+tD{v|?>l2bo~uK2f{`xJS_G3$An1YsWQ-GfX>THI}$6Okqzwo(5mb7PfnoPn_7+ ze+T+=;?1Q{pxWtuFc00ey-K0&OT_c_R#uOT7*O`Hzs;V+1xP>hGA=7q>{y#oiBddp z7NB$+;N0eGO#H_K4Ajs?TtIpt2%GB<0Q@<#&WmD1B+I7Grc+Hf{c->WjZ^UjP{T$b zVAFFrZU5)22Hf|~-hBY}Imkz1(TbieC(T`baPAgiMO3t;|IOn;+8*@_<@%{pd?Uls zodfMXOUMK$;C=EmkFuVSvR)QxYPg>6P-KBtC+Hyu3{2+v__QXo2IMr`BL*JOm~}z` zQu{>!@^u}SF+mR$zZn^L8a3s|$EDal1(W4cv_Dtao$r~l@kcSe>rP@Pz5r*SX@rPt z0h>fRyl${INIWEKYX)WS9uyH8KxKq#!?%wCy*XyfJEQOJcVf~@FXuD9i&W)ZQd0&` z|8unkoB~7@~7R$~&CzZ2d}_1}dJeC@Q-+`)i(*C`0dA4^>{b=o>vqulp<& zFA?@Rc{i-(6N8a&xkbo@$Erq~24pfS7lUjsNvDb-evWbtF2Xj$AmiiXF}G+6o20<7 zjooH)bsAPV-{uJo!#^?tIgqdiI$3nd9<4<6=$Nd7beyOluUiZmhyY4Dx;UBry689F z>~OB1$WU7aTvFOo&ZEuM?U1XC8vX1SVz0CWO+|-iX0k389tRC8+?db{?wMzB z_Yd9BjNPP|E)l&6e!ii#gW|J$&eZ9F?q*IBsBa z6q2L_ro<|6+zr{w?{WK`7fx#ACWHfAp9R4KoNreQ(HvX3qt-vBgq;aq;$2!^&Kp9r`L00V zwL*t~S#v)1kMlPR5 zHEx8cvscWGPy>~5xy-fYb>EjyUd56nyXZT}s9+XVf8z{5Y1j@?0aDJ(11g6_7HO|Z zSw7K1^t$;~!(`bR3CgX~Enn`33fn4FlKIT{pb0FS>82*{S6UCC+NZJOrrI>^CBFF`?wn>bf zyyGM1tba>^V}5jUTLzy@j)|~OxUZ6;>A0W?5rfbmkxVn>DzjLa^07!#CqXB9U{!O$ z(o!=s$Hk@5TKUWO{da%C)FpK=qLhUX8|l?}Sm34i<6*v%!Uwye z?{WOZirl1m-IPN4Tl@Xu^ou={*Ayv3Uqj5wI~tEA#(ix&M2L^@GiS$8ONR9SHg3Zu zcuongFJJli2qyxGn@s#hhFP=do%*@(`dyG}exD+C2p<=ZLbA~h6G;>N-qIF$p@tuH zCbD*tKgMCZY8T|>SH%j92PCF765SQcJMTixWfljq|3}WM;(^Y`@RPF}$ht;8t!=&n za*$n+@N-(|Rqj{GlU{Z~dWjMIWX}RU;B3q~7G}H)oGzp)Y-dM$y(WAXEN*wCTTaEF zSvw)Sw;x>s1D@Y?ru4Pc6A@$I$A8|Oom3<2BD?gHQd7skA2f`6pkf~IINzddi1#pfijvZQ+ODiNvz)Uw@5aE(&AkiMqujEJY1v{AL}2 zGVbsmzzQl?SbI2hvV8Sqn9tWO#Z=KbcR~f|&iC3Adg%7GHI;7Pi=V8nNl>QUn2B%c zZ#@^;Gff5Qp+-cm-eOVgr@eYGknJ(?nLVew`KAd%?$%zVEMT^-H=XgWzPP`%p!$8* z#!Qsw+`CoYJRj+$dDBu4UOj8iJ`Hyd7eq#Bt?amdw%OCd zU=B(p;1AqLyqf#uo}Iup)pQ#q-_w^24nfYBZ( zrqx&T{X+&^fxt2q?Fr`uJ}xWkURpY8DI2``6eylp-XTjEfiBz zh;Kp{sEK5X*qvz$$K2vQ!saAAtE##@A4Gb|ltL<^Xt9Ul10kjCV_h|l^Yil$)}LK2 zfp{!48H~5B+7{=A`kQ@rv5o@+jo6 zv0JTIR4u2gNb^@?!rWRo;O*XA3UTYt%AjB%#uA_qwLjlU=%7K1!p*PxZr*X%f|06q+KtBr~>(gS!0HeVj(D4HZ?O3|BJ_rH2V!KcPrY*|}$ z!8jDI)gY(1Sk{wmR@#aq&EDl;3OWSh3~=o1ZqiXO7YB3mPtj!Sj z?UCvdO=a82^Kl!e=Q=h*BA<-GQyhCf)qgb>1uw-d3{Wo;5Jm*~ZCo!7RUBbNVo?dk4MF{&0?2?|S7)Sa8y-aoEfyoNCsZ%wd)o@>o4=uE>mU86lH1o0|=H_V!9< zd2i9{R+1VSK^#F~1DrsJ{~ciTJ!%N+r~5-BKkH15=}dGMM{@(#=C-hx7rc}H2Z;~W AhX4Qo From f50488d7e3cd603e6ed9ba4139f41e8a658c1d7b Mon Sep 17 00:00:00 2001 From: Simon <63975668+Simyon264@users.noreply.github.com> Date: Sun, 7 Jul 2024 15:06:24 +0200 Subject: [PATCH 050/242] Raise ratking migration minimum players to 30 from 15 (#29737) --- Resources/Prototypes/GameRules/events.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index bfd9dd6230e5..39e29ad1158c 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -230,7 +230,7 @@ earliestStart: 15 weight: 6 duration: 50 - minimumPlayers: 15 # Hopefully this is enough for the Rat King's potential Army + minimumPlayers: 30 # Hopefully this is enough for the Rat King's potential Army (it was not, raised from 15 -> 30) - type: VentCrittersRule entries: - id: MobMouse From 063c5de9858989f24897cbfa6a5b52776173591c Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 7 Jul 2024 13:07:30 +0000 Subject: [PATCH 051/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ef044e720130..f5387d8fdb7a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Tayrtahn - changes: - - message: Sodas and other fizzy drinks can be shaken with the verbs menu to build - up fizziness so they spray when opened. - type: Add - id: 6378 - time: '2024-04-18T01:49:58.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25574 - author: metalgearsloth changes: - message: Fix lobby UI not resetting for character changes. @@ -3819,3 +3811,10 @@ id: 6877 time: '2024-07-07T06:20:53.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29741 +- author: Simyon + changes: + - message: Ratkings now require at least 30 players in order to spawn. + type: Tweak + id: 6878 + time: '2024-07-07T13:06:24.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29737 From 0e9ed36b8568e7deb17fe3ae1a038f983e761e28 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:19:10 -0400 Subject: [PATCH 052/242] Intercom buffs and fixes (#29580) * Intercom buffs and fixes * remove unused bui state * mild sec intercom buff * reinforce sec intercoms --- .../Radio/EntitySystems/RadioDeviceSystem.cs | 23 +++ .../Radio/Ui/IntercomBoundUserInterface.cs | 15 +- Content.Client/Radio/Ui/IntercomMenu.xaml.cs | 37 +++- .../Radio/EntitySystems/RadioDeviceSystem.cs | 84 ++++---- .../Radio/EntitySystems/RadioSystem.cs | 9 +- .../Radio/Components/IntercomComponent.cs | 21 +- .../Components/TelecomExemptComponent.cs | 9 + .../EntitySystems/EncryptionKeySystem.cs | 14 +- Content.Shared/Radio/SharedIntercom.cs | 20 +- Content.Shared/Wires/SharedWiresSystem.cs | 6 + .../en-US/radio/components/intercom.ftl | 3 +- .../Entities/Objects/Devices/radio.yml | 3 +- .../Structures/Wallmounts/intercom.yml | 186 +++++++++++++----- .../Graphs/utilities/intercom.yml | 41 +++- .../Recipes/Construction/utilities.yml | 2 +- .../Wallmounts/intercom.rsi/base.png | Bin 1438 -> 729 bytes .../Wallmounts/intercom.rsi/broadcasting.png | Bin 171 -> 137 bytes .../Wallmounts/intercom.rsi/build.png | Bin 1369 -> 656 bytes .../Wallmounts/intercom.rsi/panel.png | Bin 656 -> 316 bytes .../Wallmounts/intercom.rsi/speaker.png | Bin 157 -> 134 bytes .../Wallmounts/intercom.rsi/unshaded.png | Bin 328 -> 217 bytes Resources/migration.yml | 3 + 22 files changed, 339 insertions(+), 137 deletions(-) create mode 100644 Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs create mode 100644 Content.Shared/Radio/Components/TelecomExemptComponent.cs diff --git a/Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs new file mode 100644 index 000000000000..29d6c635ebc8 --- /dev/null +++ b/Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs @@ -0,0 +1,23 @@ +using Content.Client.Radio.Ui; +using Content.Shared.Radio; +using Content.Shared.Radio.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Radio.EntitySystems; + +public sealed class RadioDeviceSystem : EntitySystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnAfterHandleState); + } + + private void OnAfterHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (_ui.TryGetOpenUi(ent.Owner, IntercomUiKey.Key, out var bui)) + bui.Update(ent); + } +} diff --git a/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs b/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs index abbb1d58ec4b..7b3e39aa084b 100644 --- a/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs +++ b/Content.Client/Radio/Ui/IntercomBoundUserInterface.cs @@ -1,6 +1,6 @@ using Content.Shared.Radio; +using Content.Shared.Radio.Components; using JetBrains.Annotations; -using Robust.Client.GameObjects; namespace Content.Client.Radio.Ui; @@ -19,7 +19,9 @@ protected override void Open() { base.Open(); - _menu = new(); + var comp = EntMan.GetComponent(Owner); + + _menu = new((Owner, comp)); _menu.OnMicPressed += enabled => { @@ -46,13 +48,8 @@ protected override void Dispose(bool disposing) _menu?.Close(); } - protected override void UpdateState(BoundUserInterfaceState state) + public void Update(Entity ent) { - base.UpdateState(state); - - if (state is not IntercomBoundUIState msg) - return; - - _menu?.Update(msg); + _menu?.Update(ent); } } diff --git a/Content.Client/Radio/Ui/IntercomMenu.xaml.cs b/Content.Client/Radio/Ui/IntercomMenu.xaml.cs index 8b4b38753c1d..2e08913051c8 100644 --- a/Content.Client/Radio/Ui/IntercomMenu.xaml.cs +++ b/Content.Client/Radio/Ui/IntercomMenu.xaml.cs @@ -1,8 +1,9 @@ using Content.Client.UserInterface.Controls; -using Content.Shared.Radio; +using Content.Shared.Radio.Components; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Client.Radio.Ui; @@ -17,38 +18,54 @@ public sealed partial class IntercomMenu : FancyWindow private readonly List _channels = new(); - public IntercomMenu() + public IntercomMenu(Entity entity) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); MicButton.OnPressed += args => OnMicPressed?.Invoke(args.Button.Pressed); SpeakerButton.OnPressed += args => OnSpeakerPressed?.Invoke(args.Button.Pressed); + + Update(entity); } - public void Update(IntercomBoundUIState state) + public void Update(Entity entity) { - MicButton.Pressed = state.MicEnabled; - SpeakerButton.Pressed = state.SpeakerEnabled; + MicButton.Pressed = entity.Comp.MicrophoneEnabled; + SpeakerButton.Pressed = entity.Comp.SpeakerEnabled; + + MicButton.Disabled = entity.Comp.SupportedChannels.Count == 0; + SpeakerButton.Disabled = entity.Comp.SupportedChannels.Count == 0; + ChannelOptions.Disabled = entity.Comp.SupportedChannels.Count == 0; ChannelOptions.Clear(); _channels.Clear(); - for (var i = 0; i < state.AvailableChannels.Count; i++) + for (var i = 0; i < entity.Comp.SupportedChannels.Count; i++) { - var channel = state.AvailableChannels[i]; - if (!_prototype.TryIndex(channel, out var prototype)) + var channel = entity.Comp.SupportedChannels[i]; + if (!_prototype.TryIndex(channel, out var prototype)) continue; _channels.Add(channel); ChannelOptions.AddItem(Loc.GetString(prototype.Name), i); - if (channel == state.SelectedChannel) + if (channel == entity.Comp.CurrentChannel) ChannelOptions.Select(i); } + + if (entity.Comp.SupportedChannels.Count == 0) + { + ChannelOptions.AddItem(Loc.GetString("intercom-options-none"), 0); + ChannelOptions.Select(0); + } + ChannelOptions.OnItemSelected += args => { + if (!_channels.TryGetValue(args.Id, out var proto)) + return; + ChannelOptions.SelectId(args.Id); - OnChannelSelected?.Invoke(_channels[args.Id]); + OnChannelSelected?.Invoke(proto); }; } } diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 8484fb233638..1258e0b8c7eb 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Server.Chat.Systems; using Content.Server.Interaction; using Content.Server.Popups; @@ -6,13 +7,10 @@ using Content.Server.Radio.Components; using Content.Server.Speech; using Content.Server.Speech.Components; -using Content.Shared.UserInterface; -using Content.Shared.Chat; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Radio; using Content.Shared.Radio.Components; -using Robust.Server.GameObjects; using Robust.Shared.Prototypes; namespace Content.Server.Radio.EntitySystems; @@ -28,7 +26,6 @@ public sealed class RadioDeviceSystem : EntitySystem [Dependency] private readonly RadioSystem _radio = default!; [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly UserInterfaceSystem _ui = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid)> _recentlySent = new(); @@ -47,7 +44,7 @@ public override void Initialize() SubscribeLocalEvent(OnActivateSpeaker); SubscribeLocalEvent(OnReceiveRadio); - SubscribeLocalEvent(OnBeforeIntercomUiOpen); + SubscribeLocalEvent(OnIntercomEncryptionChannelsChanged); SubscribeLocalEvent(OnToggleIntercomMic); SubscribeLocalEvent(OnToggleIntercomSpeaker); SubscribeLocalEvent(OnSelectIntercomChannel); @@ -150,18 +147,18 @@ public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false SetSpeakerEnabled(uid, user, !component.Enabled, quiet, component); } - public void SetSpeakerEnabled(EntityUid uid, EntityUid user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null) + public void SetSpeakerEnabled(EntityUid uid, EntityUid? user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null) { if (!Resolve(uid, ref component)) return; component.Enabled = enabled; - if (!quiet) + if (!quiet && user != null) { var state = Loc.GetString(component.Enabled ? "handheld-radio-component-on-state" : "handheld-radio-component-off-state"); var message = Loc.GetString("handheld-radio-component-on-use", ("radioState", state)); - _popup.PopupEntity(message, user, user); + _popup.PopupEntity(message, user.Value, user.Value); } _appearance.SetData(uid, RadioDeviceVisuals.Speaker, component.Enabled); @@ -213,61 +210,74 @@ private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource)); RaiseLocalEvent(args.MessageSource, nameEv); - var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)), + var name = Loc.GetString("speech-name-relay", + ("speaker", Name(uid)), ("originalName", nameEv.Name)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); } - private void OnBeforeIntercomUiOpen(EntityUid uid, IntercomComponent component, BeforeActivatableUIOpenEvent args) + private void OnIntercomEncryptionChannelsChanged(Entity ent, ref EncryptionChannelsChangedEvent args) { - UpdateIntercomUi(uid, component); + ent.Comp.SupportedChannels = args.Component.Channels.Select(p => new ProtoId(p)).ToList(); + + var channel = args.Component.DefaultChannel; + if (ent.Comp.CurrentChannel != null && ent.Comp.SupportedChannels.Contains(ent.Comp.CurrentChannel.Value)) + channel = ent.Comp.CurrentChannel; + + SetIntercomChannel(ent, channel); } - private void OnToggleIntercomMic(EntityUid uid, IntercomComponent component, ToggleIntercomMicMessage args) + private void OnToggleIntercomMic(Entity ent, ref ToggleIntercomMicMessage args) { - if (component.RequiresPower && !this.IsPowered(uid, EntityManager)) + if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager)) return; - SetMicrophoneEnabled(uid, args.Actor, args.Enabled, true); - UpdateIntercomUi(uid, component); + SetMicrophoneEnabled(ent, args.Actor, args.Enabled, true); + ent.Comp.MicrophoneEnabled = args.Enabled; + Dirty(ent); } - private void OnToggleIntercomSpeaker(EntityUid uid, IntercomComponent component, ToggleIntercomSpeakerMessage args) + private void OnToggleIntercomSpeaker(Entity ent, ref ToggleIntercomSpeakerMessage args) { - if (component.RequiresPower && !this.IsPowered(uid, EntityManager)) + if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager)) return; - SetSpeakerEnabled(uid, args.Actor, args.Enabled, true); - UpdateIntercomUi(uid, component); + SetSpeakerEnabled(ent, args.Actor, args.Enabled, true); + ent.Comp.SpeakerEnabled = args.Enabled; + Dirty(ent); } - private void OnSelectIntercomChannel(EntityUid uid, IntercomComponent component, SelectIntercomChannelMessage args) + private void OnSelectIntercomChannel(Entity ent, ref SelectIntercomChannelMessage args) { - if (component.RequiresPower && !this.IsPowered(uid, EntityManager)) + if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager)) return; - if (!_protoMan.TryIndex(args.Channel, out _) || !component.SupportedChannels.Contains(args.Channel)) + if (!_protoMan.HasIndex(args.Channel) || !ent.Comp.SupportedChannels.Contains(args.Channel)) return; - if (TryComp(uid, out var mic)) - mic.BroadcastChannel = args.Channel; - if (TryComp(uid, out var speaker)) - speaker.Channels = new(){ args.Channel }; - UpdateIntercomUi(uid, component); + SetIntercomChannel(ent, args.Channel); } - private void UpdateIntercomUi(EntityUid uid, IntercomComponent component) + private void SetIntercomChannel(Entity ent, ProtoId? channel) { - var micComp = CompOrNull(uid); - var speakerComp = CompOrNull(uid); - - var micEnabled = micComp?.Enabled ?? false; - var speakerEnabled = speakerComp?.Enabled ?? false; - var availableChannels = component.SupportedChannels; - var selectedChannel = micComp?.BroadcastChannel ?? SharedChatSystem.CommonChannel; - var state = new IntercomBoundUIState(micEnabled, speakerEnabled, availableChannels, selectedChannel); - _ui.SetUiState(uid, IntercomUiKey.Key, state); + ent.Comp.CurrentChannel = channel; + + if (channel == null) + { + SetSpeakerEnabled(ent, null, false); + SetMicrophoneEnabled(ent, null, false); + ent.Comp.MicrophoneEnabled = false; + ent.Comp.SpeakerEnabled = false; + Dirty(ent); + return; + } + + if (TryComp(ent, out var mic)) + mic.BroadcastChannel = channel; + if (TryComp(ent, out var speaker)) + speaker.Channels = new(){ channel }; + Dirty(ent); } } diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 4341746aafe3..3ad101e62dbd 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -33,11 +33,15 @@ public sealed class RadioSystem : EntitySystem // set used to prevent radio feedback loops. private readonly HashSet _messages = new(); + private EntityQuery _exemptQuery; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnIntrinsicReceive); SubscribeLocalEvent(OnIntrinsicSpeak); + + _exemptQuery = GetEntityQuery(); } private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args) @@ -121,9 +125,8 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann var sourceMapId = Transform(radioSource).MapID; var hasActiveServer = HasActiveServer(sourceMapId, channel.ID); - var hasMicro = HasComp(radioSource); + var sourceServerExempt = _exemptQuery.HasComp(radioSource); - var speakerQuery = GetEntityQuery(); var radioQuery = EntityQueryEnumerator(); while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform)) { @@ -138,7 +141,7 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann continue; // don't need telecom server for long range channels or handheld radios and intercoms - var needServer = !channel.LongRange && (!hasMicro || !speakerQuery.HasComponent(receiver)); + var needServer = !channel.LongRange && !sourceServerExempt; if (needServer && !hasActiveServer) continue; diff --git a/Content.Shared/Radio/Components/IntercomComponent.cs b/Content.Shared/Radio/Components/IntercomComponent.cs index be2734ff1684..8d7b87597b10 100644 --- a/Content.Shared/Radio/Components/IntercomComponent.cs +++ b/Content.Shared/Radio/Components/IntercomComponent.cs @@ -1,23 +1,32 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; +using Robust.Shared.Prototypes; namespace Content.Shared.Radio.Components; /// /// Handles intercom ui and is authoritative on the channels an intercom can access. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class IntercomComponent : Component { /// - /// Does this intercom require popwer to function + /// Does this intercom require power to function /// - [DataField("requiresPower"), ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool RequiresPower = true; + [DataField, AutoNetworkedField] + public bool SpeakerEnabled; + + [DataField, AutoNetworkedField] + public bool MicrophoneEnabled; + + [DataField, AutoNetworkedField] + public ProtoId? CurrentChannel; + /// /// The list of radio channel prototypes this intercom can choose between. /// - [DataField("supportedChannels", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List SupportedChannels = new(); + [DataField, AutoNetworkedField] + public List> SupportedChannels = new(); } diff --git a/Content.Shared/Radio/Components/TelecomExemptComponent.cs b/Content.Shared/Radio/Components/TelecomExemptComponent.cs new file mode 100644 index 000000000000..7af5c1c78ca4 --- /dev/null +++ b/Content.Shared/Radio/Components/TelecomExemptComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Radio.Components; + +/// +/// This is used for a radio that doesn't need a telecom server in order to broadcast. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class TelecomExemptComponent : Component; diff --git a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs index ea07b5f8a5ce..cfa553661a39 100644 --- a/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs +++ b/Content.Shared/Radio/EntitySystems/EncryptionKeySystem.cs @@ -31,6 +31,7 @@ public sealed partial class EncryptionKeySystem : EntitySystem [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly SharedWiresSystem _wires = default!; public override void Initialize() { @@ -150,7 +151,7 @@ private void TryRemoveKey(EntityUid uid, EncryptionKeyHolderComponent component, return; } - if (TryComp(uid, out var panel) && !panel.Open) + if (!_wires.IsPanelOpen(uid)) { _popup.PopupClient(Loc.GetString("encryption-keys-panel-locked"), uid, args.User); return; @@ -184,8 +185,15 @@ private void OnHolderExamined(EntityUid uid, EncryptionKeyHolderComponent compon if (component.Channels.Count > 0) { - args.PushMarkup(Loc.GetString("examine-encryption-channels-prefix")); - AddChannelsExamine(component.Channels, component.DefaultChannel, args, _protoManager, "examine-encryption-channel"); + using (args.PushGroup(nameof(EncryptionKeyComponent))) + { + args.PushMarkup(Loc.GetString("examine-encryption-channels-prefix")); + AddChannelsExamine(component.Channels, + component.DefaultChannel, + args, + _protoManager, + "examine-encryption-channel"); + } } } diff --git a/Content.Shared/Radio/SharedIntercom.cs b/Content.Shared/Radio/SharedIntercom.cs index 410843312fd2..f697add8b90c 100644 --- a/Content.Shared/Radio/SharedIntercom.cs +++ b/Content.Shared/Radio/SharedIntercom.cs @@ -1,4 +1,5 @@ -using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Radio; @@ -8,23 +9,6 @@ public enum IntercomUiKey Key, } -[Serializable, NetSerializable] -public sealed class IntercomBoundUIState : BoundUserInterfaceState -{ - public bool MicEnabled; - public bool SpeakerEnabled; - public List AvailableChannels; - public string SelectedChannel; - - public IntercomBoundUIState(bool micEnabled, bool speakerEnabled, List availableChannels, string selectedChannel) - { - MicEnabled = micEnabled; - SpeakerEnabled = speakerEnabled; - AvailableChannels = availableChannels; - SelectedChannel = selectedChannel; - } -} - [Serializable, NetSerializable] public sealed class ToggleIntercomMicMessage : BoundUserInterfaceMessage { diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index d84766a5fc66..7032293eaf6e 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -20,6 +20,7 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnPanelDoAfter); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnExamine); @@ -28,6 +29,11 @@ public override void Initialize() SubscribeLocalEvent(OnActivatableUIPanelChanged); } + private void OnStartup(Entity ent, ref ComponentStartup args) + { + UpdateAppearance(ent, ent); + } + private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args) { if (args.Cancelled) diff --git a/Resources/Locale/en-US/radio/components/intercom.ftl b/Resources/Locale/en-US/radio/components/intercom.ftl index e56e3cd0f736..63303999c211 100644 --- a/Resources/Locale/en-US/radio/components/intercom.ftl +++ b/Resources/Locale/en-US/radio/components/intercom.ftl @@ -1,5 +1,6 @@ intercom-menu-title = Intercom intercom-channel-label = Channel: intercom-button-text-mic = Mic. -intercom-button-text-speaker = Speak +intercom-button-text-speaker = Spkr. +intercom-options-none = No channels intercom-flavor-text-left = Keep lines free of chatter diff --git a/Resources/Prototypes/Entities/Objects/Devices/radio.yml b/Resources/Prototypes/Entities/Objects/Devices/radio.yml index 43f84fe404e3..77b6cac2d375 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/radio.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/radio.yml @@ -4,6 +4,7 @@ parent: BaseItem id: RadioHandheld components: + - type: TelecomExempt - type: RadioMicrophone broadcastChannel: Handheld - type: RadioSpeaker @@ -39,4 +40,4 @@ sprite: Objects/Devices/securityhandy.rsi - type: Item sprite: Objects/Devices/securityhandy.rsi - heldPrefix: walkietalkie \ No newline at end of file + heldPrefix: walkietalkie diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml index 2cf77d843c50..ca1b1b6c40f5 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -1,5 +1,5 @@ - type: entity - id: Intercom + id: BaseIntercom name: intercom description: An intercom. For when the station just needs to know something. abstract: true @@ -9,6 +9,10 @@ - type: Electrified enabled: false usesApcPower: true + - type: TelecomExempt + - type: EncryptionKeyHolder + keySlots: 3 + keysExtractionMethod: Prying - type: RadioMicrophone powerRequired: true unobstructedRequired: true @@ -24,12 +28,14 @@ - type: InteractionOutline - type: Appearance - type: WiresVisuals + - type: WiresPanelSecurity - type: ContainerFill containers: board: [ IntercomElectronics ] - type: ContainerContainer containers: board: !type:Container + key_slots: !type:Container - type: Sprite noRot: false drawdepth: SmallObjects @@ -49,7 +55,6 @@ visible: false - state: panel map: ["enum.WiresVisualLayers.MaintenancePanel"] - shader: unshaded visible: false - type: Transform noRot: false @@ -61,6 +66,7 @@ - type: ActivatableUIRequiresPower - type: ActivatableUI key: enum.IntercomUiKey.Key + singleUser: true - type: UserInterface interfaces: enum.IntercomUiKey.Key: @@ -116,7 +122,7 @@ - Wallmount - type: entity - id: IntercomAssesmbly + id: IntercomAssembly name: intercom assembly description: An intercom. It doesn't seem very helpful right now. components: @@ -126,7 +132,18 @@ - type: Sprite drawdepth: SmallObjects sprite: Structures/Wallmounts/intercom.rsi - state: build + layers: + - state: build + - state: panel + visible: false + map: [ "wires" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ConstructionVisuals.Layer: + wires: + 0: { visible: false } + 1: { visible: true } - type: Construction graph: Intercom node: assembly @@ -137,97 +154,176 @@ snap: - Wallmount +# this weird inheritance BS exists for construction shitcode +- type: entity + id: IntercomConstructed + parent: BaseIntercom + suffix: Empty, Panel Open + components: + - type: Sprite + layers: + - state: base + - state: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + shader: unshaded + - state: broadcasting + map: ["enum.RadioDeviceVisualLayers.Broadcasting"] + shader: unshaded + visible: false + - state: speaker + map: ["enum.RadioDeviceVisualLayers.Speaker"] + shader: unshaded + visible: false + - state: panel + map: ["enum.WiresVisualLayers.MaintenancePanel"] + visible: true + - type: WiresPanel + open: true + +- type: entity + id: Intercom + parent: IntercomConstructed + suffix: "" + components: + - type: Sprite + layers: + - state: base + - state: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + shader: unshaded + - state: broadcasting + map: ["enum.RadioDeviceVisualLayers.Broadcasting"] + shader: unshaded + visible: false + - state: speaker + map: ["enum.RadioDeviceVisualLayers.Speaker"] + shader: unshaded + visible: false + - state: panel + map: ["enum.WiresVisualLayers.MaintenancePanel"] + - type: WiresPanel + open: false + - type: entity id: IntercomCommon parent: Intercom suffix: Common components: - - type: Intercom - supportedChannels: - - Common + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon - type: entity id: IntercomCommand parent: Intercom suffix: Command components: - - type: Intercom - supportedChannels: - - Common - - Command + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyCommand - type: entity id: IntercomEngineering parent: Intercom suffix: Engineering components: - - type: Intercom - supportedChannels: - - Common - - Engineering + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyEngineering - type: entity id: IntercomMedical parent: Intercom suffix: Medical components: - - type: Intercom - supportedChannels: - - Common - - Medical + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyMedical - type: entity id: IntercomScience parent: Intercom suffix: Science components: - - type: Intercom - supportedChannels: - - Common - - Science + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyScience - type: entity id: IntercomSecurity parent: Intercom suffix: Security + description: An intercom. It's been reinforced with metal from security helmets, making it a bitch-and-a-half to open. components: - - type: Intercom - supportedChannels: - - Common - - Security + - type: WiresPanel + openDelay: 5 + - type: WiresPanelSecurity + examine: wires-panel-component-on-examine-security-level2 + wiresAccessible: false + - type: Construction + node: intercomReinforced + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeySecurity - type: entity id: IntercomService parent: Intercom suffix: Service components: - - type: Intercom - supportedChannels: - - Common - - Service + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyService - type: entity id: IntercomSupply parent: Intercom suffix: Supply components: - - type: Intercom - supportedChannels: - - Common - - Supply + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyCargo - type: entity id: IntercomAll parent: Intercom suffix: All components: - - type: Intercom - supportedChannels: - - Common - - Command - - Engineering - - Medical - - Science - - Security - - Service - - Supply + - type: ContainerFill + containers: + board: + - IntercomElectronics + key_slots: + - EncryptionKeyCommon + - EncryptionKeyStationMaster diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml index 2247860f892f..ba29d72539a0 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml @@ -11,13 +11,17 @@ doAfter: 2.0 - node: assembly - entity: IntercomAssesmbly + entity: IntercomAssembly edges: - to: wired steps: - material: Cable amount: 2 doAfter: 1 + completed: + - !type:VisualizerDataInt + key: "enum.ConstructionVisuals.Layer" + data: 1 - to: start completed: - !type:GivePrototype @@ -29,7 +33,7 @@ doAfter: 2 - node: wired - entity: IntercomAssesmbly + entity: IntercomAssembly edges: - to: electronics steps: @@ -45,6 +49,9 @@ - !type:GivePrototype prototype: CableApcStack1 amount: 2 + - !type:VisualizerDataInt + key: "enum.ConstructionVisuals.Layer" + data: 0 steps: - tool: Cutting doAfter: 1 @@ -57,7 +64,11 @@ doAfter: 2 - node: intercom - entity: IntercomCommon #TODO: make this work with encryption keys + entity: IntercomConstructed + doNotReplaceInheritingEntities: true + actions: + - !type:SetWiresPanelSecurity + wiresAccessible: true edges: - to: wired conditions: @@ -72,3 +83,27 @@ steps: - tool: Prying doAfter: 1 + - to: intercomReinforced + conditions: + - !type:WirePanel + steps: + - material: Steel + amount: 1 + - tool: Welding + doAfter: 1 + + - node: intercomReinforced + actions: + - !type:SetWiresPanelSecurity + examine: wires-panel-component-on-examine-security-level2 + wiresAccessible: false + edges: + - to: intercom + conditions: + - !type:WirePanel + completed: + - !type:GivePrototype + prototype: SheetSteel1 + steps: + - tool: Welding + doAfter: 5 diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index 19f2fee18375..82c16de7b6a1 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -790,7 +790,7 @@ # INTERCOM - type: construction name: intercom - id: IntercomAssesmbly + id: IntercomAssembly graph: Intercom startNode: start targetNode: intercom diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png index 787af3f53876b0ceabe9599acf922b1e4f2748f6..a85cbfbecc6aa5f549067703687291f9ea7634a6 100644 GIT binary patch delta 693 zcmbQoeUo*9K|RxCPZ!6KiaBp*1$GMsiX8v%%Cq`wK(3P)^CrjFW~X}=?pCPfe7SJF z!2|ZF4_m8VDgNSeQ`~Xt*@m{S0n;8%X>psP_bq|nd-Ae-Z#PxHpE=`mwd9`ib>;6K z-m(0CzMi42)8zRId54zN$jg`ixv#z|wC>}fO!a_PAiv_wfv->Md1ox^{2L{@_#?}9 zqD*m4km|_r5_($biT3EX3g8TOCRrXlU2R$dik^8oX3A= z+I^IK{M&BQ$&>=thP_3WFY;yU<<8x;|6RR4rY-rl`Z>W@JTqedyyw{wt??kksPX!W z!$7;59+FmIa&mFzO2&jS z?dRM7;M3G>?c-}+{m)t;9RC@78mwF5L z70eSnVqBj;H{SERrX|OwyQGTwu%o;_f8Xo+kN0YAYClOZOxRmI=NC}${=DlG;^N(t zUaQ@!|DiHR`AW2cobj_;ReR-vR$e(}c>0(7dL}nD+JRT`07pFdJPz?u1J__-#@ntNFCz20Fq*J0IA-= zTC&10OQ~KYO`31(oMAYMmA0Qg*Xq(9#1ls<>Aed9Uc1aYDXT5^T|o6`#1PQ)jPi TO!EgO0tN<8S3j3^P619 delta 1407 zcmV-_1%Ue51)d9#Fn;0U}0flVPWyVrO&9NR4Ne+21}iOAb%Q-_Vx7{e&)jY)TkpE z43-A2*cfrT@pwGET3exTQe?vK*Y8o%sha0x?^w-yFj4eHW2gW?AABa0;qL4l%gf9B zzOuroYZw6UxUc3JB^d``SKVVV6k;b9I|s45hp7TQ9uL`URzCtPhC)1l@eJ>{k3cY3 z_qlX3&SEIUR(~YYeTlowCuW`5Y!;`}sUHEB!ePCCI~uLHzr4K^4l^}5*-cr`1=AT& zK6o($?$6C}S}c-I#_^8(YLt*p#u;(Ck-cM=Vg{Hh;MLj+cW39wX0yD@DEjq&n+@4J zrrUct)jz>b_%~J}Qw0=GiYzZL>+K`$!1Skfq#aN=DSx&pW-VeRDo0gSTrQX4Gxhq- zRw7dc3|z5s%XhZy$?_PAQoHm4X!(8S=jSW>6Y*Hb3UN5>*k8XvIXvX*gIOLue!|B3 zDw`Wy0L*{Cz_Xv80x&%@b=F_lD6pa1D6{l|O5_nRuf;jp9p^&4;ZPMIE9@-xPkjR-Y z!-ft@AQ0%v^edbc+1}o6%Ml6$0yXx3q#bZg8ZvywsNYD2o_2);fk3I;^MuCU=T0of zz!e*AkBrml)IC@u1sIyZKSFox!ggyPa9S+#K7aep(EA*5y6LxloUvl?*=$z#V2v>S z2lN6{lanlk!;HFy^}&~WpxekjZ59jhq=hyG@18eu8&r=g4F-ywAQf{R#H80(xO3 zvVX9!u&}VOu&}VOu=r1MDYr=GO81Wf}X-TY8D5eXBA!%i-jr*Kl#V*nxwBw_)L z((|SY2nK_t3BRAI$w{^%5&Z~ffd?VPMbrTte-`7G{R=iXBNc-e8Et@6s(vCz&{RxN zWO~ap!vME@e(bN`v?+e)yxlw1&#U8rB7Y>-h^AtKqpo3|zj(%|YnanwQ4c=@D2InI zB())a=S&Yw>*peQTRsC`t*tbCN7F%35t2?P;}tQus}E+$W=~P8EhfZrcoVGtw&3Ff$|vqHED>A^;I_3S55PU=ZQ$t3Qmi~3Uh6S zjuIo1{d$cM7l}xn)k~il(esHBGv>cv;P_Yr&P)}~W@^0(mTFp>0oCzyiGQX|K;+*>obGcIrd6hArqJ^H9lfI|6WCSv`0kr| zcGbNKCrAi!5vZ!l%$vsIXDGc;6y*i{)-UECP%Z&B>-k-vSDM7**LP84aE zSCyDyx_=j?a-wWr3|>@TRR<;7Er)BBZU$;h1@6^|kcEYXg@r|T_#2w_qxa3rkskm6 N002ovPDHLkV1mjqvT*#WAE}&fA;bybKCFE(g_aIH=VLv@TfG{$A0W w>8Gz-GcYh@eLMyvfnt~ZjtMEUF)-{%WY)?T;I-A0Ee45uy85}Sb4q9e05)kJdjJ3c delta 129 zcmeBVT+KMauqxHl#WAE}&f8lXc^ec&SOSugvRw~5m~8Vq5~O#~Y@3(`i_7AD<_DhM zI{jaf0SMv+z4KP)t+m#;y7uU#-_0N)1_rAhiy9y0E}N%!`j=}1`SoAF@o#!(YO(%w eha3aL_T3CUlKe&%h9?hz6nnb*xvXK;#J3tWrH3!@SFrl?xRqbOu9vkP_sQLgZ zwAQO;(7+8KJ!e}%q?Qib{xbOG>TrUj6(t5T22^4X6 zdyDyE0ibvlD5Y?!&d}+c|7iu(S^%Zg)+Pu5%^)b+>;M#Qf<|gBzrF$&-#wdUfH^z-o03U9Q8>0X|+!!~;6Pgb<#*NkBDBKt~;s$Lh zQ@Alf5ClOG1VP}z+PF}LB^*JFl^3A=bj{aiahq8GD@+-da0D?<>@a0mLLJ075r8r* zp$=l4d}JX)8J187G1dZ5h9w+9jI{uiVF^bNV=Vw>Si%v+SPMWImT&|y)&fw5B^*Ia z<)g66u!JLssT^WZh9y3N7zswIc0d`HNDw0kf*=T_d;^<`CUbQETe1KE002ovPDHLk FV1jWY7&`y} delta 1338 zcmV-A1;zT11=$LaFn%!$pwi^K0z=5%!~rQ*MYg#01_eXnWKrxv$gzXmgSA_)>)mx7 zlPvB2E;GAh&CEW(H-FxH5D^g(5fSmfWz4Ig-EO04TKl*In17}@Ha6xt%&GI4SA(W$ zZ7CijlgXg#I#N6afDnT3mzM`FMfE&6_vd4<0;lN>x?cKRkZiRupC6QdQMZ z3&2x1RaI^4x{hU8D2jq*SyU<&gb);##?hX}SqrEe>kVXCW@~FJv^L;r0)Xq|6-5DH zaZz{L{p#j!!+-TItPG_A0EsJ*d)8!Al054`=rzDs08%_gVc{~CWwBS?rP(?nm($qT zC=S&5;-XHuYz{4elh}!T1pW*N=h)eK)AzSR)VOu)maF~M_BPGdk>3!3q<5QIqj9RFVSYkX__r)N+U1!MEM@1GFb?Fl*pu(#cXW|Q(YiNoK# z>(JSDv)Qcc{8B6yhg$^0z~Z8g@u7%x<~sMvCgxHLX{RjqByHCT z+<$a`yk%LW(`hD^slYx0U1CpGS9!Fu(g}Be==j`h-`n}wSuPzj@Npc>-G)=|^#fL` zRnqBnU>|`3c(k&@lhsv>^#+AAIt$IFt9>iL>-sUtzQ5qq_o}=9&;Voed0-y{YruE1 z%$H@8@-@lTeptv4G4(O&|>1zJGJq z^_+Xw?k!yh0F3>xP)1=!3My>&2G=YnAIfSS= z&)p_4at*Le0Ngo&c52K?W1j=^`2wQe5kj{K45|UXw->*lqNvyS=G*T8`02+rUBz-)EY2KM~&}Px#_DMuRRCt{2+OZOXFbsg<=;^2qjx0Ql z@BapLaq`rIJb(_aa$Z`}9QOYf1_~FrmTMA30002crcHDJ7Jc8pevL^fnVxBzA5IKa zmGfl~Rh1znQB_I9=a^%4FJNgbf&6gl(`hU^|IIEpm~LOk^AFmy6389 zfQMDh0G&p;?dLc47000000DwPGT*39M6xXOiFH#fqY?gR8Vwx|CvII&l7V&MC)M&&M|0LzvEUD3m oY5p5fJOezNB^xwi0040H2AB(}cg~IGwEzGB07*qoM6N<$f(<}`Bme*a delta 632 zcmV-;0*C#)0+0oeBYy%{NklBjE6vzK^Tgi*28w$-(*@ohRaW*gM61hvi zKqik`0$Q{OLczNhL%%@3gOd){U?gN=MYeH3I=$cY^ai1K zdjETO?+zdcf*|&1U>JgeFEO>M56I2~z!(#k!=V@$hGN9%Y{lX!q-A{CWeAb3A9yt`?Zj=ZaJkI6m%fWe4|*HJ?-&003GC zXkLeys%oYUpzs;6*T-$E?V<-y5Cl<*N$Mph-C9|K{}_hQIxAn6z0b@0z#?nkZ*+i^-Ef#!9Hf6WFw&f#&6=-S?M%~K_B@@rWZMy?BBb@?g${RdN3Eo24& zK)YWDuyHy~8o6%#odd%Vo3cTgDv*|d$2n}r!Pkq6-6fEuOjRH$0o!rn%sproDC#08 zs|w^Do=CHtJ!lmuE1Rl791K;E~hzhr(peRbtwc(8&sGll0< T#mz~RK*~H_{an^LB{Ts5O_nYE diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png index 7b0bb630722bb12089a4a1d54ca9eeeaa7d1b32a..a8fda54fc9332fe037dae35c49093bc5c4650995 100644 GIT binary patch delta 177 zcmX@Xbdzy{K|RA{PZ!6KiaBp@IdUCV5MVg4cKw_EpE~Q*o(R8Pna^?C*>m9+v81)7 zY&I_(Lm~=dl^I@KHqoz)yBy|ru!%5L0xcm4M5|1_2Kme%zA z-@4G5fuUhq+rBQjsl8jbSxZmpJ^DbA0R$Yb)N20w8UA9{QLRZ)#qz&=*QIS-GTZ6Q d1+a96k delta 289 zcmV++0p9-E0muT7Fn<98Nkl}OyzoZF}khCwnw&_Cz0002prA4W+F-=R0Qf>EXH1*&`RYmVy z3_{TN#q0UEhC>lPcMNq?624npsppbgj?veF+byiNfC000000084ji}I`EXQ#FQ2J!L~Ha14JRfw?NpCNr*7@7UD7XwPN zywg|eRAuO$v?%qoS<8O8kU>+mU5)wYIiUU5+Tf|`!Yc670JxwIfOZJLq~?It;V2>^ n+p21=z0-9S02%-Q06cXAB#Z2r?C8JM00000NkvXXu0mjf+scRB diff --git a/Resources/migration.yml b/Resources/migration.yml index ef0a5f46b724..bd42de8f2c5a 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -358,3 +358,6 @@ FloorTileItemReinforced: PartRodMetal1 #2024-06-25 BookChefGaming: BookHowToCookForFortySpaceman + +#2024-06-29 +IntercomAssesmbly: IntercomAssembly From 71b7dddb59e880ba156191396f74035d7b5977aa Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 7 Jul 2024 14:20:16 +0000 Subject: [PATCH 053/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f5387d8fdb7a..70c1904899ba 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Fix lobby UI not resetting for character changes. - type: Fix - id: 6379 - time: '2024-04-18T03:01:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27075 - author: TokenStyle changes: - message: Benzene is no longer required for the Insuzine recipe. @@ -3818,3 +3811,13 @@ id: 6878 time: '2024-07-07T13:06:24.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29737 +- author: EmoGarbage404 + changes: + - message: Intercoms now use encryption keys to determine what channels they can + broadcast on. + type: Tweak + - message: Intercoms and handheld radios no longer rely on telecom servers. + type: Fix + id: 6879 + time: '2024-07-07T14:19:10.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29580 From 13b84870ecc079301c1e619a90e3a18e9113ec32 Mon Sep 17 00:00:00 2001 From: Verm <32827189+Vermidia@users.noreply.github.com> Date: Sun, 7 Jul 2024 09:21:53 -0500 Subject: [PATCH 054/242] Bartending+: Shaking and Stirring (#29243) * Shaking and Stirring * Remove shake message * Switch if order a bit * Add doafter supprot for reactionmixer * Fix nullability * Timespan zero * Forgot to remove loc string * Reorganize usings * Remove unneeded usings, fix b52 needing to be shaken --- .../EntitySystems/ReactionMixerSystem.cs | 57 +++++++++++++++---- .../Reaction/ReactionMixerComponent.cs | 21 +++++++ .../chemistry/components/mixing-component.ftl | 3 + .../Prototypes/Chemistry/mixing_types.yml | 14 +++++ .../Consumable/Drinks/drinks_special.yml | 6 +- .../Entities/Objects/Misc/utensils.yml | 15 +++++ .../Prototypes/Recipes/Reactions/drinks.yml | 54 ++++++++++++++++++ 7 files changed, 159 insertions(+), 11 deletions(-) diff --git a/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs b/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs index d5f7655f884c..a81f38a21d40 100644 --- a/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/ReactionMixerSystem.cs @@ -1,6 +1,9 @@ +using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reaction; +using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; +using Content.Shared.Nutrition.EntitySystems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Popups; @@ -10,34 +13,68 @@ public sealed partial class ReactionMixerSystem : EntitySystem { [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainers = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAfterInteract); + SubscribeLocalEvent(OnShake); + SubscribeLocalEvent(OnDoAfter); } private void OnAfterInteract(Entity entity, ref AfterInteractEvent args) { - if (!args.Target.HasValue || !args.CanReach) + if (!args.Target.HasValue || !args.CanReach || !entity.Comp.MixOnInteract) return; - var mixAttemptEvent = new MixingAttemptEvent(entity); - RaiseLocalEvent(entity, ref mixAttemptEvent); - if (mixAttemptEvent.Cancelled) - { + if (!MixAttempt(entity, args.Target.Value, out var solution)) return; - } - if (!_solutionContainers.TryGetMixableSolution(args.Target.Value, out var solution, out _)) + var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.TimeToMix, new ReactionMixDoAfterEvent(), entity, args.Target.Value, entity); + + _doAfterSystem.TryStartDoAfter(doAfterArgs); + } + + private void OnDoAfter(Entity entity, ref ReactionMixDoAfterEvent args) + { + //Do again to get the solution again + if (!MixAttempt(entity, args.Target!.Value, out var solution)) return; - _popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User); + _popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target!.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User); + + _solutionContainers.UpdateChemicals(solution!.Value, true, entity.Comp); + + var afterMixingEvent = new AfterMixingEvent(entity, args.Target!.Value); + RaiseLocalEvent(entity, afterMixingEvent); + } + + private void OnShake(Entity entity, ref ShakeEvent args) + { + if (!MixAttempt(entity, entity, out var solution)) + return; - _solutionContainers.UpdateChemicals(solution.Value, true, entity.Comp); + _solutionContainers.UpdateChemicals(solution!.Value, true, entity.Comp); - var afterMixingEvent = new AfterMixingEvent(entity, args.Target.Value); + var afterMixingEvent = new AfterMixingEvent(entity, entity); RaiseLocalEvent(entity, afterMixingEvent); } + + private bool MixAttempt(EntityUid ent, EntityUid target, out Entity? solution) + { + solution = null; + var mixAttemptEvent = new MixingAttemptEvent(ent); + RaiseLocalEvent(ent, ref mixAttemptEvent); + if (mixAttemptEvent.Cancelled) + { + return false; + } + + if (!_solutionContainers.TryGetMixableSolution(target, out solution, out _)) + return false; + + return true; + } } diff --git a/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs b/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs index 118f22406109..8edfa44ce850 100644 --- a/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs +++ b/Content.Shared/Chemistry/Reaction/ReactionMixerComponent.cs @@ -1,5 +1,7 @@ using Content.Shared.Chemistry.Components; +using Content.Shared.DoAfter; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; namespace Content.Shared.Chemistry.Reaction; @@ -19,9 +21,28 @@ public sealed partial class ReactionMixerComponent : Component [ViewVariables] [DataField] public LocId MixMessage = "default-mixing-success"; + + /// + /// Defines if interacting is enough to mix with this component + /// + [ViewVariables] + [DataField] + public bool MixOnInteract = true; + + /// + /// How long it takes to mix with this + /// + [ViewVariables] + [DataField] + public TimeSpan TimeToMix = TimeSpan.Zero; } [ByRefEvent] public record struct MixingAttemptEvent(EntityUid Mixed, bool Cancelled = false); public readonly record struct AfterMixingEvent(EntityUid Mixed, EntityUid Mixer); + +[Serializable, NetSerializable] +public sealed partial class ReactionMixDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Resources/Locale/en-US/chemistry/components/mixing-component.ftl b/Resources/Locale/en-US/chemistry/components/mixing-component.ftl index a486ed8ede3d..c434246fab78 100644 --- a/Resources/Locale/en-US/chemistry/components/mixing-component.ftl +++ b/Resources/Locale/en-US/chemistry/components/mixing-component.ftl @@ -6,9 +6,12 @@ mixing-verb-default-condense = condense mixing-verb-centrifuge = centrifugation mixing-verb-electrolysis = electrolyze mixing-verb-holy = bless +mixing-verb-stir = stir +mixing-verb-shake = shake ## Entity default-mixing-success = You mix the {$mixed} with the {$mixer} bible-mixing-success = You bless the {$mixed} with the {$mixer} +spoon-mixing-success = You stir the {$mixed} with the {$mixer} diff --git a/Resources/Prototypes/Chemistry/mixing_types.yml b/Resources/Prototypes/Chemistry/mixing_types.yml index 20d58e70abd9..fd7325641083 100644 --- a/Resources/Prototypes/Chemistry/mixing_types.yml +++ b/Resources/Prototypes/Chemistry/mixing_types.yml @@ -51,3 +51,17 @@ icon: sprite: Objects/Specific/Chapel/bible.rsi state: icon + +- type: mixingCategory + id: Shake + verbText: mixing-verb-shake + icon: + sprite: Objects/Consumable/Drinks/shaker.rsi + state: icon + +- type: mixingCategory + id: Stir + verbText: mixing-verb-stir + icon: + sprite: Objects/Misc/utensils.rsi + state: spoon diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml index d2c1249740e0..604ae28fb300 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_special.yml @@ -11,7 +11,7 @@ - type: MixableSolution solution: drink - type: Drink - - type: Shakeable # Doesn't do anything, but I mean... + - type: Shakeable - type: FitsInDispenser solution: drink - type: DrawableSolution @@ -34,6 +34,10 @@ - type: PhysicalComposition materialComposition: Steel: 50 + - type: ReactionMixer + mixOnInteract: false + reactionTypes: + - Shake - type: entity parent: DrinkGlassBase diff --git a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml index 86667f094fdb..e735b2dcddce 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/utensils.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/utensils.yml @@ -87,6 +87,11 @@ Blunt: 1 - type: Shovel speedModifier: 0.1 # you can try + - type: ReactionMixer + mixMessage: "spoon-mixing-success" + timeToMix: 0.5 + reactionTypes: + - Stir - type: entity parent: UtensilBasePlastic @@ -103,6 +108,11 @@ - Spoon - type: Shovel speedModifier: 0.1 # you can try + - type: ReactionMixer + mixMessage: "spoon-mixing-success" + timeToMix: 0.5 + reactionTypes: + - Stir - type: entity parent: UtensilBasePlastic @@ -137,6 +147,11 @@ - type: Utensil types: - Spoon + - type: ReactionMixer + mixMessage: "spoon-mixing-success" + timeToMix: 0.5 + reactionTypes: + - Stir - type: MeleeWeapon wideAnimationRotation: 180 attackRate: 2 diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml index 2a14c0ecd29c..96cc5b6aaa44 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -10,6 +10,8 @@ - type: reaction id: AlliesCocktail + requiredMixerCategories: + - Shake reactants: Martini: amount: 2 @@ -20,6 +22,8 @@ - type: reaction id: Amasec + requiredMixerCategories: + - Shake reactants: Wine: amount: 2 @@ -32,6 +36,8 @@ - type: reaction id: Andalusia + requiredMixerCategories: + - Stir reactants: Rum: amount: 1 @@ -98,6 +104,8 @@ - type: reaction id: BlueHawaiian + requiredMixerCategories: + - Shake reactants: CoconutRum: amount: 2 @@ -126,6 +134,8 @@ - type: reaction id: BahamaMama + requiredMixerCategories: + - Shake reactants: Ice: amount: 1 @@ -168,6 +178,8 @@ - type: reaction id: BeepskySmash + requiredMixerCategories: + - Shake reactants: Iron: amount: 1 @@ -190,6 +202,8 @@ - type: reaction id: BloodyMary + requiredMixerCategories: + - Stir reactants: JuiceLime: amount: 1 @@ -280,6 +294,8 @@ - type: reaction id: DemonsBlood + requiredMixerCategories: + - Stir reactants: Rum: amount: 1 @@ -294,6 +310,8 @@ - type: reaction id: DevilsKiss + requiredMixerCategories: + - Stir reactants: Rum: amount: 1 @@ -306,6 +324,8 @@ - type: reaction id: DoctorsDelight + requiredMixerCategories: + - Stir reactants: Cream: amount: 2 @@ -322,6 +342,8 @@ - type: reaction id: DriestMartini + requiredMixerCategories: + - Shake reactants: Gin: amount: 1 @@ -332,6 +354,8 @@ - type: reaction id: ErikaSurprise + requiredMixerCategories: + - Shake reactants: Ale: amount: 2 @@ -373,6 +397,8 @@ - type: reaction id: GargleBlaster + requiredMixerCategories: + - Shake reactants: Cognac: amount: 1 @@ -411,6 +437,8 @@ - type: reaction id: Gildlager + requiredMixerCategories: + - Shake reactants: Gold: amount: 1 @@ -548,6 +576,8 @@ - type: reaction id: IrishCoffee + requiredMixerCategories: + - Stir reactants: Coffee: amount: 1 @@ -568,6 +598,8 @@ - type: reaction id: KiraSpecial + requiredMixerCategories: + - Stir reactants: JuiceLime: amount: 1 @@ -580,6 +612,8 @@ - type: reaction id: Lemonade + requiredMixerCategories: + - Stir reactants: JuiceLemon: amount: 1 @@ -592,6 +626,8 @@ - type: reaction id: LemonLime + requiredMixerCategories: + - Stir reactants: JuiceLemon: amount: 1 @@ -604,6 +640,8 @@ - type: reaction id: LongIslandIcedTea + requiredMixerCategories: + - Stir reactants: CubaLibre: amount: 3 @@ -618,6 +656,8 @@ - type: reaction id: Manhattan + requiredMixerCategories: + - Shake reactants: Whiskey: amount: 2 @@ -658,6 +698,8 @@ - type: reaction id: Martini + requiredMixerCategories: + - Shake reactants: Gin: amount: 2 @@ -679,6 +721,8 @@ - type: reaction id: Mojito + requiredMixerCategories: + - Shake reactants: JuiceLime: amount: 1 @@ -726,6 +770,8 @@ - type: reaction id: Patron + requiredMixerCategories: + - Shake reactants: Tequila: amount: 10 @@ -736,6 +782,8 @@ - type: reaction id: Painkiller + requiredMixerCategories: + - Shake reactants: JuicePineapple: amount: 3 @@ -838,6 +886,8 @@ - type: reaction id: ScrewdriverCocktail + requiredMixerCategories: + - Shake reactants: JuiceOrange: amount: 2 @@ -955,6 +1005,8 @@ - type: reaction id: ToxinsSpecial + requiredMixerCategories: + - Shake reactants: Rum: amount: 2 @@ -967,6 +1019,8 @@ - type: reaction id: VodkaMartini + requiredMixerCategories: + - Shake reactants: Vermouth: amount: 1 From 437f6f451ad436d31ae623ceea9ee30fb151607d Mon Sep 17 00:00:00 2001 From: NotSoDamn <75203942+NotSoDana@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:22:38 +0200 Subject: [PATCH 055/242] Slicing food with Swords (#29005) * added utensil component * BaseSword added --- .../Entities/Objects/Weapons/Melee/sword.yml | 53 ++++++++----------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index 7cc33b715501..d0d85beb6f59 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -1,15 +1,28 @@ - type: entity - name: captain's sabre parent: BaseItem + id: BaseSword + abstract: true + components: + - type: Sharp + - type: MeleeWeapon + wideAnimationRotation: -135 + - type: Sprite + state: icon + - type: Item + size: Normal + - type: Utensil + types: + - Knife + +- type: entity + name: captain's sabre + parent: BaseSword id: CaptainSabre description: A ceremonial weapon belonging to the captain of the station. components: - - type: Sharp - type: Sprite sprite: Objects/Weapons/Melee/captain_sabre.rsi - state: icon - type: MeleeWeapon - wideAnimationRotation: -135 attackRate: 1.5 damage: types: @@ -21,7 +34,6 @@ reflectProb: .1 spread: 90 - type: Item - size: Normal sprite: Objects/Weapons/Melee/captain_sabre.rsi - type: Tag tags: @@ -30,26 +42,22 @@ - type: entity name: katana - parent: BaseItem + parent: BaseSword id: Katana description: Ancient craftwork made with not so ancient plasteel. components: - - type: Sharp - type: Tag tags: - Katana - type: Sprite sprite: Objects/Weapons/Melee/katana.rsi - state: icon - type: MeleeWeapon - wideAnimationRotation: -135 damage: types: Slash: 15 soundHit: path: /Audio/Weapons/bladeslice.ogg - type: Item - size: Normal sprite: Objects/Weapons/Melee/katana.rsi - type: DisarmMalus @@ -61,14 +69,12 @@ components: - type: Sprite sprite: Objects/Weapons/Melee/energykatana.rsi - state: icon - type: MeleeWeapon wideAnimationRotation: -60 damage: types: Slash: 30 - type: Item - size: Normal sprite: Objects/Weapons/Melee/energykatana.rsi - type: EnergyKatana - type: DashAbility @@ -86,41 +92,34 @@ - type: entity name: machete - parent: BaseItem + parent: BaseSword id: Machete description: A large, vicious looking blade. components: - - type: Sharp - type: Tag tags: - Machete - type: Sprite sprite: Objects/Weapons/Melee/machete.rsi - state: icon - type: MeleeWeapon - wideAnimationRotation: -135 damage: types: Slash: 15 soundHit: path: /Audio/Weapons/bladeslice.ogg - type: Item - size: Normal sprite: Objects/Weapons/Melee/machete.rsi - type: DisarmMalus - type: entity name: claymore - parent: BaseItem + parent: BaseSword id: Claymore description: An ancient war blade. components: - - type: Sharp - type: Sprite sprite: Objects/Weapons/Melee/claymore.rsi - state: icon - type: MeleeWeapon - wideAnimationRotation: -135 attackRate: 0.75 damage: types: @@ -128,7 +127,6 @@ soundHit: path: /Audio/Weapons/bladeslice.ogg - type: Item - size: Normal - type: Clothing sprite: Objects/Weapons/Melee/claymore.rsi slots: @@ -137,41 +135,34 @@ - type: entity name: cutlass - parent: BaseItem + parent: BaseSword id: Cutlass description: A wickedly curved blade, often seen in the hands of space pirates. components: - - type: Sharp - type: Tag tags: - Machete - type: Sprite sprite: Objects/Weapons/Melee/cutlass.rsi - state: icon - type: MeleeWeapon - wideAnimationRotation: -135 damage: types: Slash: 16 soundHit: path: /Audio/Weapons/bladeslice.ogg - type: Item - size: Normal sprite: Objects/Weapons/Melee/cutlass.rsi - type: DisarmMalus - type: entity name: The Throngler - parent: BaseItem + parent: BaseSword id: Throngler description: Why would you make this? components: - - type: Sharp - type: Sprite sprite: Objects/Weapons/Melee/Throngler2.rsi - state: icon - type: MeleeWeapon - wideAnimationRotation: -135 attackRate: 10 damage: types: From 70b56136aa027693c166e91e299ff8f054aefa7b Mon Sep 17 00:00:00 2001 From: Brandon Hu <103440971+Brandon-Huu@users.noreply.github.com> Date: Sun, 7 Jul 2024 14:23:03 +0000 Subject: [PATCH 056/242] fix(hardsuits): Give the carp suit and hardsuit a suit storage slot (#29322) * fix(hardsuits): Give the carp hardsuit suit storage * Both carpsuits have suit slots --- Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 79c116b3cad3..2053ced0f631 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -246,6 +246,7 @@ name: carp suit description: A special suit that makes you look just like a space carp, if your eyesight is bad. components: + - type: AllowSuitStorage - type: Sprite sprite: Clothing/OuterClothing/Suits/carpsuit.rsi - type: Item From ff93070d25579278e2c93b0a9aef9a0a623d6fbd Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 7 Jul 2024 14:23:44 +0000 Subject: [PATCH 057/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 70c1904899ba..8b060381adeb 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,18 +1,4 @@ Entries: -- author: TokenStyle - changes: - - message: Benzene is no longer required for the Insuzine recipe. - type: Tweak - id: 6380 - time: '2024-04-18T03:03:39.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26829 -- author: Golinth - changes: - - message: The Nukie Agent now comes with a medihud built into their visor! - type: Add - id: 6381 - time: '2024-04-18T03:04:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26218 - author: Krunk changes: - message: Cyborgs no longer see criminal status icons. @@ -3821,3 +3807,18 @@ id: 6879 time: '2024-07-07T14:19:10.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29580 +- author: Vermidia + changes: + - message: Some drink reactions now require shaking with the shaker or stirring + with a spoon + type: Add + id: 6880 + time: '2024-07-07T14:21:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29243 +- author: Dezzzix + changes: + - message: Now you can slice food with swords + type: Add + id: 6881 + time: '2024-07-07T14:22:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29005 From 21d0f85cc27823beda4cf4885f83f7452e468fb0 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 7 Jul 2024 10:26:58 -0400 Subject: [PATCH 058/242] metal foam grenades (#29428) * metal foam grenades * wow okay * meh * bruh * test * push --- .../Effects/AreaReactionEffect.cs | 10 ++-- .../EntitySystems/SmokeOnTriggerSystem.cs | 7 ++- Content.Server/Spreader/SpreaderSystem.cs | 17 ++++++- .../Spreader/EdgeSpreaderPrototype.cs | 6 +++ .../Tiles/ReplaceFloorOnSpawnComponent.cs | 36 +++++++++++++ .../Tiles/ReplaceFloorOnSpawnSystem.cs | 48 ++++++++++++++++++ Resources/Locale/en-US/tiles/tiles.ftl | 3 +- .../Catalog/Cargo/cargo_engineering.yml | 10 ++++ .../Catalog/Fills/Crates/engineering.yml | 11 ++++ .../Entities/Effects/chemistry_effects.yml | 11 +++- .../Objects/Weapons/Throwable/grenades.yml | 15 ++++++ Resources/Prototypes/Tiles/floors.yml | 17 ++++++- Resources/Prototypes/edge_spreaders.yml | 5 ++ .../Grenades/metalfoam.rsi/equipped-BELT.png | Bin 0 -> 240 bytes .../Weapons/Grenades/metalfoam.rsi/icon.png | Bin 0 -> 335 bytes .../Weapons/Grenades/metalfoam.rsi/meta.json | 27 ++++++++++ .../Weapons/Grenades/metalfoam.rsi/primed.png | Bin 0 -> 454 bytes Resources/Textures/Tiles/attributions.yml | 10 ++-- Resources/Textures/Tiles/foammetal.png | Bin 0 -> 2491 bytes 19 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 Content.Shared/Tiles/ReplaceFloorOnSpawnComponent.cs create mode 100644 Content.Shared/Tiles/ReplaceFloorOnSpawnSystem.cs create mode 100644 Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/equipped-BELT.png create mode 100644 Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/icon.png create mode 100644 Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/meta.json create mode 100644 Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/primed.png create mode 100644 Resources/Textures/Tiles/foammetal.png diff --git a/Content.Server/EntityEffects/Effects/AreaReactionEffect.cs b/Content.Server/EntityEffects/Effects/AreaReactionEffect.cs index 481f1fc27c5d..858da2b36056 100644 --- a/Content.Server/EntityEffects/Effects/AreaReactionEffect.cs +++ b/Content.Server/EntityEffects/Effects/AreaReactionEffect.cs @@ -1,4 +1,5 @@ using Content.Server.Fluids.EntitySystems; +using Content.Server.Spreader; using Content.Shared.Audio; using Content.Shared.Coordinates.Helpers; using Content.Shared.Database; @@ -64,16 +65,19 @@ public override void Effect(EntityEffectBaseArgs args) var transform = reagentArgs.EntityManager.GetComponent(reagentArgs.TargetEntity); var mapManager = IoCManager.Resolve(); var mapSys = reagentArgs.EntityManager.System(); - var sys = reagentArgs.EntityManager.System(); + var spreaderSys = args.EntityManager.System(); + var sys = args.EntityManager.System(); var mapCoords = sys.GetMapCoordinates(reagentArgs.TargetEntity, xform: transform); if (!mapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid) || - !mapSys.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef) || - tileRef.Tile.IsSpace()) + !mapSys.TryGetTileRef(gridUid, grid, transform.Coordinates, out var tileRef)) { return; } + if (spreaderSys.RequiresFloorToSpread(_prototypeId) && tileRef.Tile.IsSpace()) + return; + var coords = mapSys.MapToGrid(gridUid, mapCoords); var ent = reagentArgs.EntityManager.SpawnEntity(_prototypeId, coords.SnapToGrid()); diff --git a/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs index f958373ac74f..3d3c5d85630c 100644 --- a/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Explosion.Components; using Content.Shared.Explosion.EntitySystems; using Content.Server.Fluids.EntitySystems; +using Content.Server.Spreader; using Content.Shared.Chemistry.Components; using Content.Shared.Coordinates.Helpers; using Content.Shared.Maps; @@ -17,6 +18,7 @@ public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem [Dependency] private readonly IMapManager _mapMan = default!; [Dependency] private readonly SmokeSystem _smoke = default!; [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly SpreaderSystem _spreader = default!; public override void Initialize() { @@ -31,11 +33,14 @@ private void OnTrigger(EntityUid uid, SmokeOnTriggerComponent comp, TriggerEvent var mapCoords = _transform.GetMapCoordinates(uid, xform); if (!_mapMan.TryFindGridAt(mapCoords, out _, out var grid) || !grid.TryGetTileRef(xform.Coordinates, out var tileRef) || - tileRef.Tile.IsSpace()) + tileRef.Tile.IsEmpty) { return; } + if (_spreader.RequiresFloorToSpread(comp.SmokePrototype.ToString()) && tileRef.Tile.IsSpace()) + return; + var coords = grid.MapToGrid(mapCoords); var ent = Spawn(comp.SmokePrototype, coords.SnapToGrid()); if (!TryComp(ent, out var smoke)) diff --git a/Content.Server/Spreader/SpreaderSystem.cs b/Content.Server/Spreader/SpreaderSystem.cs index 7de8a43d354f..50f5d81183b6 100644 --- a/Content.Server/Spreader/SpreaderSystem.cs +++ b/Content.Server/Spreader/SpreaderSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Shuttles.Components; using Content.Shared.Atmos; +using Content.Shared.Maps; using Content.Shared.Spreader; using Content.Shared.Tag; using Robust.Shared.Collections; @@ -175,11 +176,12 @@ private void Spread(EntityUid uid, TransformComponent xform, ProtoId public void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId prototype, out ValueList<(MapGridComponent, TileRef)> freeTiles, out ValueList occupiedTiles, out ValueList neighbors) { - // TODO remove occupiedTiles -- its currently unused and just slows this method down. - DebugTools.Assert(_prototype.HasIndex(prototype)); freeTiles = []; occupiedTiles = []; neighbors = []; + // TODO remove occupiedTiles -- its currently unused and just slows this method down. + if (!_prototype.TryIndex(prototype, out var spreaderPrototype)) + return; if (!TryComp(comp.GridUid, out var grid)) return; @@ -244,6 +246,9 @@ public void GetNeighbors(EntityUid uid, TransformComponent comp, ProtoId spreader) + { + if (!_prototype.Index(spreader).TryGetComponent(out var spreaderComp, EntityManager.ComponentFactory)) + return false; + + return _prototype.Index(spreaderComp.Id).PreventSpreadOnSpaced; + } } diff --git a/Content.Shared/Spreader/EdgeSpreaderPrototype.cs b/Content.Shared/Spreader/EdgeSpreaderPrototype.cs index fee8f93a6d3c..33665d82b5df 100644 --- a/Content.Shared/Spreader/EdgeSpreaderPrototype.cs +++ b/Content.Shared/Spreader/EdgeSpreaderPrototype.cs @@ -10,4 +10,10 @@ public sealed partial class EdgeSpreaderPrototype : IPrototype { [IdDataField] public string ID { get; } = string.Empty; [DataField(required:true)] public int UpdatesPerSecond; + + /// + /// If true, this spreader can't spread onto spaced tiles like lattice. + /// + [DataField] + public bool PreventSpreadOnSpaced = true; } diff --git a/Content.Shared/Tiles/ReplaceFloorOnSpawnComponent.cs b/Content.Shared/Tiles/ReplaceFloorOnSpawnComponent.cs new file mode 100644 index 000000000000..1b87082defad --- /dev/null +++ b/Content.Shared/Tiles/ReplaceFloorOnSpawnComponent.cs @@ -0,0 +1,36 @@ +using Content.Shared.Maps; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Tiles; + +/// +/// Replaces floor tiles around this entity when it spawns +/// +[RegisterComponent, NetworkedComponent, Access(typeof(ReplaceFloorOnSpawnSystem))] +public sealed partial class ReplaceFloorOnSpawnComponent : Component +{ + /// + /// The floor tiles that will be replaced. If null, will replace all. + /// + [DataField] + public List>? ReplaceableTiles = new(); + + /// + /// The tiles that it will replace. Randomly picked from the list. + /// + [DataField] + public List> ReplacementTiles = new(); + + /// + /// Whether or not there has to be a tile in the location to be replaced. + /// + [DataField] + public bool ReplaceSpace = true; + + /// + /// List of offsets from the base tile, used to determine which tiles will be replaced. + /// + [DataField] + public List Offsets = new() { Vector2i.Up, Vector2i.Down, Vector2i.Left, Vector2i.Right, Vector2i.Zero }; +} diff --git a/Content.Shared/Tiles/ReplaceFloorOnSpawnSystem.cs b/Content.Shared/Tiles/ReplaceFloorOnSpawnSystem.cs new file mode 100644 index 000000000000..818991f823dd --- /dev/null +++ b/Content.Shared/Tiles/ReplaceFloorOnSpawnSystem.cs @@ -0,0 +1,48 @@ +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.Tiles; + +public sealed class ReplaceFloorOnSpawnSystem : EntitySystem +{ + [Dependency] private readonly ITileDefinitionManager _tile = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedMapSystem _map = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + var xform = Transform(ent); + if (xform.GridUid is not { } grid || !TryComp(grid, out var gridComp)) + return; + + if (ent.Comp.ReplaceableTiles != null && ent.Comp.ReplaceableTiles.Count == 0) + return; + + var tileIndices = _map.LocalToTile(grid, gridComp, xform.Coordinates); + + foreach (var offset in ent.Comp.Offsets) + { + var actualIndices = tileIndices + offset; + + if (!_map.TryGetTileRef(grid, gridComp, actualIndices, out var tile)) + continue; + + if (ent.Comp.ReplaceableTiles != null && + !tile.Tile.IsEmpty && + !ent.Comp.ReplaceableTiles.Contains(_tile[tile.Tile.TypeId].ID)) + continue; + + var tileToSet = _random.Pick(ent.Comp.ReplacementTiles); + _map.SetTile(grid, gridComp, tile.GridIndices, new Tile(_prototype.Index(tileToSet).TileId)); + } + } +} diff --git a/Resources/Locale/en-US/tiles/tiles.ftl b/Resources/Locale/en-US/tiles/tiles.ftl index e5b6810fcab7..35cea19f7862 100644 --- a/Resources/Locale/en-US/tiles/tiles.ftl +++ b/Resources/Locale/en-US/tiles/tiles.ftl @@ -87,6 +87,7 @@ tiles-gold-tile = gold tile tiles-silver-tile = silver tile tiles-glass-floor = glass floor tiles-reinforced-glass-floor = reinforced glass floor +tiles-metal-foam = metal foam floor tiles-green-circuit-floor = green circuit floor tiles-blue-circuit-floor = blue circuit floor tiles-snow = snow @@ -126,4 +127,4 @@ tiles-mowed-astro-grass = mowed astro-grass tiles-jungle-astro-grass = jungle astro-grass tiles-astro-ice = astro-ice tiles-astro-snow = astro-snow -tiles-wood-large = large wood \ No newline at end of file +tiles-wood-large = large wood diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml b/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml index 7ca6af84518d..d4e2b4c60dc8 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_engineering.yml @@ -28,6 +28,16 @@ category: cargoproduct-category-name-engineering group: market +- type: cargoProduct + id: EngineeringFoamGrenade + icon: + sprite: Objects/Weapons/Grenades/metalfoam.rsi + state: icon + product: CrateEngineeringFoamGrenade + cost: 2500 + category: cargoproduct-category-name-engineering + group: market + - type: cargoProduct id: EngineeringCableBulk icon: diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml b/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml index 26a8910c735e..62d07b0beda2 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engineering.yml @@ -76,6 +76,17 @@ - id: CableHVStack amount: 3 +- type: entity + id: CrateEngineeringFoamGrenade + parent: CrateEngineeringSecure + name: sealant grenade crate + description: 5 metal foam sealant grenades. + components: + - type: StorageFill + contents: + - id: MetalFoamGrenade + amount: 5 + - type: entity id: CrateEngineeringCableBulk parent: CrateElectrical diff --git a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml index 096e88bcb6fe..ee300e9aeaff 100644 --- a/Resources/Prototypes/Entities/Effects/chemistry_effects.yml +++ b/Resources/Prototypes/Entities/Effects/chemistry_effects.yml @@ -101,6 +101,8 @@ state: m_foam-north - map: [ "enum.EdgeLayer.West" ] state: m_foam-west + - type: EdgeSpreader + id: MetalFoam - type: FoamVisuals animationTime: 0.6 animationState: m_foam-dissolve @@ -135,7 +137,7 @@ - type: RCDDeconstructable cost: 2 delay: 2 - fx: EffectRCDDeconstruct2 + fx: EffectRCDDeconstruct2 - type: Clickable - type: InteractionOutline - type: Sprite @@ -159,6 +161,13 @@ - type: Transform anchored: true - type: Airtight + - type: ReplaceFloorOnSpawn + replaceableTiles: + - Plating + - Lattice + - TrainLattice + replacementTiles: + - FloorMetalFoam - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index b1d260c32761..eb382c01e5fc 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -422,6 +422,21 @@ - ReagentId: TearGas Quantity: 50 +- type: entity + parent: SmokeGrenade + id: MetalFoamGrenade + name: metal foam grenade + description: An emergency tool used for patching up holes. Almost as good as real walls. + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/metalfoam.rsi + - type: SmokeOnTrigger + duration: 10 + spreadAmount: 13 + smokePrototype: AluminiumMetalFoam + - type: StaticPrice + price: 350 + # Non-explosive "dummy" grenades to use as a distraction. - type: entity diff --git a/Resources/Prototypes/Tiles/floors.yml b/Resources/Prototypes/Tiles/floors.yml index 602e9bc441cc..91b61bec0942 100644 --- a/Resources/Prototypes/Tiles/floors.yml +++ b/Resources/Prototypes/Tiles/floors.yml @@ -201,7 +201,7 @@ collection: FootstepHull itemDrop: FloorTileItemBrassFilled heatCapacity: 10000 - + - type: tile id: FloorBrassReebe name: tiles-brass-floor-reebe @@ -1391,6 +1391,21 @@ itemDrop: SheetRGlass1 heatCapacity: 10000 +- type: tile + id: FloorMetalFoam + name: tiles-metal-foam + sprite: /Textures/Tiles/foammetal.png + variants: 1 + placementVariants: + - 1.0 + baseTurf: Plating + isSubfloor: false + deconstructTools: [ Prying ] + footstepSounds: + collection: FootstepHull + itemDrop: SheetSteel1 + heatCapacity: 10000 + # Circuits - type: tile id: FloorGreenCircuit diff --git a/Resources/Prototypes/edge_spreaders.yml b/Resources/Prototypes/edge_spreaders.yml index 061932c706db..c93cc02ba977 100644 --- a/Resources/Prototypes/edge_spreaders.yml +++ b/Resources/Prototypes/edge_spreaders.yml @@ -9,3 +9,8 @@ - type: edgeSpreader id: Smoke updatesPerSecond: 8 + +- type: edgeSpreader + id: MetalFoam + updatesPerSecond: 16 + preventSpreadOnSpaced: false diff --git a/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/equipped-BELT.png b/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/equipped-BELT.png new file mode 100644 index 0000000000000000000000000000000000000000..d3cf1cf4c9329b5e0ba25716b5eee1c78ee13068 GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%KdAc};RLpsM z%aH3(fB?${*PL9|)pHIVvvbvQoFb#ScKSyxGodw2Q+s=R7qp*vmi%BZ^O*x%b_)Wv zGALgu{W~}MxRUmY*SqpVLob?~O;Pr_IMM7t`rgjB4@Gt-&lZhbk~~v%OZ?o&WwpmY z|2x&Ca|tNDG=K5?-x6(#N^+4Kk6MFFVqj?aKl!8fgfcmO-Php>+n7`SaxIyA-23z| i(HUR1^V5~j%8-*tbW(7bvhNV%u0pUXO@geCxb0bm#a literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/icon.png b/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a13dedfc2052c67c1346cc71dc821facda8e1cd4 GIT binary patch literal 335 zcmV-V0kHmwP)Px$2}wjjR9J=WmN9F>KorM+X)7^fE3`|<;v}7fF6~#4toAeHqt&T2^b>T~>A+Ir z5Rz&IQCuV?Q735$UG7C1g!@gm{O|qmy?gf#3`o&=USWc^Rn*?F^Jo4}eli5*vUsvGPc0XCMqik|e3mK==NEUgwsZ%PXd< zCF9YMx0h$J0qqj3vTwGQMd38b7IW-87Y7#^u=AX3F~=zi?AA43v(HKfwA;{i-9PWZ zzqO(7`$ugs*XZ%51b4s$Kwg5{XMwh;JPnmn hl}2*y{{e%+um|$mboU?%v_Jp=002ovPDHLkV1h5Fli2_O literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/meta.json b/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/meta.json new file mode 100644 index 000000000000..139eebb04da7 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/meta.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by EmoGarbage404", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "primed", + "delays": [ + [ + 0.2, + 0.1 + ] + ] + }, + { + "name": "equipped-BELT", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/primed.png b/Resources/Textures/Objects/Weapons/Grenades/metalfoam.rsi/primed.png new file mode 100644 index 0000000000000000000000000000000000000000..dafc378c3bca93b686e1ae223ae5c700132750d1 GIT binary patch literal 454 zcmV;%0XhDOP)Px$fJsC_RA_NHP!xxMt=8C41zkcNoWw~GK}XlpwLZgLeFFO$f)Ai{5Q|UX zDs{S`*mMYqrb4hNSfYu?$v+f9&dqIt=6+L_lkaeHPEKwBgTY`h7z{2}RNxgQNevi@z1uY^XqSt`v zxl0tZQu$ex``&uvWbqUx;b)J)So;A0q?9UdC3O4CI+FU!usfjB>A>^6N#v4?pIw0U zt!>m-*RV78ws;lwRj&M^M;o5q{=y(d% zW&$ZI$_eD{H{Su=fHP)AbM_^~qh7chf$Aj-bK}s8d wnPx;bxA})R7i;>*4dLJ$6Wy6Jbsy3S$7}PJ=3$h<26E(5A7>3pI_4DW7CTV(iJnp-0x816`1_p7G9;c&2U7gNm z7^92tE~ewjdbOI4Ce|3?i6l<*B6rAPLJ%eZ$@B;%QS|ib(@#G6x$wk6Re&8jJ~^fg z80W@X3N(si!C9Uc>QHjdk+s*$B?DGf)nak-^3{t`n%>;pdY-h-CTVJ|1qyB7BZ8f? zd0Ez~ev|Dm#uQ<~g%G|p#u#g*l-teL_vCDLyf`^wjC(>%C(}4iFu@dHyW0^$b+3LdCua<9HYbF1Rm!;9NBgcIbA!2F51y`N?7dgfv~pIM1@8X;h_Z_~7wl zz{FWg2_cmBMpH@=LMNxEwW{yUj*6nRgKqoI4}!LByS5#V$4%QJgaqe|vFT(&0HQc5 zvuu;)G7JdDAAIl=PtaXn5Ik6?2O&HuB|@la8w?bLx7luX+3xnuMm4JId!WD>>${e7 zMhHO&hf%~hQ%c=C`yRqr)oL;xdmhKuR&{lCby*$`d6tnczxvW*r&N8YO3v6Q9aD#^ zaxb_z8c$5GogSL1x>>K9rs;b<7~6F{rBwPdP7*)xn^r|ZfUNBY6Q(1~xI^e-wff~R z|IHu_$hnJ`FI8RNtX7NDJG0pgA;bV?vuT#)&N__HfnXuMw(C4kTIY6ohA_$Y#W+np z;ay!^)^#nUf9Kv^%BXe7Ij7pzA%rj{#+Z923m{YqQB{X*U%bh8##)3>-|H}nDy0k| zhyunWO%euxDGB2+2>dsjH-zB3_wL@gbH|r{zTF1Givu4BO{5P1C-WINxMxigu1?$zO=^7C)1{Bi9^O1O6mOg`276qquJ4+s(fFD zVKAB=DOKm$4lu?br5}ad^Q4r!?babgFz)qG?hllbw(HV3F3Yl|)RSJ{_sF5;a@jTw zW1PehqEHljgi)U7jI-Ttm*+*kFBq_@s)2L9%?syHu`l-f{V)s&Awmg4*g1<3qJ-qz zZQu82XWx7J8`Y-<+h7kY& zMd*j`JxG%zPLi@Lx7#h};2dhZ&N_2!+UrXIyy3T4$|YUODSL=>Y{UMAI~8(DTJf-*(IEYrqKmo^P!c!t;ZW zbLJ4jglOFpN+ftv`p#OV)cMn=fANwu!JmEr&C*y3lYnldtMqzYwbAu6@ zj3;@KPbU+t`yh${VHAZ?6va^#Ypth~Y1elM6NJ$uO&J%vEX%W<@B6y%DW!}tKM0~E zmeLD-N%7I`?JaOt6nPi~z1GGIjZ%laAean&4?t;_?+#Vv2pT16btthzG6;O%-)(o& z_a~#2VCwQAJe`}fl+wC7oZY`))ioOoA(-s814h80*P3u4 z7K>8`kcLUuc0mwQ0AMUiV&Qq5b7L$39fpB3COruNopVQv#hcAWa&LZg6!2!TJf;-F$@K9@AE{Q6+3_qK zjmqjkDe1dbcfIs|LP$IwwbmI5gbP1T2tcFitCugkw#~NNM%8`aE2RLyISUBC{_cVj z9LM3wd`>B4o~OF*?%5g5i~XPe)n9-6Ki_c186j=k_FZ3=hj=_DoC9O&Xk?AqZa0Ub zNTaB0JH{Bn*df>VU3n-$@WtZPW1_4MeiW`>uZkj%Mx+1!;`1aO!JzeOwXW-$=NV&6 z)e1Q@o{aZdRvz|?#ho(Ghd~qT2*TDmLTMZ)M@LiXdFz{Xu`e0po85MGz0C3*_oZrE z-}j>+u*1-|4NyvSRY}ha!pK@9gs{%;>l&G%%8M)2B0?C@KctUP0D!h_*6X$J2U=^O zRCk?f+c-^x;H#@;GD@7Y6gh(NY%&3iF+r&B`nGKeAr>Krki(c!$^i($jPoE2D23f_ zE4XMHb+TAwS+1G}0Q$ZMrOxi%YuaYlYhMTsplY?gUTWQIt-)GLD1G$!!>3Pv-|OBO zb0|wf2xp*bMS)u9q?FQ=n2_t`wL^HhTxzWe#)ML9O?4>uc_yXI_j`hnb@ms3_xGF4 z78pC!HDH7(C2zm|_Txv7&cFVe5PEWQ^5Vsd#p$UhB|<0;qkVm-t6Fejoh6i%d2Woo zd*}46Ke~UtTwPsW9v`0^>S{Wh-`=d>edpamsdPU7@~ht_NrHd+vyZ>|`kZs_`?9L) zKVa~~2M^Q9 Date: Sun, 7 Jul 2024 10:27:52 -0400 Subject: [PATCH 059/242] The real AME nerf (#29587) * The real AME nerf * oh the real change * Update AmeNodeGroup.cs --- Content.Server/Ame/AmeNodeGroup.cs | 27 +++++++------------ .../Ame/EntitySystems/AmeControllerSystem.cs | 20 ++++++++++---- .../Components/AmeFuelContainerComponent.cs | 4 +-- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Content.Server/Ame/AmeNodeGroup.cs b/Content.Server/Ame/AmeNodeGroup.cs index 40da6222d201..bb482f7726b6 100644 --- a/Content.Server/Ame/AmeNodeGroup.cs +++ b/Content.Server/Ame/AmeNodeGroup.cs @@ -134,22 +134,11 @@ public float InjectFuel(int fuel, out bool overloading) // The AME is being overloaded. // Note about these maths: I would assume the general idea here is to make larger engines less safe to overload. // In other words, yes, those are supposed to be CoreCount, not safeFuelLimit. - var instability = 0; var overloadVsSizeResult = fuel - CoreCount; - // fuel > safeFuelLimit: Slow damage. Can safely run at this level for burst periods if the engine is small and someone is keeping an eye on it. - if (_random.Prob(0.5f)) - instability = 1; - // overloadVsSizeResult > 5: - if (overloadVsSizeResult > 5) - instability = 3; - // overloadVsSizeResult > 10: This will explode in at most 20 injections. - if (overloadVsSizeResult > 10) - instability = 5; - - // Apply calculated instability - if (instability == 0) - return powerOutput; + var instability = overloadVsSizeResult / CoreCount; + var fuzz = _random.Next(-1, 2); // -1 to 1 + instability += fuzz; // fuzz the values a tiny bit. overloading = true; var integrityCheck = 100; @@ -179,10 +168,12 @@ public float InjectFuel(int fuel, out bool overloading) /// public float CalculatePower(int fuel, int cores) { - // Fuel is squared so more fuel vastly increases power and efficiency - // We divide by the number of cores so a larger AME is less efficient at the same fuel settings - // this results in all AMEs having the same efficiency at the same fuel-per-core setting - return 20000f * fuel * fuel / cores; + // Balanced around a single core AME with injection level 2 producing 120KW. + // Overclocking yields diminishing returns until it evens out at around 360KW. + + // The adjustment for cores make it so that a 1 core AME at 2 injections is better than a 2 core AME at 2 injections. + // However, for the relative amounts for each (1 core at 2 and 2 core at 4), more cores has more output. + return 200000f * MathF.Log10(fuel * fuel) * MathF.Pow(0.75f, cores - 1); } public int GetTotalStability() diff --git a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs index e6abe98b95c6..eda915827394 100644 --- a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs +++ b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs @@ -274,9 +274,9 @@ public void SetInjectionAmount(EntityUid uid, int value, EntityUid? user = null, At the time of editing, players regularly "overclock" the AME and those cases require no admin attention. // Admin alert - var safeLimit = 0; + var safeLimit = int.MaxValue; if (TryGetAMENodeGroup(uid, out var group)) - safeLimit = group.CoreCount * 2; + safeLimit = group.CoreCount * 4; if (oldValue <= safeLimit && value > safeLimit) { @@ -291,10 +291,20 @@ public void SetInjectionAmount(EntityUid uid, int value, EntityUid? user = null, */ } - public void AdjustInjectionAmount(EntityUid uid, int delta, int min = 0, int max = int.MaxValue, EntityUid? user = null, AmeControllerComponent? controller = null) + public void AdjustInjectionAmount(EntityUid uid, int delta, EntityUid? user = null, AmeControllerComponent? controller = null) { - if (Resolve(uid, ref controller)) - SetInjectionAmount(uid, MathHelper.Clamp(controller.InjectionAmount + delta, min, max), user, controller); + if (!Resolve(uid, ref controller)) + return; + + var max = GetMaxInjectionAmount((uid, controller)); + SetInjectionAmount(uid, MathHelper.Clamp(controller.InjectionAmount + delta, 0, max), user, controller); + } + + public int GetMaxInjectionAmount(Entity ent) + { + if (!TryGetAMENodeGroup(ent, out var group)) + return 0; + return group.CoreCount * 8; } private void UpdateDisplay(EntityUid uid, int stability, AmeControllerComponent? controller = null, AppearanceComponent? appearance = null) diff --git a/Content.Shared/Ame/Components/AmeFuelContainerComponent.cs b/Content.Shared/Ame/Components/AmeFuelContainerComponent.cs index 757a3a515b69..455414597e5a 100644 --- a/Content.Shared/Ame/Components/AmeFuelContainerComponent.cs +++ b/Content.Shared/Ame/Components/AmeFuelContainerComponent.cs @@ -9,11 +9,11 @@ public sealed partial class AmeFuelContainerComponent : Component /// The amount of fuel in the container. /// [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public int FuelAmount = 1000; + public int FuelAmount = 500; /// /// The maximum fuel capacity of the container. /// [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] - public int FuelCapacity = 1000; + public int FuelCapacity = 500; } From 04249e8682a2216ba5d695184d8f05a54fa34e84 Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Sun, 7 Jul 2024 07:28:13 -0700 Subject: [PATCH 060/242] Removes max damage threshold on healing for hyperzine (#29712) * Removes max damage threshold for hyperzine * the description... --------- Co-authored-by: plykiya --- Resources/Locale/en-US/reagents/meta/narcotics.ftl | 2 +- Resources/Prototypes/Reagents/narcotics.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Resources/Locale/en-US/reagents/meta/narcotics.ftl b/Resources/Locale/en-US/reagents/meta/narcotics.ftl index b48eb03b7dfd..600ceffce650 100644 --- a/Resources/Locale/en-US/reagents/meta/narcotics.ftl +++ b/Resources/Locale/en-US/reagents/meta/narcotics.ftl @@ -5,7 +5,7 @@ reagent-name-ephedrine = ephedrine reagent-desc-ephedrine = A caffeinated adrenaline stimulator chemical that makes you faster and harder to knock down. Also helps combat narcolepsy at dosages over thirty, at the cost of severe nerval stress. reagent-name-stimulants = hyperzine -reagent-desc-stimulants = A chemical cocktail developed by Donk Co. that allows agents to recover from stuns faster, move more quickly, and grants a small heal while close to critical condition. Due to the complex nature of the chemical, it is much harder for the body to purge naturally. +reagent-desc-stimulants = A chemical cocktail developed by Donk Co. that allows agents to recover from stuns faster, move more quickly, and grants a small heal when you're more dead than alive. Due to the complex nature of the chemical, it is much harder for the body to purge naturally. reagent-name-experimental-stimulants = experimental stimulants reagent-desc-experimental-stimulants = A prototype version of hyperzine. Usage grants virtual immunity to stun weaponry, rapid tissue regeneration, extreme running speed by reducing lactic acid buildup, and a general feeling of euphoria. Side effects may include extreme levels of anticoagulation, tunnel vision, extreme toxin buildup in the bloodstream, and rapid liver death. Do not give to animals. diff --git a/Resources/Prototypes/Reagents/narcotics.yml b/Resources/Prototypes/Reagents/narcotics.yml index 21d73104839c..bf311f23e7be 100644 --- a/Resources/Prototypes/Reagents/narcotics.yml +++ b/Resources/Prototypes/Reagents/narcotics.yml @@ -151,8 +151,7 @@ - !type:HealthChange conditions: - !type:TotalDamage - min: 70 - max: 120 # you've got a chance to get out of crit + min: 70 # only heals when you're more dead than alive damage: # heals at the same rate as tricordrazine, doesn't heal poison because if you OD'd I'm not giving you a safety net groups: Burn: -1 From eab93cb09ed52b2de4d9523b5ca67fc6954b2716 Mon Sep 17 00:00:00 2001 From: PJBot Date: Sun, 7 Jul 2024 14:29:18 +0000 Subject: [PATCH 061/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 8b060381adeb..c1f6f5632a98 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,18 +1,4 @@ Entries: -- author: Krunk - changes: - - message: Cyborgs no longer see criminal status icons. - type: Remove - id: 6382 - time: '2024-04-18T03:20:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26207 -- author: ArZarLordOfMango - changes: - - message: Added autolathe recipe for beverage jug. - type: Add - id: 6383 - time: '2024-04-18T03:24:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25681 - author: superjj18 changes: - message: Emergency lighting now changes based on station alert level! @@ -3822,3 +3808,23 @@ id: 6881 time: '2024-07-07T14:22:38.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29005 +- author: EmoGarbage404 + changes: + - message: Changed AME power output. Lower injection amounts now produce more power + but further injections have diminishing returns. + type: Tweak + - message: Increased damage per injection overclocked AMEs. + type: Tweak + - message: Halved fuel per AME jar. + type: Tweak + id: 6882 + time: '2024-07-07T14:27:52.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29587 +- author: Plykiya + changes: + - message: Hyperzine's effective healing range has been changed from 70 to 120, + to anything above 70 total damage. + type: Tweak + id: 6883 + time: '2024-07-07T14:28:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29712 From 8c3e72c8c8a089706a224a8089626ba8412bb724 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sun, 7 Jul 2024 23:33:17 -0400 Subject: [PATCH 062/242] Silence ringtones on admin PDAs (#29801) * Silence ringtones on invisible PDAs * Revert "Silence ringtones on invisible PDAs" This reverts commit afc1041f31eebe82e83630a856a8856b877a9826. * Literally just this * Add an admin announcement for news article publishing --- Content.Server/MassMedia/Systems/NewsSystem.cs | 8 ++++++++ Resources/Locale/en-US/mass-media/news-ui.ftl | 1 + Resources/Prototypes/Entities/Objects/Devices/pda.yml | 1 + 3 files changed, 10 insertions(+) diff --git a/Content.Server/MassMedia/Systems/NewsSystem.cs b/Content.Server/MassMedia/Systems/NewsSystem.cs index 0fb5d4239494..9f917d6dbfaf 100644 --- a/Content.Server/MassMedia/Systems/NewsSystem.cs +++ b/Content.Server/MassMedia/Systems/NewsSystem.cs @@ -20,6 +20,7 @@ using Content.Shared.Popups; using Content.Shared.StationRecords; using Robust.Shared.Audio.Systems; +using Content.Server.Chat.Managers; namespace Content.Server.MassMedia.Systems; @@ -35,6 +36,7 @@ public sealed class NewsSystem : SharedNewsSystem [Dependency] private readonly GameTicker _ticker = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly IChatManager _chatManager = default!; public override void Initialize() { @@ -161,6 +163,12 @@ private void OnWriteUiPublishMessage(Entity ent, ref NewsWr $"{ToPrettyString(msg.Actor):actor} created news article {article.Title} by {article.Author}: {article.Content}" ); + _chatManager.SendAdminAnnouncement(Loc.GetString("news-publish-admin-announcement", + ("actor", msg.Actor), + ("title", article.Title), + ("author", article.Author ?? Loc.GetString("news-read-ui-no-author")) + )); + articles.Add(article); var args = new NewsArticlePublishedEvent(article); diff --git a/Resources/Locale/en-US/mass-media/news-ui.ftl b/Resources/Locale/en-US/mass-media/news-ui.ftl index 9f62fe75c0c1..1553a24b4afe 100644 --- a/Resources/Locale/en-US/mass-media/news-ui.ftl +++ b/Resources/Locale/en-US/mass-media/news-ui.ftl @@ -34,3 +34,4 @@ news-write-ui-richtext-tooltip = News articles support rich text {"[bullet/]bullet[/color]"} news-pda-notification-header = New news article +news-publish-admin-announcement = {$actor} published news article {$title} by {$author}" diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index b1a6ab0b8fa6..84f0f4ae94c8 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -667,6 +667,7 @@ scanDelay: 0 - type: CartridgeLoader uiKey: enum.PdaUiKey.Key + notificationsEnabled: false preinstalled: - CrewManifestCartridge - NotekeeperCartridge From f6d093ef5c1f5052bbfc9c3b144ca5d46f7b0358 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 8 Jul 2024 03:34:24 +0000 Subject: [PATCH 063/242] 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 c1f6f5632a98..335776e58f04 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: superjj18 - changes: - - message: Emergency lighting now changes based on station alert level! - type: Tweak - id: 6384 - time: '2024-04-18T10:50:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26932 - author: Dutch-VanDerLinde changes: - message: Chemists now start with chemical analysis goggles. @@ -3828,3 +3821,10 @@ id: 6883 time: '2024-07-07T14:28:13.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29712 +- author: Tayrtahn + changes: + - message: Fixed admin ghosts briefly becoming visible when a news article is published. + type: Fix + id: 6884 + time: '2024-07-08T03:33:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29801 From c8a87ceaab1b011565799230f17999ce47ff8f23 Mon Sep 17 00:00:00 2001 From: JIPDawg <51352440+JIPDawg@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:30:41 -0500 Subject: [PATCH 064/242] Fixed the guide book entry for Diona's blood type (#29805) Fixed the entry for Diona's blood type Co-authored-by: JIP --- Resources/ServerInfo/Guidebook/Mobs/Diona.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/ServerInfo/Guidebook/Mobs/Diona.xml b/Resources/ServerInfo/Guidebook/Mobs/Diona.xml index 575fae561adf..eedf23b14f20 100644 --- a/Resources/ServerInfo/Guidebook/Mobs/Diona.xml +++ b/Resources/ServerInfo/Guidebook/Mobs/Diona.xml @@ -7,7 +7,7 @@ They can't wear shoes, but are not slowed by Kudzu. They get hungry and thirsty slower. - Their "blood" is normal water and can't be metabolised from Iron. + Their "blood" is tree sap and can't be metabolised from Iron. Being plants, Weed Killer poisons them, while Robust Harvest heals them (but not without risk when overused!) They take [color=#1e90ff]30% less Blunt damage and 20% less Slash damage[/color]; From 92e29805344dd2100d0c00cd4af35dce148b6d26 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:03:53 +0200 Subject: [PATCH 065/242] Improve throwing precision (#29726) * improve throwing precision * remove debugging logs * minor fixes * f * Update Content.Shared/Throwing/LandAtCursorComponent.cs --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- .../Throwing/ThrownItemVisualizerSystem.cs | 1 - Content.Server/Hands/Systems/HandsSystem.cs | 8 +- Content.Server/Lube/LubedSystem.cs | 2 +- .../EntitySystems/ContainmentFieldSystem.cs | 2 +- .../Hands/Components/HandsComponent.cs | 6 +- Content.Shared/Throwing/BeforeThrowEvent.cs | 6 +- .../Throwing/LandAtCursorComponent.cs | 12 +++ Content.Shared/Throwing/ThrowingSystem.cs | 80 +++++++++++++------ Content.Shared/Throwing/ThrownItemSystem.cs | 2 +- .../Prototypes/Entities/Objects/Fun/darts.yml | 1 + .../Prototypes/Entities/Objects/Misc/pen.yml | 1 + .../Objects/Specific/Janitorial/janitor.yml | 1 + .../Guns/Ammunition/Projectiles/toy.yml | 1 + .../Entities/Objects/Weapons/Melee/knife.yml | 3 + .../Entities/Objects/Weapons/Melee/spear.yml | 1 + .../Objects/Weapons/Throwable/bola.yml | 2 +- .../Objects/Weapons/Throwable/clusterbang.yml | 2 + .../Weapons/Throwable/throwing_stars.yml | 1 + 18 files changed, 93 insertions(+), 39 deletions(-) create mode 100644 Content.Shared/Throwing/LandAtCursorComponent.cs diff --git a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs index b25b4fbb7def..28a07ae94a70 100644 --- a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs +++ b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs @@ -59,7 +59,6 @@ private void OnShutdown(EntityUid uid, ThrownItemComponent component, ComponentS if (length <= TimeSpan.Zero) return null; - length += TimeSpan.FromSeconds(ThrowingSystem.FlyTime); var scale = ent.Comp2.Scale; var lenFloat = (float) length.TotalSeconds; diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index feae130ce8e3..e2bb991318ff 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -207,13 +207,13 @@ hands.ActiveHandEntity is not { } throwEnt || var length = direction.Length(); var distance = Math.Clamp(length, minDistance, hands.ThrowRange); - direction *= distance/length; + direction *= distance / length; - var throwStrength = hands.ThrowForceMultiplier; + var throwSpeed = hands.BaseThrowspeed; // Let other systems change the thrown entity (useful for virtual items) // or the throw strength. - var ev = new BeforeThrowEvent(throwEnt, direction, throwStrength, player); + var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player); RaiseLocalEvent(player, ref ev); if (ev.Cancelled) @@ -223,7 +223,7 @@ hands.ActiveHandEntity is not { } throwEnt || if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands)) return false; - _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowStrength, ev.PlayerUid); + _throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp(ev.ItemUid)); return true; } diff --git a/Content.Server/Lube/LubedSystem.cs b/Content.Server/Lube/LubedSystem.cs index c2d15c8a2840..3c536dcceb27 100644 --- a/Content.Server/Lube/LubedSystem.cs +++ b/Content.Server/Lube/LubedSystem.cs @@ -43,7 +43,7 @@ private void OnHandPickUp(EntityUid uid, LubedComponent component, ContainerGett var user = args.Container.Owner; _transform.SetCoordinates(uid, Transform(user).Coordinates); _transform.AttachToGridOrMap(uid); - _throwing.TryThrow(uid, _random.NextVector2(), strength: component.SlipStrength); + _throwing.TryThrow(uid, _random.NextVector2(), baseThrowSpeed: component.SlipStrength); _popup.PopupEntity(Loc.GetString("lube-slip", ("target", Identity.Entity(uid, EntityManager))), user, user, PopupType.MediumCaution); } diff --git a/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs b/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs index 3944e94c20d8..0af495451901 100644 --- a/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs +++ b/Content.Server/Singularity/EntitySystems/ContainmentFieldSystem.cs @@ -37,7 +37,7 @@ private void HandleFieldCollide(EntityUid uid, ContainmentFieldComponent compone var fieldDir = Transform(uid).WorldPosition; var playerDir = Transform(otherBody).WorldPosition; - _throwing.TryThrow(otherBody, playerDir-fieldDir, strength: component.ThrowForce); + _throwing.TryThrow(otherBody, playerDir-fieldDir, baseThrowSpeed: component.ThrowForce); } } diff --git a/Content.Shared/Hands/Components/HandsComponent.cs b/Content.Shared/Hands/Components/HandsComponent.cs index 919d55f294aa..84a389a39c5f 100644 --- a/Content.Shared/Hands/Components/HandsComponent.cs +++ b/Content.Shared/Hands/Components/HandsComponent.cs @@ -38,11 +38,11 @@ public sealed partial class HandsComponent : Component public bool DisableExplosionRecursion = false; /// - /// The amount of throw impulse per distance the player is from the throw target. + /// Modifies the speed at which items are thrown. /// - [DataField("throwForceMultiplier")] + [DataField] [ViewVariables(VVAccess.ReadWrite)] - public float ThrowForceMultiplier { get; set; } = 10f; //should be tuned so that a thrown item lands about under the player's cursor + public float BaseThrowspeed { get; set; } = 10f; /// /// Distance after which longer throw targets stop increasing throw impulse. diff --git a/Content.Shared/Throwing/BeforeThrowEvent.cs b/Content.Shared/Throwing/BeforeThrowEvent.cs index 36e7dd758b8f..d2bf2f38b8e0 100644 --- a/Content.Shared/Throwing/BeforeThrowEvent.cs +++ b/Content.Shared/Throwing/BeforeThrowEvent.cs @@ -5,17 +5,17 @@ namespace Content.Shared.Throwing; [ByRefEvent] public struct BeforeThrowEvent { - public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwStrength, EntityUid playerUid) + public BeforeThrowEvent(EntityUid itemUid, Vector2 direction, float throwSpeed, EntityUid playerUid) { ItemUid = itemUid; Direction = direction; - ThrowStrength = throwStrength; + ThrowSpeed = throwSpeed; PlayerUid = playerUid; } public EntityUid ItemUid { get; set; } public Vector2 Direction { get; } - public float ThrowStrength { get; set;} + public float ThrowSpeed { get; set;} public EntityUid PlayerUid { get; } public bool Cancelled = false; diff --git a/Content.Shared/Throwing/LandAtCursorComponent.cs b/Content.Shared/Throwing/LandAtCursorComponent.cs new file mode 100644 index 000000000000..f8e99e2fc876 --- /dev/null +++ b/Content.Shared/Throwing/LandAtCursorComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Throwing +{ + /// + /// Makes an item land at the cursor when thrown and slide a little further. + /// Without it the item lands slightly in front and stops moving at the cursor. + /// Use this for throwing weapons that should pierce the opponent, for example spears. + /// + [RegisterComponent, NetworkedComponent] + public sealed partial class LandAtCursorComponent : Component { } +} diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 7d94ada924d0..56bbf4c2bf3d 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -1,13 +1,12 @@ using System.Numerics; using Content.Shared.Administration.Logs; using Content.Shared.Camera; +using Content.Shared.CCVar; using Content.Shared.Database; +using Content.Shared.Friction; using Content.Shared.Gravity; -using Content.Shared.Hands.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Interaction; using Content.Shared.Projectiles; -using Content.Shared.Tag; +using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; @@ -31,7 +30,10 @@ public sealed class ThrowingSystem : EntitySystem /// The minimum amount of time an entity needs to be thrown before the timer can be run. /// Anything below this threshold never enters the air. /// - public const float FlyTime = 0.15f; + public const float MinFlyTime = 0.15f; + public const float FlyTimePercentage = 0.8f; + + private float _frictionModifier; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!; @@ -40,13 +42,23 @@ public sealed class ThrowingSystem : EntitySystem [Dependency] private readonly ThrownItemSystem _thrownSystem = default!; [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + + public override void Initialize() + { + base.Initialize(); + + Subs.CVar(_configManager, CCVars.TileFrictionModifier, value => _frictionModifier = value, true); + } public void TryThrow( EntityUid uid, EntityCoordinates coordinates, - float strength = 1.0f, + float baseThrowSpeed = 10.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, + float? friction = null, + bool compensateFriction = false, bool recoil = true, bool animated = true, bool playSound = true, @@ -58,7 +70,7 @@ public void TryThrow( if (mapPos.MapId != thrownPos.MapId) return; - TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); + TryThrow(uid, mapPos.Position - thrownPos.Position, baseThrowSpeed, user, pushbackRatio, friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); } /// @@ -66,14 +78,18 @@ public void TryThrow( /// /// The entity being thrown. /// A vector pointing from the entity to its destination. - /// How much the direction vector should be multiplied for velocity. + /// Throw velocity. Gets modified if compensateFriction is true. /// The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced + /// friction value used for the distance calculation. If set to null this defaults to the standard tile values + /// True will adjust the throw so the item stops at the target coordinates. False means it will land at the target and keep sliding. /// Whether spin will be applied to the thrown entity. public void TryThrow(EntityUid uid, Vector2 direction, - float strength = 1.0f, + float baseThrowSpeed = 10.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, + float? friction = null, + bool compensateFriction = false, bool recoil = true, bool animated = true, bool playSound = true, @@ -91,9 +107,10 @@ public void TryThrow(EntityUid uid, physics, Transform(uid), projectileQuery, - strength, + baseThrowSpeed, user, - pushbackRatio, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); + pushbackRatio, + friction, compensateFriction: compensateFriction, recoil: recoil, animated: animated, playSound: playSound, doSpin: doSpin); } /// @@ -101,23 +118,27 @@ public void TryThrow(EntityUid uid, /// /// The entity being thrown. /// A vector pointing from the entity to its destination. - /// How much the direction vector should be multiplied for velocity. + /// Throw velocity. Gets modified if compensateFriction is true. /// The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced + /// friction value used for the distance calculation. If set to null this defaults to the standard tile values + /// True will adjust the throw so the item stops at the target coordinates. False means it will land at the target and keep sliding. /// Whether spin will be applied to the thrown entity. public void TryThrow(EntityUid uid, Vector2 direction, PhysicsComponent physics, TransformComponent transform, EntityQuery projectileQuery, - float strength = 1.0f, + float baseThrowSpeed = 10.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, + float? friction = null, + bool compensateFriction = false, bool recoil = true, bool animated = true, bool playSound = true, bool doSpin = true) { - if (strength <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero) + if (baseThrowSpeed <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero || friction < 0) return; if ((physics.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) @@ -136,16 +157,22 @@ public void TryThrow(EntityUid uid, Animate = animated, }; - // Estimate time to arrival so we can apply OnGround status and slow it much faster. - var time = direction.Length() / strength; + // if not given, get the default friction value for distance calculation + var tileFriction = friction ?? _frictionModifier * TileFrictionController.DefaultFriction; + + if (tileFriction == 0f) + compensateFriction = false; // cannot calculate this if there is no friction + + // Set the time the item is supposed to be in the air so we can apply OnGround status. + // This is a free parameter, but we should set it to something reasonable. + var flyTime = direction.Length() / baseThrowSpeed; + if (compensateFriction) + flyTime *= FlyTimePercentage; + + if (flyTime < MinFlyTime) + flyTime = 0f; comp.ThrownTime = _gameTiming.CurTime; - // TODO: This is a bandaid, don't do this. - // if you want to force landtime have the caller handle it or add a new method. - // did we launch this with something stronger than our hands? - if (TryComp(comp.Thrower, out var hands) && strength > hands.ThrowForceMultiplier) - comp.LandTime = comp.ThrownTime + TimeSpan.FromSeconds(time); - else - comp.LandTime = time < FlyTime ? default : comp.ThrownTime + TimeSpan.FromSeconds(time - FlyTime); + comp.LandTime = comp.ThrownTime + TimeSpan.FromSeconds(flyTime); comp.PlayLandSound = playSound; AddComp(uid, comp, true); @@ -173,7 +200,12 @@ public void TryThrow(EntityUid uid, if (user != null) _adminLogger.Add(LogType.Throw, LogImpact.Low, $"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):entity}"); - var impulseVector = direction.Normalized() * strength * physics.Mass; + // if compensateFriction==true compensate for the distance the item will slide over the floor after landing by reducing the throw speed accordingly. + // else let the item land on the cursor and from where it slides a little further. + // This is an exact formula we get from exponentially decaying velocity after landing. + // If someone changes how tile friction works at some point, this will have to be adjusted. + var throwSpeed = compensateFriction ? direction.Length() / (flyTime + 1 / tileFriction) : baseThrowSpeed; + var impulseVector = direction.Normalized() * throwSpeed * physics.Mass; _physics.ApplyLinearImpulse(uid, impulseVector, body: physics); if (comp.LandTime == null || comp.LandTime <= TimeSpan.Zero) diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index 770273fa0327..54dc5c3490e8 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -153,7 +153,7 @@ public override void Update(float frameTime) LandComponent(uid, thrown, physics, thrown.PlayLandSound); } - var stopThrowTime = (thrown.LandTime ?? thrown.ThrownTime) + TimeSpan.FromSeconds(ThrowingSystem.FlyTime); + var stopThrowTime = thrown.LandTime ?? thrown.ThrownTime; if (stopThrowTime <= _gameTiming.CurTime) { StopThrow(uid, thrown); diff --git a/Resources/Prototypes/Entities/Objects/Fun/darts.yml b/Resources/Prototypes/Entities/Objects/Fun/darts.yml index c0b5eb33999d..dd25d503ce23 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/darts.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/darts.yml @@ -10,6 +10,7 @@ offset: 0.0,0.0 - type: ThrowingAngle angle: 315 + - type: LandAtCursor - type: Fixtures fixtures: fix1: diff --git a/Resources/Prototypes/Entities/Objects/Misc/pen.yml b/Resources/Prototypes/Entities/Objects/Misc/pen.yml index 635df230a4a3..187672ec4dd7 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/pen.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/pen.yml @@ -29,6 +29,7 @@ removalTime: 0.0 - type: ThrowingAngle angle: 315 + - type: LandAtCursor - type: DamageOtherOnHit damage: types: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml index f4cc09430a20..2a6f314904a0 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml @@ -238,6 +238,7 @@ embedOnThrow: True - type: ThrowingAngle angle: 0 + - type: LandAtCursor - type: Ammo muzzleFlash: null - type: Projectile diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml index 9b6c288e3786..14595bd34a2e 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/toy.yml @@ -31,3 +31,4 @@ removalTime: .2 - type: ThrowingAngle angle: 180 + - type: LandAtCursor diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml index afe4644517c8..f862a0f10aab 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml @@ -92,6 +92,7 @@ Slash: 12 - type: EmbeddableProjectile sound: /Audio/Weapons/star_hit.ogg + - type: LandAtCursor - type: DamageOtherOnHit damage: types: @@ -150,6 +151,7 @@ damage: types: Slash: 10 + - type: LandAtCursor - type: Sprite sprite: Clothing/Head/Hats/greyflatcap.rsi - type: Clothing @@ -274,6 +276,7 @@ Slash: 5 - type: EmbeddableProjectile sound: /Audio/Weapons/star_hit.ogg + - type: LandAtCursor - type: DamageOtherOnHit ignoreResistances: true damage: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml index 0def916ddc73..ea2e73ac1517 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/spear.yml @@ -8,6 +8,7 @@ offset: 0.15,0.15 - type: ThrowingAngle angle: 225 + - type: LandAtCursor - type: Tag tags: - Spear diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml index ca3edf68c0df..fea9a8d5ea3b 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/bola.yml @@ -47,4 +47,4 @@ staminaDamage: 55 # Sudden weight increase sapping stamina canThrowTrigger: true canMoveBreakout: true - + - type: LandAtCursor diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml index 35174ba34d26..cc55aab237c0 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml @@ -96,6 +96,7 @@ damage: types: Blunt: 10 + - type: LandAtCursor - type: Damageable damageContainer: Inorganic - type: EmitSoundOnTrigger @@ -224,6 +225,7 @@ damage: types: Blunt: 10 + - type: LandAtCursor - type: EmitSoundOnTrigger sound: path: "/Audio/Effects/flash_bang.ogg" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml index c68feff0b5c8..0dfc9a0312fd 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/throwing_stars.yml @@ -31,6 +31,7 @@ friction: 0.2 - type: EmbeddableProjectile sound: /Audio/Weapons/star_hit.ogg + - type: LandAtCursor - type: DamageOtherOnHit damage: types: From d6c4ebdd3769edb9bc15bfab967cb603352b8455 Mon Sep 17 00:00:00 2001 From: PJBot Date: Mon, 8 Jul 2024 09:04:59 +0000 Subject: [PATCH 066/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 335776e58f04..d9c904f79021 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Chemists now start with chemical analysis goggles. - type: Tweak - id: 6385 - time: '2024-04-18T10:52:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27047 - author: TheShuEd changes: - message: botanist can now mutate blood tomatoes into killer tomatoes! they will @@ -3828,3 +3821,12 @@ id: 6884 time: '2024-07-08T03:33:17.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29801 +- author: slarticodefast + changes: + - message: Improved throwing calculations. Thrown items now stop moving exactly + below your cursor. Throwing weapons will land at your cursor and then slide + until stopped by friction. + type: Tweak + id: 6885 + time: '2024-07-08T09:03:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29726 From 40f735acb1d8b36c345b6221d6141c2a6c34d2e9 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 9 Jul 2024 04:05:01 +1000 Subject: [PATCH 067/242] Don't treat vgroid as station grid (#29811) Forgot I added this toggle. --- .../Prototypes/Entities/Stations/base.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Resources/Prototypes/Entities/Stations/base.yml b/Resources/Prototypes/Entities/Stations/base.yml index 7b5809158834..c7b54f457170 100644 --- a/Resources/Prototypes/Entities/Stations/base.yml +++ b/Resources/Prototypes/Entities/Stations/base.yml @@ -46,15 +46,6 @@ path: /Maps/Shuttles/cargo.yml - type: GridSpawn groups: - vgroid: !type:DungeonSpawnGroup - minimumDistance: 1000 - nameDataset: names_borer - addComponents: - - type: Gravity - enabled: true - inherent: true - protos: - - VGRoid trade: !type:GridSpawnGroup addComponents: - type: ProtectedGrid @@ -81,6 +72,16 @@ - /Maps/Ruins/syndicate_dropship.yml - /Maps/Ruins/whiteship_ancient.yml - /Maps/Ruins/whiteship_bluespacejumper.yml + vgroid: !type:DungeonSpawnGroup + minimumDistance: 1000 + nameDataset: names_borer + stationGrid: false + addComponents: + - type: Gravity + enabled: true + inherent: true + protos: + - VGRoid - type: entity id: BaseStationCentcomm From 149b6a89ca52db5577f1f9d27f68220b6b9b4f52 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Tue, 9 Jul 2024 02:28:33 +0200 Subject: [PATCH 068/242] Vox guidebook entry (#29713) * vox guidebook entry * skreee * Removed incorrect diet reference --- Resources/Prototypes/Guidebook/species.yml | 6 ++++++ Resources/ServerInfo/Guidebook/Mobs/Species.xml | 1 + Resources/ServerInfo/Guidebook/Mobs/Vox.xml | 16 ++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 Resources/ServerInfo/Guidebook/Mobs/Vox.xml diff --git a/Resources/Prototypes/Guidebook/species.yml b/Resources/Prototypes/Guidebook/species.yml index 5b9efd036614..78111d0fd1b8 100644 --- a/Resources/Prototypes/Guidebook/species.yml +++ b/Resources/Prototypes/Guidebook/species.yml @@ -10,6 +10,7 @@ - Moth - Reptilian - SlimePerson + - Vox - type: guideEntry id: Arachnid @@ -45,3 +46,8 @@ id: SlimePerson name: species-name-slime text: "/ServerInfo/Guidebook/Mobs/SlimePerson.xml" + +- type: guideEntry + id: Vox + name: species-name-vox + text: "/ServerInfo/Guidebook/Mobs/Vox.xml" diff --git a/Resources/ServerInfo/Guidebook/Mobs/Species.xml b/Resources/ServerInfo/Guidebook/Mobs/Species.xml index 5fb4ca741e09..f1b36f9d732d 100644 --- a/Resources/ServerInfo/Guidebook/Mobs/Species.xml +++ b/Resources/ServerInfo/Guidebook/Mobs/Species.xml @@ -13,6 +13,7 @@ + diff --git a/Resources/ServerInfo/Guidebook/Mobs/Vox.xml b/Resources/ServerInfo/Guidebook/Mobs/Vox.xml new file mode 100644 index 000000000000..173ba89b411e --- /dev/null +++ b/Resources/ServerInfo/Guidebook/Mobs/Vox.xml @@ -0,0 +1,16 @@ + + # Vox + + + + + + [color=#ffa500]Caution! This species has a severely limiting game mechanic and is not recommended for new players. [/color] + + They breathe nitrogen, and [color=#ffa500] oxygen is very toxic to them.[/color] + They need to be on internals at all times to avoid being poisoned by the station's oxygen. + Eating or drinking will require you to remove your breath mask, although injecting liquid food is an option. + + Their unarmed claw attacks deal Slash damage instead of Blunt. + + From f12b395f2a8ed977a34518a2ca10b6a0b839a82c Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 00:29:39 +0000 Subject: [PATCH 069/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d9c904f79021..4f27c925c4ef 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: TheShuEd - changes: - - message: botanist can now mutate blood tomatoes into killer tomatoes! they will - be personal pets, aggressively defending the owner from any social interactions. - type: Add - id: 6386 - time: '2024-04-18T11:21:56.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26053 - author: TheShuEd changes: - message: Now winter around Europa is different every round @@ -3830,3 +3822,10 @@ id: 6885 time: '2024-07-08T09:03:53.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29726 +- author: Errant + changes: + - message: Vox now have their entry in the guidebook. + type: Fix + id: 6886 + time: '2024-07-09T00:28:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29713 From c7f752dc96882dd1707ef32aa230bca9f70eb3e0 Mon Sep 17 00:00:00 2001 From: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com> Date: Mon, 8 Jul 2024 23:14:18 -0500 Subject: [PATCH 070/242] Restore a panic bunker cvar in wizardsDen.toml (#29832) Update wizardsDen.toml --- Resources/ConfigPresets/WizardsDen/wizardsDen.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/ConfigPresets/WizardsDen/wizardsDen.toml b/Resources/ConfigPresets/WizardsDen/wizardsDen.toml index 807b21b46947..4f1b616fb256 100644 --- a/Resources/ConfigPresets/WizardsDen/wizardsDen.toml +++ b/Resources/ConfigPresets/WizardsDen/wizardsDen.toml @@ -5,6 +5,7 @@ desc = "Official English Space Station 14 servers. Vanilla, roleplay ruleset." lobbyenabled = true soft_max_players = 80 +panic_bunker.enabled = true panic_bunker.disable_with_admins = true panic_bunker.enable_without_admins = true panic_bunker.show_reason = true From 8dde49db9547dabfb2231739393d3dfcf2cc41d4 Mon Sep 17 00:00:00 2001 From: Whisper <121047731+QuietlyWhisper@users.noreply.github.com> Date: Tue, 9 Jul 2024 00:14:51 -0400 Subject: [PATCH 071/242] all toggle light actions have a 1 second use delay (#29833) --- Resources/Prototypes/Actions/types.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index 4b3d75bf920e..07d1a622adaf 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -37,6 +37,7 @@ description: Turn the light on and off. components: - type: InstantAction + useDelay: 1 icon: { sprite: Objects/Tools/flashlight.rsi, state: flashlight } iconOn: { sprite: Objects/Tools/flashlight.rsi, state: flashlight-on } event: !type:ToggleActionEvent From 8bd8787819010e6d55c3d027efb8d07165bca634 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 04:15:58 +0000 Subject: [PATCH 072/242] 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 4f27c925c4ef..ecf5fe584609 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: TheShuEd - changes: - - message: Now winter around Europa is different every round - type: Tweak - id: 6387 - time: '2024-04-18T11:27:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26510 - author: Flareguy changes: - message: The Research Director's hardsuit is now 5x5 in the inventory instead @@ -3829,3 +3822,10 @@ id: 6886 time: '2024-07-09T00:28:33.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29713 +- author: Whisper + changes: + - message: Light toggle actions now have a 1 second cooldown between uses. + type: Tweak + id: 6887 + time: '2024-07-09T04:14:51.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29833 From 08b12f34b3a57508ccca1d1f250b612fba3a8494 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Mon, 8 Jul 2024 23:23:08 -0500 Subject: [PATCH 073/242] Fix Shotgun Spam Loading (#29827) --- .../Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 9123661c8e96..503459cefd99 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -86,6 +86,9 @@ private void OnBallisticAfterInteract(EntityUid uid, BallisticAmmoProviderCompon private void OnBallisticAmmoFillDoAfter(EntityUid uid, BallisticAmmoProviderComponent component, AmmoFillDoAfterEvent args) { + if (args.Handled || args.Cancelled) + return; + if (Deleted(args.Target) || !TryComp(args.Target, out var target) || target.Whitelist == null) From 9bf3c33cca9634783b0d0a727c2472863a62a75d Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 04:24:14 +0000 Subject: [PATCH 074/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ecf5fe584609..9709740f452a 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Flareguy - changes: - - message: The Research Director's hardsuit is now 5x5 in the inventory instead - of 2x2. You'll need a duffelbag to store it now. - type: Tweak - id: 6388 - time: '2024-04-18T18:05:45.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27094 - author: Dutch-VanDerLinde changes: - message: Roboticist gear in now available in the scientist loadout. @@ -3829,3 +3821,10 @@ id: 6887 time: '2024-07-09T04:14:51.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29833 +- author: Cojoke-dot + changes: + - message: Shotgun loading doafter now does something + type: Fix + id: 6888 + time: '2024-07-09T04:23:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29827 From 74d9ac7241486b6da96d5ea5ca11239fea4b601f Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:41:47 +0200 Subject: [PATCH 075/242] Add popup for healing target (#29804) * Add popup for healing target * hop * huh * fix one * fix showing popup to all --- Content.Server/Medical/HealingSystem.cs | 8 ++++++++ .../Locale/en-US/medical/components/healing-component.ftl | 1 + 2 files changed, 9 insertions(+) diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index ed33e60dac48..52d6a3fafa76 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -10,12 +10,14 @@ using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Medical; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Popups; using Content.Shared.Stacks; using Robust.Shared.Audio.Systems; using Robust.Shared.Random; @@ -188,6 +190,12 @@ targetDamage.DamageContainerID is not null && var isNotSelf = user != target; + if (isNotSelf) + { + var msg = Loc.GetString("medical-item-popup-target", ("user", Identity.Entity(user, EntityManager)), ("item", uid)); + _popupSystem.PopupEntity(msg, target, target, PopupType.Medium); + } + var delay = isNotSelf ? component.Delay : component.Delay * GetScaledHealingPenalty(user, component); diff --git a/Resources/Locale/en-US/medical/components/healing-component.ftl b/Resources/Locale/en-US/medical/components/healing-component.ftl index 5c2aaaabd9bc..1deb39db091a 100644 --- a/Resources/Locale/en-US/medical/components/healing-component.ftl +++ b/Resources/Locale/en-US/medical/components/healing-component.ftl @@ -1,3 +1,4 @@ medical-item-finished-using = You have finished healing with the {$item} medical-item-cant-use = There is no damage you can heal with the {$item} medical-item-stop-bleeding = They have stopped bleeding +medical-item-popup-target = {CAPITALIZE(THE($user))} is trying to heal you with the {$item}! From 061ec432ffa9d89231b52dbe39573843e6bb995c Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:42:35 +0200 Subject: [PATCH 076/242] Add popup for health analyzer target (#29803) * Add popup for health analyzer target * addition * fix showing popup to all --- Content.Server/Medical/HealthAnalyzerSystem.cs | 6 ++++++ .../en-US/medical/components/health-analyzer-component.ftl | 2 ++ 2 files changed, 8 insertions(+) diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index 7282ea197c98..c72cd2ddf6f6 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -5,10 +5,12 @@ using Content.Server.Temperature.Components; using Content.Shared.Damage; using Content.Shared.DoAfter; +using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.MedicalScanner; using Content.Shared.Mobs.Components; +using Content.Shared.Popups; using Content.Shared.PowerCell; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; @@ -27,6 +29,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; public override void Initialize() { @@ -85,6 +88,9 @@ private void OnAfterInteract(Entity uid, ref AfterInter NeedHand = true, BreakOnMove = true }); + + var msg = Loc.GetString("health-analyzer-popup-scan-target", ("user", Identity.Entity(args.User, EntityManager))); + _popupSystem.PopupEntity(msg, args.Target.Value, args.Target.Value, PopupType.Medium); } private void OnDoAfter(Entity uid, ref HealthAnalyzerDoAfterEvent args) diff --git a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl index 8460bcc27b06..121e50b923e6 100644 --- a/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl +++ b/Resources/Locale/en-US/medical/components/health-analyzer-component.ftl @@ -14,3 +14,5 @@ health-analyzer-window-scan-mode-active = ACTIVE health-analyzer-window-scan-mode-inactive = INACTIVE health-analyzer-window-malnutrition = Severely malnourished + +health-analyzer-popup-scan-target = {CAPITALIZE(THE($user))} is trying to scan you! From f3342c51554933c2d2aa2425267eff765faebecb Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:36:33 +0300 Subject: [PATCH 077/242] localization and change of appearance of the phrase about the remaining time (#29844) --- .../Communications/UI/CommunicationsConsoleMenu.xaml.cs | 4 +++- .../en-US/communications/communications-console-component.ftl | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs index 4d8dd86a4dcc..bbca06f5194a 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml.cs @@ -113,7 +113,9 @@ public void UpdateCountdown() } EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle"); - CountdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s"); + var infoText = Loc.GetString($"comms-console-menu-time-remaining", + ("time", Owner.Countdown.ToString())); + CountdownLabel.SetMessage(infoText); } public override void Close() diff --git a/Resources/Locale/en-US/communications/communications-console-component.ftl b/Resources/Locale/en-US/communications/communications-console-component.ftl index bead43df286f..d3200914b3cc 100644 --- a/Resources/Locale/en-US/communications/communications-console-component.ftl +++ b/Resources/Locale/en-US/communications/communications-console-component.ftl @@ -5,6 +5,7 @@ comms-console-menu-announcement-button = Announce comms-console-menu-broadcast-button = Broadcast comms-console-menu-call-shuttle = Call emergency shuttle comms-console-menu-recall-shuttle = Recall emergency shuttle +comms-console-menu-time-remaining = Time remaining: {$time} # Popup comms-console-permission-denied = Permission denied From 04cb2657bf87219b21fae440bbbbeb25e5141c07 Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:37:00 +0300 Subject: [PATCH 078/242] Action menu localization (#29839) --- .../UserInterface/Systems/Actions/Windows/ActionsWindow.xaml | 2 +- .../Systems/Actions/Windows/ActionsWindow.xaml.cs | 2 +- Resources/Locale/en-US/actions/ui/actionmenu.ftl | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml index 52346adba274..443b79242e7d 100644 --- a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml +++ b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml @@ -3,7 +3,7 @@ xmlns:windows="clr-namespace:Content.Client.UserInterface.Systems.Actions.Windows" Name="ActionsList" HorizontalExpand="True" - Title="Actions" + Title="{Loc ui-actionmenu-title}" VerticalExpand="True" Resizable="True" MinHeight="300" diff --git a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs index fbe1e71535dd..f972a96eb74f 100644 --- a/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs +++ b/Content.Client/UserInterface/Systems/Actions/Windows/ActionsWindow.xaml.cs @@ -26,7 +26,7 @@ public ActionsWindow() foreach (var filter in Enum.GetValues()) { - FilterButton.AddItem(filter.ToString(), filter); + FilterButton.AddItem(Loc.GetString($"ui-actionmenu-{filter.ToString().ToLower()}"), filter); } } diff --git a/Resources/Locale/en-US/actions/ui/actionmenu.ftl b/Resources/Locale/en-US/actions/ui/actionmenu.ftl index a6f0dee53421..47fdb43f6992 100644 --- a/Resources/Locale/en-US/actions/ui/actionmenu.ftl +++ b/Resources/Locale/en-US/actions/ui/actionmenu.ftl @@ -9,3 +9,8 @@ ui-actionmenu-clear-button = Clear ui-actionsui-function-lock-action-slots = (Un)lock dragging and clearing action slots ui-actionsui-function-open-abilities-menu = Open action menu +ui-actionmenu-enabled = Enabled +ui-actionmenu-item = Item +ui-actionmenu-innate = Innate +ui-actionmenu-instant = Instant +ui-actionmenu-targeted = Targeted From 7e087a6eb8bb6e05cb959969925a4f359003c75d Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:39:47 +0200 Subject: [PATCH 079/242] fix passive vent sprite in construction menu (#29820) --- Resources/Prototypes/Recipes/Construction/utilities.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index 82c16de7b6a1..930768b3ea75 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -489,12 +489,12 @@ canBuildInImpassable: false icon: sprite: Structures/Piping/Atmospherics/vent.rsi - state: vent_off + state: vent_passive layers: - sprite: Structures/Piping/Atmospherics/pipe.rsi state: pipeHalf - sprite: Structures/Piping/Atmospherics/vent.rsi - state: vent_off + state: vent_passive conditions: - !type:NoUnstackableInTile From 7bf77beb9e3c8dba951fc60effd60e8b94681206 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 13:40:53 +0000 Subject: [PATCH 080/242] 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 9709740f452a..d84d8423bf30 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: Dutch-VanDerLinde - changes: - - message: Roboticist gear in now available in the scientist loadout. - type: Tweak - id: 6389 - time: '2024-04-18T23:38:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27045 - author: Whisper changes: - message: Raised the difficulty class of RD hardsuit objective, it will be less @@ -3828,3 +3821,11 @@ id: 6888 time: '2024-07-09T04:23:08.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29827 +- author: slarticodefast + changes: + - message: The construction menu and building preview now show the correct passive + vent sprite. + type: Fix + id: 6889 + time: '2024-07-09T13:39:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29820 From 62d8665484e03ae97bb3c36b3e61a74a1f9fe5e4 Mon Sep 17 00:00:00 2001 From: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Date: Tue, 9 Jul 2024 08:46:21 -0500 Subject: [PATCH 081/242] Let Pacifists Use Certain Guns(Foam Weapons) (#29835) Let Pacifists Use Certain Guns(foam) --- .../CombatMode/Pacification/PacificationSystem.cs | 3 +++ .../Pacification/PacifismAllowedGunComponent.cs | 11 +++++++++++ Resources/Prototypes/Entities/Objects/Fun/toys.yml | 1 + .../Entities/Objects/Weapons/Guns/Rifles/rifles.yml | 3 ++- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Content.Shared/CombatMode/Pacification/PacifismAllowedGunComponent.cs diff --git a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs index 14507ce1f5ce..6bc32c5b96c6 100644 --- a/Content.Shared/CombatMode/Pacification/PacificationSystem.cs +++ b/Content.Shared/CombatMode/Pacification/PacificationSystem.cs @@ -62,6 +62,9 @@ private void ShowPopup(Entity user, EntityUid target, string private void OnShootAttempt(Entity ent, ref ShotAttemptedEvent args) { + if (HasComp(args.Used)) + return; + // Disallow firing guns in all cases. ShowPopup(ent, args.Used, "pacified-cannot-fire-gun"); args.Cancel(); diff --git a/Content.Shared/CombatMode/Pacification/PacifismAllowedGunComponent.cs b/Content.Shared/CombatMode/Pacification/PacifismAllowedGunComponent.cs new file mode 100644 index 000000000000..7decbeb3bc6a --- /dev/null +++ b/Content.Shared/CombatMode/Pacification/PacifismAllowedGunComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.CombatMode.Pacification; + +/// +/// Guns with this component can be fired by pacifists +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class PacifismAllowedGunComponent : Component +{ +} diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 1ac622db86c8..310f92e60c31 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -869,6 +869,7 @@ - type: Sprite - type: Item size: Normal + - type: PacifismAllowedGun - type: entity parent: FoamWeaponBase diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index 9300367cdec3..fef793487c2d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml @@ -199,7 +199,7 @@ sprite: Objects/Weapons/Guns/Rifles/foam_rifle.rsi - type: Item sprite: Objects/Weapons/Guns/Rifles/foam_rifle_inhand_64x.rsi - - type: BallisticAmmoProvider + - type: BallisticAmmoProvider whitelist: tags: - BulletFoam @@ -216,3 +216,4 @@ soundEmpty: path: /Audio/Weapons/Guns/Empty/empty.ogg clumsyProof: true + - type: PacifismAllowedGun From 8c598799b43b184e70523a57c144e23f01f202f3 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 13:47:27 +0000 Subject: [PATCH 082/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d84d8423bf30..abd590c47151 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,12 +1,4 @@ Entries: -- author: Whisper - changes: - - message: Raised the difficulty class of RD hardsuit objective, it will be less - likely to get it with other hard objectives. - type: Tweak - id: 6390 - time: '2024-04-18T23:41:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27103 - author: iztokbajcar changes: - message: Glass textures are now less transparent. @@ -3829,3 +3821,10 @@ id: 6889 time: '2024-07-09T13:39:47.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29820 +- author: Cojoke-dot + changes: + - message: Pacifists can now use foam weaponry + type: Tweak + id: 6890 + time: '2024-07-09T13:46:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29835 From 245c99cc78076433412608daa313a45e933a0153 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:51:03 +0200 Subject: [PATCH 083/242] Fix lobby time typo (#29841) --- Resources/Locale/en-US/lobby/lobby-state.ftl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Resources/Locale/en-US/lobby/lobby-state.ftl b/Resources/Locale/en-US/lobby/lobby-state.ftl index 2e60b66bde1a..0c4c401daa37 100644 --- a/Resources/Locale/en-US/lobby/lobby-state.ftl +++ b/Resources/Locale/en-US/lobby/lobby-state.ftl @@ -9,7 +9,14 @@ lobby-state-player-status-not-ready = Not Ready lobby-state-player-status-ready = Ready lobby-state-player-status-observer = Observer lobby-state-player-status-round-not-started = The round hasn't started yet -lobby-state-player-status-round-time = The round time is: {$hours} hours and {$minutes} minutes +lobby-state-player-status-round-time = + The round time is: {$hours} {$hours -> + [1]hour + *[other]hours + } and {$minutes} {$minutes -> + [1]minute + *[other]minutes + } lobby-state-song-text = Playing: [color=white]{$songTitle}[/color] by [color=white]{$songArtist}[/color] lobby-state-song-no-song-text = No lobby song playing. lobby-state-song-unknown-title = [color=dimgray]Unknown title[/color] From c9be1ef96b59830b9daf005269a6fbbcd1b849c4 Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:57:36 +0300 Subject: [PATCH 084/242] Localization of the threat level in an emergency lamp (#29847) --- Content.Server/Light/EntitySystems/EmergencyLightSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index f2c4c7dcc59e..156088ea072e 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -70,7 +70,7 @@ private void OnEmergencyExamine(EntityUid uid, EmergencyLightComponent component args.PushMarkup( Loc.GetString("emergency-light-component-on-examine-alert", ("color", color.ToHex()), - ("level", name))); + ("level", Loc.GetString($"alert-level-{name.ToString().ToLower()}")))); } } @@ -234,6 +234,6 @@ private void TurnOn(Entity entity, Color color) _pointLight.SetColor(entity.Owner, color); _appearance.SetData(entity.Owner, EmergencyLightVisuals.Color, color); _appearance.SetData(entity.Owner, EmergencyLightVisuals.On, true); - _ambient.SetAmbience(entity.Owner, true); + _ambient.SetAmbience(entity.Owner, true); } } From b511d8e18033a0f2a4ce1c83e8cd866901a353fc Mon Sep 17 00:00:00 2001 From: chavonadelal <156101927+chavonadelal@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:11:07 +0300 Subject: [PATCH 085/242] Nozzle Direction Localization (#29849) --- Content.Server/Shuttles/Systems/ThrusterSystem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index e82235e44f5b..fd149630814c 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -18,6 +18,7 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; using Robust.Shared.Utility; +using Content.Shared.Localizations; namespace Content.Server.Shuttles.Systems; @@ -67,8 +68,9 @@ private void OnThrusterExamine(EntityUid uid, ThrusterComponent component, Exami EntityManager.TryGetComponent(uid, out TransformComponent? xform) && xform.Anchored) { + var nozzleLocalization = ContentLocalizationManager.FormatDirection(xform.LocalRotation.Opposite().ToWorldVec().GetDir()).ToLower(); var nozzleDir = Loc.GetString("thruster-comp-nozzle-direction", - ("direction", xform.LocalRotation.Opposite().ToWorldVec().GetDir().ToString().ToLowerInvariant())); + ("direction", nozzleLocalization)); args.PushMarkup(nozzleDir); From 49128ba9d3492e41f64a3ad12f40226015a22387 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:55:47 +0200 Subject: [PATCH 086/242] Aghosts can now /ghost (#29360) I will not be subjected to criminal abuse --- Content.Server/GameTicking/GameTicker.GamePreset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index 6e297789528d..5a2b375dd68c 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -226,7 +226,7 @@ public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaComma return false; } - if (HasComp(playerEntity)) + if (TryComp(playerEntity, out var comp) && !comp.CanGhostInteract) return false; if (mind.VisitingEntity != default) From bb82b4cd6faa28ecb5b29b6d88e84bc73aa601ed Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 9 Jul 2024 23:14:25 +0200 Subject: [PATCH 087/242] Change Cluster evac (#29828) --- Resources/Prototypes/Maps/cluster.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Prototypes/Maps/cluster.yml b/Resources/Prototypes/Maps/cluster.yml index 0841a12b1d54..ef9d7586bd18 100644 --- a/Resources/Prototypes/Maps/cluster.yml +++ b/Resources/Prototypes/Maps/cluster.yml @@ -9,7 +9,7 @@ stationProto: StandardNanotrasenStation components: - type: StationEmergencyShuttle - emergencyShuttlePath: /Maps/Shuttles/emergency_transit.yml + emergencyShuttlePath: /Maps/Shuttles/emergency_omega.yml - type: StationNameSetup mapNameTemplate: '{0} Cluster Station {1}' nameGenerator: From 742290c6066f52ee4e1bac96970a61d79f52f9a1 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Tue, 9 Jul 2024 23:35:31 +0200 Subject: [PATCH 088/242] Update map changes labeler (#29858) Update labeler.yml --- .github/labeler.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 886ce89708c8..9d9fe3a08d33 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,8 +5,8 @@ "Changes: Map": - changed-files: - any-glob-to-any-file: - - 'Resources/Maps/*.yml' - - 'Resources/Prototypes/Maps/*.yml' + - 'Resources/Maps/**/*.yml' + - 'Resources/Prototypes/Maps/**/*.yml' "Changes: UI": - changed-files: From 2349fb485e1ebabb99a01dce511e5c33c868ba2b Mon Sep 17 00:00:00 2001 From: Plykiya <58439124+Plykiya@users.noreply.github.com> Date: Tue, 9 Jul 2024 16:12:40 -0700 Subject: [PATCH 089/242] Stop eating food if you drop it (#29854) * Stop eating food if you drop it * woops, unused param * comments --------- Co-authored-by: plykiya --- Content.Server/Nutrition/EntitySystems/DrinkSystem.cs | 11 ++++++++--- Content.Server/Nutrition/EntitySystems/FoodSystem.cs | 9 ++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index b8e334fb0daf..d9122ff278c3 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.DoAfter; using Content.Shared.EntityEffects; using Content.Shared.FixedPoint; +using Content.Shared.Hands.EntitySystems; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; @@ -48,6 +49,7 @@ public sealed class DrinkSystem : SharedDrinkSystem [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly StomachSystem _stomach = default!; @@ -156,6 +158,9 @@ public void UpdateAppearance(EntityUid uid, DrinkComponent component) _appearance.SetData(uid, FoodVisuals.Visual, drainAvailable.Float(), appearance); } + /// + /// Tries to feed the drink item to the target entity + /// private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item) { if (!HasComp(target)) @@ -210,9 +215,9 @@ private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, En BreakOnDamage = true, MovementThreshold = 0.01f, DistanceThreshold = 1.0f, - // Mice and the like can eat without hands. - // TODO maybe set this based on some CanEatWithoutHands event or component? - NeedHand = forceDrink, + // do-after will stop if item is dropped when trying to feed someone else + // or if the item started out in the user's own hands + NeedHand = forceDrink || _hands.IsHolding(user, item), }; _doAfter.TryStartDoAfter(doAfterEventArgs); diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 1862b4e19f14..fc9d228b056a 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -99,6 +99,9 @@ private void OnFeedFood(Entity entity, ref AfterInteractEvent arg args.Handled = result.Handled; } + /// + /// Tries to feed the food item to the target entity + /// public (bool Success, bool Handled) TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp) { //Suppresses eating yourself and alive mobs @@ -189,9 +192,9 @@ private void OnFeedFood(Entity entity, ref AfterInteractEvent arg BreakOnDamage = true, MovementThreshold = 0.01f, DistanceThreshold = MaxFeedDistance, - // Mice and the like can eat without hands. - // TODO maybe set this based on some CanEatWithoutHands event or component? - NeedHand = forceFeed, + // do-after will stop if item is dropped when trying to feed someone else + // or if the item started out in the user's own hands + NeedHand = forceFeed || _hands.IsHolding(user, food), }; _doAfter.TryStartDoAfter(doAfterArgs); From edf8c6cb4ae244c90ea98d22da3ee5e82785cf79 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 23:13:46 +0000 Subject: [PATCH 090/242] 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 abd590c47151..7cc4cbdef1f2 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: iztokbajcar - changes: - - message: Glass textures are now less transparent. - type: Tweak - id: 6391 - time: '2024-04-18T23:43:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/26948 - author: SlamBamActionman changes: - message: Snouts and noses no longer disappear with toggled masks. @@ -3828,3 +3821,10 @@ id: 6890 time: '2024-07-09T13:46:21.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29835 +- author: Plykiya + changes: + - message: You can now drop food and drinks to stop consuming it. + type: Fix + id: 6891 + time: '2024-07-09T23:12:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29854 From 93197b6cce69bf380e9807f449c2b1a28d982281 Mon Sep 17 00:00:00 2001 From: lzk <124214523+lzk228@users.noreply.github.com> Date: Wed, 10 Jul 2024 01:24:38 +0200 Subject: [PATCH 091/242] Fix colornetwork desc and help (#29856) * Fix colornetwork description * suggested changes * forgor * done * ok now it IS done --- Content.Server/Sandbox/Commands/ColorNetworkCommand.cs | 10 ++++------ .../Locale/en-US/commands/colornetwork-command.ftl | 3 +++ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 Resources/Locale/en-US/commands/colornetwork-command.ftl diff --git a/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs b/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs index d5dca64eaaca..1fc207058d8c 100644 --- a/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs +++ b/Content.Server/Sandbox/Commands/ColorNetworkCommand.cs @@ -9,21 +9,19 @@ namespace Content.Server.Sandbox.Commands { [AnyCommand] - public sealed class ColorNetworkCommand : IConsoleCommand + public sealed class ColorNetworkCommand : LocalizedCommands { [Dependency] private readonly IEntityManager _entManager = default!; - public string Command => "colornetwork"; - public string Description => Loc.GetString("color-network-command-description"); - public string Help => Loc.GetString("color-network-command-help-text", ("command",Command)); + public override string Command => "colornetwork"; - public void Execute(IConsoleShell shell, string argStr, string[] args) + 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))) { - shell.WriteError("You are not currently able to use mapping commands."); + shell.WriteError(Loc.GetString("cmd-colornetwork-no-access")); } if (args.Length != 3) diff --git a/Resources/Locale/en-US/commands/colornetwork-command.ftl b/Resources/Locale/en-US/commands/colornetwork-command.ftl new file mode 100644 index 000000000000..3fd60bcb89f8 --- /dev/null +++ b/Resources/Locale/en-US/commands/colornetwork-command.ftl @@ -0,0 +1,3 @@ +cmd-colornetwork-desc = Paints the atmos devices in the specified color +cmd-colornetwork-help = colornetwork Pipe +cmd-colornetwork-no-access = You are not currently able to use mapping commands. From d3495fc51c811d6fbd8dbaa1a20bb1da77c8dbcd Mon Sep 17 00:00:00 2001 From: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Date: Wed, 10 Jul 2024 01:28:10 +0200 Subject: [PATCH 092/242] made the guitars be able to be worn on suitstorage (#29048) * made the guitars be able to be worn on suitstorage * minor fix --- .../Fun/Instruments/instruments_string.yml | 4 ++++ .../bassguitar.rsi/equipped-SUITSTORAGE.png | Bin 0 -> 862 bytes .../Fun/Instruments/bassguitar.rsi/meta.json | 4 ++++ .../eguitar.rsi/equipped-SUITSTORAGE.png | Bin 0 -> 845 bytes .../Fun/Instruments/eguitar.rsi/meta.json | 4 ++++ .../guitar.rsi/equipped-SUITSTORAGE.png | Bin 0 -> 1515 bytes .../Objects/Fun/Instruments/guitar.rsi/meta.json | 4 ++++ .../rockguitar.rsi/equipped-SUITSTORAGE.png | Bin 0 -> 803 bytes .../Fun/Instruments/rockguitar.rsi/meta.json | 4 ++++ 9 files changed, 20 insertions(+) create mode 100644 Resources/Textures/Objects/Fun/Instruments/bassguitar.rsi/equipped-SUITSTORAGE.png create mode 100644 Resources/Textures/Objects/Fun/Instruments/eguitar.rsi/equipped-SUITSTORAGE.png create mode 100644 Resources/Textures/Objects/Fun/Instruments/guitar.rsi/equipped-SUITSTORAGE.png create mode 100644 Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/equipped-SUITSTORAGE.png diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml index 7224efa9e023..6ceb5e23edc1 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_string.yml @@ -21,6 +21,7 @@ quickEquip: false slots: - back + - suitStorage sprite: Objects/Fun/Instruments/eguitar.rsi - type: Tag tags: @@ -50,6 +51,7 @@ quickEquip: false slots: - back + - suitStorage sprite: Objects/Fun/Instruments/bassguitar.rsi - type: Tag tags: @@ -78,6 +80,7 @@ quickEquip: false slots: - back + - suitStorage sprite: Objects/Fun/Instruments/rockguitar.rsi - type: Tag tags: @@ -120,6 +123,7 @@ quickEquip: false slots: - back + - suitStorage sprite: Objects/Fun/Instruments/guitar.rsi - type: Wieldable - type: Damageable # Smash it! Does 20 damage a hit, but breaks after 1 hit. diff --git a/Resources/Textures/Objects/Fun/Instruments/bassguitar.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Fun/Instruments/bassguitar.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000000000000000000000000000000000..72146d40e5a27251471ab50183fd21a0f611e125 GIT binary patch literal 862 zcmV-k1EKthP)R?X_mg)Z_$Q*M$%QQ6~(AfpF|hA}ua9NUMEvJe9Ev(uq%Yo*QPf$8ig zofqJLve8=iEGHD)qFEfTn8oo*)9!1n!yKlRGD@kYvmck+a<-EyrA)P24f|8FiH25b z`Uh5ObJpY95DjtK^@IINI2VQb{OmLUgpus~e#^C3FH}lt;s%S$x{l+d<^VFjG1MQw zzY~6c^xz&kF`P(sr@8^1b%Kn4{%j0O%c~t5PQ=~~4s<$}78hqvC~ zmsVgLXBu!!7UnF ziS9ySq*|@!gb<4;43QIT7G*PDn#9d{_tjtP=dGo& z#`2h}PCJo!Z3-mX;oIv|>%M*IfP?)C#BGVU+hZ3-ZQnVd-2#nkbFOVz7%iKi?pyCZ o&}R`4)csyBfngYiVXhPZ02e#ZDRbKY*8l(j07*qoM6N<$f=Fek;gOdstXE#BFB3PVsDh_5V z4X;B}$k3q(g27@Tf%Xj@I+fc&&TA51UY_wL(QrNpyyTwTd(XM&ocDO=01U%048t%C z0|3b7i>A?-<2Vh+x{c#FCTxFUdif}&j8e)32|JEsq?Gs8001DR3^YP6Uo@m!{a7De zD5Z=L0zwF67iQYM*N&21m_aI)f)E1rM(7LrLy#bGpS&QQj2rqil2J;To&6(JDwV)- z-vB%NNANrkp63C$X$04GV~IopK=+M6N^eY!VqdI(C!j<)KQ!R~_Wa!kyc&B_BWL^W(-L7%Wp|x7 zos64YzG!m!V$&zUsN5+Bq?2*eX{%SeJHs#x!!QiPFbu;m3}c%}HT7PF>T2~ypqgN} z3#7IPe4nSaHlF7pl}h34?CeI-p}sCyDwR6+UQkMDY_1pBAF#E(2LLrS_39IMT{lMM z@TSzptcgNwQ1CPLUgiy#`apFu0)|QI5>!Mxg1)5v+bH;IF4g%gMQ}ST!!QiPFoFC7 X?$d`GV%U4o00000NkvXXu0mjfG|h-o literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Fun/Instruments/eguitar.rsi/meta.json b/Resources/Textures/Objects/Fun/Instruments/eguitar.rsi/meta.json index 0891477f1b59..8723daef066c 100644 --- a/Resources/Textures/Objects/Fun/Instruments/eguitar.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Instruments/eguitar.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-BACKPACK", "directions": 4 }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Objects/Fun/Instruments/guitar.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Fun/Instruments/guitar.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000000000000000000000000000000000..71667f4e21cef619cd105ef332058d670f6adebd GIT binary patch literal 1515 zcmV~osik1& z3tj^F3E@v`mN65L$M|=B-r-X9c2xEH|K6XgXH~Z}aCHe!YKB=6mT8EsR}975!hKpk zy()Yt95!V@;VY$Q9DY`ubNF4cnM}-rTlEa)esJaoYk9LMJR%$_7OGln9`z+Yy|ged zbwKtY773z62$G@78U-qhN-ardfBVLozB$U`(d2DV!xTr3ym-T+R;8;hH7mLPIo>uoJ`0KJ=VcG=c4 zyKue*{SQXkOgANuYLLgPBfId*E(^kfqN^w>EHCn+3oDG07F$-! zoYU#%w$pi5Co(_w?3{N;7w-c@(D(eG|MPpF_dWBx2aq8NNCJ{T1_;!aLdk%vaLHWZ zimD(L?#V0XaoI?t38 zWo2a^jzl&Ns~eP3Un zu637=+ehbW6>B7lYmYym@mMpQy*ClF<{5xyg7;rSxPGM>0nZ*pVhIGsM-3NG0_;722Ai?dPuCrx;SgH3xe;|ZaQ6IZ zv^Q2FcToLrOAWm0A+BfEBQbi@Bin-OnFYXBoNeU;LOs*zYqlZadS*Qnr1f;E-h-wW3B==ZSk&kA z6v1F{&K{L(6GG<{4)t+4h32ijDi_TUd&Rw|@$|6g&w2d6ouKzzUPMRDl$(yz$g_wX zNsa9uhtjd5bVfn#s7zQwhR|#|$t!tg$bhv8Hk3I~l=rLcrwckJGZO;kn(b(gypm^5 zu#U#u?&ZI=`Z=0&KBi>lQj{cJc*S^Gv&5!6J5`mkrOfC8P7d-)p2bwJs*qrP#ln<= zW^9O@9ORWVVFZY^2L#sa!_ke-lt9fmJ?7*fujH8%tUVy{par8b_4SM_iP7N*id4^> zU;()CdXS?vZ^m@?1=(I$0Q~AnY&J!<`OPM_(yJsO2}lBxfFvLZNCN*4fuCb0go|%H R58(g+002ovPDHLkV1g2w*Hi!i literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Fun/Instruments/guitar.rsi/meta.json b/Resources/Textures/Objects/Fun/Instruments/guitar.rsi/meta.json index 681328237274..bcd517578fc7 100644 --- a/Resources/Textures/Objects/Fun/Instruments/guitar.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Instruments/guitar.rsi/meta.json @@ -29,6 +29,10 @@ { "name": "equipped-BACKPACK", "directions": 4 + }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 } ] } diff --git a/Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/equipped-SUITSTORAGE.png b/Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/equipped-SUITSTORAGE.png new file mode 100644 index 0000000000000000000000000000000000000000..36cb11d801bfcf45f16274f89a34c7ed8b56d6ee GIT binary patch literal 803 zcmV+;1Kj+HP)Z-fv`&7ES&b;^C_uk070SJO12!bF8B9%s?5ggivI2w&c zu!((!`lZYH(g3%fx43WHb|8d+r(DY$ob!M&2G?~LHYWK50HyFCEz3g7vUX?X0k{|p zP&LiSdn*FGOs9C6P64c{2h3H@+Q41V>Ib%M2LK?10DxT71T|7ymW7Jr z;Lr6no+c9jt9h@YZTyeZ)8O0l^HNa+{s|%ZS}sF*$gM85-N^^*Io}lFE0V;IWF0G_--SBtU;_)!k34K zm1jny5ng*e0Kl)eHvsE74@9g2c_i%oXfy%<)M_-k&|Eyfb=bIbU zYBd0J5#;B`P6tnuNj?PFG~{*04T}H(-_OqhZ1(nnf~0_o>im7X4PbNUfdUF3xo@{I z_WiBL(Ow4gfSAqj*y%vbX0iY5)+N_PFpOLp<(tn!fWoCfmd+CdK@bE%5ClOG1aX{1 zZYuQa^&oEJL2ZoYp)a^Ci913q%UbEpd7fu5#=sZ@=RDYVr#S2R5(SXw1i}-$u4~A4 zxfCKxqxMOp0R7?6keiQH(_9=q38MedD5g21=hT7mN>Y1EfqY9pd5Q!-u zMQ92Ud&Wkx&nQSsKY2W+U$3Xyle#UgMuLt}`$A2>F!E)MeIIEeX!Ql82s%dTjF%-* zbKZT_E*-QRBmLp9Fl+ugkyxnSpe{`zFvdI;iuB@$EYPX^Duru~CZT(zPUXK6;OJR5 h`~ivpf*^?F<{gKAM`X|(z<2-v002ovPDHLkV1j}ba;g9T literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/meta.json b/Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/meta.json index 7fb73d450dc3..96bd8a37e53d 100644 --- a/Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/Instruments/rockguitar.rsi/meta.json @@ -14,6 +14,10 @@ "name": "equipped-BACKPACK", "directions": 4 }, + { + "name": "equipped-SUITSTORAGE", + "directions": 4 + }, { "name": "inhand-left", "directions": 4 From 7a66da31c7de5f8adb26434135eb0e5f3cdd5ae1 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 23:29:16 +0000 Subject: [PATCH 093/242] 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 7cc4cbdef1f2..f404d68589db 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,11 +1,4 @@ Entries: -- author: SlamBamActionman - changes: - - message: Snouts and noses no longer disappear with toggled masks. - type: Fix - id: 6392 - time: '2024-04-19T05:39:47.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25716 - author: shampunj changes: - message: 'Now for crafting stunprod you need: igniter, cable cuffs, small power @@ -3828,3 +3821,10 @@ id: 6891 time: '2024-07-09T23:12:40.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29854 +- author: Boaz1111 + changes: + - message: Guitars can now be worn in the suit storage slot + type: Add + id: 6892 + time: '2024-07-09T23:28:10.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29048 From c57009b646e63fb158dd2af9ff1a7586720a2202 Mon Sep 17 00:00:00 2001 From: Winkarst-cpu <74284083+Winkarst-cpu@users.noreply.github.com> Date: Wed, 10 Jul 2024 02:48:56 +0300 Subject: [PATCH 094/242] Fix borg's popup spam (#29861) Fix borg popup spam Co-authored-by: Winkarst-cpu --- Content.Shared/Lock/LockSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index bf4af1cb9a4e..22a90aa0a625 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -365,7 +365,7 @@ private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent c { args.Cancel(); if (lockComp.Locked) - _sharedPopupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); + _sharedPopupSystem.PopupClient(Loc.GetString("entity-storage-component-locked-message"), uid, args.User); } } From d3642c7383bcd76b4ded7d951c4ba05817d3daf3 Mon Sep 17 00:00:00 2001 From: PJBot Date: Tue, 9 Jul 2024 23:50:02 +0000 Subject: [PATCH 095/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index f404d68589db..4f38a9f753d0 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,15 +1,4 @@ Entries: -- author: shampunj - changes: - - message: 'Now for crafting stunprod you need: igniter, cable cuffs, small power - cell, metal rod.' - type: Tweak - - message: Stunprod deals 35 stamina damage per hit and 5 shock damage. The battery - charge is enough for 3 hits. - type: Tweak - id: 6393 - time: '2024-04-19T05:50:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/25922 - author: EmoGarbage404 changes: - message: Halved passive fuel consumption of welding tools. @@ -3828,3 +3817,10 @@ id: 6892 time: '2024-07-09T23:28:10.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29048 +- author: Winkarst-cpu + changes: + - message: Fixed popup spam when trying to open borg's UI while the borg is locked. + type: Fix + id: 6893 + time: '2024-07-09T23:48:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29861 From eef6f920120cc49a4bdf773a3eb776d34c41ce7e Mon Sep 17 00:00:00 2001 From: Ed <96445749+TheShuEd@users.noreply.github.com> Date: Wed, 10 Jul 2024 08:24:25 +0300 Subject: [PATCH 096/242] Steal Objective Condition now support stacks (#29843) * Update StealConditionSystem.cs * Update StealConditionSystem.cs --- .../Objectives/Systems/StealConditionSystem.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Content.Server/Objectives/Systems/StealConditionSystem.cs b/Content.Server/Objectives/Systems/StealConditionSystem.cs index 0fe6f0947c87..42a296ab925c 100644 --- a/Content.Server/Objectives/Systems/StealConditionSystem.cs +++ b/Content.Server/Objectives/Systems/StealConditionSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Components; using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Stacks; namespace Content.Server.Objectives.Systems; @@ -105,7 +106,7 @@ private float GetProgress(MindComponent mind, StealConditionComponent condition) if (pulledEntity != null) { // check if this is the item - if (CheckStealTarget(pulledEntity.Value, condition)) count++; + count += CheckStealTarget(pulledEntity.Value, condition); //we don't check the inventories of sentient entity if (!HasComp(pulledEntity)) @@ -126,7 +127,7 @@ private float GetProgress(MindComponent mind, StealConditionComponent condition) foreach (var entity in container.ContainedEntities) { // check if this is the item - if (CheckStealTarget(entity, condition)) count++; //To Do: add support for stackable items + count += CheckStealTarget(entity, condition); // if it is a container check its contents if (_containerQuery.TryGetComponent(entity, out var containerManager)) @@ -140,14 +141,14 @@ private float GetProgress(MindComponent mind, StealConditionComponent condition) return result; } - private bool CheckStealTarget(EntityUid entity, StealConditionComponent condition) + private int CheckStealTarget(EntityUid entity, StealConditionComponent condition) { // check if this is the target if (!TryComp(entity, out var target)) - return false; + return 0; if (target.StealGroup != condition.StealGroup) - return false; + return 0; // check if needed target alive if (condition.CheckAlive) @@ -155,9 +156,10 @@ private bool CheckStealTarget(EntityUid entity, StealConditionComponent conditio if (TryComp(entity, out var state)) { if (!_mobState.IsAlive(entity, state)) - return false; + return 0; } } - return true; + + return TryComp(entity, out var stack) ? stack.Count : 1; } } From 3f2793c17987f6f792d0633a2346e11122ed2935 Mon Sep 17 00:00:00 2001 From: Moomoobeef <62638182+Moomoobeef@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:25:24 -0500 Subject: [PATCH 097/242] fixed missing characters in OwO accent (#29047) replaced two characters with ones that actually show up ingame --- Content.Server/Speech/EntitySystems/OwOAccentSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs b/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs index cac3debe8196..2b3d5aa3d491 100644 --- a/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/OwOAccentSystem.cs @@ -8,7 +8,7 @@ public sealed class OwOAccentSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; private static readonly IReadOnlyList Faces = new List{ - " (・`ω´・)", " ;;w;;", " owo", " UwU", " >w<", " ^w^" + " (•`ω´•)", " ;;w;;", " owo", " UwU", " >w<", " ^w^" }.AsReadOnly(); private static readonly IReadOnlyDictionary SpecialWords = new Dictionary() From a0165773a456d85877e422cdfa083cfa4c58307e Mon Sep 17 00:00:00 2001 From: Ivan <69372103+lokachop@users.noreply.github.com> Date: Wed, 10 Jul 2024 07:26:33 +0200 Subject: [PATCH 098/242] Add scarf to warm clothing bounty (#29779) * Add scarf to warm clothing bounty * Added ClothingScarfBase prototype and assigned to all of the scarves --- .../Prototypes/Catalog/Bounties/bounties.yml | 4 +++- .../Clothing/Neck/base_clothingneck.yml | 9 +++++++ .../Entities/Clothing/Neck/scarfs.yml | 24 +++++++++---------- Resources/Prototypes/tags.yml | 3 +++ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index 08bb2a142250..f656eb1962f2 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -1,4 +1,4 @@ -- type: cargoBounty +- type: cargoBounty id: BountyArtifact reward: 2500 description: bounty-description-artifact @@ -500,6 +500,8 @@ whitelist: components: - TemperatureProtection + tags: + - Scarf - type: cargoBounty id: BountyBattery diff --git a/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml b/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml index 1bccb4c92a3e..d2fffb815379 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/base_clothingneck.yml @@ -29,3 +29,12 @@ tags: - ClothMade - WhitelistChameleon + +- type: entity + abstract: true + parent: ClothingNeckBase + id: ClothingScarfBase + components: + - type: Tag + tags: + - Scarf diff --git a/Resources/Prototypes/Entities/Clothing/Neck/scarfs.yml b/Resources/Prototypes/Entities/Clothing/Neck/scarfs.yml index 33f582d30dee..1f9d6b8b2c67 100644 --- a/Resources/Prototypes/Entities/Clothing/Neck/scarfs.yml +++ b/Resources/Prototypes/Entities/Clothing/Neck/scarfs.yml @@ -1,5 +1,5 @@ - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedRed name: striped red scarf description: A stylish striped red scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -10,7 +10,7 @@ sprite: Clothing/Neck/Scarfs/red.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedBlue name: striped blue scarf description: A stylish striped blue scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -21,7 +21,7 @@ sprite: Clothing/Neck/Scarfs/blue.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedGreen name: striped green scarf description: A stylish striped green scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -32,7 +32,7 @@ sprite: Clothing/Neck/Scarfs/green.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedBlack name: striped black scarf description: A stylish striped black scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -43,7 +43,7 @@ sprite: Clothing/Neck/Scarfs/black.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedBrown name: striped brown scarf description: A stylish striped brown scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -54,7 +54,7 @@ sprite: Clothing/Neck/Scarfs/brown.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedLightBlue name: striped light blue scarf description: A stylish striped light blue scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -65,7 +65,7 @@ sprite: Clothing/Neck/Scarfs/lightblue.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedOrange name: striped orange scarf description: A stylish striped orange scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -76,7 +76,7 @@ sprite: Clothing/Neck/Scarfs/orange.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedPurple name: striped purple scarf description: A stylish striped purple scarf. The perfect winter accessory for those with a keen fashion sense, and those who just can't handle a cold breeze on their necks. @@ -87,7 +87,7 @@ sprite: Clothing/Neck/Scarfs/purple.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedSyndieGreen name: striped syndicate green scarf description: A stylish striped syndicate green scarf. The perfect winter accessory for those with a keen fashion sense, and those who are in the mood to steal something. @@ -98,7 +98,7 @@ sprite: Clothing/Neck/Scarfs/syndiegreen.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedSyndieRed name: striped syndicate red scarf description: A stylish striped syndicate red scarf. The perfect winter accessory for those with a keen fashion sense, and those who are in the mood to steal something. @@ -109,7 +109,7 @@ sprite: Clothing/Neck/Scarfs/syndiered.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedCentcom name: striped CentCom scarf description: A stylish striped centcom colored scarf. The perfect winter accessory for those with a keen fashion sense, and those who need to do paperwork in the cold. @@ -120,7 +120,7 @@ sprite: Clothing/Neck/Scarfs/centcom.rsi - type: entity - parent: ClothingNeckBase + parent: ClothingScarfBase id: ClothingNeckScarfStripedZebra name: zebra scarf description: A striped scarf, a mandatory accessory for artists. diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index bc12ea0af215..d7a7f089b1b6 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -1105,6 +1105,9 @@ - type: Tag id: SalvageExperiment +- type: Tag + id: Scarf + - type: Tag id: Screwdriver From 8d48096c577b6ee58e81125a3fb8c0952f27965f Mon Sep 17 00:00:00 2001 From: PJBot Date: Wed, 10 Jul 2024 05:27:40 +0000 Subject: [PATCH 099/242] Automatic changelog update --- Resources/Changelog/Changelog.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 4f38a9f753d0..4b10c79f6b4f 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,13 +1,4 @@ Entries: -- author: EmoGarbage404 - changes: - - message: Halved passive fuel consumption of welding tools. - type: Tweak - - message: Completing an action with a welding tool now directly drains the fuel. - type: Tweak - id: 6394 - time: '2024-04-19T23:20:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27030 - author: superjj18 changes: - message: Pneumatic cannon inventory reduced in size to 2x2, item blacklist removed @@ -3824,3 +3815,10 @@ id: 6893 time: '2024-07-09T23:48:56.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/29861 +- author: Lokachop + changes: + - message: Scarves now count as warm clothing for the warm clothing cargo bounty. + type: Tweak + id: 6894 + time: '2024-07-10T05:26:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/29779 From e8b3042a3874b839f0c3898864f64ecb547ef8ba Mon Sep 17 00:00:00 2001 From: dffdff2423 Date: Wed, 10 Jul 2024 00:28:36 -0500 Subject: [PATCH 100/242] Add an option to the admin fax menu to lock papers such that they can't be edited by cybersun pens (#28972) * Add option to adminfax for locking papers. * Replace dummy control with margin --- Content.Client/Fax/AdminUI/AdminFaxEui.cs | 2 +- Content.Client/Fax/AdminUI/AdminFaxWindow.xaml | 4 ++-- Content.Client/Fax/AdminUI/AdminFaxWindow.xaml.cs | 5 +++-- Content.Server/Fax/AdminUI/AdminFaxEui.cs | 4 +++- Content.Server/Fax/FaxConstants.cs | 1 + Content.Server/Fax/FaxSystem.cs | 9 +++++++-- Content.Server/Paper/PaperComponent.cs | 4 +++- Content.Server/Paper/PaperSystem.cs | 8 ++++++++ Content.Shared/Fax/AdminFaxEui.cs | 4 +++- Content.Shared/Fax/Components/FaxMachineComponent.cs | 6 +++++- Resources/Locale/en-US/fax/fax-admin.ftl | 2 ++ Resources/Locale/en-US/paper/paper-component.ftl | 2 ++ 12 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Content.Client/Fax/AdminUI/AdminFaxEui.cs b/Content.Client/Fax/AdminUI/AdminFaxEui.cs index ace3f3eb7b35..452c54eb7972 100644 --- a/Content.Client/Fax/AdminUI/AdminFaxEui.cs +++ b/Content.Client/Fax/AdminUI/AdminFaxEui.cs @@ -16,7 +16,7 @@ public AdminFaxEui() _window.OnClose += () => SendMessage(new AdminFaxEuiMsg.Close()); _window.OnFollowFax += entity => SendMessage(new AdminFaxEuiMsg.Follow(entity)); _window.OnMessageSend += args => SendMessage(new AdminFaxEuiMsg.Send(args.entity, args.title, - args.stampedBy, args.message, args.stampSprite, args.stampColor)); + args.stampedBy, args.message, args.stampSprite, args.stampColor, args.locked)); } public override void Opened() diff --git a/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml b/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml index d469a0e9d383..dc4092a3b534 100644 --- a/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml +++ b/Content.Client/Fax/AdminUI/AdminFaxWindow.xaml @@ -23,7 +23,7 @@