From dc99cb3d0c8fa7f9aa4ec39924e354b552725f7c Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 12 Aug 2024 13:00:55 +1000 Subject: [PATCH 01/40] Station AI overlay --- Content.Client/StationAi/StationAiOverlay.cs | 67 ++++++++++++++++++++ Content.Client/StationAi/StationAiSystem.cs | 65 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 Content.Client/StationAi/StationAiOverlay.cs create mode 100644 Content.Client/StationAi/StationAiSystem.cs diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/StationAi/StationAiOverlay.cs new file mode 100644 index 00000000000000..ebc300ec72bf9b --- /dev/null +++ b/Content.Client/StationAi/StationAiOverlay.cs @@ -0,0 +1,67 @@ +using System.Numerics; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client.StationAi; + +public sealed class StationAiOverlay : Overlay +{ + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private IRenderTexture? _staticTexture; + + public IRenderTexture? _blep; + + public StationAiOverlay() + { + IoCManager.InjectDependencies(this); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (_blep != null) + { + var worldHandle = args.WorldHandle; + + /* + * TODO: + * Wall shadows still fucked + * Need to draw it over the wall layer or whatever, can't just turn FOV off because that will break stuff + */ + + // Stencil + //worldHandle.SetTransform(Matrix3x2.Identity); + + var worldAabb = args.WorldAABB; + var worldBounds = args.WorldBounds; + + // Use the lighting as a mask + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_proto.Index("StencilMask").Instance()); + worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + + // Draw the static + worldHandle.UseShader(_proto.Index("StencilDraw").Instance()); + worldHandle.DrawTextureRect(Texture.White, worldBounds); + + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(null); + } + + // Will be a frame out of sync but makes sure we don't have issues with data not being ready after lighting. + if (_blep?.Texture.Size != args.Viewport.Size) + { + _staticTexture?.Dispose(); + _blep?.Dispose(); + _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); + _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, + new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), + name: "station-ai-static"); + } + } +} diff --git a/Content.Client/StationAi/StationAiSystem.cs b/Content.Client/StationAi/StationAiSystem.cs new file mode 100644 index 00000000000000..ecee86d9b9b7ee --- /dev/null +++ b/Content.Client/StationAi/StationAiSystem.cs @@ -0,0 +1,65 @@ +using System.Numerics; +using Content.Client.SurveillanceCamera; +using Robust.Client.Graphics; +using Robust.Client.Graphics.Clyde; + +namespace Content.Client.StationAi; + +public sealed class StationAiSystem : EntitySystem +{ + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IOverlayManager _overlayMgr = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; + + private StationAiOverlay? _overlay; + + public bool Enabled => _overlay != null; + + private HashSet> _entities = new(); + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnLightingPass); + _overlay = new StationAiOverlay(); + _overlayMgr.AddOverlay(_overlay); + } + + public override void Shutdown() + { + base.Shutdown(); + _overlayMgr.RemoveOverlay(); + } + + private void OnLightingPass(ref LightingPassEvent ev) + { + _entities.Clear(); + + if (_overlay == null || _overlay._blep == null) + return; + + var pass = new LightingPass() + { + Target = _overlay._blep, + }; + + _lookup.GetEntitiesIntersecting(ev.MapId, ev.WorldAabb.Enlarged(10f), _entities); + + foreach (var ent in _entities) + { + var (worldPos, worldRot) = _xforms.GetWorldPositionRotation(ent); + + pass.Lights.Add(new RenderLight() + { + Color = Color.White, + Radius = 10f, + Energy = 1f, + Position = worldPos, + Rotation = worldRot, + Softness = 1f, + }); + } + ev.Add(pass); + } +} From be541eb11e4251a32fa70d5600efeb01a5f0ce30 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 12 Aug 2024 23:06:22 +1000 Subject: [PATCH 02/40] implement --- Content.Client/StationAi/StationAiOverlay.cs | 310 ++++++++++++++++-- Content.Client/StationAi/StationAiSystem.cs | 34 -- .../DungeonJob/DungeonJob.DunGenExterior.cs | 3 +- .../NPC/SharedPathfindingSystem.Line.cs | 6 +- Content.Shared/NPC/SharedPathfindingSystem.cs | 29 +- 5 files changed, 314 insertions(+), 68 deletions(-) rename Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs => Content.Shared/NPC/SharedPathfindingSystem.Line.cs (92%) diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/StationAi/StationAiOverlay.cs index ebc300ec72bf9b..856dfb2537b1a7 100644 --- a/Content.Client/StationAi/StationAiOverlay.cs +++ b/Content.Client/StationAi/StationAiOverlay.cs @@ -1,7 +1,11 @@ using System.Numerics; +using Content.Client.SurveillanceCamera; +using Content.Shared.NPC; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; +using Robust.Shared.Map; +using Robust.Shared.Physics; using Robust.Shared.Prototypes; namespace Content.Client.StationAi; @@ -9,12 +13,15 @@ namespace Content.Client.StationAi; public sealed class StationAiOverlay : Overlay { [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IPrototypeManager _proto = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; - private IRenderTexture? _staticTexture; + private HashSet> _seeds = new(); + private IRenderTexture? _staticTexture; public IRenderTexture? _blep; public StationAiOverlay() @@ -24,44 +31,289 @@ public StationAiOverlay() protected override void Draw(in OverlayDrawArgs args) { - if (_blep != null) + if (_blep?.Texture.Size != args.Viewport.Size) + { + _staticTexture?.Dispose(); + _blep?.Dispose(); + _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); + _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, + new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), + name: "station-ai-static"); + } + + var worldHandle = args.WorldHandle; + + var mapId = args.MapId; + var worldAabb = args.WorldAABB; + var worldBounds = args.WorldBounds; + var maps = _entManager.System(); + var lookups = _entManager.System(); + var xforms = _entManager.System(); + var visibleTiles = new HashSet(); + + _mapManager.TryFindGridAt(mapId, worldAabb.Center, out var gridUid, out var grid); + + var invMatrix = args.Viewport.GetWorldToLocalMatrix(); + + // Skrunkly line of sight to be generous like the byond one. + var expandedBounds = worldAabb.Enlarged(7.5f); + var cleared = new HashSet(); + var opaque = new HashSet(); + var occluders = new HashSet>(); + var viewportTiles = new HashSet(); + + if (grid != null) { - var worldHandle = args.WorldHandle; + // Code based upon https://github.com/OpenDreamProject/OpenDream/blob/c4a3828ccb997bf3722673620460ebb11b95ccdf/OpenDreamShared/Dream/ViewAlgorithm.cs + var tileEnumerator = maps.GetTilesEnumerator(gridUid, grid, expandedBounds, ignoreEmpty: false); + + // Get what tiles are blocked up-front. + while (tileEnumerator.MoveNext(out var tileRef)) + { + var tileBounds = lookups.GetLocalBounds(tileRef.GridIndices, grid.TileSize).Enlarged(-0.05f); + + occluders.Clear(); + lookups.GetLocalEntitiesIntersecting(gridUid, tileBounds, occluders, LookupFlags.Static); + + if (occluders.Count > 0) + { + opaque.Add(tileRef.GridIndices); + } + else + { + cleared.Add(tileRef.GridIndices); + } + } + + // TODO: Changes + + // Run seeds in parallel + // Iterate get_hear for each camera (instead of expanding) and store vis2. + + _seeds.Clear(); + lookups.GetEntitiesIntersecting(args.MapId, expandedBounds, _seeds, LookupFlags.Static); + var vis1 = new Dictionary(); + var vis2 = new Dictionary(); + var seedTiles = new HashSet(); + var boundary = new HashSet(); + + foreach (var seed in _seeds) + { + var range = 7.5f; + boundary.Clear(); + seedTiles.Clear(); + vis1.Clear(); + vis2.Clear(); + + var maxDepthMax = 0; + var sumDepthMax = 0; + + var eyePos = maps.GetTileRef(gridUid, grid, _entManager.GetComponent(seed).Coordinates).GridIndices; + + for (var x = Math.Floor(eyePos.X - range); x <= eyePos.X + range; x++) + { + for (var y = Math.Floor(eyePos.Y - range); y <= eyePos.Y + range; y++) + { + var tile = new Vector2i((int)x, (int)y); + var delta = tile - eyePos; + var xDelta = Math.Abs(delta.X); + var yDelta = Math.Abs(delta.Y); + + var deltaSum = xDelta + yDelta; + + maxDepthMax = Math.Max(maxDepthMax, Math.Max(xDelta, yDelta)); + sumDepthMax = Math.Max(sumDepthMax, deltaSum); + seedTiles.Add(tile); + } + } + + // Step 3, Diagonal shadow loop + for (var d = 0; d < maxDepthMax; d++) + { + foreach (var tile in seedTiles) + { + var maxDelta = GetMaxDelta(tile, eyePos); + + if (maxDelta == d + 1 && CheckNeighborsVis(vis2, tile, d)) + { + vis2[tile] = (opaque.Contains(tile) ? -1 : d + 1); + } + } + } + + // Step 4, Straight shadow loop + for (var d = 0; d < sumDepthMax; d++) + { + foreach (var tile in seedTiles) + { + var sumDelta = GetSumDelta(tile, eyePos); + + if (sumDelta == d + 1 && CheckNeighborsVis(vis1, tile, d)) + { + if (opaque.Contains(tile)) + { + vis1[tile] = -1; + } + else if (vis2.GetValueOrDefault(tile) != 0) + { + vis1[tile] = d + 1; + } + } + } + } + + // Add the eye itself + vis1[eyePos] = 1; + + // Step 6. + + // Step 7. - /* - * TODO: - * Wall shadows still fucked - * Need to draw it over the wall layer or whatever, can't just turn FOV off because that will break stuff - */ + // Step 8. + foreach (var tile in seedTiles) + { + vis2[tile] = vis1.GetValueOrDefault(tile, 0); + } - // Stencil - //worldHandle.SetTransform(Matrix3x2.Identity); + // Step 9 + foreach (var tile in seedTiles) + { + if (!opaque.Contains(tile)) + continue; - var worldAabb = args.WorldAABB; - var worldBounds = args.WorldBounds; + var tileVis1 = vis1.GetValueOrDefault(tile); - // Use the lighting as a mask - worldHandle.SetTransform(Matrix3x2.Identity); - worldHandle.UseShader(_proto.Index("StencilMask").Instance()); - worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + if (tileVis1 != 0) + continue; - // Draw the static - worldHandle.UseShader(_proto.Index("StencilDraw").Instance()); - worldHandle.DrawTextureRect(Texture.White, worldBounds); + if (IsCorner(seedTiles, opaque, vis1, tile, Vector2i.One) || + IsCorner(seedTiles, opaque, vis1, tile, new Vector2i(1, -1)) || + IsCorner(seedTiles, opaque, vis1, tile, new Vector2i(-1, -1)) || + IsCorner(seedTiles, opaque, vis1, tile, new Vector2i(-1, 1))) + { + boundary.Add(tile); + } + } - worldHandle.SetTransform(Matrix3x2.Identity); - worldHandle.UseShader(null); + // Make all wall/corner tiles visible + foreach (var tile in boundary) + { + vis1[tile] = -1; + } + + // vis2 is what we care about for LOS. + foreach (var tile in seedTiles) + { + var tileVis2 = vis2.GetValueOrDefault(tile, 0); + + if (tileVis2 != 0) + visibleTiles.Add(tile); + } + } } - // Will be a frame out of sync but makes sure we don't have issues with data not being ready after lighting. - if (_blep?.Texture.Size != args.Viewport.Size) + // TODO: Combine tiles into viewer draw-calls + + // Draw visible tiles to stencil + worldHandle.RenderInRenderTarget(_blep!, () => { - _staticTexture?.Dispose(); - _blep?.Dispose(); - _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); - _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, - new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), - name: "station-ai-static"); + if (!gridUid.IsValid()) + return; + + var matrix = xforms.GetWorldMatrix(gridUid); + var matty = Matrix3x2.Multiply(matrix, invMatrix); + worldHandle.SetTransform(matty); + + foreach (var tile in visibleTiles) + { + var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); + worldHandle.DrawRect(aabb, Color.White); + } + }, + Color.Transparent); + + // Create static texture + worldHandle.RenderInRenderTarget(_staticTexture!, + () => + { + worldHandle.SetTransform(invMatrix); + worldHandle.UseShader(_proto.Index("CameraStatic").Instance()); + worldHandle.DrawRect(worldAabb, Color.White); + }, Color.Transparent); + + // Use the lighting as a mask + worldHandle.UseShader(_proto.Index("StencilMask").Instance()); + worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + + // Draw the static + worldHandle.UseShader(_proto.Index("StencilDraw").Instance()); + worldHandle.DrawTextureRect(_staticTexture!.Texture, worldBounds); + + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(null); + + } + + private int GetMaxDelta(Vector2i tile, Vector2i center) + { + var delta = tile - center; + return Math.Max(Math.Abs(delta.X), Math.Abs(delta.Y)); + } + + private int GetSumDelta(Vector2i tile, Vector2i center) + { + var delta = tile - center; + return Math.Abs(delta.X) + Math.Abs(delta.Y); + } + + /// + /// Checks if any of a tile's neighbors are visible. + /// + private bool CheckNeighborsVis( + Dictionary vis, + Vector2i index, + int d) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x == 0 && y == 0) + continue; + + var neighbor = index + new Vector2i(x, y); + var neighborD = vis.GetValueOrDefault(neighbor); + + if (neighborD == d) + return true; + } } + return false; + } + + /// + /// Checks whether this tile fits the definition of a "corner" + /// + private bool IsCorner( + HashSet tiles, + HashSet blocked, + Dictionary vis1, + Vector2i index, + Vector2i delta) + { + var diagonalIndex = index + delta; + + if (!tiles.TryGetValue(diagonalIndex, out var diagonal)) + return false; + + var cardinal1 = new Vector2i(index.X, diagonal.Y); + var cardinal2 = new Vector2i(diagonal.X, index.Y); + + return vis1.GetValueOrDefault(diagonal) != 0 && + vis1.GetValueOrDefault(cardinal1) != 0 && + vis1.GetValueOrDefault(cardinal2) != 0 && + blocked.Contains(cardinal1) && + blocked.Contains(cardinal2) && + !blocked.Contains(diagonal); } } diff --git a/Content.Client/StationAi/StationAiSystem.cs b/Content.Client/StationAi/StationAiSystem.cs index ecee86d9b9b7ee..2192d99de2d874 100644 --- a/Content.Client/StationAi/StationAiSystem.cs +++ b/Content.Client/StationAi/StationAiSystem.cs @@ -16,12 +16,9 @@ public sealed class StationAiSystem : EntitySystem public bool Enabled => _overlay != null; - private HashSet> _entities = new(); - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnLightingPass); _overlay = new StationAiOverlay(); _overlayMgr.AddOverlay(_overlay); } @@ -31,35 +28,4 @@ public override void Shutdown() base.Shutdown(); _overlayMgr.RemoveOverlay(); } - - private void OnLightingPass(ref LightingPassEvent ev) - { - _entities.Clear(); - - if (_overlay == null || _overlay._blep == null) - return; - - var pass = new LightingPass() - { - Target = _overlay._blep, - }; - - _lookup.GetEntitiesIntersecting(ev.MapId, ev.WorldAabb.Enlarged(10f), _entities); - - foreach (var ent in _entities) - { - var (worldPos, worldRot) = _xforms.GetWorldPositionRotation(ent); - - pass.Lights.Add(new RenderLight() - { - Color = Color.White, - Radius = 10f, - Energy = 1f, - Position = worldPos, - Rotation = worldRot, - Softness = 1f, - }); - } - ev.Add(pass); - } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs index acffd057fad0db..bb2c1cbbbfc6ea 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenExterior.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Content.Server.NPC.Pathfinding; using Content.Shared.Maps; +using Content.Shared.NPC; using Content.Shared.Procedural; using Content.Shared.Procedural.DungeonGenerators; using Robust.Shared.Collections; @@ -29,7 +30,7 @@ private async Task> GenerateExteriorDungen(Vector2i position, Exte var pathfinder = _entManager.System(); // Gridcast - pathfinder.GridCast(startTile, position, tile => + SharedPathfindingSystem.GridCast(startTile, position, tile => { if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) || tileRef.Tile.IsSpace(_tileDefManager)) diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs b/Content.Shared/NPC/SharedPathfindingSystem.Line.cs similarity index 92% rename from Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs rename to Content.Shared/NPC/SharedPathfindingSystem.Line.cs index 479d5ad77f6b12..500ed157436998 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Line.cs +++ b/Content.Shared/NPC/SharedPathfindingSystem.Line.cs @@ -1,8 +1,8 @@ -namespace Content.Server.NPC.Pathfinding; +namespace Content.Shared.NPC; -public sealed partial class PathfindingSystem +public abstract partial class SharedPathfindingSystem { - public void GridCast(Vector2i start, Vector2i end, Vector2iCallback callback) + public static 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 diff --git a/Content.Shared/NPC/SharedPathfindingSystem.cs b/Content.Shared/NPC/SharedPathfindingSystem.cs index 8831acc1ddb733..0be5f69755622f 100644 --- a/Content.Shared/NPC/SharedPathfindingSystem.cs +++ b/Content.Shared/NPC/SharedPathfindingSystem.cs @@ -2,7 +2,7 @@ namespace Content.Shared.NPC; -public abstract class SharedPathfindingSystem : EntitySystem +public abstract partial class SharedPathfindingSystem : EntitySystem { /// /// This is equivalent to agent radii for navmeshes. In our case it's preferable that things are cleanly @@ -37,4 +37,31 @@ public static float OctileDistance(Vector2i start, Vector2i end) var ab = Vector2.Abs(diff); return ab.X + ab.Y + (1.41f - 2) * Math.Min(ab.X, ab.Y); } + + public static IEnumerable GetTileOutline(Vector2i center, float radius) + { + // https://www.redblobgames.com/grids/circle-drawing/ + var vecCircle = center + Vector2.One / 2f; + + for (var r = 0; r <= Math.Floor(radius * MathF.Sqrt(0.5f)); r++) + { + var d = MathF.Floor(MathF.Sqrt(radius * radius - r * r)); + + yield return new Vector2(vecCircle.X - d, vecCircle.Y + r).Floored(); + + yield return new Vector2(vecCircle.X + d, vecCircle.Y + r).Floored(); + + yield return new Vector2(vecCircle.X - d, vecCircle.Y - r).Floored(); + + yield return new Vector2(vecCircle.X + d, vecCircle.Y - r).Floored(); + + yield return new Vector2(vecCircle.X + r, vecCircle.Y - d).Floored(); + + yield return new Vector2(vecCircle.X + r, vecCircle.Y + d).Floored(); + + yield return new Vector2(vecCircle.X - r, vecCircle.Y - d).Floored(); + + yield return new Vector2(vecCircle.X - r, vecCircle.Y + d).Floored(); + } + } } From 0bb9a18f8076a7e3707c511c58c2c7a53326e4e7 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 14 Aug 2024 22:44:57 +1000 Subject: [PATCH 03/40] Bunch of ports --- Content.Client/StationAi/StationAiOverlay.cs | 112 +++++++--- .../Silicons/StationAi/StationAiComponent.cs | 26 +++ .../StationAi/StationAiVisionComponent.cs | 19 ++ Resources/Locale/en-US/job/job-names.ftl | 1 + Resources/Prototypes/Datasets/Names/ai.yml | 2 +- .../Entities/Mobs/Player/silicon.yml | 13 ++ .../Prototypes/Roles/Jobs/Science/borg.yml | 14 ++ .../Prototypes/Roles/Jobs/departments.yml | 10 +- Resources/Prototypes/StatusIcon/job.yml | 8 + .../Misc/job_icons.rsi/StationAi.png | Bin 0 -> 204 bytes .../Interface/Misc/job_icons.rsi/meta.json | 5 +- .../Textures/Interface/noise.rsi/meta.json | 58 ++++++ .../Textures/Interface/noise.rsi/noise.png | Bin 0 -> 45602 bytes .../Mobs/Silicon/station_ai.rsi/ai-banned.png | Bin 0 -> 1554 bytes .../Silicon/station_ai.rsi/ai-banned_dead.png | Bin 0 -> 426 bytes .../Mobs/Silicon/station_ai.rsi/ai-empty.png | Bin 0 -> 437 bytes .../Silicon/station_ai.rsi/ai-holo-old.png | Bin 0 -> 6565 bytes .../Mobs/Silicon/station_ai.rsi/ai.png | Bin 0 -> 5879 bytes .../Mobs/Silicon/station_ai.rsi/ai_dead.png | Bin 0 -> 451 bytes .../Mobs/Silicon/station_ai.rsi/default.png | Bin 0 -> 2082 bytes .../Silicon/station_ai.rsi/floating_face.png | Bin 0 -> 785 bytes .../Mobs/Silicon/station_ai.rsi/horror.png | Bin 0 -> 1490 bytes .../Mobs/Silicon/station_ai.rsi/meta.json | 192 ++++++++++++++++++ .../Silicon/station_ai.rsi/xeno_queen.png | Bin 0 -> 2496 bytes 24 files changed, 425 insertions(+), 35 deletions(-) create mode 100644 Content.Shared/Silicons/StationAi/StationAiComponent.cs create mode 100644 Content.Shared/StationAi/StationAiVisionComponent.cs create mode 100644 Resources/Textures/Interface/Misc/job_icons.rsi/StationAi.png create mode 100644 Resources/Textures/Interface/noise.rsi/meta.json create mode 100644 Resources/Textures/Interface/noise.rsi/noise.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-banned.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-banned_dead.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-empty.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-holo-old.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_dead.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/default.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/floating_face.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/horror.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/xeno_queen.png diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/StationAi/StationAiOverlay.cs index 856dfb2537b1a7..ed466357cbe78f 100644 --- a/Content.Client/StationAi/StationAiOverlay.cs +++ b/Content.Client/StationAi/StationAiOverlay.cs @@ -1,12 +1,13 @@ using System.Numerics; using Content.Client.SurveillanceCamera; -using Content.Shared.NPC; +using Content.Shared.StationAi; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Shared.Enums; using Robust.Shared.Map; -using Robust.Shared.Physics; using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; namespace Content.Client.StationAi; @@ -19,10 +20,10 @@ public sealed class StationAiOverlay : Overlay public override OverlaySpace Space => OverlaySpace.WorldSpace; - private HashSet> _seeds = new(); + private HashSet> _seeds = new(); private IRenderTexture? _staticTexture; - public IRenderTexture? _blep; + public IRenderTexture? _stencilTexture; public StationAiOverlay() { @@ -31,11 +32,13 @@ public StationAiOverlay() protected override void Draw(in OverlayDrawArgs args) { - if (_blep?.Texture.Size != args.Viewport.Size) + return; + + if (_stencilTexture?.Texture.Size != args.Viewport.Size) { _staticTexture?.Dispose(); - _blep?.Dispose(); - _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); + _stencilTexture?.Dispose(); + _stencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-static"); @@ -99,6 +102,15 @@ protected override void Draw(in OverlayDrawArgs args) foreach (var seed in _seeds) { + if (!seed.Comp.Enabled) + continue; + + // TODO: Iterate tiles direct. + if (!seed.Comp.Occluded) + { + + } + var range = 7.5f; boundary.Clear(); seedTiles.Clear(); @@ -210,40 +222,76 @@ protected override void Draw(in OverlayDrawArgs args) visibleTiles.Add(tile); } } - } - // TODO: Combine tiles into viewer draw-calls + // TODO: Combine tiles into viewer draw-calls + var gridMatrix = xforms.GetWorldMatrix(gridUid); + var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); - // Draw visible tiles to stencil - worldHandle.RenderInRenderTarget(_blep!, () => - { - if (!gridUid.IsValid()) - return; + // Draw visible tiles to stencil + worldHandle.RenderInRenderTarget(_stencilTexture!, () => + { + if (!gridUid.IsValid()) + return; - var matrix = xforms.GetWorldMatrix(gridUid); - var matty = Matrix3x2.Multiply(matrix, invMatrix); - worldHandle.SetTransform(matty); + worldHandle.SetTransform(matty); - foreach (var tile in visibleTiles) - { - var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); - worldHandle.DrawRect(aabb, Color.White); - } - }, - Color.Transparent); + foreach (var tile in visibleTiles) + { + var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); + worldHandle.DrawRect(aabb, Color.White); + } + }, + Color.Transparent); + + // Create static texture + var curTime = IoCManager.Resolve().RealTime; - // Create static texture - worldHandle.RenderInRenderTarget(_staticTexture!, - () => + var noiseTexture = _entManager.System() + .GetFrame(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/noise.rsi"), "noise"), curTime); + + // Once this is gucci optimise rendering. + worldHandle.RenderInRenderTarget(_staticTexture!, + () => + { + // TODO: Handle properly + if (!gridUid.IsValid()) + return; + + worldHandle.SetTransform(matty); + + tileEnumerator = maps.GetTilesEnumerator(gridUid, grid!, worldBounds, ignoreEmpty: false); + + while (tileEnumerator.MoveNext(out var tileRef)) + { + if (visibleTiles.Contains(tileRef.GridIndices)) + continue; + + var bounds = lookups.GetLocalBounds(tileRef, grid!.TileSize); + worldHandle.DrawTextureRect(noiseTexture, bounds, Color.White.WithAlpha(80)); + } + + }, + Color.Black); + } + // Not on a grid + else { - worldHandle.SetTransform(invMatrix); - worldHandle.UseShader(_proto.Index("CameraStatic").Instance()); - worldHandle.DrawRect(worldAabb, Color.White); - }, Color.Transparent); + worldHandle.RenderInRenderTarget(_stencilTexture!, () => + { + }, + Color.Transparent); + + worldHandle.RenderInRenderTarget(_staticTexture!, + () => + { + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.DrawRect(worldBounds, Color.Black); + }, Color.Black); + } // Use the lighting as a mask worldHandle.UseShader(_proto.Index("StencilMask").Instance()); - worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds); // Draw the static worldHandle.UseShader(_proto.Index("StencilDraw").Instance()); diff --git a/Content.Shared/Silicons/StationAi/StationAiComponent.cs b/Content.Shared/Silicons/StationAi/StationAiComponent.cs new file mode 100644 index 00000000000000..ae60d1b9ab5963 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates this entity can interact with station equipment and is a "Station AI". +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StationAiComponent : Component +{ + /* + * I couldn't think of any other reason you'd want to split these out. + */ + + /// + /// Can it move its camera around and interact remotely with things. + /// + [DataField, AutoNetworkedField] + public bool Remote = true; + + /// + /// The invisible eye entity being used to look around. + /// + [DataField, AutoNetworkedField] + public EntityUid? RemoteEntity; +} diff --git a/Content.Shared/StationAi/StationAiVisionComponent.cs b/Content.Shared/StationAi/StationAiVisionComponent.cs new file mode 100644 index 00000000000000..3fd82fe2017036 --- /dev/null +++ b/Content.Shared/StationAi/StationAiVisionComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.StationAi; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class StationAiVisionComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Enabled = true; + + [DataField, AutoNetworkedField] + public bool Occluded = false; + + /// + /// Range in tiles + /// + [DataField, AutoNetworkedField] + public float Range = 7.5f; +} diff --git a/Resources/Locale/en-US/job/job-names.ftl b/Resources/Locale/en-US/job/job-names.ftl index 33e501cb690875..f5cdafa672a83b 100644 --- a/Resources/Locale/en-US/job/job-names.ftl +++ b/Resources/Locale/en-US/job/job-names.ftl @@ -60,6 +60,7 @@ job-name-virologist = Virologist job-name-zombie = Zombie # Role timers - Make these alphabetical or I cut you +JobStationAi = Station AI JobAtmosphericTechnician = Atmospheric Technician JobBartender = Bartender JobBorg = Borg diff --git a/Resources/Prototypes/Datasets/Names/ai.yml b/Resources/Prototypes/Datasets/Names/ai.yml index 702adc8688cb6f..af97dc9efb8320 100644 --- a/Resources/Prototypes/Datasets/Names/ai.yml +++ b/Resources/Prototypes/Datasets/Names/ai.yml @@ -2,7 +2,7 @@ id: names_ai values: - 16-20 - - 790 + - "790" - Adaptive Manipulator - ALICE - Allied Mastercomputer diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 0f8998bdec81ab..f2645fa4f11482 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -1,3 +1,16 @@ +- type: entity + id: PlayerStationAi + parent: BaseStructureDynamic + components: + - type: Sprite + sprite: Mobs/Silicon/station_ai.rsi + layers: + - + - type: StationAi + - type: RandomMetadata + # TODO: Loadout name support + nameSegments: [names_ai] + - type: entity id: PlayerBorgGeneric parent: BorgChassisGeneric diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index fe829110051bef..ad5e94904ad64a 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -1,3 +1,17 @@ +# No idea why it's in sci but we ball. +- type: job + id: StationAi + name: job-name-station-ai + description: job-description-station-ai + playTimeTracker: JobStationAi + requirements: + - !type:OverallPlaytimeRequirement + time: 216000 #60 hrs + canBeAntag: false + icon: JobIconStationAi + supervisors: job-supervisors-rd + jobEntity: PlayerStationAi + - type: job id: Borg name: job-name-borg diff --git a/Resources/Prototypes/Roles/Jobs/departments.yml b/Resources/Prototypes/Roles/Jobs/departments.yml index 6d25d8fd881ce7..6178ff89a2a9d6 100644 --- a/Resources/Prototypes/Roles/Jobs/departments.yml +++ b/Resources/Prototypes/Roles/Jobs/departments.yml @@ -16,7 +16,6 @@ weight: -10 roles: - Bartender - - Borg - Botanist - Boxer - Chaplain @@ -98,6 +97,15 @@ - Scientist - ResearchAssistant +- type: department + id: Silicon + name: department-Silicon + description: department-Silicon-description + color: "#D381C9" + roles: + - Borg + - StationAi + - type: department id: Specific name: department-Specific diff --git a/Resources/Prototypes/StatusIcon/job.yml b/Resources/Prototypes/StatusIcon/job.yml index eb9896fc480613..0abb86aeab8f29 100644 --- a/Resources/Prototypes/StatusIcon/job.yml +++ b/Resources/Prototypes/StatusIcon/job.yml @@ -29,6 +29,14 @@ state: Borg jobName: job-name-borg +- type: jobIcon + parent: JobIcon + id: JobIconStationAi + icon: + sprite: /Textures/Interface/Misc/job_icons.rsi + state: StationAi + jobName: job-name-station-ai + - type: jobIcon parent: JobIcon id: JobIconBotanist diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/StationAi.png b/Resources/Textures/Interface/Misc/job_icons.rsi/StationAi.png new file mode 100644 index 0000000000000000000000000000000000000000..aba35c034b9f1508e953a030300987dcf1265f1e GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85qP=L734qNaX`iFwxV+F+?Lc`42yfUun1Gg@gN+MQ|M6Sa$LM ziP^uEd4~`UkiU=&WgIWMue|ZFBN9!-0c9%NRUe{an^LB{Ts5`?Wy_ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index fff9f78288cfbd..7b09250bd986b6 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/master/icons/mob/huds/hud.dmi | Admin recolored from MedicalIntern by TsjipTsjip", + "copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/ce6beb8a4d61235d9a597a7126c407160ed674ea/icons/mob/huds/hud.dmi | Admin recolored from MedicalIntern by TsjipTsjip", "size": { "x": 8, @@ -177,6 +177,9 @@ [1.0,1.0] ] }, + { + "name": "StationAi" + }, { "name": "Syndicate" }, diff --git a/Resources/Textures/Interface/noise.rsi/meta.json b/Resources/Textures/Interface/noise.rsi/meta.json new file mode 100644 index 00000000000000..068ecab968eefa --- /dev/null +++ b/Resources/Textures/Interface/noise.rsi/meta.json @@ -0,0 +1,58 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/4b4e9dff1d7d891cfb75d25ca5bf5172d1c02be6/icons/hud/screen_gen.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "noise", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/noise.rsi/noise.png b/Resources/Textures/Interface/noise.rsi/noise.png new file mode 100644 index 0000000000000000000000000000000000000000..ba74952b409b6d3673c9949e6a81c0a84ecd472c GIT binary patch literal 45602 zcmW(+1z3~s7hS*@FdAi}k?zq5NDD}p0us_8-5}wJ(J3tgLlHzkx}b`g9J@?%6-9>9_DiaY<6951JqGu|Ky7%Yy`(qgjxxatz<{)r?!go_K z@&o{gyZ<|YY23s#06^vBGetRlzYpubj_XC|#<7Pv1y(=%DSn>a-@38tPCx6MC9`Jh zoJYGyt^>~kTJ28xFA2ZTNbG*_T>3@x+`#mCljxhvzi;mXmS=r$Hf8P*ky7hSy%}x) zHrzj~(c00TH}ukx1Z^qw#=CfZa(|cd!TR6st2^q!s<LO>FQi>liz|Fh_;(O*Uf6vf#bXS`-es&T5qp@xGx^(`UYNfG0@BWTOsKsa&Zeg zjFkCjA$4~-%240*YY*GBc>2v9&23^)w@Df)-tD(3x%8MXU~X_^^zGgCQsS=3ro`HR z!HFLwCrT|ICiuqbeBVOK1}-9_&lf5w};ql~s8!5Y^*r(Uh#qZId# z6gl=%J35Kf|9wDFoax=aO_^SH>BDIEQY$*g=KHtO_e!5t+BI%UZ9ewh(1OU^9wrt! z(Vt-OhL8R=uMoLr-ZY-ombaAD|M$8zE`!E_K+Ru4`?sTSZ;#jh*vVX9Trb^8(_i)} zC*Vmf1J`(6{RjWFZ4!$wqx{QnO?PxNsQK>$#S*#tbyGza z-(EK?Z19QQf9qRylYjfZZ8W{&Sb@TY)X%R5B$qBn#DWjP7}QMz|E)3P<^9M~`Q-op0n}fv{;Ld(1P}_UoXu)GV-uF`(8zJ z&s4Q$TaU9ly(X79bvAC>MuYZ+ZtoMAc)#koAL0Q0Pr!8!<;Mg1TmSuFhvDo}77M4} zd~*gG$IyJje108-3HNwfSu;ETK0_&$gnPTwaPpb|$y+e}nx~Fn*P|MtoNe_rfkgiy z@xy2>DWIHRtK_hQrcmxZ91;mlmO<6=^TC}Ixo)&)6_Iq2k_#)gZ6|3KbSqHh%z+o| z`4k`PZdR$cVAtZt3=LpU*4a`z zqeU+eJ^W z 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

vl0gv6N8x+z5N%Q37{ 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 F{}(0Gc*g($ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-banned.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-banned.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ef043cc8a8c8da50656e432e001c65fc729b54 GIT binary patch literal 1554 zcmah}drXs86mNO7T9C3y1qD}#Q9-J-qD&ON^74UVVTGY&5GtskglDnPmC{mhxQTS} z0SE{*h^SitA5fsu!T?ED5M#}X2$shX5TS(@`}MsKbW65m@sIO6_uQMD$M2kTe+(lX zUWeL*g27|nEJN+O$+$D^cnVqRpJo!`~v;@WD_S2KXg$giW+#QkzzFxkH^z6d9N_-AUA z93ya9OJ6Ep|Ek!~GMvIUS+g&lfAq=$hWXaeH$7b+luM_dQ@yidERi&bhg{ZnV9<{Wi>%CyUK7pm-U&RI9U; zOG|N8)zu=y3z}fBn@t4K*Y{>&Vc}%DtA0xBE?u8QzwZySSq5V_YN+23GrXjwF(~18 zW#{IzD!9aLF1aPdfhEY*_qESId@-`2S6`T&lcV>t1i|<(ClWLg9P13MU|;=M1W1B| zQh64s>z?A4nW9lPXlG3><1BhwPRaU(D@lFc(`fkp#F=ptAk)6CG1UX-XGx0HD^eAWJ zZbKiYsbr9T>ft<~pVDyMDe9w|wyxMarY1A5zWj(W4{2<3XfvYBQD}v5Q^EaFOh3H} zzUZiBC#V(`5@b(oc{Sjidv6tF0<=Tzy4`i5(@Q$9h_|XN!H(r*{dyhy_)fP12j>0? zQB*7MM%Z|&tdJOh#s49t@tEy^X()YQO3vt>K)*L9X)LU}q}Au63HtD-ksQe3kbZ=T zJN+8}n46$f8;X;nsbKOGp|D)1W9SK5_Iy_F$vD+)yHO}A9ZU7v2g73AVC&nj0-3~ z2ViY)KS%b2Vr@k{boL=Eng|hDr;U+tqNYTrwW(kHt>|n~fM1 zJjR|B+IDx7No%;KV+V>PZ*Kr!86%i}(u?8&#LQ-<57d!U!P%i*LS`$?<2Ls?bHARh zK~4rWc#mEX&8f-iS;h1uwvrRWAOz-9eHW~Z7jzvHK@FpFAbQDLn+Ylv+y!$Q-)h+W zak9q{;_AcDLZXLhEN^BZ`E^RmpuuG zU7l4W3?tv?K5hpx1h{(;MG~MUNuGFV%L5&1UjVcWVS~b z!;XHp620!*q`0n!^&f;^`8&f=j?9Q29S4 z@M(nfv=aE71=J!FyU&1&1qqI+zLE*CccLqB>^P2(4VY^-*JuWUF3VBe=f>pD0PR$K zBMsp1F%-BxhmeGS7jQm64bBJ54qtFS!1(~@1Aoc~Z1HkFU<=+E@#a*8OTZ=Ie1P)- f&IdRj;C$d4kggP(K_^b500000NkvXXu0mjf2l=~P literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-holo-old.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-holo-old.png new file mode 100644 index 0000000000000000000000000000000000000000..3297549f2ae41ec0f9a4b98830ac69b0601e5c52 GIT binary patch literal 6565 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f37c65f2d3e124f5f11cfa628861fed0c7a587 GIT binary patch literal 5879 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/default.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/default.png new file mode 100644 index 0000000000000000000000000000000000000000..d52aceaf5b13f21bb46feabc481fb2d1e7fb0026 GIT binary patch literal 2082 zcmV+-2;KLIP)Tc-8*xCD(aUsw|DN{`DVU3=gi!hQ%?VJ{4ap^FSmY?qf;-HIoBu=Ah)^z zAT9iVx|kM&8bh5MeG6U-O=a z2+ow?z(6KVlrm`9@At%Yc1hC=8tq-_JU7I>F4SVc*SBR58mLHE zqrYsnyy6}r(2m==Fs*?3YlZj?3#~wmE0Lki?^ytlFIi*I!8W49n~CnkgbUmx*?{I$ zjCP+kXkV-KUKk>v-VG}ZDfpMyL>ol>A;CS@BswVWw>23uHjyH~Cjpq8Ob}U}2k~4G zn$|taWf|TJSOkZk?9ST*&?5!_BH(`aX)zGux#P}oJKbYj0E>aq z`Sb$OCocvk0`ng@FFLoFq?PUYe3bUSTJZ+S8;Di;+u zS!G{!_tTaIar)?6>k5Dz@9jKch${t#x6cx-OCr!&F1T$~r4+q~Eb&{#y(|}P+f<*H z?zpMmTisI%0QJxOX2_8r5Tn~TuUB+3Ni%0zoyO>Ip|})d5fyidLQM%W>z~1*8bnPscVs zBHwxa>`-oHqZpbj`Nq;EgQ8=mGsmM-on(o?7SL&n<9=slW6TqONdeAvBxu9aCUM2J zskjq`IS5)&I8N(bisN^12fygNT0(6)-g&Ab_{?8;J$TL{)l_!FDXEEH96#5 zhCsBtZ6f;OUPfg5y#d$n{32FGG-F0CkN?B1l~Q;f0Nle09DcDb@ycIPfYtL&x?gKj zZH3|Z*#2Wgvl{w^`f(W#_8*H&zXvcvePdbiJ8?5czOnqNKFaltiF%)q{IEVucWn0H zgFB!cfe-fsSyQ@hATD4e5N%>k=Ye-$f$zno`X8*0Pt3_=HEO?210Tl%UWag?G{BEGlTR0bKe6fY{~$csY0~QEm<&K5zas8imd2<(^Y`EQ>3lK% zv>08iJl(SZ+Hpmc*OEGaNed`e39RD!J4mFCaAy}}$TXnhq$0`L{`H!MtQ)%Cy~j9j{r zowQuX1(gL@(J)SCfAR7lmcCI#S%B|;O4xJmgbyxGChdht_%R@8ex)5DT!)5=Kj-o= zcZ9;=_q-#7?Jz8XvBoYIgsQ8Pbn+KNt`dV50rjD70Q^{twYws|TndYFen*Ahvjt#^ zb2d4~^Pkm^4^zEue%C|V9Cm-%wRhz|!>+K`4Uj^35@0Gd!#)cLoBx5E27`t_0saL! z0(t^WcaA_|Zh(&*0e=Y+#h!&NLcsjN$ABD%;(9px&Rz`1^N;(rx<7SBR}4w=R&jq2z%~KHZMQG-G=CL zglrL7jRdvYjgh;w_lGEKTo4?!nc4Dx2mu`BHfg|D3~>MNKA8y5QW#(n+)Ggers?<=xRtP% z;u(Dl^t~k(RbIek++_NzLt6kolP{Jm5fwODy6~t@1->jmp2;IQsbHHb3dnRgVfMGi zV&n(D1i(i~R9PWYwE-C`y3iH?XYx1x?5BSM&=z;TfC_vEK{myx!C9iz?Qal#1wpa+ zv=<>Y&MwW~XUamI4teVfpT*qyX$gWdC!lILuPh2nV2-?1-#BUGFWo9+qc@+RasU7T M07*qoM6N<$g4CVXe*gdg literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/floating_face.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/floating_face.png new file mode 100644 index 0000000000000000000000000000000000000000..e549c21cb34eef034ad256555f4b67d978eaeb6a GIT binary patch literal 785 zcmV+s1Md8ZP)Rn7v*7_69^yGZmeQJP;?@X8l-27S@ zK$WRuG2MYoVH4Tm`~E#5r$*2(Q*dzFD-Xw0Ba(uXKT?3=$3-|brEjnL4u@xCbzWa{ z0PteNW%PU!aPw!uY_veTk9Q@h>sIjPK;qezEIMaSP zSaMP-Qg0AQebfI2x%gvc09E?_=7^iUok7p78~)@&s5*Aszb51!Ji+RtewO_J4*u91 zB2;+}aQm)ZFPnL39Oj=rWqWxp{))E5h$Qbdf_g%Mv*$VaE8Y`SJrTaf4#E_FqD|4A zerXPV*%gN65CLF0L;zS05dfA$1c2ob0pdO&{s8d@L__=mOb~y7_yfcrApQXH2Z%pF z`~gP!#UG&C2=NDqKS2Be;tvpi0K^{^p{Sz3Ar`K2%!w&5M@7ML$BC$U9!(s}1lH|+m9;vX zKj_J>4vmYZHwyBwa4SbeM9gX}n9lrO>4io9y?tk9-#q#5Sy?{&r@wX_p7-`t z?%)3K@!sv5f7bgtUy?e_&^>#;TKK)28|2O7aw2$}?Cs`@I>a#Zet4E%ueNh{nDFMj zU$u&VU(I>EQf%qMlnCR?xmCtRe|Ge1Qb?|)f$633#o0jLO zm*?L;`0lJVteyOq-m)Z|keSN4N9dZmy}mK4~lOY*X*JisanY{~eh3`_!XtM?nPa{xBYlLP;k=jHKlLgZu#5&VqM;q?JwM}znfxXb@JG@Vm^}(uOCk5 z*)Z$K{fOlP z^~K`(4(YopML$(jH+0o9{rQq($*q3n@FjW1I@v$-7@IzSI(&5I;wo>Cy1P}Ud-uQN zS1gPcjEh_K$@7?BOT2jffyD{$CzLeg6y?Q#(mSO-UEXO816OTrbiuEn#7b$sAD18W ze@MKLn7pl4xsdr+CR8*#>zJD(NFQWY1tlaex_r70au~TjS zZ}Ri(BI84Hfp_Nraq#vmSn&FJ!&=M#(G9z+o#)s6NK~$CdmCEU##?{u&pj>yJ4Y_r zc1?>97R3vG-}FzKap%TrQE$ONyMCO#BkA~jZa}i`mzd}CLyq$8xnVK4>o4a&lb@}| z2a>+c`)s{B>_<=KjYPE_YhJxQb;yD3&&$uVKR$5ZJvZ+2f_BlHU4{C4)A#<^wy6HS z)~Wu;lqbT?@%;N|+BbA84E=B;u>JirKb~k0p7llXHfaSJ63lh6f70H~JIC;S%6ZPk z855)r2Z{07eR%!I{IC9MahW|f8w35;r5^o%n{B^d-Fu!3l8Fsk9u3pg4NtWssR7-a z{3rUyWF7CxB7ZzTtmGEBF2UAu_q$5^lQB_{E=ZX6@+yR8VO5^x>nLQ-^y$6`VS}U6bGZ)MnG4Q`)7UJ`^+837(fbrG2~R z`MdzRyeaAD!DM_MRL4=EvhL5CK$C*y^v*4}iJ9YHxx->U!bmg)dJZu#4M3~8nIX1o zH|#KI*ul{u@O(2No;a*72DWXp*pi;yHB%r#b!(Ef(_;MxrWM4s;Jp1JperrGv;l buj?l~7x?r#)?fj!kY@07^>bP0l+XkK4nV{^ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json new file mode 100644 index 00000000000000..4878a403ee601e --- /dev/null +++ b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json @@ -0,0 +1,192 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/2a19963297f91efb452dbb5c1d4eb28a14776b0a/icons/mob/silicon/ai.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "ai", + "delays": [ + [ + 0.2, + 0.2, + 0.1, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.1 + ] + ] + }, + { + "name": "ai_dead" + }, + { + "name": "ai-empty", + "delays": [ + [ + 0.7, + 0.7 + ] + ] + }, + { + "name": "ai-holo-old", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "default", + "directions": 4 + }, + { + "name": "horror", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "xeno_queen", + "directions": 4 + }, + { + "name": "floating_face", + "directions": 4 + }, + { + "name": "ai-banned", + "delays": [ + [ + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7 + ] + ] + }, + { + "name": "ai-banned_dead", + "delays": [ + [ + 0.7, + 0.7 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/xeno_queen.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/xeno_queen.png new file mode 100644 index 0000000000000000000000000000000000000000..1bce0bd99f27960431b1f8fec07834456d0b3300 GIT binary patch literal 2496 zcmV;x2|xCUP)JPg7T5zE-4ArfDM+Iih*>_d;iSbd4J~5 zo0&gx(F0-ryqWjzyLaxr@4kCq2IYT_TmrcI%kqxCzy9&aKvJ7jgevHJ3jl=1CR$!? zRS2Of`d$LSpr?+vWG(nTJtJ~$%C_HKZOK^Y9k$~=>9Y_>|H{1$bN}XLT`r%G=zo1% zWbZ-y?8bOL`h|SJx4}fd{IDLo7pY6t&@{m`uMkE^|8>4Gy#!#S+4&0i%F{EJ*yg5v z{_@YdX&(-VFFao9yC2Z>uRPcYHGs@h18$7hbJI^1fOakzP)}lLDEjKS4h_}2n0Nc< zWjc*MRD5lpWf~uVLKuEsFGN380CUeRqZpiyg_F2^z7-I*TEMTduox!}=fp`CK^pzl zUtjDzS#Fqi_#JA%v5|F|DL_9}085t!29A8Qvt#YO6-xB_?4sPBnC#eH1hn%V5J$hw z&AJ(R?SIxc>g#q#O!_k9_} z;H$ylaDs3)pN&Toz&+Y$pRSwtIH96Qa$sO8CIp9uWBSJ3?fHytUd}Tbd99=iWL`?vi z0l+Jt8(lFG+L>lIBeDVc+(m7{?Vdngaa{W8jOfc2r)|hrsaxh~d}q zFwLKw31=Dt8 zWbX;8Za?8NqqR>9NYh8S+-znq$C&~^mSh$jDy+jgd!#pW$ zACu`R01grk7Dye-&W&-aUSt^Rb|qoCWIw%I>!k&*-6y?Ef50(uZ_HGz`s?}jB^1_! zAjG|Swmb8Kdt_=Z-mz*M<__wFvYx)^tnq#;Fip}7Qrb^pRl6sK!oFM#I=djYB`z8IEGpFKK-38$(1 z(3>s5Gw?-zb<`b zi)Ar?Mml$0AT928LuM&~Uj2RO=RvO07_MnqR<>7J8mof7cWI1!Zp8g=goXQ|p;ow8 z@3#=Dr0=Z(u)`$hM?_JZR05Yi8KfU5O5eXUR^VB`;In!Z9owrkRu%DnpzJ(*Y5)Te zR$?`L`jr>Z}!h_1j-kCI0>Cldv8MZNxSh z`voLk^e)mDT>|haY1x`QwxscdwxTUD0m%V%`57=C7s<&2sqZq-U!RE(&eyFXvkiaXZQ-#U zBc=hmK@+&&NpvP(fd*nOdPUNS*sOG1&o}__by_l$^+BE$0LmVX@dX*>xddIC)WF=a zME1%QB?=&(pHG$^f(|U1?_2}K20*J4FG_?p2@@)(hvTvx6 z=7m+u{rc`%1`5GA%oInRY65y~)qq(%c`2s!%+s1Q0oYvCumzTtkR~HQqv}9{y6c$5 zMN9y?FxlMhj;kR_5XU4~ghVcGTGa}w-VNSewZEAk49_o$HB0ww0=3{tFz2KqZI=eH zFxiUu zLrUg2YOqw&T- zwz>zSZmKS=NE;J?Z()*v3fj{l(WFqHmx{pJKYoKIPO-!DBi47pd1n=AW1+B5VUjTQ zzy}+<7W^c^(moKLUk0F|`tWpCkv66Qy$X~1y`(b%T%L9Vq@~X*aQ^N*vx>A`tANiE zmUJed=zoS3K@!c>0sLpG>H{Ix|44J8qzM)hKy+HuhM(7_YzAqm zXk literal 0 HcmV?d00001 From f0a21a12d8c6eef4d5536d91171ce055141c6002 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 15 Aug 2024 13:45:06 +1000 Subject: [PATCH 04/40] Fix a heap of bugs and basic scouting --- .../Physics/Controllers/MoverController.cs | 4 +- Content.Client/StationAi/StationAiOverlay.cs | 2 - Content.Client/StationAi/StationAiSystem.cs | 64 +++++++-- .../Physics/Controllers/MoverController.cs | 4 +- .../Silicons/StationAi/StationAiSystem.cs | 8 ++ .../StationAi/SharedStationAiSystem.cs | 125 ++++++++++++++++++ .../Silicons/StationAi/StationAiComponent.cs | 6 + .../StationAi/StationAiOverlayComponent.cs | 9 ++ .../Entities/Mobs/Player/observer.yml | 46 ++++--- .../Entities/Mobs/Player/silicon.yml | 77 ++++++++++- .../Wallmounts/surveillance_camera.yml | 1 + Resources/Prototypes/Maps/debug.yml | 1 + .../Prototypes/Roles/Jobs/Science/borg.yml | 4 +- .../Silicon/output.rsi/ai-banned-unshaded.png | Bin 0 -> 820 bytes .../Mobs/Silicon/output.rsi/ai-banned.png | Bin 0 -> 1589 bytes .../output.rsi/ai-banned_dead-unshaded.png | Bin 0 -> 109 bytes .../Silicon/output.rsi/ai-banned_dead.png | Bin 0 -> 463 bytes .../Silicon/output.rsi/ai-empty-unshaded.png | Bin 0 -> 109 bytes .../Mobs/Silicon/output.rsi/ai-empty.png | Bin 0 -> 451 bytes .../output.rsi/ai-holo-old-unshaded.png | Bin 0 -> 2483 bytes .../Mobs/Silicon/output.rsi/ai-holo-old.png | Bin 0 -> 7280 bytes .../Mobs/Silicon/output.rsi/ai-unshaded.png | Bin 0 -> 6255 bytes .../Textures/Mobs/Silicon/output.rsi/ai.png | Bin 0 -> 6761 bytes .../Silicon/output.rsi/ai_dead-unshaded.png | Bin 0 -> 453 bytes .../Mobs/Silicon/output.rsi/ai_dead.png | Bin 0 -> 453 bytes .../Silicon/output.rsi/default-unshaded.png | Bin 0 -> 2005 bytes .../Mobs/Silicon/output.rsi/default.png | Bin 0 -> 2005 bytes .../output.rsi/floating_face-unshaded.png | Bin 0 -> 721 bytes .../Mobs/Silicon/output.rsi/floating_face.png | Bin 0 -> 721 bytes .../Silicon/output.rsi/horror-unshaded.png | Bin 0 -> 1777 bytes .../Mobs/Silicon/output.rsi/horror.png | Bin 0 -> 1404 bytes .../Mobs/Silicon/output.rsi/meta.json | 1 + .../output.rsi/xeno_queen-unshaded.png | Bin 0 -> 2484 bytes .../Mobs/Silicon/output.rsi/xeno_queen.png | Bin 0 -> 2484 bytes .../Mobs/Silicon/station_ai.rsi/ai-banned.png | Bin 1554 -> 0 bytes .../Silicon/station_ai.rsi/ai-banned_dead.png | Bin 426 -> 0 bytes .../Mobs/Silicon/station_ai.rsi/ai-empty.png | Bin 437 -> 0 bytes .../Mobs/Silicon/station_ai.rsi/ai.png | Bin 5879 -> 9757 bytes .../Mobs/Silicon/station_ai.rsi/ai_dead.png | Bin 451 -> 4405 bytes .../Mobs/Silicon/station_ai.rsi/ai_empty.png | Bin 0 -> 4405 bytes .../Mobs/Silicon/station_ai.rsi/base.png | Bin 0 -> 4421 bytes .../Silicon/station_ai.rsi/floating_face.png | Bin 785 -> 0 bytes .../{ai-holo-old.png => holo.png} | Bin .../Mobs/Silicon/station_ai.rsi/horror.png | Bin 1490 -> 0 bytes .../Mobs/Silicon/station_ai.rsi/meta.json | 72 +--------- .../Silicon/station_ai.rsi/xeno_queen.png | Bin 2496 -> 0 bytes 46 files changed, 317 insertions(+), 107 deletions(-) create mode 100644 Content.Server/Silicons/StationAi/StationAiSystem.cs create mode 100644 Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs create mode 100644 Content.Shared/Silicons/StationAi/StationAiOverlayComponent.cs create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-banned-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-banned.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-banned_dead.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-empty-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-empty.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai_dead-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/ai_dead.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/default-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/default.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/floating_face-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/floating_face.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/horror-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/horror.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/meta.json create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen-unshaded.png create mode 100644 Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen.png delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-banned.png delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-banned_dead.png delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai-empty.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_empty.png create mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/base.png delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/floating_face.png rename Resources/Textures/Mobs/Silicon/station_ai.rsi/{ai-holo-old.png => holo.png} (100%) delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/horror.png delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/xeno_queen.png diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 74a5e7afdcdff4..03df383eebce1b 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -60,7 +60,7 @@ private void OnRelayPlayerAttached(Entity entity, ref Physics.UpdateIsPredicted(entity.Owner); Physics.UpdateIsPredicted(entity.Comp.RelayEntity); if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Owner, inputMover), MoveButtons.None); + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); } private void OnRelayPlayerDetached(Entity entity, ref LocalPlayerDetachedEvent args) @@ -68,7 +68,7 @@ private void OnRelayPlayerDetached(Entity entity, ref Physics.UpdateIsPredicted(entity.Owner); Physics.UpdateIsPredicted(entity.Comp.RelayEntity); if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Owner, inputMover), MoveButtons.None); + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); } private void OnPlayerAttached(Entity entity, ref LocalPlayerAttachedEvent args) diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/StationAi/StationAiOverlay.cs index ed466357cbe78f..522512b5537144 100644 --- a/Content.Client/StationAi/StationAiOverlay.cs +++ b/Content.Client/StationAi/StationAiOverlay.cs @@ -32,8 +32,6 @@ public StationAiOverlay() protected override void Draw(in OverlayDrawArgs args) { - return; - if (_stencilTexture?.Texture.Size != args.Viewport.Size) { _staticTexture?.Dispose(); diff --git a/Content.Client/StationAi/StationAiSystem.cs b/Content.Client/StationAi/StationAiSystem.cs index 2192d99de2d874..0e02a5800a65ae 100644 --- a/Content.Client/StationAi/StationAiSystem.cs +++ b/Content.Client/StationAi/StationAiSystem.cs @@ -1,28 +1,74 @@ -using System.Numerics; -using Content.Client.SurveillanceCamera; +using Content.Shared.Silicons.StationAi; using Robust.Client.Graphics; -using Robust.Client.Graphics.Clyde; +using Robust.Client.Player; +using Robust.Shared.Player; namespace Content.Client.StationAi; -public sealed class StationAiSystem : EntitySystem +public sealed class StationAiSystem : SharedStationAiSystem { - [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IOverlayManager _overlayMgr = default!; - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SharedTransformSystem _xforms = default!; + [Dependency] private readonly IPlayerManager _player = default!; private StationAiOverlay? _overlay; - public bool Enabled => _overlay != null; - public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnAiAttached); + SubscribeLocalEvent(OnAiDetached); + SubscribeLocalEvent(OnAiOverlayInit); + SubscribeLocalEvent(OnAiOverlayRemove); + } + + private void OnAiOverlayInit(Entity ent, ref ComponentInit args) + { + var attachedEnt = _player.LocalEntity; + + if (attachedEnt != ent.Owner) + return; + + AddOverlay(); + } + + private void OnAiOverlayRemove(Entity ent, ref ComponentRemove args) + { + var attachedEnt = _player.LocalEntity; + + if (attachedEnt != ent.Owner) + return; + + RemoveOverlay(); + } + + private void AddOverlay() + { + if (_overlay != null) + return; + _overlay = new StationAiOverlay(); _overlayMgr.AddOverlay(_overlay); } + private void RemoveOverlay() + { + if (_overlay == null) + return; + + _overlayMgr.RemoveOverlay(_overlay); + _overlay = null; + } + + private void OnAiAttached(Entity ent, ref PlayerAttachedEvent args) + { + AddOverlay(); + } + + private void OnAiDetached(Entity ent, ref PlayerDetachedEvent args) + { + RemoveOverlay(); + } + public override void Shutdown() { base.Shutdown(); diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index 8b6839ddd0e031..19d58438b35b39 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -33,13 +33,13 @@ public override void Initialize() private void OnRelayPlayerAttached(Entity entity, ref PlayerAttachedEvent args) { if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Owner, inputMover), MoveButtons.None); + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); } private void OnRelayPlayerDetached(Entity entity, ref PlayerDetachedEvent args) { if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover)) - SetMoveInput((entity.Owner, inputMover), MoveButtons.None); + SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None); } private void OnPlayerAttached(Entity entity, ref PlayerAttachedEvent args) diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs new file mode 100644 index 00000000000000..6303020464fa28 --- /dev/null +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Silicons.StationAi; + +namespace Content.Server.Silicons.StationAi; + +public sealed class StationAiSystem : SharedStationAiSystem +{ + +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs new file mode 100644 index 00000000000000..18fad829366cb2 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -0,0 +1,125 @@ +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Systems; +using Robust.Shared.Containers; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +public abstract class SharedStationAiSystem : EntitySystem +{ + [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; + + /* + * TODO: Sprite / vismask visibility + */ + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnAiInsert); + SubscribeLocalEvent(OnAiRemove); + SubscribeLocalEvent(OnAiMapInit); + SubscribeLocalEvent(OnAiShutdown); + } + + private void OnAiShutdown(Entity ent, ref ComponentShutdown args) + { + QueueDel(ent.Comp.RemoteEntity); + ent.Comp.RemoteEntity = null; + } + + private void OnAiMapInit(Entity ent, ref MapInitEvent args) + { + UpdateAppearance((ent.Owner, ent.Comp)); + SetupEye(ent); + AttachEye(ent); + } + + private void SetupEye(Entity ent) + { + if (ent.Comp.RemoteEntityProto != null) + { + ent.Comp.RemoteEntity = SpawnAtPosition(ent.Comp.RemoteEntityProto, Transform(ent.Owner).Coordinates); + } + } + + private void AttachEye(Entity ent) + { + if (ent.Comp.RemoteEntity == null) + return; + + if (!_containers.TryGetContainer(ent.Owner, StationAiComponent.Container, out var container) || + container.ContainedEntities.Count != 1) + { + return; + } + + if (TryComp(container.ContainedEntities[0], out EyeComponent? eyeComp)) + { + _eye.SetTarget(container.ContainedEntities[0], ent.Comp.RemoteEntity.Value, eyeComp); + } + + _mover.SetRelay(container.ContainedEntities[0], ent.Comp.RemoteEntity.Value); + } + + private void OnAiInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + // Just so text and the likes works properly + _metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName); + + EnsureComp(args.Entity); + UpdateAppearance((ent.Owner, ent.Comp)); + + AttachEye(ent); + } + + private void OnAiRemove(Entity ent, ref EntRemovedFromContainerMessage args) + { + // Reset name to whatever + _metadata.SetEntityName(ent.Owner, Prototype(ent.Owner)?.Name ?? string.Empty); + + // Remove eye relay + RemCompDeferred(args.Entity); + + if (TryComp(args.Entity, out EyeComponent? eyeComp)) + { + _eye.SetTarget(args.Entity, null, eyeComp); + } + + RemCompDeferred(args.Entity); + UpdateAppearance((ent.Owner, ent.Comp)); + } + + public void UpdateAppearance(Entity entity) + { + if (!Resolve(entity.Owner, ref entity.Comp)) + return; + + if (!_containers.TryGetContainer(entity.Owner, StationAiComponent.Container, out var container) || + container.Count == 0) + { + Appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty); + return; + } + + Appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied); + } +} + +[Serializable, NetSerializable] +public enum StationAiVisualState : byte +{ + Key, +} + +[Serializable, NetSerializable] +public enum StationAiState : byte +{ + Empty, + Occupied, + Dead, +} diff --git a/Content.Shared/Silicons/StationAi/StationAiComponent.cs b/Content.Shared/Silicons/StationAi/StationAiComponent.cs index ae60d1b9ab5963..60e64e6c7f9ff9 100644 --- a/Content.Shared/Silicons/StationAi/StationAiComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiComponent.cs @@ -1,4 +1,5 @@ using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.Silicons.StationAi; @@ -23,4 +24,9 @@ public sealed partial class StationAiComponent : Component ///

[DataField, AutoNetworkedField] public EntityUid? RemoteEntity; + + [DataField(readOnly: true)] + public EntProtoId? RemoteEntityProto = "StationAiHolo"; + + public const string Container = "station_ai_mind_slot"; } diff --git a/Content.Shared/Silicons/StationAi/StationAiOverlayComponent.cs b/Content.Shared/Silicons/StationAi/StationAiOverlayComponent.cs new file mode 100644 index 00000000000000..8416d44d5a19e9 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiOverlayComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Handles the static overlay for station AI. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StationAiOverlayComponent : Component; diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index cf3cf104368683..17dd1a460942a6 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -1,11 +1,36 @@ - type: entity - parent: BaseMob + id: Incorporeal + noSpawn: true + description: Mobs without physical bodies + components: + - type: CargoSellBlacklist + - type: MovementSpeedModifier + baseSprintSpeed: 12 + baseWalkSpeed: 8 + - type: MovementIgnoreGravity + - type: Physics + bodyType: KinematicController + bodyStatus: InAir + - type: CanMoveInAir + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 15 + mask: + - GhostImpassable + +- type: entity + parent: + - Incorporeal + - BaseMob id: MobObserver name: observer description: Boo! categories: [ HideSpawnMenu ] components: - - type: CargoSellBlacklist - type: Sprite overrideContainerOcclusion: true # Ghosts always show up regardless of where they're contained. drawdepth: Ghosts @@ -16,15 +41,6 @@ shader: unshaded - type: ContentEye maxZoom: 1.44,1.44 - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeCircle - radius: 0.35 - density: 15 - mask: - - GhostImpassable - type: Eye drawFov: false - type: Input @@ -33,18 +49,10 @@ skipChecks: true - type: Ghost - type: GhostHearing - - type: MovementSpeedModifier - baseSprintSpeed: 12 - baseWalkSpeed: 8 - - type: MovementIgnoreGravity - type: IntrinsicRadioReceiver - type: ActiveRadio receiveAllChannels: true globalReceive: true - - type: Physics - bodyType: KinematicController - bodyStatus: InAir - - type: CanMoveInAir - type: Tag tags: - BypassInteractionRangeChecks diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index f2645fa4f11482..5f8eafcc9fc3a2 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -1,15 +1,86 @@ - type: entity - id: PlayerStationAi + id: Holocard + name: Holocard + parent: BaseItem + suffix: Empty + # TODO: Sprite + # TODO: Item toggle to enable / disable AI functionality + # TODO: StationAiComponent + # TODO: Interact on the big box (if state good) puts it in. + + + # Fix all of the interaction stuff etc. and make it actually work + # Also thanks moony for whatever gameticker spawning is doing. + # TODO: move camera stuff to shared and check it works + parallel + # TODO: Bump viewer range / PVS for AI specifically + # TODO: Get interactions working (need some kind of component for it idk) + # TODO: Need interaction validation + # TODO: Merge borg PR and get the vis working + +- type: entity + id: PlayerStationAiEmpty + name: Station AI parent: BaseStructureDynamic + suffix: Empty components: + - type: StationAiVision + - type: ItemSlots + slots: + station_ai_mind_slot: + name: station-ai-mind-slot - type: Sprite sprite: Mobs/Silicon/station_ai.rsi layers: - - + - state: base + - state: ai_empty + shader: unshaded - type: StationAi + +- type: entity + id: PlayerStationAi + parent: PlayerStationAiEmpty + suffix: Job spawn + components: + - type: ContainerSpawnPoint + containerId: station_ai_mind_slot + job: StationAi + - type: Sprite + sprite: Mobs/Silicon/station_ai.rsi + layers: + - state: base + - state: ai + shader: unshaded + +- type: entity + id: StationAiBrain + parent: PositronicBrain + noSpawn: true + suffix: DO NOT MAP + components: + - type: Eye + drawFov: false + - type: ContentEye + maxZoom: 1.44,1.44 + - type: InputMover - type: RandomMetadata # TODO: Loadout name support - nameSegments: [names_ai] + # TODO: Copy it on station AI insertion + nameSegments: [ names_ai ] + +- type: entity + parent: + - Incorporeal + - BaseMob + id: StationAiHolo + name: Hologram + noSpawn: true + suffix: DO NOT MAP + components: + - type: Sprite + sprite: Mobs/Silicon/station_ai.rsi + layers: + - state: holo + shader: unshaded - type: entity id: PlayerBorgGeneric diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 2ac81b463af47b..2a96da27604a9d 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -4,6 +4,7 @@ name: camera description: A surveillance camera. It's watching you. Kinda. components: + - type: StationAiVision - type: Clickable - type: InteractionOutline - type: Construction diff --git a/Resources/Prototypes/Maps/debug.yml b/Resources/Prototypes/Maps/debug.yml index 8d4cc550a27c97..ee9ab75e078964 100644 --- a/Resources/Prototypes/Maps/debug.yml +++ b/Resources/Prototypes/Maps/debug.yml @@ -27,6 +27,7 @@ - type: StationJobs availableJobs: Captain: [ -1, -1 ] + StationAi: [1, 1] - type: gameMap id: TestTeg diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index ad5e94904ad64a..2a5e47f0a0a406 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -1,4 +1,6 @@ # No idea why it's in sci but we ball. +# TODO: Need to just force it to spawn on a spawner and allow takeover. +# If no takeover then fallback. - type: job id: StationAi name: job-name-station-ai @@ -10,7 +12,7 @@ canBeAntag: false icon: JobIconStationAi supervisors: job-supervisors-rd - jobEntity: PlayerStationAi + jobEntity: StationAiBrain - type: job id: Borg diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-banned-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..9b72b39492803a112ab679408d6b450bf93c7b8f GIT binary patch literal 820 zcmeAS@N?(olHy`uVBq!ia0vp^3qY8I4M=vMPuFE&VEXOp;uumf=k1-qzS#yMtO3hj z?YdE37a3`<g@l?#vLz$=4}0wIjgNuMt)|?&Yw$< zUrDaJ#?oW;`|VSE({*?LT*~i}`#js8d0F4}3Gqcaw$J3}HGCGIRX;!dTk!R^=%3!@ zz1rsWXPc{9XVri{QE5ZJG0(;?q|nOKdwIgb@P+`*79AyI^_;Xo-V&}S#(?W z&+PYKb|3rt?N34U@_$mF=1chd|GasN>EjH!zO{KUF}2ZQ+XGzYV7)|7`=x9{k%7z5L$RxFV{bITDwWr1juYTMs-nr!+xX&5s69Ko%uxW#kPe?ve}r_ z$>f@@Uz(zl#*&7_U=)+lxXc*Mb-%HHw5NaeocH~`&-0%1d!FZgF6Ye)@ZYYBS%pC$ z5V}6z9)a-rr+VvX!&tzMwm~3Javu-3-N!CXU2;hnjmLTsao?~CUyYxYOUb7r_v$?P*-mR2M8?W~xG1P%F>)*BIq8|DUn3lZmjLrb&Ny@l++n?^T5#cgkZEZv=#aAMLVtUE&Xh#fP4L|1@FHSfrg(Y}0RdpFsU6uQ`!C%}-31{R z+SFhPMb9b}hbR;hiL~7FOjcwJrBs-m(r~~lh&9CN{9&tTF*xgdyhK3`ot1)xK1eKDL)=|t7UjKLM{;kW#vU3 zf}{7`vix8ycwi@|D|WV;XRS9O1=-OddUeF_kFlqKkKFSLXXg>Im>Lj^t06qF(tzW2 zM3L7rlSd?c<~!}xQnv7BQ69GlYSb+J7J6SdBbzO)U2^AW=cB%1$$Kb7qqr0`D&e#D zJYx35b}v19qhv%f4r`;uvq70$Mun51cfU3BN5pW0+T*go3FIh4KbOm_Aez%_z54F(%G8R29No^_UwSZzhjxnDew~=hdY|*O7xa zGgJE=x0)5L2y(@Py}~e44p#Zy(9qCy@IttRKiz}YY7g)_Uk9Ckh8wkh5}*u~=;j*P zye73RVYz>1*$G6|=KhW5AHrVX_l50jwV!9xFb@xG-PwwhyZ|lSb)29E~e!nJ#&#LMuNLKBJ?>lCJ(?**+`BYjSPE?xH_jRtp{>ng5hFXQre7C?iF*~R|q#FNX6I&zB4961Y35ZGj?SIIR{Lmb8D*VS5wet|_RzR$z)fpHcdTG1*X> z0}_=lSa!brp21l+XJwkymoawx32au5_Mn6Ww0PKBv6fetBRH=ub0^ z=w9C^xIb9|*&0J+b-n`s14$O@EM7hA<=`EO(T0;d1D8R_A>$38*IxkGvW$0=oC8d< z_qa0QwCq*AeVJOJr?yk5IE{arHqkS}k#ZN4nWek6_lngM32Ff1AP1f>-@FgdCEA~9chwyGO{JGsMUUF>o^DD(S_DpY#5sB z;xpB1RZrc0G2hje4u=z zyxruRk+(S(m4|jY1hFzO{KwWG{aq$u9P+1W%%14E}~O zW6R^6l+s|u+5uQ~0Ac`8N|C@1hXbhjvAy{^9<7W*NqTt6vLtWQift>T%_R#!j_yA+fs{^kTn(qQ|KA*iiVf*c}sQEDfM0l@WoMO9U!m!ysct;`lI{>CTGq!CD z0Jz<5kW!*33IP4lu(nTRf59Y4oXyVo@9_VsIlfU!86kw>oEyfNVT>6(_6UnzwqMS< z5keTHlxw5p&!)f{G@AlT>NhD6f*%2ob+IhVMS*iF%d)o$Q1Z7Fz?K4A3anbIPk|Wt z*&Urur$wR9WO=qGNXd685DNZ`Ma7?-1D5{%KJ7Cke-r>8@K5><%^%eSU)AhT0s z0LrpB72gG*(K_hC#FUMK?*cG9sR=DA{&qM@S@Zi3x&w%1QN~tqHsk;R002ovPDHLk FV1n-x%*g-% literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..6539176b8487d93162dd7df57f3528eca69df99d GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=$zQYM}*jv*Cu-k#gY$)F&>e4zZr zd?oMbXfClu<*^DALKqns?4_)qs(8P9w>UlTK08oH1G~>>eKw}0-xuEo@)$f_{an^L HB{Ts5#}6My literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..eda1f4bfb568d5b1ea19f15f24d9242cb27defa7 GIT binary patch literal 451 zcmV;!0X+VRP)zo)Bxs`n(q@(*L5!lA_@v~{l2pP0^bTDtPsK~ zrL2_FN+~U#-v^e6{PmLJ`c+DKmTP0?!)mQ95e)_qDu~wrh{$TKeH$mgHwSb@M7Ufo zbB{$+Y<>p7zRryI`yBxAcszRWt|#9K%=vG>-}^c@tVRR;|@rAP2txj?U+EQ0SN(&+Q3v z^1TI8!N*u!{J}XO^zeJP&y@UL0%E}5={q&Qw5Oyi))G002ovPDHLkV1j~s%9a2C literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-holo-old-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..63616e70b52120b670f04f00c276c707f979efe9 GIT binary patch literal 2483 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�*G;w$U9N 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 IluN|_0b@DVZ2$lO literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ba4b591abcf9615ea0cee2d2ef3a5d1e7c42a8 GIT binary patch literal 6255 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 MrlCf;x^2||0(0FdR{#J2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..3c81e3a751ec86844a1fb383a91185e12b041561 GIT binary patch literal 6761 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..96e953660f8eaecc60da577108813a81695c3102 GIT binary patch literal 453 zcmV;$0XqJPP)1ujNNuMuM7Rv8xB%yAu8^IjQjE&J&&^HKE1RzCYSt8Bzuyyvp*eU%VHlF8X=z|gO#mf?&>h*`d(qhbd~qCabbGmc zJb$uRRN~{lWMvs}Ou{IN%qF2%CSepsItfP)0Tdz7rMUue9P7HT-F0qrthxf4b^H?l z{}9CSrj~Hk<8%Ghu|8e&6#PkEQ8k}}DL|U01VM0RV!whQxco)b48d=fyGx^ozykDg z_`ZK-YPU{2$8pLK_`Xk;t&GF7Y*m|RcMfa~kbCagHedpN=H4CugzI*fLDdZ0-n`c|PdIq-Ef=QyE(0Z%&gW8I#X9TE>0xyqkY{00000NkvXXu0mjfY`w_) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead.png b/Resources/Textures/Mobs/Silicon/output.rsi/ai_dead.png new file mode 100644 index 0000000000000000000000000000000000000000..96e953660f8eaecc60da577108813a81695c3102 GIT binary patch literal 453 zcmV;$0XqJPP)1ujNNuMuM7Rv8xB%yAu8^IjQjE&J&&^HKE1RzCYSt8Bzuyyvp*eU%VHlF8X=z|gO#mf?&>h*`d(qhbd~qCabbGmc zJb$uRRN~{lWMvs}Ou{IN%qF2%CSepsItfP)0Tdz7rMUue9P7HT-F0qrthxf4b^H?l z{}9CSrj~Hk<8%Ghu|8e&6#PkEQ8k}}DL|U01VM0RV!whQxco)b48d=fyGx^ozykDg z_`ZK-YPU{2$8pLK_`Xk;t&GF7Y*m|RcMfa~kbCagHedpN=H4CugzI*fLDdZ0-n`c|PdIq-Ef=QyE(0Z%&gW8I#X9TE>0xyqkY{00000NkvXXu0mjfY`w_) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/default-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/default-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..f14b4ef0fa6b3eecdf2106102180f93fc8b3f4c2 GIT binary patch literal 2005 zcmV;`2P*i9P)s{73%tM!h~dZ&mgD%Ga(jfy*W$`C{<%a^N(Mpxm#QYNCh{=>DJQtV7 zqVco^0C}$rRhAG|h!tpt3EzqVz+Ee#{60lRFa*rr2RqW2!pmQ=1w`X%YiFN|t8*#_ zc1Z|^$l`+`U|^Sot8*%L_Nh$BS|b`yTOaII;ZszMCV>uoIhOTq_WU)UjDKcK*@(Llf{P>LYR@rc}61V z)E$N{L+DME;~%+B=06z$KD)B)xaYGg%QzV!*M}$g_$zXOq07L)4h^G8LXyyd3)gZ0 z$dQhXCdqw6mpNBi24DURgjX|(RkYWZN43c_cXE~ z=TlUKmGv%=(BZhJQvQZ=LiF|+&9`84*-1n^AoGL z#{>+~r5CYl=OXgD$rcigr>z8t5@z9Cy#1cu9;qV!vM%t;w9I}-<7unE+s~pfjR&Ji zpugMi@H_i+)gbX_i-X^%sEAE3BO{Tqj0D7{m*G=XA^x%oXl^c&)3K8gFh3ifAm?@= z?ax&UX&dnJ%a()x+>u7d3pjVA(ZQeh$`$jMRltER6RSQGK1F3?UtI#4w&#$M$nmR7 z#ru7VidCP916^i`+4!;N%>`KhtT6u3BpFZWyLmA(+`-y=>vWikO1O~U5X0GKwhs%hE4HWLo1-X%J`4&BW52~02&K25(zX6Lgcaas!gToH&4kI4pi<9>V&qV#C!d{n%%vG7%R?xChe^go z_{&;AsYz5>!tEOxZqsv{du;TKDAvxk>N643Omul=jL+oI4?m_>48R8#^?CTY{|&~p zqHo!W?Bi~t%S)nrUe_=^(CVSCLC1N)t$_F5C{47{c&!l(tzT@HS4J=-HXpYFE?mpu zPB!ZpyOYhb)VLlf#V-osP0_8w)ZNmqw$XSwa4TSM@EOxkFx%+?xoSJ*LjybmMaizgra3b4K51zBRlQx^xZ`_W@ptHUFQIz77| zJ$Cd5*k!0s5VR3RRGA5r(1Bl9H%QcLh+kKqu$|zki-U-$vO^|bR8_X36S=fE_FI61?puX73ZSOZ*NYQfV2-_d==lHAI?1a$My zCu9NMc2b54hQJyT4hBe}LS#N3Ob@hrDBoxjtR6sR;suuietOUjprCDlS-{mf6>Bpz zH{nxAx7s=ju*p~{xZl`27oS>@{f*EJ6UoJ8oIc=3LMKfMAHF`11zEy(@3y&O2_OHF zwOS1mKRswiWPc;V3h?>O9QL&|LX{=NrkC;OCr$2t2EFYjG=uaE0LA7g3YM9<0Q=mu zVGAGyQ_V2pZ8y2UL_$H3_6($uBRPQrB=kn)b$5%TG0i|Lz}|jF0x(mk70N(zak<%gsm_qQ!#e1`n)@Ak7=eLb>4>RQow+RB7fbeKej@tGWxjTgeHupJLq473_%UH=O# z$kMthvFoWYQ0%|}z*5nAE}qKJYM3k)2gEI~>MgN)^#!=aEv$e$*{q{EYAY~%pTwVP z`U8Z%0u)nsiTT$v38L^RD)PF9hbvO~n>RsKE|8k7N`Ey$wK51RU~P?lD^ZOsRR=+8 ziuFcb*J|Q=!uCS~kGGJ7w6Of(#M>TMKZj@kvaGzW;rdbwP)LC8WU~;hjteUw@N5Zz nZA*@^dRPMGaAbW|v?u)wE5X)16nVyP00000NkvXXu0mjfe%i)b literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/default.png b/Resources/Textures/Mobs/Silicon/output.rsi/default.png new file mode 100644 index 0000000000000000000000000000000000000000..f14b4ef0fa6b3eecdf2106102180f93fc8b3f4c2 GIT binary patch literal 2005 zcmV;`2P*i9P)s{73%tM!h~dZ&mgD%Ga(jfy*W$`C{<%a^N(Mpxm#QYNCh{=>DJQtV7 zqVco^0C}$rRhAG|h!tpt3EzqVz+Ee#{60lRFa*rr2RqW2!pmQ=1w`X%YiFN|t8*#_ zc1Z|^$l`+`U|^Sot8*%L_Nh$BS|b`yTOaII;ZszMCV>uoIhOTq_WU)UjDKcK*@(Llf{P>LYR@rc}61V z)E$N{L+DME;~%+B=06z$KD)B)xaYGg%QzV!*M}$g_$zXOq07L)4h^G8LXyyd3)gZ0 z$dQhXCdqw6mpNBi24DURgjX|(RkYWZN43c_cXE~ z=TlUKmGv%=(BZhJQvQZ=LiF|+&9`84*-1n^AoGL z#{>+~r5CYl=OXgD$rcigr>z8t5@z9Cy#1cu9;qV!vM%t;w9I}-<7unE+s~pfjR&Ji zpugMi@H_i+)gbX_i-X^%sEAE3BO{Tqj0D7{m*G=XA^x%oXl^c&)3K8gFh3ifAm?@= z?ax&UX&dnJ%a()x+>u7d3pjVA(ZQeh$`$jMRltER6RSQGK1F3?UtI#4w&#$M$nmR7 z#ru7VidCP916^i`+4!;N%>`KhtT6u3BpFZWyLmA(+`-y=>vWikO1O~U5X0GKwhs%hE4HWLo1-X%J`4&BW52~02&K25(zX6Lgcaas!gToH&4kI4pi<9>V&qV#C!d{n%%vG7%R?xChe^go z_{&;AsYz5>!tEOxZqsv{du;TKDAvxk>N643Omul=jL+oI4?m_>48R8#^?CTY{|&~p zqHo!W?Bi~t%S)nrUe_=^(CVSCLC1N)t$_F5C{47{c&!l(tzT@HS4J=-HXpYFE?mpu zPB!ZpyOYhb)VLlf#V-osP0_8w)ZNmqw$XSwa4TSM@EOxkFx%+?xoSJ*LjybmMaizgra3b4K51zBRlQx^xZ`_W@ptHUFQIz77| zJ$Cd5*k!0s5VR3RRGA5r(1Bl9H%QcLh+kKqu$|zki-U-$vO^|bR8_X36S=fE_FI61?puX73ZSOZ*NYQfV2-_d==lHAI?1a$My zCu9NMc2b54hQJyT4hBe}LS#N3Ob@hrDBoxjtR6sR;suuietOUjprCDlS-{mf6>Bpz zH{nxAx7s=ju*p~{xZl`27oS>@{f*EJ6UoJ8oIc=3LMKfMAHF`11zEy(@3y&O2_OHF zwOS1mKRswiWPc;V3h?>O9QL&|LX{=NrkC;OCr$2t2EFYjG=uaE0LA7g3YM9<0Q=mu zVGAGyQ_V2pZ8y2UL_$H3_6($uBRPQrB=kn)b$5%TG0i|Lz}|jF0x(mk70N(zak<%gsm_qQ!#e1`n)@Ak7=eLb>4>RQow+RB7fbeKej@tGWxjTgeHupJLq473_%UH=O# z$kMthvFoWYQ0%|}z*5nAE}qKJYM3k)2gEI~>MgN)^#!=aEv$e$*{q{EYAY~%pTwVP z`U8Z%0u)nsiTT$v38L^RD)PF9hbvO~n>RsKE|8k7N`Ey$wK51RU~P?lD^ZOsRR=+8 ziuFcb*J|Q=!uCS~kGGJ7w6Of(#M>TMKZj@kvaGzW;rdbwP)LC8WU~;hjteUw@N5Zz nZA*@^dRPMGaAbW|v?u)wE5X)16nVyP00000NkvXXu0mjfe%i)b literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/floating_face-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/floating_face-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..05de742794341c5e5489daa52bef423933d51512 GIT binary patch literal 721 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 x`nAeQgj#8{L5ZW1os&KkQ{U=i>h9Up} literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/horror.png b/Resources/Textures/Mobs/Silicon/output.rsi/horror.png new file mode 100644 index 0000000000000000000000000000000000000000..0a807c0c2354086be2730017fb4e4a7eb95afdd4 GIT binary patch literal 1404 zcmai!e>l?#9LK-y80+ka9x^|+GHuC2$u(+>{AxWyQPEag57H$jg}M8NSy$w)iI6qX zg}VH_{8)>=l>8>)a7N9{!WMF?r@QO^>Hhe9KF{;|y=yYE-er1zx_SNA68W1Rq?=|*;U4Pf%5yoPX-Fw>TN|2@_oBkM7)H?M2yPU+bf zG7%R>3{9hURO7Fv*!1bma7m9=SdHv^18c9xY8O3yrr&n(?)pr#wsrWc0hnZu5XS=Q z#s0~ci0ZW`7j%AW#!JrI%kdo&K1_G0Fi*Se1lO_F?SfV9 zg;Wv^@{fo?F)FCx4d6`CC?SB7*Fs8MQAyh?%60-n4C$ zGjIFy8awCEm9+~YrjmqGnwpK5Z2alGLKfVdj}x9KyZ=t(Xfc43z4U_dVt*!2kM@Xq zCbxHi&3_PxzIp&aG!WXYz--x<%@TJp2$M+Ybolrdnq)mqB5Enia!V9P z2^HIkMpu;ROc^uxSh>mYRaURg6iU~)-1l-W3)i#eNb3?VP&_m;0h3g-_Qm{I+N3-) zn|Y_Q?@vRap)e5LwRd-Q@Yz9BcWrerS#ix+=?!)+Ju`2toA9n1z*~A>D+~_%-PGJ7 zNK6^^Li@NkcqOLzshpTvQhsg__f5jLC^wJ-;v|8)qR5vzWWmP^;!DNFe9Lc!_d@4W zNsI)W1Yd5UHGf-wi_%&l%~oz#=Eu{Do${S^^}C_8jxMHLI=ii%mblcT$W~U3(8OV) zDrQHFaVjY-D8L94niY@G&*w0VY~KB#JPd0q(n!Z{x@CBi$jz&;Orl!p;wDOnJYL0Y zR5aAl)UNtGn41kz+Z{0Xd3q?`!I3sD8UtUAw~L3yONdl3N$_6uUm+t>iQ%ok(G;w( z^xlyh*3kIs@n$^d_(V7YlcVpFfhA`Y>OSAF4qK_HL11%qHngeiQZN&pm}H(UTRR&1 zEMN>5xPWz_v3t5ARL}Kkj;n^F@PR8!8LQUNL({kKrhh8*q~~t)mXE070kEm$l*!r@ z#*16@tvdR@sXpF@oPnKW*J#5LufYgdyXF5z_)ka*0k0>tp~2UT{ga6w>4$-isMmO+ zbq%Su`D8hH)yWUe?@@`3ONZ4ETvJS$#|P@LHDaM`AbQiPwmA}N&)09tCn7b~UlkyG L__&wvq9^|aHQlu+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/meta.json b/Resources/Textures/Mobs/Silicon/output.rsi/meta.json new file mode 100644 index 00000000000000..a40ed37c6040bc --- /dev/null +++ b/Resources/Textures/Mobs/Silicon/output.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/2a19963297f91efb452dbb5c1d4eb28a14776b0a/icons/mob/silicon/ai.dmi", "states": [{"name": "ai", "directions": 1, "delays": [[0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.1]]}, {"name": "ai-banned", "directions": 1, "delays": [[0.7, 0.7, 0.7, 0.7, 0.7, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]]}, {"name": "ai-banned-unshaded", "directions": 1, "delays": [[0.7, 0.7, 0.7, 0.7, 0.7, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]]}, {"name": "ai-banned_dead", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-banned_dead-unshaded", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-empty", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-empty-unshaded", "directions": 1, "delays": [[0.7, 0.7]]}, {"name": "ai-holo-old", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "ai-holo-old-unshaded", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "ai-unshaded", "directions": 1, "delays": [[0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.1]]}, {"name": "ai_dead", "directions": 1, "delays": [[1.0]]}, {"name": "ai_dead-unshaded", "directions": 1, "delays": [[1.0]]}, {"name": "default", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "default-unshaded", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "floating_face", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "floating_face-unshaded", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "horror", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "horror-unshaded", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "xeno_queen", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "xeno_queen-unshaded", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen-unshaded.png b/Resources/Textures/Mobs/Silicon/output.rsi/xeno_queen-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..3ea194039f4fee0b41d8c2f4b5f602592936f88f GIT binary patch literal 2484 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 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai.png index c0f37c65f2d3e124f5f11cfa628861fed0c7a587..420a07c1f96662a077d518546c866ea074cd556f 100644 GIT binary patch literal 9757 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 W51pTEd9e|sD$u89>Gr2AU-w^7I7~1A delta 425 zcmV;a0apICBEtiaB!3BTNLh0L01m_e01m_fl`9S#0004XNkl2(8s(!#BUXmm(^})lhwr!n=2SIQ)fGp;7c$m#tgnvJdM|j`wMY}VA;yAzo zov23#zP?|048TTj;LNLK`IXWe8i-Dx9k9_EsOuWyIEDjez?O+q+{e$2&{7P9VJJGj z?}Ig>zb5KQEn%Q(8Ys&WilP7x@;rw$P1WasD}({8y$RdxTi0GLpGMz@nU>iW25?cB zlwgV|CCjpZRDS|YGtN||Ti2GEpug>pQ6`l@2B3kiwRQJUCYONDxZm$*fL_03LNaO> z^n*;vhK`%<0#k0X3&t+EYZyO>#x6J=+7g%=F+u7nB@nOT!?W_%vH@zKmNcPX13L@K z7&97P9vZMAej&Cd11sP}F>rskW|GzXEyptf=fPxPKq8)0xDf_$Ke_^s=iriGIFzwN TQvZ4~00000NkvXXu0mjf`dq*7 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_empty.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/ai_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..40e8ac5216186918389147a014046b6e454f32cf GIT binary patch literal 4405 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/floating_face.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/floating_face.png deleted file mode 100644 index e549c21cb34eef034ad256555f4b67d978eaeb6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 785 zcmV+s1Md8ZP)Rn7v*7_69^yGZmeQJP;?@X8l-27S@ zK$WRuG2MYoVH4Tm`~E#5r$*2(Q*dzFD-Xw0Ba(uXKT?3=$3-|brEjnL4u@xCbzWa{ z0PteNW%PU!aPw!uY_veTk9Q@h>sIjPK;qezEIMaSP zSaMP-Qg0AQebfI2x%gvc09E?_=7^iUok7p78~)@&s5*Aszb51!Ji+RtewO_J4*u91 zB2;+}aQm)ZFPnL39Oj=rWqWxp{))E5h$Qbdf_g%Mv*$VaE8Y`SJrTaf4#E_FqD|4A zerXPV*%gN65CLF0L;zS05dfA$1c2ob0pdO&{s8d@L__=mOb~y7_yfcrApQXH2Z%pF z`~gP!#UG&C2=NDqKS2Be;tvpi0K^{^p{Sz3Ar`K2%!w&5M@7ML$BC$U9!(s}1lH|+m9;vX zKj_J>4vmYZHwyBwa4SbeM9gX}n9lrO>4io9y?tk9-#q#5Sy?{&r@wX_p7-`t z?%)3K@!sv5f7bgtUy?e_&^>#;TKK)28|2O7aw2$}?Cs`@I>a#Zet4E%ueNh{nDFMj zU$u&VU(I>EQf%qMlnCR?xmCtRe|Ge1Qb?|)f$633#o0jLO zm*?L;`0lJVteyOq-m)Z|keSN4N9dZmy}mK4~lOY*X*JisanY{~eh3`_!XtM?nPa{xBYlLP;k=jHKlLgZu#5&VqM;q?JwM}znfxXb@JG@Vm^}(uOCk5 z*)Z$K{fOlP z^~K`(4(YopML$(jH+0o9{rQq($*q3n@FjW1I@v$-7@IzSI(&5I;wo>Cy1P}Ud-uQN zS1gPcjEh_K$@7?BOT2jffyD{$CzLeg6y?Q#(mSO-UEXO816OTrbiuEn#7b$sAD18W ze@MKLn7pl4xsdr+CR8*#>zJD(NFQWY1tlaex_r70au~TjS zZ}Ri(BI84Hfp_Nraq#vmSn&FJ!&=M#(G9z+o#)s6NK~$CdmCEU##?{u&pj>yJ4Y_r zc1?>97R3vG-}FzKap%TrQE$ONyMCO#BkA~jZa}i`mzd}CLyq$8xnVK4>o4a&lb@}| z2a>+c`)s{B>_<=KjYPE_YhJxQb;yD3&&$uVKR$5ZJvZ+2f_BlHU4{C4)A#<^wy6HS z)~Wu;lqbT?@%;N|+BbA84E=B;u>JirKb~k0p7llXHfaSJ63lh6f70H~JIC;S%6ZPk z855)r2Z{07eR%!I{IC9MahW|f8w35;r5^o%n{B^d-Fu!3l8Fsk9u3pg4NtWssR7-a z{3rUyWF7CxB7ZzTtmGEBF2UAu_q$5^lQB_{E=ZX6@+yR8VO5^x>nLQ-^y$6`VS}U6bGZ)MnG4Q`)7UJ`^+837(fbrG2~R z`MdzRyeaAD!DM_MRL4=EvhL5CK$C*y^v*4}iJ9YHxx->U!bmg)dJZu#4M3~8nIX1o zH|#KI*ul{u@O(2No;a*72DWXp*pi;yHB%r#b!(Ef(_;MxrWM4s;Jp1JperrGv;l buj?l~7x?r#)?fj!kY@07^>bP0l+XkK4nV{^ diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json index 4878a403ee601e..eb42b02a5546a9 100644 --- a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json @@ -33,7 +33,7 @@ "name": "ai_dead" }, { - "name": "ai-empty", + "name": "ai_empty", "delays": [ [ 0.7, @@ -42,7 +42,7 @@ ] }, { - "name": "ai-holo-old", + "name": "holo", "directions": 4, "delays": [ [ @@ -120,73 +120,7 @@ "directions": 4 }, { - "name": "horror", - "delays": [ - [ - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1 - ] - ] - }, - { - "name": "xeno_queen", - "directions": 4 - }, - { - "name": "floating_face", - "directions": 4 - }, - { - "name": "ai-banned", - "delays": [ - [ - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7, - 0.7 - ] - ] - }, - { - "name": "ai-banned_dead", - "delays": [ - [ - 0.7, - 0.7 - ] - ] + "name": "base" } ] } \ No newline at end of file diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/xeno_queen.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/xeno_queen.png deleted file mode 100644 index 1bce0bd99f27960431b1f8fec07834456d0b3300..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2496 zcmV;x2|xCUP)JPg7T5zE-4ArfDM+Iih*>_d;iSbd4J~5 zo0&gx(F0-ryqWjzyLaxr@4kCq2IYT_TmrcI%kqxCzy9&aKvJ7jgevHJ3jl=1CR$!? zRS2Of`d$LSpr?+vWG(nTJtJ~$%C_HKZOK^Y9k$~=>9Y_>|H{1$bN}XLT`r%G=zo1% zWbZ-y?8bOL`h|SJx4}fd{IDLo7pY6t&@{m`uMkE^|8>4Gy#!#S+4&0i%F{EJ*yg5v z{_@YdX&(-VFFao9yC2Z>uRPcYHGs@h18$7hbJI^1fOakzP)}lLDEjKS4h_}2n0Nc< zWjc*MRD5lpWf~uVLKuEsFGN380CUeRqZpiyg_F2^z7-I*TEMTduox!}=fp`CK^pzl zUtjDzS#Fqi_#JA%v5|F|DL_9}085t!29A8Qvt#YO6-xB_?4sPBnC#eH1hn%V5J$hw z&AJ(R?SIxc>g#q#O!_k9_} z;H$ylaDs3)pN&Toz&+Y$pRSwtIH96Qa$sO8CIp9uWBSJ3?fHytUd}Tbd99=iWL`?vi z0l+Jt8(lFG+L>lIBeDVc+(m7{?Vdngaa{W8jOfc2r)|hrsaxh~d}q zFwLKw31=Dt8 zWbX;8Za?8NqqR>9NYh8S+-znq$C&~^mSh$jDy+jgd!#pW$ zACu`R01grk7Dye-&W&-aUSt^Rb|qoCWIw%I>!k&*-6y?Ef50(uZ_HGz`s?}jB^1_! zAjG|Swmb8Kdt_=Z-mz*M<__wFvYx)^tnq#;Fip}7Qrb^pRl6sK!oFM#I=djYB`z8IEGpFKK-38$(1 z(3>s5Gw?-zb<`b zi)Ar?Mml$0AT928LuM&~Uj2RO=RvO07_MnqR<>7J8mof7cWI1!Zp8g=goXQ|p;ow8 z@3#=Dr0=Z(u)`$hM?_JZR05Yi8KfU5O5eXUR^VB`;In!Z9owrkRu%DnpzJ(*Y5)Te zR$?`L`jr>Z}!h_1j-kCI0>Cldv8MZNxSh z`voLk^e)mDT>|haY1x`QwxscdwxTUD0m%V%`57=C7s<&2sqZq-U!RE(&eyFXvkiaXZQ-#U zBc=hmK@+&&NpvP(fd*nOdPUNS*sOG1&o}__by_l$^+BE$0LmVX@dX*>xddIC)WF=a zME1%QB?=&(pHG$^f(|U1?_2}K20*J4FG_?p2@@)(hvTvx6 z=7m+u{rc`%1`5GA%oInRY65y~)qq(%c`2s!%+s1Q0oYvCumzTtkR~HQqv}9{y6c$5 zMN9y?FxlMhj;kR_5XU4~ghVcGTGa}w-VNSewZEAk49_o$HB0ww0=3{tFz2KqZI=eH zFxiUu zLrUg2YOqw&T- zwz>zSZmKS=NE;J?Z()*v3fj{l(WFqHmx{pJKYoKIPO-!DBi47pd1n=AW1+B5VUjTQ zzy}+<7W^c^(moKLUk0F|`tWpCkv66Qy$X~1y`(b%T%L9Vq@~X*aQ^N*vx>A`tANiE zmUJed=zoS3K@!c>0sLpG>H{Ix|44J8qzM)hKy+HuhM(7_YzAqm zXk From cbdfd2f219969a13ecdaf5f03fb09c7ddc42e428 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 15 Aug 2024 20:24:43 +1000 Subject: [PATCH 05/40] helldivers --- Content.Client/StationAi/StationAiSystem.cs | 8 +- .../Containers/ItemSlot/ItemSlotsSystem.cs | 4 +- .../Interaction/SharedInteractionSystem.cs | 28 +++- .../StationAi/RemoteInteractComponent.cs | 12 ++ .../StationAi/SharedStationAiSystem.cs | 155 +++++++++++++++--- ...Component.cs => StationAiCoreComponent.cs} | 2 +- .../StationAi/StationAiHeldComponent.cs | 12 ++ .../StationAi/StationAiHolderComponent.cs | 16 ++ .../StationAi/StationAiVisionComponent.cs | 0 .../StationAi/StationAiWhitelistComponent.cs | 13 ++ Resources/Maps/Test/dev_map.yml | 7 + .../Entities/Mobs/NPCs/miscellaneous.yml | 5 +- .../Entities/Mobs/NPCs/revenant.yml | 26 +-- .../Entities/Mobs/NPCs/simplemob.yml | 1 + .../Entities/Mobs/Player/observer.yml | 9 +- .../Entities/Mobs/Player/silicon.yml | 81 +++++++-- .../Prototypes/Entities/Mobs/Species/base.yml | 1 + Resources/Prototypes/Entities/Mobs/base.yml | 8 +- .../Objects/Specific/Robotics/mmi.yml | 1 - .../Doors/Airlocks/base_structureairlocks.yml | 1 + Resources/Prototypes/tags.yml | 4 + .../Objects/Devices/ai_card.rsi/base.png | Bin 0 -> 4432 bytes .../Objects/Devices/ai_card.rsi/empty.png | Bin 0 -> 4292 bytes .../Objects/Devices/ai_card.rsi/full.png | Bin 0 -> 5121 bytes .../Objects/Devices/ai_card.rsi/meta.json | 50 ++++++ .../output.rsi/aicard-full-unshaded.png | Bin 0 -> 777 bytes .../Devices/output.rsi/aicard-full.png | Bin 0 -> 1746 bytes .../Devices/output.rsi/aicard-unshaded.png | Bin 0 -> 138 bytes .../Objects/Devices/output.rsi/aicard.png | Bin 0 -> 414 bytes .../Objects/Devices/output.rsi/meta.json | 1 + 30 files changed, 365 insertions(+), 80 deletions(-) create mode 100644 Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs rename Content.Shared/Silicons/StationAi/{StationAiComponent.cs => StationAiCoreComponent.cs} (93%) create mode 100644 Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs create mode 100644 Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs rename Content.Shared/{ => Silicons}/StationAi/StationAiVisionComponent.cs (100%) create mode 100644 Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs create mode 100644 Resources/Textures/Objects/Devices/ai_card.rsi/base.png create mode 100644 Resources/Textures/Objects/Devices/ai_card.rsi/empty.png create mode 100644 Resources/Textures/Objects/Devices/ai_card.rsi/full.png create mode 100644 Resources/Textures/Objects/Devices/ai_card.rsi/meta.json create mode 100644 Resources/Textures/Objects/Devices/output.rsi/aicard-full-unshaded.png create mode 100644 Resources/Textures/Objects/Devices/output.rsi/aicard-full.png create mode 100644 Resources/Textures/Objects/Devices/output.rsi/aicard-unshaded.png create mode 100644 Resources/Textures/Objects/Devices/output.rsi/aicard.png create mode 100644 Resources/Textures/Objects/Devices/output.rsi/meta.json diff --git a/Content.Client/StationAi/StationAiSystem.cs b/Content.Client/StationAi/StationAiSystem.cs index 0e02a5800a65ae..74d4ba5329b368 100644 --- a/Content.Client/StationAi/StationAiSystem.cs +++ b/Content.Client/StationAi/StationAiSystem.cs @@ -15,8 +15,8 @@ public sealed class StationAiSystem : SharedStationAiSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAiAttached); - SubscribeLocalEvent(OnAiDetached); + SubscribeLocalEvent(OnAiAttached); + SubscribeLocalEvent(OnAiDetached); SubscribeLocalEvent(OnAiOverlayInit); SubscribeLocalEvent(OnAiOverlayRemove); } @@ -59,12 +59,12 @@ private void RemoveOverlay() _overlay = null; } - private void OnAiAttached(Entity ent, ref PlayerAttachedEvent args) + private void OnAiAttached(Entity ent, ref LocalPlayerAttachedEvent args) { AddOverlay(); } - private void OnAiDetached(Entity ent, ref PlayerDetachedEvent args) + private void OnAiDetached(Entity ent, ref LocalPlayerDetachedEvent args) { RemoveOverlay(); } diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 48f4f07cbe1ad5..e0c499bec71c65 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -252,7 +252,7 @@ private void Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? use if (inserted != null && inserted.Value && user != null) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} inserted {ToPrettyString(item)} into {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}"); - _audioSystem.PlayPredicted(slot.InsertSound, uid, excludeUserAudio ? user : null); + _audioSystem.PlayPredicted(slot.InsertSound, uid, !excludeUserAudio ? user : null); } /// @@ -450,7 +450,7 @@ private void Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user if (ejected != null && ejected.Value && user != null) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} ejected {ToPrettyString(item)} from {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}"); - _audioSystem.PlayPredicted(slot.EjectSound, uid, excludeUserAudio ? user : null); + _audioSystem.PlayPredicted(slot.EjectSound, uid, !excludeUserAudio ? user : null); } /// diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index e8cc831e4957c4..4fa82995eee5ab 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Silicons.StationAi; using Content.Shared.Storage; using Content.Shared.Tag; using Content.Shared.Timing; @@ -630,6 +631,9 @@ public bool InRangeUnobstructed( if (!Resolve(other, ref other.Comp)) return false; + if (HasComp(origin)) + return true; + return InRangeUnobstructed(origin, other, other.Comp.Coordinates, @@ -1182,6 +1186,12 @@ public bool InRangeAndAccessible( /// public bool IsAccessible(Entity user, Entity target) { + var ev = new AccessibleOverrideEvent(user, target); + RaiseLocalEvent(user, ref ev, true); + + if (ev.Handled) + return ev.Accessible; + if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container)) return true; @@ -1369,17 +1379,23 @@ public record struct GetUsedEntityEvent() }; /// - /// Raised directed by-ref on an item and a user to determine if interactions can occur. + /// Raised directed by-ref on an item to determine if hand interactions should go through. + /// Defaults to allowing hand interactions to go through. Cancel to force the item to be attacked instead. /// /// Whether the hand interaction should be cancelled. [ByRefEvent] - public record struct AttemptUseInteractEvent(EntityUid User, EntityUid Used, bool Cancelled = false); + public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false); /// - /// Raised directed by-ref on an item to determine if hand interactions should go through. - /// Defaults to allowing hand interactions to go through. Cancel to force the item to be attacked instead. + /// Override event raised directed on a user to say it can access the target. /// - /// Whether the hand interaction should be cancelled. [ByRefEvent] - public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false); + public record struct AccessibleOverrideEvent(EntityUid User, EntityUid Target) + { + public readonly EntityUid User = User; + public readonly EntityUid Target = Target; + + public bool Handled; + public bool Accessible = true; + } } diff --git a/Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs b/Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs new file mode 100644 index 00000000000000..6ad3ad7aa41509 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates that InRangeUnobstructed checks should be bypassed for this entity, effectively giving it infinite interaction range. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class RemoteInteractComponent : Component +{ + +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 18fad829366cb2..06b0731fde8af6 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -1,45 +1,142 @@ +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; +using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Serialization; +using Robust.Shared.Timing; namespace Content.Shared.Silicons.StationAi; public abstract class SharedStationAiSystem : EntitySystem { - [Dependency] protected readonly SharedAppearanceSystem Appearance = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; /* - * TODO: Sprite / vismask visibility + * TODO: Sprite / vismask visibility + action + * TODO: Double-check positronic interactions didn't break + * Need action bar + * AI wire for doors. + * Need to check giving comp "just works" and maybe map act for it + * Need to move the view stuff to shared + parallel + optimise + * Need cameras to be snip-snippable and to turn ai vision off, also xray support, also power + * Need to bump PVS range for AI to like 20 or something + * Make it a screen-space overlay + * Need interaction whitelist or something working + * Need destruction and all that */ + // StationAiHeld is added to anything inside of an AI core. + // StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core). + // StationAiCore holds functionality related to the core itself. + public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAiInsert); - SubscribeLocalEvent(OnAiRemove); - SubscribeLocalEvent(OnAiMapInit); - SubscribeLocalEvent(OnAiShutdown); + + SubscribeLocalEvent(OnAiAccessible); + SubscribeLocalEvent(OnAiInteraction); + + SubscribeLocalEvent(OnHolderInit); + SubscribeLocalEvent(OnHolderRemove); + SubscribeLocalEvent(OnHolderInteract); + SubscribeLocalEvent(OnHolderMapInit); + SubscribeLocalEvent(OnHolderConInsert); + SubscribeLocalEvent(OnHolderConRemove); + + SubscribeLocalEvent(OnAiInsert); + SubscribeLocalEvent(OnAiRemove); + SubscribeLocalEvent(OnAiMapInit); + SubscribeLocalEvent(OnAiShutdown); + } + + private void OnAiInteraction(Entity ent, ref InteractionAttemptEvent args) + { + args.Cancelled = !HasComp(args.Target); + } + + private void OnAiAccessible(Entity ent, ref AccessibleOverrideEvent args) + { + // TODO: Validate it's near cameras + args.Accessible = true; + args.Handled = true; + } + + private void OnHolderInteract(Entity ent, ref AfterInteractEvent args) + { + if (!TryComp(args.Target, out StationAiHolderComponent? targetHolder)) + return; + + // Try to insert our thing into them + if (_slots.CanEject(ent.Owner, args.User, ent.Comp.Slot)) + { + if (!_slots.TryInsert(args.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User)) + { + return; + } + + args.Handled = true; + return; + } + + // Otherwise try to take from them + if (_slots.CanEject(args.Target.Value, args.User, targetHolder.Slot)) + { + if (!_slots.TryInsert(ent.Owner, ent.Comp.Slot, targetHolder.Slot.Item!.Value, args.User)) + { + return; + } + + args.Handled = true; + } + } + + private void OnHolderInit(Entity ent, ref ComponentInit args) + { + _slots.AddItemSlot(ent.Owner, StationAiCoreComponent.Container, ent.Comp.Slot); + } + + private void OnHolderRemove(Entity ent, ref ComponentRemove args) + { + _slots.RemoveItemSlot(ent.Owner, ent.Comp.Slot); + } + + private void OnHolderConInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + UpdateAppearance((ent.Owner, ent.Comp)); + } + + private void OnHolderConRemove(Entity ent, ref EntRemovedFromContainerMessage args) + { + UpdateAppearance((ent.Owner, ent.Comp)); + } + + private void OnHolderMapInit(Entity ent, ref MapInitEvent args) + { + UpdateAppearance(ent.Owner); } - private void OnAiShutdown(Entity ent, ref ComponentShutdown args) + private void OnAiShutdown(Entity ent, ref ComponentShutdown args) { QueueDel(ent.Comp.RemoteEntity); ent.Comp.RemoteEntity = null; } - private void OnAiMapInit(Entity ent, ref MapInitEvent args) + private void OnAiMapInit(Entity ent, ref MapInitEvent args) { - UpdateAppearance((ent.Owner, ent.Comp)); SetupEye(ent); AttachEye(ent); } - private void SetupEye(Entity ent) + private void SetupEye(Entity ent) { if (ent.Comp.RemoteEntityProto != null) { @@ -47,38 +144,47 @@ private void SetupEye(Entity ent) } } - private void AttachEye(Entity ent) + private void AttachEye(Entity ent) { if (ent.Comp.RemoteEntity == null) return; - if (!_containers.TryGetContainer(ent.Owner, StationAiComponent.Container, out var container) || + if (!_containers.TryGetContainer(ent.Owner, StationAiCoreComponent.Container, out var container) || container.ContainedEntities.Count != 1) { return; } - if (TryComp(container.ContainedEntities[0], out EyeComponent? eyeComp)) + var user = container.ContainedEntities[0]; + + if (TryComp(user, out EyeComponent? eyeComp)) { - _eye.SetTarget(container.ContainedEntities[0], ent.Comp.RemoteEntity.Value, eyeComp); + _eye.SetTarget(user, ent.Comp.RemoteEntity.Value, eyeComp); } - _mover.SetRelay(container.ContainedEntities[0], ent.Comp.RemoteEntity.Value); + _mover.SetRelay(user, ent.Comp.RemoteEntity.Value); } - private void OnAiInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + private void OnAiInsert(Entity ent, ref EntInsertedIntoContainerMessage args) { + if (_timing.ApplyingState) + return; + // Just so text and the likes works properly _metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName); + EnsureComp(args.Entity); EnsureComp(args.Entity); - UpdateAppearance((ent.Owner, ent.Comp)); + EnsureComp(args.Entity); AttachEye(ent); } - private void OnAiRemove(Entity ent, ref EntRemovedFromContainerMessage args) + private void OnAiRemove(Entity ent, ref EntRemovedFromContainerMessage args) { + if (_timing.ApplyingState) + return; + // Reset name to whatever _metadata.SetEntityName(ent.Owner, Prototype(ent.Owner)?.Name ?? string.Empty); @@ -90,23 +196,24 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromContai _eye.SetTarget(args.Entity, null, eyeComp); } + RemCompDeferred(args.Entity); RemCompDeferred(args.Entity); - UpdateAppearance((ent.Owner, ent.Comp)); + RemCompDeferred(args.Entity); } - public void UpdateAppearance(Entity entity) + public void UpdateAppearance(Entity entity) { - if (!Resolve(entity.Owner, ref entity.Comp)) + if (!Resolve(entity.Owner, ref entity.Comp, false)) return; - if (!_containers.TryGetContainer(entity.Owner, StationAiComponent.Container, out var container) || + if (!_containers.TryGetContainer(entity.Owner, StationAiCoreComponent.Container, out var container) || container.Count == 0) { - Appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty); + _appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty); return; } - Appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied); + _appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied); } } diff --git a/Content.Shared/Silicons/StationAi/StationAiComponent.cs b/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs similarity index 93% rename from Content.Shared/Silicons/StationAi/StationAiComponent.cs rename to Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs index 60e64e6c7f9ff9..b7a8b4cd5fa70f 100644 --- a/Content.Shared/Silicons/StationAi/StationAiComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Silicons.StationAi; /// Indicates this entity can interact with station equipment and is a "Station AI". /// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class StationAiComponent : Component +public sealed partial class StationAiCoreComponent : Component { /* * I couldn't think of any other reason you'd want to split these out. diff --git a/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs b/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs new file mode 100644 index 00000000000000..17c0548e52b669 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates this entity is currently held inside of a station AI core. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StationAiHeldComponent : Component +{ + +} diff --git a/Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs b/Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs new file mode 100644 index 00000000000000..221845d493dcbb --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiHolderComponent.cs @@ -0,0 +1,16 @@ +using Content.Shared.Containers.ItemSlots; +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Allows moving a contained entity to and from this component. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StationAiHolderComponent : Component +{ + public const string Container = StationAiCoreComponent.Container; + + [DataField] + public ItemSlot Slot = new(); +} diff --git a/Content.Shared/StationAi/StationAiVisionComponent.cs b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs similarity index 100% rename from Content.Shared/StationAi/StationAiVisionComponent.cs rename to Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs diff --git a/Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs b/Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs new file mode 100644 index 00000000000000..51d8793be0682a --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiWhitelistComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Indicates an entity that has can interact with this. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStationAiSystem))] +public sealed partial class StationAiWhitelistComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Enabled = true; +} diff --git a/Resources/Maps/Test/dev_map.yml b/Resources/Maps/Test/dev_map.yml index 520a4da5ae752f..ce735e7318f551 100644 --- a/Resources/Maps/Test/dev_map.yml +++ b/Resources/Maps/Test/dev_map.yml @@ -4386,6 +4386,13 @@ entities: - type: Transform pos: 1.5,-14.5 parent: 179 +- proto: PlayerStationAi + entities: + - uid: 14 + components: + - type: Transform + pos: -5.5,-5.5 + parent: 179 - proto: PortableGeneratorSuperPacman entities: - uid: 1016 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml index 7c4850fafcfa1c..9f2f9516625093 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -69,9 +69,10 @@ - type: entity id: MobTomatoKiller - parent: + parent: - BaseSimpleMob - MobDamageable + - MobPolymorphable - MobBloodstream - MobFlammable - MobCombat @@ -90,7 +91,7 @@ components: - HumanoidAppearance - type: Sprite - sprite: Mobs/Demons/tomatokiller.rsi + sprite: Mobs/Demons/tomatokiller.rsi noRot: true layers: - map: [ "enum.DamageStateVisualLayers.Base" ] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index 45171fff6768f0..19f43bf5509e1f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -1,45 +1,27 @@ - type: entity id: MobRevenant + parent: + - BaseMob + - Incorporeal name: revenant description: A spooky ghostie. components: - - type: MindContainer - - type: InputMover - - type: MobMover - type: Input context: "ghost" - type: MovementSpeedModifier baseWalkSpeed: 6 baseSprintSpeed: 6 - type: Sprite - noRot: true - drawdepth: Ghosts sprite: Mobs/Ghosts/revenant.rsi layers: - state: active - - type: Clickable - type: StatusEffects allowed: - Stun - Corporeal - - type: InteractionOutline - - type: Physics - bodyType: KinematicController - - type: Fixtures - fixtures: - fix1: - shape: - !type:PhysShapeCircle - radius: 0.40 - density: 80 - mask: - - GhostImpassable - - type: MovementIgnoreGravity - type: Damageable damageContainer: Biological - - type: Examiner - type: NoSlip - - type: Actions - type: Eye drawFov: false visMask: @@ -47,8 +29,6 @@ - Ghost - type: ContentEye maxZoom: 1.2, 1.2 - - type: DoAfter - - type: Alerts - type: NameIdentifier group: GenericNumber - type: GhostRole diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 41545aef89430b..8deefe9b8ec0ff 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -3,6 +3,7 @@ parent: - BaseMob - MobDamageable + - MobPolymorphable - MobAtmosExposed id: BaseSimpleMob suffix: AI diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 17dd1a460942a6..52994a72c65914 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -1,8 +1,13 @@ - type: entity id: Incorporeal - noSpawn: true + save: false + abstract: true description: Mobs without physical bodies components: + - type: Sprite + noRot: true + overrideContainerOcclusion: true # Always show up regardless of where they're contained. + drawdepth: Ghosts - type: CargoSellBlacklist - type: MovementSpeedModifier baseSprintSpeed: 12 @@ -32,8 +37,6 @@ categories: [ HideSpawnMenu ] components: - type: Sprite - overrideContainerOcclusion: true # Ghosts always show up regardless of where they're contained. - drawdepth: Ghosts sprite: Mobs/Ghosts/ghost_human.rsi color: "#fff8" layers: diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 5f8eafcc9fc3a2..3f3e3fdcf6ac06 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -1,12 +1,49 @@ +# Actions +# TODO + +# Ai +- type: entity + id: AiHolder + abstract: true + description: Handles AI interactions across holocards + AI cores + components: + - type: StationAiHolder + slot: + name: station-ai-mind-slot + whitelist: + tags: + - StationAi + - type: ContainerContainer + containers: + station_ai_mind_slot: !type:ContainerSlot + # Load-bearing. + # The issue is verbs check for same transparent container. + # The alternative is you add a bunch of events trying to override it; we don't even really need the container functionality + # anyway it's just a quality of life thing. + showEnts: True + - type: entity id: Holocard name: Holocard - parent: BaseItem + parent: + - BaseItem + - AiHolder suffix: Empty - # TODO: Sprite - # TODO: Item toggle to enable / disable AI functionality - # TODO: StationAiComponent - # TODO: Interact on the big box (if state good) puts it in. + components: + - type: Sprite + sprite: Objects/Devices/ai_card.rsi + layers: + - state: base + - state: full + map: ["unshaded"] + shader: unshaded + - type: Appearance + - type: GenericVisualizer + visuals: + enum.StationAiVisualState.Key: + unshaded: + Empty: { state: empty } + Occupied: { state: full } # Fix all of the interaction stuff etc. and make it actually work @@ -20,21 +57,28 @@ - type: entity id: PlayerStationAiEmpty name: Station AI - parent: BaseStructureDynamic + description: The latest in Artificial Intelligences. + parent: + - BaseStructure + - AiHolder suffix: Empty components: + - type: StationAiCore - type: StationAiVision - - type: ItemSlots - slots: - station_ai_mind_slot: - name: station-ai-mind-slot - type: Sprite sprite: Mobs/Silicon/station_ai.rsi layers: - state: base - state: ai_empty + map: ["unshaded"] shader: unshaded - - type: StationAi + - type: Appearance + - type: GenericVisualizer + visuals: + enum.StationAiVisualState.Key: + unshaded: + Empty: { state: ai_empty } + Occupied: { state: ai } - type: entity id: PlayerStationAi @@ -57,11 +101,18 @@ noSpawn: true suffix: DO NOT MAP components: + - type: Sprite + # Once it's in a core it's pretty much an abstract entity at that point. + visible: false + - type: ComplexInteraction + - type: Actions - type: Eye drawFov: false - - type: ContentEye - maxZoom: 1.44,1.44 - type: InputMover + - type: Tag + tags: + - HideContextMenu + - StationAi - type: RandomMetadata # TODO: Loadout name support # TODO: Copy it on station AI insertion @@ -76,12 +127,16 @@ noSpawn: true suffix: DO NOT MAP components: + - type: Eye + pvsScale: 1.5 - type: Sprite sprite: Mobs/Silicon/station_ai.rsi + visible: false layers: - state: holo shader: unshaded +# Borgs - type: entity id: PlayerBorgGeneric parent: BorgChassisGeneric diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 4a14b5332ec811..cbe09c29ad916d 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -3,6 +3,7 @@ parent: - BaseMob - MobDamageable + - MobPolymorphable - MobCombat - StripableInventoryBase id: BaseMobSpecies diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index 3a555beebfbdb4..feb9906ec90eb4 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -41,11 +41,17 @@ - type: CameraRecoil - type: MindContainer - type: MovementSpeedModifier - - type: Polymorphable - type: StatusIcon - type: RequireProjectileTarget active: False +- type: entity + save: false + id: MobPolymorphable + abstract: true + components: + - type: Polymorphable + # Used for mobs that have health and can take damage. - type: entity save: false diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml index d27c49e263c253..0dd3f6d59850e1 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml @@ -77,7 +77,6 @@ map: ["base"] - type: Input context: human - - type: BlockMovement - type: ToggleableGhostRole examineTextMindPresent: positronic-brain-installed examineTextMindSearching: positronic-brain-still-searching diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 86657eb46e0563..f303fe6f9d04da 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -4,6 +4,7 @@ name: airlock description: It opens, it closes, and maybe crushes you. components: + - type: StationAiWhitelist - type: MeleeSound soundGroups: Brute: diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 5e1b5cbe4dac31..c82d0028add277 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -1176,6 +1176,7 @@ - type: Tag id: Soup + - type: Tag id: Spear @@ -1203,6 +1204,9 @@ - type: Tag id: SpreaderIgnore +- type: Tag + id: StationAi + - type: Tag id: StationMapElectronics diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/base.png b/Resources/Textures/Objects/Devices/ai_card.rsi/base.png new file mode 100644 index 0000000000000000000000000000000000000000..244183c078c3d580cda31d0431ced9150a7cf7c9 GIT binary patch literal 4432 zcmeHKdr%X19$$12&o1Gq!oMKz-D(7_8{4~yO6+) z8pPgtR!=;9jcu{E_-Ko?w#*HsqoUxz**PD0^`>XVO3|VRC)2ZMTfB0=4ezk_YavbVi1Qt58|_5Zu};9R*?@>4$wh z=VD74Sgi0dFcV;15ES^k$9^NK4U2sIC?>J$Kw?}6oBvjV_}FQFqkh1eDs#V9_5SI? z%X|X77b&~yI$332v$G{aUX}Xu^rdU<&(#&)zZ2cLx?i95HhSwpnQ?B<{ii>v-P4_4 z@a|UYcmFt$ySe?%H?Lku+O=}m!HiaIpDI>+?T+z$%eklGkI#MaT3Pw*vmdTJb2{tm zEeDG!+UMF|9PHS0{-&&KRpa;3niTilO^(9Ov!;RMU;J|E`s;>j`8o&+t7gq+yVYzS z%Liof>ZU!W>^6JMOKbCwW-d?chkI&{5T731Vc5Ss_FM+LqUnCoS6ykvT{ZR126^M0 zh%K@2%u`%yA)4h|KcBz-gX-EhUfvrh%qw2-MfJx+h6k^;ZF&@z@zEuR0EeA8#L)TH zQ}zu{$=;1b`W+jb$LcPjcW-}q=AXB}82a^2cg3wCA!@~v+OX6;hI3*Y(hb+$Y!WXG zwJ&&KUrdkCv#dIP@#fx^{;v|g+|f4c^3J$#`;8m76*N3N^;t*5Z%(vp+H*5MNiIxo zZt!*YG+ckB;l?vt$P@J^dz*7~e~8&%vnm{+wYlcRSZ7m7{lU(zimtY_yWePf|M=(U zWl0^g;_Zv@&4X#*n11zvG0|U}VJ#ZG@Tat;!B)i;_S1hqI_2@_K*Ji}(XSuxO2Hmk zE8jVuxjQ(R;@f!hTCeGIJ|b>Y*Ag?oIBlO@A$#C{Xv-|>jg5Oz`0~1`S5A2WY!;7PMD^Z4E(j9tBBrPDT;%_(0!5 zkTEUbBdHQbgq@6=^_q|eADu;DmNp^lbT-W9Gc(0(b|uf`SLQgV$`T5vk+f8~F+czS z4Hgh}!YZY1rhGquXay61FhmAb#BJwP& zMj^nP2`LsuAE8wG{eFdCqu_YAQibEV5>qSHY7|(YLWNf(1E^O>k|0JfEQ~<$tWRV) zFDzk_POeNeAqddJWAS->HrqJ7R~Tgh@SzNlKBY>5DLo$LWDh~iEC(Q?0sWwd-~dyq z%wq(ujHj5)a>gqrO@^SUaerSKUm8k>rj$%6;{m1uSXDJ;$tPZl5c@}gfSvol?35o_#E=)sfQ*bBhR2lTB76gYnDT+c} zIHlF-7_G+1=t595MXcg@4++Z2dPp~;^m*MOg+w@!Znv5cwE~-v*h@*#1suQ(uwI(; z3lk~_>tXUmQsPskS8Mbbj_Y-}TBXPF38NPnUI4u)p{g*2MjO&dg&{yXfLc=OQ~(f? zgKP*h&yXU=J2Kp=(+UOv`wtyJqN`76a2Vp6Rat8(s2= zk156r{sH;Hr_w7gHx7W$tZ?V*EDJO&{a!zPycS5F@MW(TAZT8@bcXF)F)slKBSfn$ zGomkYe&oWW+SN}4f#|W-lI|#Iql)6@Bqd^be_1?mW!P}1vC&muC-bd$yb2vrnx5Tm z>vYkBEB|wOurDkp>%E%NijA}0UV=djujRKYcFW$g$24sVy65y{yy z?Bi$Xg@=otJodf2p!`ts(KWN5?^{;$v*FxhmU~N5XJ^lmJw2;ZUenyMaJU^0eij|P z*GAz3=HT+&XW7WP#qn`bRac|q g&)vN>7n8?<1IlWm!V?ox>i`dsH8aQ3m{GL#zrOQGZU6uP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png b/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..7e61f368de294ae506cdab73e818f0f594bb260c GIT binary patch literal 4292 zcmeHKeQ?v}8CR0!%Ui-=X$Wa^6QboSlVw?oE$b`-u?_LHPTj=Kpkqs9Np@5s%ScM> zWI!`$3Zq?tru1mLcC3R!*TBXKy_8S_Bs~~y(=7!$$_O;1(C)(EC~dl#mc6p`dF$OS zck6$#PnPt2{GR9ez0dQW-(PymmSh?74G;unxl5hp;0}XpVMZGG{rQu1d%^9kM&DAc zoQ=R?MGlCy0<1NJ1z3oR0SJnID0$Cib;94fHJDRy6x%RkJ^9M1EP72={qJ{NotfRC zSswf9^HoQrCikMZ{Rd8O%$7cEYiIL5Y00d+@txqwukO9;X6YZ-cU5J6@bH-*uKagK z*Zb$^&zadC^uIa#q5A6Bw3Ca?v-2-W^G=nQQq!JJW)3$WIIDbp?BJz4a;K;5d4-;H ztg34Ih2F|Tn@rbA)^;QLDJR+tJ?Blqr}~;n=?h|;GOg-F2UE~wwcoYu-O%M#4hTwG zCpsKnx5F`95Gdo@-E!tUO&r@yVN=*gUCHZM*?c=7>9L9T!QiXGcd^fdHz z7GM67=zr?Z52nmIo}BNUX_)F#|uh|PCEhX;i>u$OWYpsh=)!= zP?W-PFF^J7Y$CvY1b3YEaP4tpVCC`9PMNFI({no+M2}DNGAHtM z{IQ7SDJy3qP|7N>C`oc0$^sEeP$t4^4Fqu9VjctK4yhU&;shNEfEz`C!*f=%%|?(Y zVJ#w2lC%0ziX#Y==XeWiu@qT(KRX8EenkXb$<~g|N{8YB6i1N(jud#12o*p{fiQ!- zC_l>bCacBb=ZS!oi$n1oy;xQx7A&VIu|WX~hk|j1PB^{D>t+zM(KIUY*0NdvIDi=t zL%bYOM^!#i5-K!S=M%S@2`g^GNdhNHTM<5LR4FJb=tUhBHyH^suF)5U2IT;1S-n#M zKwJ*0p&g3AYO>;!OlY({65w!;l6whj`lhp(OYBH0wiK6i$A5PI`Gi}BTaT9GC zV=wb!py7X6>*E946N_Fdsvv$tT$Grp3Snj9E%90_#)k=p<6}XyT%rV(trz%so&YPM z;%eAXPyqYKP{R((#cyba0B^=AB0!;5oW(&i1Z*h9lSL@c2Pna6v9UHb(N81jsvOWF ztRgH50v-WZph4qY!E=X}YTo#4A~k}(4uCS$M4_XU5ePO^ET-=m!`0fc|Kh_QHyE?U z0KbF`Y+Ya{#74HlA-+Ja-{xm%9lp&FFgzJ#Li|qBHA&Zm7?_apWOq%{H6aEjWIWkj z|2Mh}qlYOW1pWadknc>o9DgqFrsh>KHm1~!0 z_MJYK_72f#zcVE-cB{$A zT&!7@oSNfZ_=^j<5OiqD^%n+{IzBec-QJXJus;ZGf8)FDdn%ue&1i1~p&_@c%(=Iu HdhLGz@YfZx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/full.png b/Resources/Textures/Objects/Devices/ai_card.rsi/full.png new file mode 100644 index 0000000000000000000000000000000000000000..59131c8c0aafc5f647d4409168265f23cc05e667 GIT binary patch literal 5121 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json b/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json new file mode 100644 index 00000000000000..0982109f9ab2c7 --- /dev/null +++ b/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/1feffb747a33434a9d28450fc52ade75253aeba5/icons/obj/aicards.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "empty", + "delays": [ + [ + 0.4, + 0.4 + ] + ] + }, + { + "name": "full", + "delays": [ + [ + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4, + 0.4 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Devices/output.rsi/aicard-full-unshaded.png b/Resources/Textures/Objects/Devices/output.rsi/aicard-full-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..51a309d579ec0babdbdb117920c416e178e37599 GIT binary patch literal 777 zcmYk2dq`6O6vk(g4nv2%j4Ul*DZWTh!`IbqxvA+wd?bbTPz;7!*mS1LYZ6o-Dnw>Z z(HLfA?UK5N6W@~9%!x>ZyWQy@dElJGIh^zH<9l^8i{b8e-i<^e zxu-Gd*+lOmvX1OR)P@K5y-B2F^J(;yoT_$2BQH}YpFTdH)*O*dJ-am_gbWdr`zBktjW<{^8itBWe&P~yV146sa(nyVDz9T|4mJ2v5$fSYW`#yhmH9wv*!?&zGcvg*%qut)xX9S z>t1$sLdZ9NC58Es!%wye(tNxDhSH3bNFM~7SL#Ta~aGBsJb0AQ^lC|r2;F9 z7;S4q*z(Spzw4apJ>1FJu^s_Q7JjzzB;xf?+LrNEBRJr^y$Tz)xUG;7_-@6`%UDwA zn4maCCz%G|(Os)V%1s1SR))T(rdBp=C!=>@mfVVBMyN)kN9%!jFO#4q##qEr`+=%; z`VREOb#{TJN_TjLdMvWNX7U{VX-%F9`q!WzZfjC#DA%fi4y)3nr6fVa;U_TX|K{QP lKXoFrat0r{5DgqqcUW9ur=%6PVI|Wsr(MsYi>Y_({{hD)pQQi* literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/output.rsi/aicard-full.png b/Resources/Textures/Objects/Devices/output.rsi/aicard-full.png new file mode 100644 index 0000000000000000000000000000000000000000..03908b5284f4fd89c2ea79adc1227a271591a21b GIT binary patch literal 1746 zcmcJOeKb^Q7{JGOqpjhvO$n3Hu9UJzM`epE5lT&47D0*#GvP^WOJ8?|I+zJkRg>z2|pq z_n&8OV~)XK<^=}$?$phfb%Qa-RQLZmX5V5A#NcVjTENK)iLb@X3&mK}`X9XiY- z+=sX9ddB!IH+P|(;_j{>^D9Os+k%;k@CCWKLdGWUy5TFUuVwi%PiLg|nh1E8c2~8) zQZDTSM;^dTt4SPci*y);akQa`Mm`=_&z=Uu#M< zm%$o%(y#R^)u?5UX>|>_Y|;0tcJi|#FY1>HnZKjV2>pOGO_?-pj<+h!BNDXk*)6H? z5!`7Y6_jTW)ScR3U-70#92|SR3Z-Rg6?xUKy_jA1d&cR>Y12tGKq(MX=?32xJ`(>C z(c0o7!%-ip1%IltQcWkKCG50u#BI6k1M3-UO>x+%xMjD-upyzM<8Esy{8(uf z{;^b@4iA!Dx_PZ(Wn(!&5Pj0vwm8Fu>)VP}oSGzwh<$kC(dZXu6j5M7e+QySSGIY{ zHhNG7q9hFzUhJx)Uf*bUv!@yv^zjZ(72k;ORd89+`hX9w`+ z;Td1Bc{B&uj)qv>%upLQT2tEg3MkZvJfr8;M?7mGO<}r^km_#W$vNzKlvgWb0`grHl)vBjV?XN5Y_l~^CDVq*ht{-PD!io+8}4Wm*S1$HyoERj;FIsaJaJFm zY?%S1vZY#+P%XMOY4P|M?NB9K`C)hrU%ipFsZHCA=(U{v{O2N!=^T~VtR3nl0ZY)U zbd|xF(_|0s9B>_?dusv_Rh*BTq`XI^&r@}yano@59v8ohgvYm1{m|G*R>y~-BqSc~ zpL7%t^{8UfwCksg%7)8pVBvEnOd4Xzx@00a4^9?7BfY%gFlnI@`}*?J*5D5|A;)rV zD{WY=Zx!bq?yoQg=dsq2cJ|vnZrgCU`^%M zm$$A$j)enC`qz~s6LLF0Gz4MP8^tT{ z%F2`a<`O4*Zre9N@nrf`T<7mlRRy3=*S7egUBYn{kACL_)Frm6fw(G3ZXG_3jn;a6 zDIS_TlFcN;mPjsTrj$L~+Ln9#Q+x$>IM7Y3@7|2uqG$TtVDfyXUT03J9)?y_qL=s+ zgcVLvtdDR7!cg75T93Aa10TZW$IQg}To^(<;S)#$2TBMQvphaOIAazW+!j!#3jn&a z&vC*VHluS8MtU!4tvz#4Qkm*$=hDV#-|vBhL#ZzL-ay`38ZVf@n7*W%chZIYc$F>O;bH2m8MN0{tu|2u*vQx z#nPMPJau9TvY?^#sptg47j(kIx&k-7FR#(^>+Jj4E5PJSnw0-rw*2V-rnqD=YWB3V UyS=uT6R77J=(pXMjv*Cu-d=FzZ7>jFJ=nyn zvQXUf#{s8@n>goMn*W_`4pf|dD@}gh@juJ2|5aVObN_z-pW@6{7){ae)~DY-p1Xj+HA|+@=plM{%My0) zwSCu9*GrFHe&I(SSDz;Ayp+mxd+8Umj`uJ8OK>>Y3bKK%$(@S-T#Y-0@Ywq#$= z;m;oV{Cvvi(-Tm=RPqZp9|qW6ftXbM+1j%olg~E5djP@)crQTb1`IOb`PDVV>;Xaz zVDi%$P^%Za0SYyM$xmy*oZpAy2N}TNr#B!ru?7%P1IR1XfJ_Yl)BrW$+74@UVnV&j0`b07*qo IM6N<$f`|IS_5c6? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/output.rsi/meta.json b/Resources/Textures/Objects/Devices/output.rsi/meta.json new file mode 100644 index 00000000000000..500ecb8e3e7d14 --- /dev/null +++ b/Resources/Textures/Objects/Devices/output.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/1feffb747a33434a9d28450fc52ade75253aeba5/icons/obj/aicards.dmi", "states": [{"name": "aicard", "directions": 1, "delays": [[0.4, 0.4]]}, {"name": "aicard-full", "directions": 1, "delays": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]]}, {"name": "aicard-full-unshaded", "directions": 1, "delays": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]]}, {"name": "aicard-unshaded", "directions": 1, "delays": [[0.4, 0.4]]}]} \ No newline at end of file From 0a8d73fe41afd2cdae42f1e2b44c39c2e5c0d262 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 15 Aug 2024 21:12:24 +1000 Subject: [PATCH 06/40] Shuffle interactions a bit --- .../ActionBlocker/ActionBlockerSystem.cs | 15 +++++++++++ .../Interaction/SharedInteractionSystem.cs | 13 +++------- .../StationAi/SharedStationAiSystem.cs | 5 ++++ .../UserInterface/ActivatableUIComponent.cs | 8 +++--- .../UserInterface/ActivatableUISystem.cs | 25 +++++++++++-------- Content.Shared/Verbs/SharedVerbSystem.cs | 17 +++++++------ Content.Shared/Verbs/VerbEvents.cs | 8 +++++- .../Entities/Structures/Power/apc.yml | 1 + 8 files changed, 59 insertions(+), 33 deletions(-) diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index afa1e19eaded26..8a4b5baffd34d2 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Emoting; using Content.Shared.Hands; using Content.Shared.Interaction; +using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Events; using Content.Shared.Item; using Content.Shared.Movement.Components; @@ -22,9 +23,14 @@ public sealed class ActionBlockerSystem : EntitySystem { [Dependency] private readonly SharedContainerSystem _container = default!; + private EntityQuery _complexInteractionQuery; + public override void Initialize() { base.Initialize(); + + _complexInteractionQuery = GetEntityQuery(); + SubscribeLocalEvent(OnMoverStartup); } @@ -53,6 +59,15 @@ public bool UpdateCanMove(EntityUid uid, InputMoverComponent? component = null) return !ev.Cancelled; } + /// + /// Checks if a given entity is able to do specific complex interactions. + /// This is used to gate manipulation to general humanoids. If a mouse shouldn't be able to do something, then it's complex. + /// + public bool CanComplexInteract(EntityUid user) + { + return _complexInteractionQuery.HasComp(user); + } + /// /// Raises an event directed at both the user and the target entity to check whether a user is capable of /// interacting with this entity. diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 4fa82995eee5ab..38350c2c438197 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -75,7 +75,6 @@ public abstract partial class SharedInteractionSystem : EntitySystem private EntityQuery _wallMountQuery; private EntityQuery _delayQuery; private EntityQuery _uiQuery; - private EntityQuery _complexInteractionQuery; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable; @@ -98,7 +97,6 @@ public override void Initialize() _wallMountQuery = GetEntityQuery(); _delayQuery = GetEntityQuery(); _uiQuery = GetEntityQuery(); - _complexInteractionQuery = GetEntityQuery(); SubscribeLocalEvent(HandleUserInterfaceRangeCheck); SubscribeLocalEvent(OnBoundInterfaceInteractAttempt); @@ -166,7 +164,7 @@ private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev return; } - if (uiComp.RequireHands && !_handsQuery.HasComp(ev.Actor)) + if (uiComp.RequiresComplex && !_actionBlockerSystem.CanComplexInteract(ev.Actor)) ev.Cancel(); } @@ -441,7 +439,7 @@ public void UserInteraction( public void InteractHand(EntityUid user, EntityUid target) { - var complexInteractions = SupportsComplexInteractions(user); + var complexInteractions = _actionBlockerSystem.CanComplexInteract(user); if (!complexInteractions) { InteractionActivate(user, @@ -1334,13 +1332,10 @@ public bool TryGetUsedEntity(EntityUid user, [NotNullWhen(true)] out EntityUid? return ev.Handled; } - /// - /// Checks if a given entity is able to do specific complex interactions. - /// This is used to gate manipulation to general humanoids. If a mouse shouldn't be able to do something, then it's complex. - /// + [Obsolete("Use ActionBlockerSystem")] public bool SupportsComplexInteractions(EntityUid user) { - return _complexInteractionQuery.HasComp(user); + return _actionBlockerSystem.CanComplexInteract(user); } } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 06b0731fde8af6..ebf03e71b6863d 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -21,6 +21,9 @@ public abstract class SharedStationAiSystem : EntitySystem [Dependency] private readonly MetaDataSystem _metadata = default!; /* + * TODO: Fix examines + * TODO: Vismask on the AI eye + * TODO: Add door bolting * TODO: Sprite / vismask visibility + action * TODO: Double-check positronic interactions didn't break * Need action bar @@ -173,6 +176,7 @@ private void OnAiInsert(Entity ent, ref EntInsertedIntoC // Just so text and the likes works properly _metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName); + EnsureComp(args.Entity); EnsureComp(args.Entity); EnsureComp(args.Entity); EnsureComp(args.Entity); @@ -196,6 +200,7 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo _eye.SetTarget(args.Entity, null, eyeComp); } + RemCompDeferred(args.Entity); RemCompDeferred(args.Entity); RemCompDeferred(args.Entity); RemCompDeferred(args.Entity); diff --git a/Content.Shared/UserInterface/ActivatableUIComponent.cs b/Content.Shared/UserInterface/ActivatableUIComponent.cs index 93f05acac05370..0e124070fc7b47 100644 --- a/Content.Shared/UserInterface/ActivatableUIComponent.cs +++ b/Content.Shared/UserInterface/ActivatableUIComponent.cs @@ -12,7 +12,7 @@ public sealed partial class ActivatableUIComponent : Component /// /// Whether the item must be held in one of the user's hands to work. - /// This is ignored unless is true. + /// This is ignored unless is true. /// [ViewVariables(VVAccess.ReadWrite)] [DataField] @@ -29,15 +29,15 @@ public sealed partial class ActivatableUIComponent : Component public LocId VerbText = "ui-verb-toggle-open"; /// - /// Whether you need a hand to operate this UI. The hand does not need to be free, you just need to have one. + /// Whether you need to be able to do complex interactions to operate this UI. /// /// /// This should probably be true for most machines & computers, but there will still be UIs that represent a - /// more generic interaction / configuration that might not require hands. + /// more generic interaction / configuration that might not require complex. /// [ViewVariables(VVAccess.ReadWrite)] [DataField] - public bool RequireHands = true; + public bool RequiresComplex = true; /// /// Entities that are required to open this UI. diff --git a/Content.Shared/UserInterface/ActivatableUISystem.cs b/Content.Shared/UserInterface/ActivatableUISystem.cs index b1006b2a742382..7eb195c0b19d31 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.cs @@ -101,7 +101,7 @@ private bool ShouldAddVerb(EntityUid uid, ActivatableUIComponent component, G if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Using ?? default)) return false; - if (component.RequireHands) + if (component.RequiresComplex) { if (args.Hands == null) return false; @@ -191,19 +191,22 @@ private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUICompone if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp(user) || aui.BlockSpectators)) return false; - if (aui.RequireHands) + if (aui.RequiresComplex) + { + if (!_blockerSystem.CanComplexInteract(user)) + return false; + } + + if (aui.InHandsOnly) { if (!TryComp(user, out HandsComponent? hands)) return false; - if (aui.InHandsOnly) - { - if (!_hands.IsHolding(user, uiEntity, out var hand, hands)) - return false; + if (!_hands.IsHolding(user, uiEntity, out var hand, hands)) + return false; - if (aui.RequireActiveHand && hands.ActiveHand != hand) - return false; - } + if (aui.RequireActiveHand && hands.ActiveHand != hand) + return false; } if (aui.AdminOnly && !_adminManager.IsAdmin(user)) @@ -274,13 +277,13 @@ public void CloseAll(EntityUid uid, ActivatableUIComponent? aui = null) private void OnHandDeselected(Entity ent, ref HandDeselectedEvent args) { - if (ent.Comp.RequireHands && ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand) + if (ent.Comp.InHandsOnly && ent.Comp.RequireActiveHand) CloseAll(ent, ent); } private void OnHandUnequipped(Entity ent, ref GotUnequippedHandEvent args) { - if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) + if (ent.Comp.InHandsOnly) CloseAll(ent, ent); } } diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index 319f927c7b3d41..c3f906f2936e75 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -77,6 +77,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, out var hands); @@ -84,7 +85,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -93,35 +94,35 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(@using.Value, verbEvent, true); // directed at used, not at target verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(InnateVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(user, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(AlternativeVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(ActivationVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } if (types.Contains(typeof(ExamineVerb))) { - var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -129,7 +130,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, canAccess, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent, true); verbs.UnionWith(verbEvent.Verbs); } @@ -137,7 +138,7 @@ public SortedSet GetLocalVerbs(EntityUid target, EntityUid user, List(user, target, @using, hands, canInteract, access, extraCategories); + var verbEvent = new GetVerbsEvent(user, target, @using, hands, canInteract: canInteract, canComplexInteract: canComplexInteract, canAccess: canAccess, extraCategories); RaiseLocalEvent(target, verbEvent); verbs.UnionWith(verbEvent.Verbs); } diff --git a/Content.Shared/Verbs/VerbEvents.cs b/Content.Shared/Verbs/VerbEvents.cs index 6b3fd327c99773..6bca97925bf99a 100644 --- a/Content.Shared/Verbs/VerbEvents.cs +++ b/Content.Shared/Verbs/VerbEvents.cs @@ -113,6 +113,11 @@ public sealed class GetVerbsEvent : EntityEventArgs where TVerb : Verb /// public readonly bool CanInteract; + /// + /// Cached version of CanComplexInteract + /// + public readonly bool CanComplexInteract; + /// /// The User's hand component. /// @@ -130,13 +135,14 @@ public sealed class GetVerbsEvent : EntityEventArgs where TVerb : Verb /// public readonly EntityUid? Using; - public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canAccess, List extraCategories) + public GetVerbsEvent(EntityUid user, EntityUid target, EntityUid? @using, HandsComponent? hands, bool canInteract, bool canComplexInteract, bool canAccess, List extraCategories) { User = user; Target = target; Using = @using; Hands = hands; CanAccess = canAccess; + CanComplexInteract = canComplexInteract; CanInteract = canInteract; ExtraCategories = extraCategories; } diff --git a/Resources/Prototypes/Entities/Structures/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml index d8f32922c827ad..a52292d7fb902e 100644 --- a/Resources/Prototypes/Entities/Structures/Power/apc.yml +++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml @@ -6,6 +6,7 @@ placement: mode: SnapgridCenter components: + - type: StationAiWhitelist - type: AmbientOnPowered - type: AmbientSound volume: -9 From d1f89dd4be83233e22cf5dd062b2581f3c6da062 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 15 Aug 2024 22:17:12 +1000 Subject: [PATCH 07/40] navmap stuff --- .../CrewMonitoringNavMapControl.cs | 10 +- Content.Client/Pinpointer/NavMapData.cs | 389 ++++++++++++++++++ Content.Client/Pinpointer/UI/NavMapControl.cs | 374 +---------------- .../PowerMonitoringConsoleNavMapControl.cs | 46 +-- Content.Client/StationAi/StationAiOverlay.cs | 118 ++---- 5 files changed, 463 insertions(+), 474 deletions(-) create mode 100644 Content.Client/Pinpointer/NavMapData.cs diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs index 340cc9af891c76..fb3b12122a07de 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs @@ -15,9 +15,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl public CrewMonitoringNavMapControl() : base() { - WallColor = new Color(192, 122, 196); - TileColor = new(71, 42, 72); - BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); + NavData.WallColor = Color.FromSrgb(new Color(192, 122, 196)); + NavData.TileColor = Color.FromSrgb(new(71, 42, 72)); + BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); _trackedEntityLabel = new Label { @@ -41,7 +41,7 @@ public CrewMonitoringNavMapControl() : base() }; _trackedEntityPanel.AddChild(_trackedEntityLabel); - this.AddChild(_trackedEntityPanel); + AddChild(_trackedEntityPanel); } protected override void FrameUpdate(FrameEventArgs args) @@ -56,7 +56,7 @@ protected override void FrameUpdate(FrameEventArgs args) return; } - foreach ((var netEntity, var blip) in TrackedEntities) + foreach (var (netEntity, blip) in TrackedEntities) { if (netEntity != Focus) continue; diff --git a/Content.Client/Pinpointer/NavMapData.cs b/Content.Client/Pinpointer/NavMapData.cs new file mode 100644 index 00000000000000..c2bfaba4414f31 --- /dev/null +++ b/Content.Client/Pinpointer/NavMapData.cs @@ -0,0 +1,389 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using Content.Shared.Atmos; +using Content.Shared.Pinpointer; +using Robust.Client.Graphics; +using Robust.Shared.Collections; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Utility; + +namespace Content.Client.Pinpointer; + +public sealed class NavMapData +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + // Default colors + public Color WallColor = Color.ToSrgb(new Color(102, 217, 102)); + public Color TileColor = new(30, 67, 30); + + /// + /// Offset for the data to be drawn at. + /// + public Vector2 Offset; + + public List<(Vector2, Vector2)> TileLines = new(); + public List<(Vector2, Vector2)> TileRects = new(); + + public Dictionary TilePolygons = new(); + + private Dictionary _horizLines = new(); + private Dictionary _horizLinesReversed = new(); + private Dictionary _vertLines = new(); + private Dictionary _vertLinesReversed = new(); + + protected float FullWallInstep = 0.165f; + protected float ThinWallThickness = 0.165f; + protected float ThinDoorThickness = 0.30f; + + // TODO: Power should be updating it on its own. + /// + /// Called if navmap updates + /// + public event Action? OnUpdate; + + public NavMapData() + { + IoCManager.InjectDependencies(this); + } + + public void Draw(DrawingHandleBase handle, Func scale, Box2 localAABB) + { + var verts = new ValueList(TileLines.Count * 2); + + // Draw floor tiles + if (TilePolygons.Count != 0) + { + foreach (var tri in TilePolygons.Values) + { + verts.Clear(); + + foreach (var vert in tri) + { + verts.Add(new Vector2(vert.X, -vert.Y)); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts.Span, TileColor); + } + } + + // Draw map lines + if (TileLines.Count != 0) + { + verts.Clear(); + + foreach (var (o, t) in TileLines) + { + var origin = scale.Invoke(o - Offset); + var terminus = scale.Invoke(t - Offset); + + verts.Add(origin); + verts.Add(terminus); + } + + if (verts.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts.Span, WallColor); + } + + // Draw map rects + if (TileRects.Count != 0) + { + var rects = new ValueList(TileRects.Count * 8); + + foreach (var (lt, rb) in TileRects) + { + var leftTop = scale.Invoke(lt - Offset); + var rightBottom = scale.Invoke(rb - Offset); + + var rightTop = new Vector2(rightBottom.X, leftTop.Y); + var leftBottom = new Vector2(leftTop.X, rightBottom.Y); + + rects.Add(leftTop); + rects.Add(rightTop); + rects.Add(rightTop); + rects.Add(rightBottom); + rects.Add(rightBottom); + rects.Add(leftBottom); + rects.Add(leftBottom); + rects.Add(leftTop); + } + + if (rects.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, WallColor); + } + } + + public void UpdateNavMap(EntityUid entity) + { + // Clear stale values + TilePolygons.Clear(); + TileLines.Clear(); + TileRects.Clear(); + + UpdateNavMapFloorTiles(entity); + UpdateNavMapWallLines(entity); + UpdateNavMapAirlocks(entity); + } + + private void UpdateNavMapFloorTiles(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp)) + { + return; + } + + var lookup = _entManager.System(); + var tiles = _entManager.System().GetAllTilesEnumerator(entity.Owner, entity.Comp); + + while (tiles.MoveNext(out var tile)) + { + var box = lookup.GetLocalBounds(tile.Value.GridIndices, entity.Comp.TileSize); + box = new Box2(box.Left, -box.Bottom, box.Right, -box.Top); + var arr = new Vector2[4]; + + arr[0] = box.BottomLeft; + arr[1] = box.BottomRight; + arr[2] = box.TopRight; + arr[3] = box.TopLeft; + + TilePolygons[tile.Value.GridIndices] = arr; + } + } + + private void UpdateNavMapWallLines(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || + !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) + { + return; + } + + // We'll use the following dictionaries to combine collinear wall lines + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); + + const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; + const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; + const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; + const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; + + foreach (var (chunkOrigin, chunk) in entity.Comp2.Chunks) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Wall; + + var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * entity.Comp1.TileSize; + + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinWall(tileData, tile); + continue; + } + + tile = tile with { Y = -tile.Y }; + NavMapChunk? neighborChunk; + + // North edge + var neighborData = 0; + if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+1]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) + neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; + + if ((neighborData & southMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), + tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), _horizLines, + _horizLinesReversed); + } + + // East edge + neighborData = 0; + if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) + neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; + + if ((neighborData & westMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), + tile + new Vector2i(entity.Comp1.TileSize, 0), _vertLines, _vertLinesReversed); + } + + // South edge + neighborData = 0; + if (relativeTile.Y != 0) + neighborData = chunk.TileData[i - 1]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) + neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; + + if ((neighborData & northMask) == 0) + { + AddOrUpdateNavMapLine(tile, tile + new Vector2i(entity.Comp1.TileSize, 0), _horizLines, + _horizLinesReversed); + } + + // West edge + neighborData = 0; + if (relativeTile.X != 0) + neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) + neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; + + if ((neighborData & eastMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), tile, _vertLines, + _vertLinesReversed); + } + + // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these + TileLines.Add((tile + new Vector2(0, -entity.Comp1.TileSize), tile + new Vector2(entity.Comp1.TileSize, 0))); + } + } + + // Record the combined lines + foreach (var (origin, terminal) in _horizLines) + { + TileLines.Add((origin, terminal)); + } + + foreach (var (origin, terminal) in _vertLines) + { + TileLines.Add((origin, terminal)); + } + } + + private void UpdateNavMapAirlocks(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || + !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) + { + return; + } + + foreach (var chunk in entity.Comp2.Chunks.Values) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Airlock; + + var relative = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * entity.Comp1.TileSize; + + // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinAirlock(tileData, tile); + continue; + } + + // Otherwise add a single full tile airlock + TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), + new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); + + TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), + new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); + } + } + } + + private void AddRectForThinWall(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); + var rightBottom = new Vector2(0.5f, 0.5f); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + + // TODO NAVMAP + // Consider using faster rotation operations, given that these are always 90 degree increments + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + } + } + + private void AddRectForThinAirlock(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); + var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); + var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); + var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); + } + } + + public void AddOrUpdateNavMapLine( + Vector2i origin, + Vector2i terminus, + Dictionary lookup, + Dictionary lookupReversed) + { + Vector2i foundTermius; + Vector2i foundOrigin; + + // Does our new line end at the beginning of an existing line? + if (lookup.Remove(terminus, out foundTermius)) + { + DebugTools.Assert(lookupReversed[foundTermius] == terminus); + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just connects two existing lines + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = foundTermius; + lookupReversed[foundTermius] = foundOrigin; + } + else + { + // Our new line precedes an existing line, extending it further to the left + lookup[origin] = foundTermius; + lookupReversed[foundTermius] = origin; + } + return; + } + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just extends an existing line further to the right + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = terminus; + lookupReversed[terminus] = foundOrigin; + return; + } + + // Completely disconnected line segment. + lookup.Add(origin, terminus); + lookupReversed.Add(terminus, origin); + } +} diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index 413b41c36a6f43..d7d821b37f5946 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -45,13 +45,7 @@ public partial class NavMapControl : MapGridControl public Dictionary TrackedCoordinates = new(); public Dictionary TrackedEntities = new(); - public List<(Vector2, Vector2)> TileLines = new(); - public List<(Vector2, Vector2)> TileRects = new(); - public List<(Vector2[], Color)> TilePolygons = new(); - - // Default colors - public Color WallColor = new(102, 217, 102); - public Color TileColor = new(30, 67, 30); + public NavMapData NavData = new(); // Constants protected float UpdateTime = 1.0f; @@ -61,22 +55,13 @@ public partial class NavMapControl : MapGridControl protected static float MaxDisplayedRange = 128f; protected static float DefaultDisplayedRange = 48f; protected float MinmapScaleModifier = 0.075f; - protected float FullWallInstep = 0.165f; - protected float ThinWallThickness = 0.165f; - protected float ThinDoorThickness = 0.30f; // Local variables private float _updateTimer = 1.0f; - private Dictionary _sRGBLookUp = new(); protected Color BackgroundColor; protected float BackgroundOpacity = 0.9f; private int _targetFontsize = 8; - private Dictionary _horizLines = new(); - private Dictionary _horizLinesReversed = new(); - private Dictionary _vertLines = new(); - private Dictionary _vertLinesReversed = new(); - // Components private NavMapComponent? _navMap; private MapGridComponent? _grid; @@ -119,7 +104,7 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl _transformSystem = EntManager.System(); _navMapSystem = EntManager.System(); - BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); + BackgroundColor = Color.FromSrgb(NavData.TileColor.WithAlpha(BackgroundOpacity)); RectClipContent = true; HorizontalExpand = true; @@ -179,13 +164,12 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl public void ForceNavMapUpdate() { - EntManager.TryGetComponent(MapUid, out _navMap); - EntManager.TryGetComponent(MapUid, out _grid); - EntManager.TryGetComponent(MapUid, out _xform); - EntManager.TryGetComponent(MapUid, out _physics); - EntManager.TryGetComponent(MapUid, out _fixtures); + if (MapUid == null) + { + return; + } - UpdateNavMap(); + NavData.UpdateNavMap(MapUid.Value); } public void CenterToCoordinates(EntityCoordinates coordinates) @@ -228,7 +212,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { if (!blip.Selectable) continue; - + var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length(); if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance) @@ -293,80 +277,11 @@ protected override void Draw(DrawingHandleScreen handle) if (_physics != null) offset += _physics.LocalCenter; - var offsetVec = new Vector2(offset.X, -offset.Y); - - // Wall sRGB - if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB)) - { - wallsRGB = Color.ToSrgb(WallColor); - _sRGBLookUp[WallColor] = wallsRGB; - } - - // Draw floor tiles - if (TilePolygons.Any()) - { - Span verts = new Vector2[8]; - - foreach (var (polygonVerts, polygonColor) in TilePolygons) - { - for (var i = 0; i < polygonVerts.Length; i++) - { - var vert = polygonVerts[i] - offset; - verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y)); - } - - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor); - } - } - - // Draw map lines - if (TileLines.Any()) - { - var lines = new ValueList(TileLines.Count * 2); - - foreach (var (o, t) in TileLines) - { - var origin = ScalePosition(o - offsetVec); - var terminus = ScalePosition(t - offsetVec); - - lines.Add(origin); - lines.Add(terminus); - } - - if (lines.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB); - } - - // Draw map rects - if (TileRects.Any()) - { - var rects = new ValueList(TileRects.Count * 8); - - foreach (var (lt, rb) in TileRects) - { - var leftTop = ScalePosition(lt - offsetVec); - var rightBottom = ScalePosition(rb - offsetVec); - - var rightTop = new Vector2(rightBottom.X, leftTop.Y); - var leftBottom = new Vector2(leftTop.X, rightBottom.Y); - - rects.Add(leftTop); - rects.Add(rightTop); - rects.Add(rightTop); - rects.Add(rightBottom); - rects.Add(rightBottom); - rects.Add(leftBottom); - rects.Add(leftBottom); - rects.Add(leftTop); - } - - if (rects.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB); - } + NavData.Offset = new Vector2(offset.X, -offset.Y); + NavData.Draw(handle, ScalePosition, Box2.UnitCentered); // Invoke post wall drawing action - if (PostWallDrawingAction != null) - PostWallDrawingAction.Invoke(handle); + PostWallDrawingAction?.Invoke(handle); // Beacons if (_beacons.Pressed) @@ -436,279 +351,18 @@ protected override void Draw(DrawingHandleScreen handle) protected override void FrameUpdate(FrameEventArgs args) { // Update the timer + // TODO: Sub to state changes. _updateTimer += args.DeltaSeconds; if (_updateTimer >= UpdateTime) { _updateTimer -= UpdateTime; - UpdateNavMap(); - } - } - - protected virtual void UpdateNavMap() - { - // Clear stale values - TilePolygons.Clear(); - TileLines.Clear(); - TileRects.Clear(); - - UpdateNavMapFloorTiles(); - UpdateNavMapWallLines(); - UpdateNavMapAirlocks(); - } - - private void UpdateNavMapFloorTiles() - { - if (_fixtures == null) - return; - - var verts = new Vector2[8]; - - foreach (var fixture in _fixtures.Fixtures.Values) - { - if (fixture.Shape is not PolygonShape poly) - continue; - - for (var i = 0; i < poly.VertexCount; i++) - { - var vert = poly.Vertices[i]; - verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y)); - } - - TilePolygons.Add((verts[..poly.VertexCount], TileColor)); - } - } - - private void UpdateNavMapWallLines() - { - if (_navMap == null || _grid == null) - return; - - // We'll use the following dictionaries to combine collinear wall lines - _horizLines.Clear(); - _horizLinesReversed.Clear(); - _vertLines.Clear(); - _vertLinesReversed.Clear(); - - const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; - const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; - const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; - const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; - - foreach (var (chunkOrigin, chunk) in _navMap.Chunks) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Wall; - - var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; - - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinWall(tileData, tile); - continue; - } - - tile = tile with { Y = -tile.Y }; - NavMapChunk? neighborChunk; - - // North edge - var neighborData = 0; - if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i+1]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) - neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; - - if ((neighborData & southMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), - tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines, - _horizLinesReversed); - } - - // East edge - neighborData = 0; - if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) - neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; - - if ((neighborData & westMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), - tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed); - } - - // South edge - neighborData = 0; - if (relativeTile.Y != 0) - neighborData = chunk.TileData[i - 1]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) - neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; - - if ((neighborData & northMask) == 0) - { - AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines, - _horizLinesReversed); - } - - // West edge - neighborData = 0; - if (relativeTile.X != 0) - neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) - neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; - - if ((neighborData & eastMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines, - _vertLinesReversed); - } - - // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these - TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0))); - } - } - - // Record the combined lines - foreach (var (origin, terminal) in _horizLines) - { - TileLines.Add((origin, terminal)); - } - - foreach (var (origin, terminal) in _vertLines) - { - TileLines.Add((origin, terminal)); - } - } - - private void UpdateNavMapAirlocks() - { - if (_navMap == null || _grid == null) - return; - - foreach (var chunk in _navMap.Chunks.Values) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Airlock; - - var relative = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize; - - // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinAirlock(tileData, tile); - continue; - } - - // Otherwise add a single full tile airlock - TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), - new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); - - TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), - new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); - } + if (MapUid != null) + NavData.UpdateNavMap(MapUid.Value); } } - private void AddRectForThinWall(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); - var rightBottom = new Vector2(0.5f, 0.5f); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - - // TODO NAVMAP - // Consider using faster rotation operations, given that these are always 90 degree increments - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - } - } - - private void AddRectForThinAirlock(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); - var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); - var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); - var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); - } - } - - protected void AddOrUpdateNavMapLine( - Vector2i origin, - Vector2i terminus, - Dictionary lookup, - Dictionary lookupReversed) - { - Vector2i foundTermius; - Vector2i foundOrigin; - - // Does our new line end at the beginning of an existing line? - if (lookup.Remove(terminus, out foundTermius)) - { - DebugTools.Assert(lookupReversed[foundTermius] == terminus); - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just connects two existing lines - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = foundTermius; - lookupReversed[foundTermius] = foundOrigin; - } - else - { - // Our new line precedes an existing line, extending it further to the left - lookup[origin] = foundTermius; - lookupReversed[foundTermius] = origin; - } - return; - } - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just extends an existing line further to the right - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = terminus; - lookupReversed[terminus] = foundOrigin; - return; - } - - // Completely disconnected line segment. - lookup.Add(origin, terminus); - lookupReversed.Add(terminus, origin); - } - protected Vector2 GetOffset() { return Offset + (_physics?.LocalCenter ?? new Vector2()); diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs index d5057416cf84ed..dd29778819654a 100644 --- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs +++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs @@ -20,9 +20,7 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private readonly Color[] _powerCableColors = { Color.OrangeRed, Color.Yellow, Color.LimeGreen }; private readonly Vector2[] _powerCableOffsets = { new Vector2(-0.2f, -0.2f), Vector2.Zero, new Vector2(0.2f, 0.2f) }; - private Dictionary _sRGBLookUp = new Dictionary(); - public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks; public List HiddenLineGroups = new(); public List PowerCableNetwork = new(); public List FocusCableNetwork = new(); @@ -34,20 +32,19 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private MapGridComponent? _grid; - public PowerMonitoringConsoleNavMapControl() : base() + public PowerMonitoringConsoleNavMapControl() { // Set colors - TileColor = new Color(30, 57, 67); - WallColor = new Color(102, 164, 217); - BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); + NavData.TileColor = Color.FromSrgb(new Color(30, 57, 67)); + BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); PostWallDrawingAction += DrawAllCableNetworks; + + NavData.OnUpdate += UpdateNavMap; } - protected override void UpdateNavMap() + private void UpdateNavMap() { - base.UpdateNavMap(); - if (Owner == null) return; @@ -64,14 +61,14 @@ public void DrawAllCableNetworks(DrawingHandleScreen handle) return; // Draw full cable network - if (PowerCableNetwork != null && PowerCableNetwork.Count > 0) + if (PowerCableNetwork.Count > 0) { - var modulator = (FocusCableNetwork != null && FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; + var modulator = (FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; DrawCableNetwork(handle, PowerCableNetwork, modulator); } // Draw focus network - if (FocusCableNetwork != null && FocusCableNetwork.Count > 0) + if (FocusCableNetwork.Count > 0) DrawCableNetwork(handle, FocusCableNetwork, Color.White); } @@ -106,15 +103,8 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = _powerCableColors[cableNetworkIdx] * modulator; - - if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) - { - sRGB = Color.ToSrgb(color); - _sRGBLookUp[color] = sRGB; - } - - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, sRGB); + var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, color); } } } @@ -164,15 +154,9 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = _powerCableColors[cableNetworkIdx] * modulator; - - if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) - { - sRGB = Color.ToSrgb(color); - _sRGBLookUp[color] = sRGB; - } + var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, sRGB); + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, color); } } } @@ -236,7 +220,7 @@ public List GetDecodedPowerCableChunks(Dictionary GetDecodedPowerCableChunks(Dictionary OverlaySpace.WorldSpace; - private HashSet> _seeds = new(); + private HashSet> _seeds = new(); private IRenderTexture? _staticTexture; - public IRenderTexture? _stencilTexture; + public IRenderTexture? _blep; + + protected NavMapData _data = new(); public StationAiOverlay() { @@ -32,11 +35,11 @@ public StationAiOverlay() protected override void Draw(in OverlayDrawArgs args) { - if (_stencilTexture?.Texture.Size != args.Viewport.Size) + if (_blep?.Texture.Size != args.Viewport.Size) { _staticTexture?.Dispose(); - _stencilTexture?.Dispose(); - _stencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); + _blep?.Dispose(); + _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-static"); @@ -61,10 +64,12 @@ protected override void Draw(in OverlayDrawArgs args) var cleared = new HashSet(); var opaque = new HashSet(); var occluders = new HashSet>(); - var viewportTiles = new HashSet(); if (grid != null) { + _data.UpdateNavMap(gridUid); + _data.Offset = Vector2.Zero; + // Code based upon https://github.com/OpenDreamProject/OpenDream/blob/c4a3828ccb997bf3722673620460ebb11b95ccdf/OpenDreamShared/Dream/ViewAlgorithm.cs var tileEnumerator = maps.GetTilesEnumerator(gridUid, grid, expandedBounds, ignoreEmpty: false); @@ -100,15 +105,6 @@ protected override void Draw(in OverlayDrawArgs args) foreach (var seed in _seeds) { - if (!seed.Comp.Enabled) - continue; - - // TODO: Iterate tiles direct. - if (!seed.Comp.Occluded) - { - - } - var range = 7.5f; boundary.Clear(); seedTiles.Clear(); @@ -220,76 +216,42 @@ protected override void Draw(in OverlayDrawArgs args) visibleTiles.Add(tile); } } + } - // TODO: Combine tiles into viewer draw-calls - var gridMatrix = xforms.GetWorldMatrix(gridUid); - var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); - - // Draw visible tiles to stencil - worldHandle.RenderInRenderTarget(_stencilTexture!, () => - { - if (!gridUid.IsValid()) - return; - - worldHandle.SetTransform(matty); - - foreach (var tile in visibleTiles) - { - var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); - worldHandle.DrawRect(aabb, Color.White); - } - }, - Color.Transparent); - - // Create static texture - var curTime = IoCManager.Resolve().RealTime; - - var noiseTexture = _entManager.System() - .GetFrame(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/noise.rsi"), "noise"), curTime); - - // Once this is gucci optimise rendering. - worldHandle.RenderInRenderTarget(_staticTexture!, - () => - { - // TODO: Handle properly - if (!gridUid.IsValid()) - return; - - worldHandle.SetTransform(matty); + // TODO: Combine tiles into viewer draw-calls + var matrix = xforms.GetWorldMatrix(gridUid); + var matty = Matrix3x2.Multiply(matrix, invMatrix); - tileEnumerator = maps.GetTilesEnumerator(gridUid, grid!, worldBounds, ignoreEmpty: false); + // Draw visible tiles to stencil + worldHandle.RenderInRenderTarget(_blep!, () => + { + if (!gridUid.IsValid()) + return; - while (tileEnumerator.MoveNext(out var tileRef)) - { - if (visibleTiles.Contains(tileRef.GridIndices)) - continue; + worldHandle.SetTransform(matty); - var bounds = lookups.GetLocalBounds(tileRef, grid!.TileSize); - worldHandle.DrawTextureRect(noiseTexture, bounds, Color.White.WithAlpha(80)); - } + foreach (var tile in visibleTiles) + { + var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); + worldHandle.DrawRect(aabb, Color.White); + } + }, + Color.Transparent); - }, - Color.Black); - } - // Not on a grid - else + // Create background texture. + worldHandle.RenderInRenderTarget(_staticTexture!, + () => { - worldHandle.RenderInRenderTarget(_stencilTexture!, () => - { - }, - Color.Transparent); + worldHandle.SetTransform(invMatrix); + worldHandle.DrawRect(worldAabb, Color.Black); + worldHandle.SetTransform(matty); - worldHandle.RenderInRenderTarget(_staticTexture!, - () => - { - worldHandle.SetTransform(Matrix3x2.Identity); - worldHandle.DrawRect(worldBounds, Color.Black); - }, Color.Black); - } + _data.Draw(worldHandle, vec => new Vector2(vec.X, -vec.Y), worldAabb); + }, Color.Transparent); // Use the lighting as a mask worldHandle.UseShader(_proto.Index("StencilMask").Instance()); - worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds); + worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); // Draw the static worldHandle.UseShader(_proto.Index("StencilDraw").Instance()); From 3c32123457387c5836976da5ff06a05e87d97fa5 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 15 Aug 2024 22:17:24 +1000 Subject: [PATCH 08/40] Revert "navmap stuff" This reverts commit d1f89dd4be83233e22cf5dd062b2581f3c6da062. --- .../CrewMonitoringNavMapControl.cs | 10 +- Content.Client/Pinpointer/NavMapData.cs | 389 ------------------ Content.Client/Pinpointer/UI/NavMapControl.cs | 374 ++++++++++++++++- .../PowerMonitoringConsoleNavMapControl.cs | 46 ++- Content.Client/StationAi/StationAiOverlay.cs | 118 ++++-- 5 files changed, 474 insertions(+), 463 deletions(-) delete mode 100644 Content.Client/Pinpointer/NavMapData.cs diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs index fb3b12122a07de..340cc9af891c76 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs @@ -15,9 +15,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl public CrewMonitoringNavMapControl() : base() { - NavData.WallColor = Color.FromSrgb(new Color(192, 122, 196)); - NavData.TileColor = Color.FromSrgb(new(71, 42, 72)); - BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); + WallColor = new Color(192, 122, 196); + TileColor = new(71, 42, 72); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); _trackedEntityLabel = new Label { @@ -41,7 +41,7 @@ public CrewMonitoringNavMapControl() : base() }; _trackedEntityPanel.AddChild(_trackedEntityLabel); - AddChild(_trackedEntityPanel); + this.AddChild(_trackedEntityPanel); } protected override void FrameUpdate(FrameEventArgs args) @@ -56,7 +56,7 @@ protected override void FrameUpdate(FrameEventArgs args) return; } - foreach (var (netEntity, blip) in TrackedEntities) + foreach ((var netEntity, var blip) in TrackedEntities) { if (netEntity != Focus) continue; diff --git a/Content.Client/Pinpointer/NavMapData.cs b/Content.Client/Pinpointer/NavMapData.cs deleted file mode 100644 index c2bfaba4414f31..00000000000000 --- a/Content.Client/Pinpointer/NavMapData.cs +++ /dev/null @@ -1,389 +0,0 @@ -using System.Numerics; -using System.Runtime.InteropServices; -using Content.Shared.Atmos; -using Content.Shared.Pinpointer; -using Robust.Client.Graphics; -using Robust.Shared.Collections; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Collision.Shapes; -using Robust.Shared.Utility; - -namespace Content.Client.Pinpointer; - -public sealed class NavMapData -{ - [Dependency] private readonly IEntityManager _entManager = default!; - - // Default colors - public Color WallColor = Color.ToSrgb(new Color(102, 217, 102)); - public Color TileColor = new(30, 67, 30); - - /// - /// Offset for the data to be drawn at. - /// - public Vector2 Offset; - - public List<(Vector2, Vector2)> TileLines = new(); - public List<(Vector2, Vector2)> TileRects = new(); - - public Dictionary TilePolygons = new(); - - private Dictionary _horizLines = new(); - private Dictionary _horizLinesReversed = new(); - private Dictionary _vertLines = new(); - private Dictionary _vertLinesReversed = new(); - - protected float FullWallInstep = 0.165f; - protected float ThinWallThickness = 0.165f; - protected float ThinDoorThickness = 0.30f; - - // TODO: Power should be updating it on its own. - /// - /// Called if navmap updates - /// - public event Action? OnUpdate; - - public NavMapData() - { - IoCManager.InjectDependencies(this); - } - - public void Draw(DrawingHandleBase handle, Func scale, Box2 localAABB) - { - var verts = new ValueList(TileLines.Count * 2); - - // Draw floor tiles - if (TilePolygons.Count != 0) - { - foreach (var tri in TilePolygons.Values) - { - verts.Clear(); - - foreach (var vert in tri) - { - verts.Add(new Vector2(vert.X, -vert.Y)); - } - - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts.Span, TileColor); - } - } - - // Draw map lines - if (TileLines.Count != 0) - { - verts.Clear(); - - foreach (var (o, t) in TileLines) - { - var origin = scale.Invoke(o - Offset); - var terminus = scale.Invoke(t - Offset); - - verts.Add(origin); - verts.Add(terminus); - } - - if (verts.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts.Span, WallColor); - } - - // Draw map rects - if (TileRects.Count != 0) - { - var rects = new ValueList(TileRects.Count * 8); - - foreach (var (lt, rb) in TileRects) - { - var leftTop = scale.Invoke(lt - Offset); - var rightBottom = scale.Invoke(rb - Offset); - - var rightTop = new Vector2(rightBottom.X, leftTop.Y); - var leftBottom = new Vector2(leftTop.X, rightBottom.Y); - - rects.Add(leftTop); - rects.Add(rightTop); - rects.Add(rightTop); - rects.Add(rightBottom); - rects.Add(rightBottom); - rects.Add(leftBottom); - rects.Add(leftBottom); - rects.Add(leftTop); - } - - if (rects.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, WallColor); - } - } - - public void UpdateNavMap(EntityUid entity) - { - // Clear stale values - TilePolygons.Clear(); - TileLines.Clear(); - TileRects.Clear(); - - UpdateNavMapFloorTiles(entity); - UpdateNavMapWallLines(entity); - UpdateNavMapAirlocks(entity); - } - - private void UpdateNavMapFloorTiles(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp)) - { - return; - } - - var lookup = _entManager.System(); - var tiles = _entManager.System().GetAllTilesEnumerator(entity.Owner, entity.Comp); - - while (tiles.MoveNext(out var tile)) - { - var box = lookup.GetLocalBounds(tile.Value.GridIndices, entity.Comp.TileSize); - box = new Box2(box.Left, -box.Bottom, box.Right, -box.Top); - var arr = new Vector2[4]; - - arr[0] = box.BottomLeft; - arr[1] = box.BottomRight; - arr[2] = box.TopRight; - arr[3] = box.TopLeft; - - TilePolygons[tile.Value.GridIndices] = arr; - } - } - - private void UpdateNavMapWallLines(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || - !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) - { - return; - } - - // We'll use the following dictionaries to combine collinear wall lines - _horizLines.Clear(); - _horizLinesReversed.Clear(); - _vertLines.Clear(); - _vertLinesReversed.Clear(); - - const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; - const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; - const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; - const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; - - foreach (var (chunkOrigin, chunk) in entity.Comp2.Chunks) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Wall; - - var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * entity.Comp1.TileSize; - - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinWall(tileData, tile); - continue; - } - - tile = tile with { Y = -tile.Y }; - NavMapChunk? neighborChunk; - - // North edge - var neighborData = 0; - if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i+1]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) - neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; - - if ((neighborData & southMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), - tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), _horizLines, - _horizLinesReversed); - } - - // East edge - neighborData = 0; - if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) - neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; - - if ((neighborData & westMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), - tile + new Vector2i(entity.Comp1.TileSize, 0), _vertLines, _vertLinesReversed); - } - - // South edge - neighborData = 0; - if (relativeTile.Y != 0) - neighborData = chunk.TileData[i - 1]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) - neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; - - if ((neighborData & northMask) == 0) - { - AddOrUpdateNavMapLine(tile, tile + new Vector2i(entity.Comp1.TileSize, 0), _horizLines, - _horizLinesReversed); - } - - // West edge - neighborData = 0; - if (relativeTile.X != 0) - neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) - neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; - - if ((neighborData & eastMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), tile, _vertLines, - _vertLinesReversed); - } - - // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these - TileLines.Add((tile + new Vector2(0, -entity.Comp1.TileSize), tile + new Vector2(entity.Comp1.TileSize, 0))); - } - } - - // Record the combined lines - foreach (var (origin, terminal) in _horizLines) - { - TileLines.Add((origin, terminal)); - } - - foreach (var (origin, terminal) in _vertLines) - { - TileLines.Add((origin, terminal)); - } - } - - private void UpdateNavMapAirlocks(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || - !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) - { - return; - } - - foreach (var chunk in entity.Comp2.Chunks.Values) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Airlock; - - var relative = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * entity.Comp1.TileSize; - - // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinAirlock(tileData, tile); - continue; - } - - // Otherwise add a single full tile airlock - TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), - new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); - - TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), - new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); - } - } - } - - private void AddRectForThinWall(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); - var rightBottom = new Vector2(0.5f, 0.5f); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - - // TODO NAVMAP - // Consider using faster rotation operations, given that these are always 90 degree increments - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - } - } - - private void AddRectForThinAirlock(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); - var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); - var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); - var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); - } - } - - public void AddOrUpdateNavMapLine( - Vector2i origin, - Vector2i terminus, - Dictionary lookup, - Dictionary lookupReversed) - { - Vector2i foundTermius; - Vector2i foundOrigin; - - // Does our new line end at the beginning of an existing line? - if (lookup.Remove(terminus, out foundTermius)) - { - DebugTools.Assert(lookupReversed[foundTermius] == terminus); - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just connects two existing lines - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = foundTermius; - lookupReversed[foundTermius] = foundOrigin; - } - else - { - // Our new line precedes an existing line, extending it further to the left - lookup[origin] = foundTermius; - lookupReversed[foundTermius] = origin; - } - return; - } - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just extends an existing line further to the right - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = terminus; - lookupReversed[terminus] = foundOrigin; - return; - } - - // Completely disconnected line segment. - lookup.Add(origin, terminus); - lookupReversed.Add(terminus, origin); - } -} diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index d7d821b37f5946..413b41c36a6f43 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -45,7 +45,13 @@ public partial class NavMapControl : MapGridControl public Dictionary TrackedCoordinates = new(); public Dictionary TrackedEntities = new(); - public NavMapData NavData = new(); + public List<(Vector2, Vector2)> TileLines = new(); + public List<(Vector2, Vector2)> TileRects = new(); + public List<(Vector2[], Color)> TilePolygons = new(); + + // Default colors + public Color WallColor = new(102, 217, 102); + public Color TileColor = new(30, 67, 30); // Constants protected float UpdateTime = 1.0f; @@ -55,13 +61,22 @@ public partial class NavMapControl : MapGridControl protected static float MaxDisplayedRange = 128f; protected static float DefaultDisplayedRange = 48f; protected float MinmapScaleModifier = 0.075f; + protected float FullWallInstep = 0.165f; + protected float ThinWallThickness = 0.165f; + protected float ThinDoorThickness = 0.30f; // Local variables private float _updateTimer = 1.0f; + private Dictionary _sRGBLookUp = new(); protected Color BackgroundColor; protected float BackgroundOpacity = 0.9f; private int _targetFontsize = 8; + private Dictionary _horizLines = new(); + private Dictionary _horizLinesReversed = new(); + private Dictionary _vertLines = new(); + private Dictionary _vertLinesReversed = new(); + // Components private NavMapComponent? _navMap; private MapGridComponent? _grid; @@ -104,7 +119,7 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl _transformSystem = EntManager.System(); _navMapSystem = EntManager.System(); - BackgroundColor = Color.FromSrgb(NavData.TileColor.WithAlpha(BackgroundOpacity)); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); RectClipContent = true; HorizontalExpand = true; @@ -164,12 +179,13 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl public void ForceNavMapUpdate() { - if (MapUid == null) - { - return; - } + EntManager.TryGetComponent(MapUid, out _navMap); + EntManager.TryGetComponent(MapUid, out _grid); + EntManager.TryGetComponent(MapUid, out _xform); + EntManager.TryGetComponent(MapUid, out _physics); + EntManager.TryGetComponent(MapUid, out _fixtures); - NavData.UpdateNavMap(MapUid.Value); + UpdateNavMap(); } public void CenterToCoordinates(EntityCoordinates coordinates) @@ -212,7 +228,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { if (!blip.Selectable) continue; - + var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length(); if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance) @@ -277,11 +293,80 @@ protected override void Draw(DrawingHandleScreen handle) if (_physics != null) offset += _physics.LocalCenter; - NavData.Offset = new Vector2(offset.X, -offset.Y); - NavData.Draw(handle, ScalePosition, Box2.UnitCentered); + var offsetVec = new Vector2(offset.X, -offset.Y); + + // Wall sRGB + if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB)) + { + wallsRGB = Color.ToSrgb(WallColor); + _sRGBLookUp[WallColor] = wallsRGB; + } + + // Draw floor tiles + if (TilePolygons.Any()) + { + Span verts = new Vector2[8]; + + foreach (var (polygonVerts, polygonColor) in TilePolygons) + { + for (var i = 0; i < polygonVerts.Length; i++) + { + var vert = polygonVerts[i] - offset; + verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y)); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor); + } + } + + // Draw map lines + if (TileLines.Any()) + { + var lines = new ValueList(TileLines.Count * 2); + + foreach (var (o, t) in TileLines) + { + var origin = ScalePosition(o - offsetVec); + var terminus = ScalePosition(t - offsetVec); + + lines.Add(origin); + lines.Add(terminus); + } + + if (lines.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB); + } + + // Draw map rects + if (TileRects.Any()) + { + var rects = new ValueList(TileRects.Count * 8); + + foreach (var (lt, rb) in TileRects) + { + var leftTop = ScalePosition(lt - offsetVec); + var rightBottom = ScalePosition(rb - offsetVec); + + var rightTop = new Vector2(rightBottom.X, leftTop.Y); + var leftBottom = new Vector2(leftTop.X, rightBottom.Y); + + rects.Add(leftTop); + rects.Add(rightTop); + rects.Add(rightTop); + rects.Add(rightBottom); + rects.Add(rightBottom); + rects.Add(leftBottom); + rects.Add(leftBottom); + rects.Add(leftTop); + } + + if (rects.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB); + } // Invoke post wall drawing action - PostWallDrawingAction?.Invoke(handle); + if (PostWallDrawingAction != null) + PostWallDrawingAction.Invoke(handle); // Beacons if (_beacons.Pressed) @@ -351,18 +436,279 @@ protected override void Draw(DrawingHandleScreen handle) protected override void FrameUpdate(FrameEventArgs args) { // Update the timer - // TODO: Sub to state changes. _updateTimer += args.DeltaSeconds; if (_updateTimer >= UpdateTime) { _updateTimer -= UpdateTime; - if (MapUid != null) - NavData.UpdateNavMap(MapUid.Value); + UpdateNavMap(); + } + } + + protected virtual void UpdateNavMap() + { + // Clear stale values + TilePolygons.Clear(); + TileLines.Clear(); + TileRects.Clear(); + + UpdateNavMapFloorTiles(); + UpdateNavMapWallLines(); + UpdateNavMapAirlocks(); + } + + private void UpdateNavMapFloorTiles() + { + if (_fixtures == null) + return; + + var verts = new Vector2[8]; + + foreach (var fixture in _fixtures.Fixtures.Values) + { + if (fixture.Shape is not PolygonShape poly) + continue; + + for (var i = 0; i < poly.VertexCount; i++) + { + var vert = poly.Vertices[i]; + verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y)); + } + + TilePolygons.Add((verts[..poly.VertexCount], TileColor)); + } + } + + private void UpdateNavMapWallLines() + { + if (_navMap == null || _grid == null) + return; + + // We'll use the following dictionaries to combine collinear wall lines + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); + + const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; + const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; + const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; + const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; + + foreach (var (chunkOrigin, chunk) in _navMap.Chunks) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Wall; + + var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; + + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinWall(tileData, tile); + continue; + } + + tile = tile with { Y = -tile.Y }; + NavMapChunk? neighborChunk; + + // North edge + var neighborData = 0; + if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) + neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; + + if ((neighborData & southMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines, + _horizLinesReversed); + } + + // East edge + neighborData = 0; + if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) + neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; + + if ((neighborData & westMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed); + } + + // South edge + neighborData = 0; + if (relativeTile.Y != 0) + neighborData = chunk.TileData[i - 1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) + neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; + + if ((neighborData & northMask) == 0) + { + AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines, + _horizLinesReversed); + } + + // West edge + neighborData = 0; + if (relativeTile.X != 0) + neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) + neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; + + if ((neighborData & eastMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines, + _vertLinesReversed); + } + + // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these + TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0))); + } + } + + // Record the combined lines + foreach (var (origin, terminal) in _horizLines) + { + TileLines.Add((origin, terminal)); + } + + foreach (var (origin, terminal) in _vertLines) + { + TileLines.Add((origin, terminal)); + } + } + + private void UpdateNavMapAirlocks() + { + if (_navMap == null || _grid == null) + return; + + foreach (var chunk in _navMap.Chunks.Values) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Airlock; + + var relative = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize; + + // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinAirlock(tileData, tile); + continue; + } + + // Otherwise add a single full tile airlock + TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), + new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); + + TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), + new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); + } } } + private void AddRectForThinWall(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); + var rightBottom = new Vector2(0.5f, 0.5f); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + + // TODO NAVMAP + // Consider using faster rotation operations, given that these are always 90 degree increments + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + } + } + + private void AddRectForThinAirlock(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); + var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); + var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); + var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); + } + } + + protected void AddOrUpdateNavMapLine( + Vector2i origin, + Vector2i terminus, + Dictionary lookup, + Dictionary lookupReversed) + { + Vector2i foundTermius; + Vector2i foundOrigin; + + // Does our new line end at the beginning of an existing line? + if (lookup.Remove(terminus, out foundTermius)) + { + DebugTools.Assert(lookupReversed[foundTermius] == terminus); + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just connects two existing lines + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = foundTermius; + lookupReversed[foundTermius] = foundOrigin; + } + else + { + // Our new line precedes an existing line, extending it further to the left + lookup[origin] = foundTermius; + lookupReversed[foundTermius] = origin; + } + return; + } + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just extends an existing line further to the right + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = terminus; + lookupReversed[terminus] = foundOrigin; + return; + } + + // Completely disconnected line segment. + lookup.Add(origin, terminus); + lookupReversed.Add(terminus, origin); + } + protected Vector2 GetOffset() { return Offset + (_physics?.LocalCenter ?? new Vector2()); diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs index dd29778819654a..d5057416cf84ed 100644 --- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs +++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs @@ -20,7 +20,9 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private readonly Color[] _powerCableColors = { Color.OrangeRed, Color.Yellow, Color.LimeGreen }; private readonly Vector2[] _powerCableOffsets = { new Vector2(-0.2f, -0.2f), Vector2.Zero, new Vector2(0.2f, 0.2f) }; + private Dictionary _sRGBLookUp = new Dictionary(); + public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks; public List HiddenLineGroups = new(); public List PowerCableNetwork = new(); public List FocusCableNetwork = new(); @@ -32,19 +34,20 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private MapGridComponent? _grid; - public PowerMonitoringConsoleNavMapControl() + public PowerMonitoringConsoleNavMapControl() : base() { // Set colors - NavData.TileColor = Color.FromSrgb(new Color(30, 57, 67)); - BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); + TileColor = new Color(30, 57, 67); + WallColor = new Color(102, 164, 217); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); PostWallDrawingAction += DrawAllCableNetworks; - - NavData.OnUpdate += UpdateNavMap; } - private void UpdateNavMap() + protected override void UpdateNavMap() { + base.UpdateNavMap(); + if (Owner == null) return; @@ -61,14 +64,14 @@ public void DrawAllCableNetworks(DrawingHandleScreen handle) return; // Draw full cable network - if (PowerCableNetwork.Count > 0) + if (PowerCableNetwork != null && PowerCableNetwork.Count > 0) { - var modulator = (FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; + var modulator = (FocusCableNetwork != null && FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; DrawCableNetwork(handle, PowerCableNetwork, modulator); } // Draw focus network - if (FocusCableNetwork.Count > 0) + if (FocusCableNetwork != null && FocusCableNetwork.Count > 0) DrawCableNetwork(handle, FocusCableNetwork, Color.White); } @@ -103,8 +106,15 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, color); + var color = _powerCableColors[cableNetworkIdx] * modulator; + + if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) + { + sRGB = Color.ToSrgb(color); + _sRGBLookUp[color] = sRGB; + } + + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, sRGB); } } } @@ -154,9 +164,15 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); + var color = _powerCableColors[cableNetworkIdx] * modulator; + + if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) + { + sRGB = Color.ToSrgb(color); + _sRGBLookUp[color] = sRGB; + } - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, color); + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, sRGB); } } } @@ -220,7 +236,7 @@ public List GetDecodedPowerCableChunks(Dictionary GetDecodedPowerCableChunks(Dictionary OverlaySpace.WorldSpace; - private HashSet> _seeds = new(); + private HashSet> _seeds = new(); private IRenderTexture? _staticTexture; - public IRenderTexture? _blep; - - protected NavMapData _data = new(); + public IRenderTexture? _stencilTexture; public StationAiOverlay() { @@ -35,11 +32,11 @@ public StationAiOverlay() protected override void Draw(in OverlayDrawArgs args) { - if (_blep?.Texture.Size != args.Viewport.Size) + if (_stencilTexture?.Texture.Size != args.Viewport.Size) { _staticTexture?.Dispose(); - _blep?.Dispose(); - _blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); + _stencilTexture?.Dispose(); + _stencilTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-stencil"); _staticTexture = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "station-ai-static"); @@ -64,12 +61,10 @@ protected override void Draw(in OverlayDrawArgs args) var cleared = new HashSet(); var opaque = new HashSet(); var occluders = new HashSet>(); + var viewportTiles = new HashSet(); if (grid != null) { - _data.UpdateNavMap(gridUid); - _data.Offset = Vector2.Zero; - // Code based upon https://github.com/OpenDreamProject/OpenDream/blob/c4a3828ccb997bf3722673620460ebb11b95ccdf/OpenDreamShared/Dream/ViewAlgorithm.cs var tileEnumerator = maps.GetTilesEnumerator(gridUid, grid, expandedBounds, ignoreEmpty: false); @@ -105,6 +100,15 @@ protected override void Draw(in OverlayDrawArgs args) foreach (var seed in _seeds) { + if (!seed.Comp.Enabled) + continue; + + // TODO: Iterate tiles direct. + if (!seed.Comp.Occluded) + { + + } + var range = 7.5f; boundary.Clear(); seedTiles.Clear(); @@ -216,42 +220,76 @@ protected override void Draw(in OverlayDrawArgs args) visibleTiles.Add(tile); } } - } - // TODO: Combine tiles into viewer draw-calls - var matrix = xforms.GetWorldMatrix(gridUid); - var matty = Matrix3x2.Multiply(matrix, invMatrix); + // TODO: Combine tiles into viewer draw-calls + var gridMatrix = xforms.GetWorldMatrix(gridUid); + var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); - // Draw visible tiles to stencil - worldHandle.RenderInRenderTarget(_blep!, () => - { - if (!gridUid.IsValid()) - return; + // Draw visible tiles to stencil + worldHandle.RenderInRenderTarget(_stencilTexture!, () => + { + if (!gridUid.IsValid()) + return; - worldHandle.SetTransform(matty); + worldHandle.SetTransform(matty); - foreach (var tile in visibleTiles) - { - var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); - worldHandle.DrawRect(aabb, Color.White); - } - }, - Color.Transparent); + foreach (var tile in visibleTiles) + { + var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); + worldHandle.DrawRect(aabb, Color.White); + } + }, + Color.Transparent); - // Create background texture. - worldHandle.RenderInRenderTarget(_staticTexture!, - () => + // Create static texture + var curTime = IoCManager.Resolve().RealTime; + + var noiseTexture = _entManager.System() + .GetFrame(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/noise.rsi"), "noise"), curTime); + + // Once this is gucci optimise rendering. + worldHandle.RenderInRenderTarget(_staticTexture!, + () => + { + // TODO: Handle properly + if (!gridUid.IsValid()) + return; + + worldHandle.SetTransform(matty); + + tileEnumerator = maps.GetTilesEnumerator(gridUid, grid!, worldBounds, ignoreEmpty: false); + + while (tileEnumerator.MoveNext(out var tileRef)) + { + if (visibleTiles.Contains(tileRef.GridIndices)) + continue; + + var bounds = lookups.GetLocalBounds(tileRef, grid!.TileSize); + worldHandle.DrawTextureRect(noiseTexture, bounds, Color.White.WithAlpha(80)); + } + + }, + Color.Black); + } + // Not on a grid + else { - worldHandle.SetTransform(invMatrix); - worldHandle.DrawRect(worldAabb, Color.Black); - worldHandle.SetTransform(matty); + worldHandle.RenderInRenderTarget(_stencilTexture!, () => + { + }, + Color.Transparent); - _data.Draw(worldHandle, vec => new Vector2(vec.X, -vec.Y), worldAabb); - }, Color.Transparent); + worldHandle.RenderInRenderTarget(_staticTexture!, + () => + { + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.DrawRect(worldBounds, Color.Black); + }, Color.Black); + } // Use the lighting as a mask worldHandle.UseShader(_proto.Index("StencilMask").Instance()); - worldHandle.DrawTextureRect(_blep!.Texture, worldBounds); + worldHandle.DrawTextureRect(_stencilTexture!.Texture, worldBounds); // Draw the static worldHandle.UseShader(_proto.Index("StencilDraw").Instance()); From 45e87a441b3fcc3d708904528b47a39c515e82ec Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 16 Aug 2024 00:07:21 +1000 Subject: [PATCH 09/40] AI wires implemented --- .../Silicons/StationAi/AiVisionWireAction.cs | 40 +++++++++++++++++++ .../Silicons/StationAi/StationAiSystem.cs | 30 ++++++++++++++ Content.Server/Wires/WiresSystem.cs | 11 +++-- Content.Shared/Doors/AirlockWireStatus.cs | 2 +- .../StationAi/SharedStationAiSystem.cs | 15 ++++++- .../StationAi/StationAiVisionComponent.cs | 3 +- .../StationAi/StationAiVisionSystem.cs | 36 +++++++++++++++++ Resources/Prototypes/Wires/layouts.yml | 1 + 8 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 Content.Server/Silicons/StationAi/AiVisionWireAction.cs create mode 100644 Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs diff --git a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs new file mode 100644 index 00000000000000..161c6e76ad2383 --- /dev/null +++ b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs @@ -0,0 +1,40 @@ +using Content.Server.Wires; +using Content.Shared.Doors; +using Content.Shared.Silicons.StationAi; +using Content.Shared.StationAi; +using Content.Shared.Wires; + +namespace Content.Server.Silicons.StationAi; + +/// +/// Handles StationAiVision functionality for the attached entity. +/// +public sealed partial class AiVisionWireAction : ComponentWireAction +{ + public override string Name { get; set; } = "wire-name-ai-light"; + public override Color Color { get; set; } = Color.DeepSkyBlue; + public override object StatusKey => AirlockWireStatus.AiControlIndicator; + + public override StatusLightState? GetLightState(Wire wire, StationAiVisionComponent component) + { + return component.Enabled ? StatusLightState.On : StatusLightState.Off; + } + + public override bool Cut(EntityUid user, Wire wire, StationAiVisionComponent component) + { + return EntityManager.System() + .SetEnabled((component.Owner, component), false, announce: true); + } + + public override bool Mend(EntityUid user, Wire wire, StationAiVisionComponent component) + { + return EntityManager.System() + .SetEnabled((component.Owner, component), true); + } + + public override void Pulse(EntityUid user, Wire wire, StationAiVisionComponent component) + { + // TODO: This should turn it off for a bit + // Need timer cleanup first out of scope. + } +} diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index 6303020464fa28..e4e75e37cd345d 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -1,8 +1,38 @@ +using Content.Server.Chat.Systems; using Content.Shared.Silicons.StationAi; +using Content.Shared.StationAi; +using Robust.Shared.Player; namespace Content.Server.Silicons.StationAi; public sealed class StationAiSystem : SharedStationAiSystem { + [Dependency] private readonly ChatSystem _chats = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + private HashSet> _ais = new(); + + public override bool SetEnabled(Entity entity, bool enabled, bool announce = false) + { + if (!base.SetEnabled(entity, enabled, announce)) + return false; + + var xform = Transform(entity); + + _ais.Clear(); + _lookup.GetChildEntities(xform.ParentUid, _ais); + var filter = Filter.Empty(); + + foreach (var ai in _ais) + { + // TODO: Filter API? + if (TryComp(ai.Owner, out ActorComponent? actorComp)) + { + filter.AddPlayer(actorComp.PlayerSession); + } + } + + _chats.DispatchFilteredAnnouncement(filter, Loc.GetString("ai-wire-snipped"), entity.Owner, announcementSound: null); + return true; + } } diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index 944b0a0e2500c0..beb84d3e116bde 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -152,18 +152,17 @@ private void SetOrCreateWireLayout(EntityUid uid, WiresComponent? wires = null) { (int id, Wire d) = enumeratedList[i]; + d.Id = i; + if (d.Action != null) { var actionType = d.Action.GetType(); - if (types.ContainsKey(actionType)) + if (!types.TryAdd(actionType, 1)) types[actionType] += 1; - else - types.Add(actionType, 1); if (!d.Action.AddWire(d, types[actionType])) d.Action = null; } - d.Id = i; data.Add(id, new WireLayout.WireData(d.Letter, d.Color, i)); wires.WiresList[i] = wireSet[id]; @@ -727,7 +726,7 @@ private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, i break; } - Tool.PlayToolSound(toolEntity, tool, user); + Tool.PlayToolSound(toolEntity, tool, null); if (wire.Action == null || wire.Action.Cut(user, wire)) { wire.IsCut = true; @@ -748,7 +747,7 @@ private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, i break; } - Tool.PlayToolSound(toolEntity, tool, user); + Tool.PlayToolSound(toolEntity, tool, null); if (wire.Action == null || wire.Action.Mend(user, wire)) { wire.IsCut = false; diff --git a/Content.Shared/Doors/AirlockWireStatus.cs b/Content.Shared/Doors/AirlockWireStatus.cs index a50ee2c88e9cca..d3fa15ed1b650a 100644 --- a/Content.Shared/Doors/AirlockWireStatus.cs +++ b/Content.Shared/Doors/AirlockWireStatus.cs @@ -8,7 +8,7 @@ public enum AirlockWireStatus PowerIndicator, BoltIndicator, BoltLightIndicator, - AIControlIndicator, + AiControlIndicator, TimingIndicator, SafetyIndicator, } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index ebf03e71b6863d..64b55c8dc4d62c 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -1,8 +1,10 @@ +using Content.Shared.Chat; using Content.Shared.Containers.ItemSlots; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; +using Content.Shared.StationAi; using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Serialization; @@ -61,6 +63,17 @@ public override void Initialize() SubscribeLocalEvent(OnAiShutdown); } + public virtual bool SetEnabled(Entity entity, bool enabled, bool announce = false) + { + if (entity.Comp.Enabled == enabled) + return false; + + entity.Comp.Enabled = enabled; + Dirty(entity); + + return true; + } + private void OnAiInteraction(Entity ent, ref InteractionAttemptEvent args) { args.Cancelled = !HasComp(args.Target); @@ -206,7 +219,7 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo RemCompDeferred(args.Entity); } - public void UpdateAppearance(Entity entity) + protected void UpdateAppearance(Entity entity) { if (!Resolve(entity.Owner, ref entity.Comp, false)) return; diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs index 3fd82fe2017036..2b184df7f7b114 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs @@ -1,8 +1,9 @@ +using Content.Shared.Silicons.StationAi; using Robust.Shared.GameStates; namespace Content.Shared.StationAi; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStationAiSystem))] public sealed partial class StationAiVisionComponent : Component { [DataField, AutoNetworkedField] diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs new file mode 100644 index 00000000000000..0bb13b77a13b44 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared.StationAi; +using Robust.Shared.Map.Components; + +namespace Content.Shared.Silicons.StationAi; + +public sealed class StationAiVisionSystem +{ + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedMapSystem _maps = default!; + + public void GetView(Entity grid, Box2 worldAabb, List> seeds, HashSet tiles) + { + var tileEnumerator = _maps.GetTilesEnumerator(grid, grid, worldAabb, ignoreEmpty: false); + /* + + // Get what tiles are blocked up-front. + while (tileEnumerator.MoveNext(out var tileRef)) + { + var tileBounds = _lookup.GetLocalBounds(tileRef.GridIndices, grid.Comp.TileSize).Enlarged(-0.05f); + + occluders.Clear(); + lookups.GetLocalEntitiesIntersecting(gridUid, tileBounds, occluders, LookupFlags.Static); + + if (occluders.Count > 0) + { + opaque.Add(tileRef.GridIndices); + } + else + { + cleared.Add(tileRef.GridIndices); + } + } + + */ + } +} diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index 3939655707e103..ec72f006a67a80 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -100,6 +100,7 @@ dummyWires: 4 wires: - !type:PowerWireAction + - !type:AiVisionWireAction - type: wireLayout id: CryoPod From 0c5b54f04866a973957930e0100b397756f7d66d Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 16 Aug 2024 00:16:31 +1000 Subject: [PATCH 10/40] Fix examines --- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 3f3e3fdcf6ac06..f8b4121d8a94ee 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -56,7 +56,7 @@ - type: entity id: PlayerStationAiEmpty - name: Station AI + name: AI Core description: The latest in Artificial Intelligences. parent: - BaseStructure @@ -108,6 +108,8 @@ - type: Actions - type: Eye drawFov: false + - type: Examiner + checkInRangeUnoccluded: false - type: InputMover - type: Tag tags: From 8222fc7f4985828d34ae2cf4f494e118a8393360 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 16 Aug 2024 16:17:10 +1000 Subject: [PATCH 11/40] Optimise the overlay significantly --- Content.Client/StationAi/StationAiOverlay.cs | 251 +----------- .../StationAi/SharedStationAiSystem.cs | 1 - .../StationAi/StationAiVisionComponent.cs | 2 +- .../StationAi/StationAiVisionSystem.cs | 357 +++++++++++++++++- 4 files changed, 364 insertions(+), 247 deletions(-) diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/StationAi/StationAiOverlay.cs index 522512b5537144..fa1e879ca3b888 100644 --- a/Content.Client/StationAi/StationAiOverlay.cs +++ b/Content.Client/StationAi/StationAiOverlay.cs @@ -1,10 +1,13 @@ using System.Numerics; using Content.Client.SurveillanceCamera; +using Content.Shared.Silicons.StationAi; using Content.Shared.StationAi; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -15,12 +18,12 @@ public sealed class StationAiOverlay : Overlay { [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPrototypeManager _proto = default!; public override OverlaySpace Space => OverlaySpace.WorldSpace; - private HashSet> _seeds = new(); + private readonly HashSet _visibleTiles = new(); private IRenderTexture? _staticTexture; public IRenderTexture? _stencilTexture; @@ -44,184 +47,27 @@ protected override void Draw(in OverlayDrawArgs args) var worldHandle = args.WorldHandle; - var mapId = args.MapId; - var worldAabb = args.WorldAABB; var worldBounds = args.WorldBounds; var maps = _entManager.System(); var lookups = _entManager.System(); var xforms = _entManager.System(); - var visibleTiles = new HashSet(); - _mapManager.TryFindGridAt(mapId, worldAabb.Center, out var gridUid, out var grid); + var playerEnt = _player.LocalEntity; + _entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform); + var gridUid = playerXform?.GridUid ?? EntityUid.Invalid; + _entManager.TryGetComponent(gridUid, out MapGridComponent? grid); var invMatrix = args.Viewport.GetWorldToLocalMatrix(); - // Skrunkly line of sight to be generous like the byond one. - var expandedBounds = worldAabb.Enlarged(7.5f); - var cleared = new HashSet(); - var opaque = new HashSet(); - var occluders = new HashSet>(); - var viewportTiles = new HashSet(); - if (grid != null) { - // Code based upon https://github.com/OpenDreamProject/OpenDream/blob/c4a3828ccb997bf3722673620460ebb11b95ccdf/OpenDreamShared/Dream/ViewAlgorithm.cs - var tileEnumerator = maps.GetTilesEnumerator(gridUid, grid, expandedBounds, ignoreEmpty: false); - - // Get what tiles are blocked up-front. - while (tileEnumerator.MoveNext(out var tileRef)) - { - var tileBounds = lookups.GetLocalBounds(tileRef.GridIndices, grid.TileSize).Enlarged(-0.05f); - - occluders.Clear(); - lookups.GetLocalEntitiesIntersecting(gridUid, tileBounds, occluders, LookupFlags.Static); - - if (occluders.Count > 0) - { - opaque.Add(tileRef.GridIndices); - } - else - { - cleared.Add(tileRef.GridIndices); - } - } - - // TODO: Changes - - // Run seeds in parallel - // Iterate get_hear for each camera (instead of expanding) and store vis2. - - _seeds.Clear(); - lookups.GetEntitiesIntersecting(args.MapId, expandedBounds, _seeds, LookupFlags.Static); - var vis1 = new Dictionary(); - var vis2 = new Dictionary(); - var seedTiles = new HashSet(); - var boundary = new HashSet(); - - foreach (var seed in _seeds) - { - if (!seed.Comp.Enabled) - continue; - - // TODO: Iterate tiles direct. - if (!seed.Comp.Occluded) - { - - } - - var range = 7.5f; - boundary.Clear(); - seedTiles.Clear(); - vis1.Clear(); - vis2.Clear(); - - var maxDepthMax = 0; - var sumDepthMax = 0; - - var eyePos = maps.GetTileRef(gridUid, grid, _entManager.GetComponent(seed).Coordinates).GridIndices; - - for (var x = Math.Floor(eyePos.X - range); x <= eyePos.X + range; x++) - { - for (var y = Math.Floor(eyePos.Y - range); y <= eyePos.Y + range; y++) - { - var tile = new Vector2i((int)x, (int)y); - var delta = tile - eyePos; - var xDelta = Math.Abs(delta.X); - var yDelta = Math.Abs(delta.Y); - - var deltaSum = xDelta + yDelta; - - maxDepthMax = Math.Max(maxDepthMax, Math.Max(xDelta, yDelta)); - sumDepthMax = Math.Max(sumDepthMax, deltaSum); - seedTiles.Add(tile); - } - } - - // Step 3, Diagonal shadow loop - for (var d = 0; d < maxDepthMax; d++) - { - foreach (var tile in seedTiles) - { - var maxDelta = GetMaxDelta(tile, eyePos); - - if (maxDelta == d + 1 && CheckNeighborsVis(vis2, tile, d)) - { - vis2[tile] = (opaque.Contains(tile) ? -1 : d + 1); - } - } - } - - // Step 4, Straight shadow loop - for (var d = 0; d < sumDepthMax; d++) - { - foreach (var tile in seedTiles) - { - var sumDelta = GetSumDelta(tile, eyePos); - - if (sumDelta == d + 1 && CheckNeighborsVis(vis1, tile, d)) - { - if (opaque.Contains(tile)) - { - vis1[tile] = -1; - } - else if (vis2.GetValueOrDefault(tile) != 0) - { - vis1[tile] = d + 1; - } - } - } - } - - // Add the eye itself - vis1[eyePos] = 1; - - // Step 6. + // TODO: Pass in attached entity's grid. + // TODO: Credit OD on the moved to code + // TODO: Call the moved-to code here. - // Step 7. + _visibleTiles.Clear(); + _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); - // Step 8. - foreach (var tile in seedTiles) - { - vis2[tile] = vis1.GetValueOrDefault(tile, 0); - } - - // Step 9 - foreach (var tile in seedTiles) - { - if (!opaque.Contains(tile)) - continue; - - var tileVis1 = vis1.GetValueOrDefault(tile); - - if (tileVis1 != 0) - continue; - - if (IsCorner(seedTiles, opaque, vis1, tile, Vector2i.One) || - IsCorner(seedTiles, opaque, vis1, tile, new Vector2i(1, -1)) || - IsCorner(seedTiles, opaque, vis1, tile, new Vector2i(-1, -1)) || - IsCorner(seedTiles, opaque, vis1, tile, new Vector2i(-1, 1))) - { - boundary.Add(tile); - } - } - - // Make all wall/corner tiles visible - foreach (var tile in boundary) - { - vis1[tile] = -1; - } - - // vis2 is what we care about for LOS. - foreach (var tile in seedTiles) - { - var tileVis2 = vis2.GetValueOrDefault(tile, 0); - - if (tileVis2 != 0) - visibleTiles.Add(tile); - } - } - - // TODO: Combine tiles into viewer draw-calls var gridMatrix = xforms.GetWorldMatrix(gridUid); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); @@ -233,7 +79,7 @@ protected override void Draw(in OverlayDrawArgs args) worldHandle.SetTransform(matty); - foreach (var tile in visibleTiles) + foreach (var tile in _visibleTiles) { var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); worldHandle.DrawRect(aabb, Color.White); @@ -257,11 +103,11 @@ protected override void Draw(in OverlayDrawArgs args) worldHandle.SetTransform(matty); - tileEnumerator = maps.GetTilesEnumerator(gridUid, grid!, worldBounds, ignoreEmpty: false); + var tileEnumerator = maps.GetTilesEnumerator(gridUid, grid!, worldBounds, ignoreEmpty: false); while (tileEnumerator.MoveNext(out var tileRef)) { - if (visibleTiles.Contains(tileRef.GridIndices)) + if (_visibleTiles.Contains(tileRef.GridIndices)) continue; var bounds = lookups.GetLocalBounds(tileRef, grid!.TileSize); @@ -299,67 +145,4 @@ protected override void Draw(in OverlayDrawArgs args) worldHandle.UseShader(null); } - - private int GetMaxDelta(Vector2i tile, Vector2i center) - { - var delta = tile - center; - return Math.Max(Math.Abs(delta.X), Math.Abs(delta.Y)); - } - - private int GetSumDelta(Vector2i tile, Vector2i center) - { - var delta = tile - center; - return Math.Abs(delta.X) + Math.Abs(delta.Y); - } - - /// - /// Checks if any of a tile's neighbors are visible. - /// - private bool CheckNeighborsVis( - Dictionary vis, - Vector2i index, - int d) - { - for (var x = -1; x <= 1; x++) - { - for (var y = -1; y <= 1; y++) - { - if (x == 0 && y == 0) - continue; - - var neighbor = index + new Vector2i(x, y); - var neighborD = vis.GetValueOrDefault(neighbor); - - if (neighborD == d) - return true; - } - } - return false; - } - - /// - /// Checks whether this tile fits the definition of a "corner" - /// - private bool IsCorner( - HashSet tiles, - HashSet blocked, - Dictionary vis1, - Vector2i index, - Vector2i delta) - { - var diagonalIndex = index + delta; - - if (!tiles.TryGetValue(diagonalIndex, out var diagonal)) - return false; - - var cardinal1 = new Vector2i(index.X, diagonal.Y); - var cardinal2 = new Vector2i(diagonal.X, index.Y); - - return vis1.GetValueOrDefault(diagonal) != 0 && - vis1.GetValueOrDefault(cardinal1) != 0 && - vis1.GetValueOrDefault(cardinal2) != 0 && - blocked.Contains(cardinal1) && - blocked.Contains(cardinal2) && - !blocked.Contains(diagonal); - } } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 64b55c8dc4d62c..74e70e89035b62 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -23,7 +23,6 @@ public abstract class SharedStationAiSystem : EntitySystem [Dependency] private readonly MetaDataSystem _metadata = default!; /* - * TODO: Fix examines * TODO: Vismask on the AI eye * TODO: Add door bolting * TODO: Sprite / vismask visibility + action diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs index 2b184df7f7b114..f047fe41e4db12 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionComponent.cs @@ -10,7 +10,7 @@ public sealed partial class StationAiVisionComponent : Component public bool Enabled = true; [DataField, AutoNetworkedField] - public bool Occluded = false; + public bool Occluded = true; /// /// Range in tiles diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index 0bb13b77a13b44..cc5a81fc4e540a 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -1,36 +1,371 @@ +using Content.Shared.NPC; using Content.Shared.StationAi; +using Microsoft.Extensions.ObjectPool; +using Robust.Shared.Map; using Robust.Shared.Map.Components; +using Robust.Shared.Threading; +using Robust.Shared.Utility; namespace Content.Shared.Silicons.StationAi; -public sealed class StationAiVisionSystem +public sealed class StationAiVisionSystem : EntitySystem { + [Dependency] private readonly IParallelManager _parallel = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedMapSystem _maps = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; - public void GetView(Entity grid, Box2 worldAabb, List> seeds, HashSet tiles) + private SeedJob _seedJob; + private ViewJob _job; + + private HashSet> _occluders = new(); + private HashSet> _seeds = new(); + private HashSet _viewportTiles = new(); + + // Occupied tiles per-run. + // For now it's only 1-grid supported but updating to TileRefs if required shouldn't be too hard. + private readonly HashSet _opaque = new(); + private readonly HashSet _clear = new(); + + public override void Initialize() { - var tileEnumerator = _maps.GetTilesEnumerator(grid, grid, worldAabb, ignoreEmpty: false); - /* + base.Initialize(); + + _seedJob = new() + { + System = this, + }; + + _job = new ViewJob() + { + EntManager = EntityManager, + Maps = _maps, + System = this, + }; + } + + /// + /// Gets a byond-equivalent for tiles in the specified worldAABB. + /// + /// How much to expand the bounds before to find vision intersecting it. Makes this as small as you can. + public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 7.5f) + { + _viewportTiles.Clear(); + _opaque.Clear(); + _clear.Clear(); + _seeds.Clear(); + var expandedBounds = worldBounds.Enlarged(expansionSize); + + // TODO: Would be nice to be able to run this while running the other stuff. + _seedJob.Grid = grid; + _seedJob.ExpandedBounds = expandedBounds; + _parallel.ProcessNow(_seedJob); + + // Get viewport tiles + var tileEnumerator = _maps.GetTilesEnumerator(grid, grid, worldBounds, ignoreEmpty: false); - // Get what tiles are blocked up-front. while (tileEnumerator.MoveNext(out var tileRef)) { var tileBounds = _lookup.GetLocalBounds(tileRef.GridIndices, grid.Comp.TileSize).Enlarged(-0.05f); - occluders.Clear(); - lookups.GetLocalEntitiesIntersecting(gridUid, tileBounds, occluders, LookupFlags.Static); + _occluders.Clear(); + _lookup.GetLocalEntitiesIntersecting(grid.Owner, tileBounds, _occluders, LookupFlags.Static); - if (occluders.Count > 0) + if (_occluders.Count > 0) { - opaque.Add(tileRef.GridIndices); + _opaque.Add(tileRef.GridIndices); } else { - cleared.Add(tileRef.GridIndices); + _clear.Add(tileRef.GridIndices); + } + + _viewportTiles.Add(tileRef.GridIndices); + } + + tileEnumerator = _maps.GetTilesEnumerator(grid, grid, expandedBounds, ignoreEmpty: false); + + // Get all other relevant tiles. + while (tileEnumerator.MoveNext(out var tileRef)) + { + if (_viewportTiles.Contains(tileRef.GridIndices)) + continue; + + var tileBounds = _lookup.GetLocalBounds(tileRef.GridIndices, grid.Comp.TileSize).Enlarged(-0.05f); + + _occluders.Clear(); + _lookup.GetLocalEntitiesIntersecting(grid.Owner, tileBounds, _occluders, LookupFlags.Static); + + if (_occluders.Count > 0) + { + _opaque.Add(tileRef.GridIndices); + } + else + { + _clear.Add(tileRef.GridIndices); + } + } + + _job.Data.Clear(); + // Wait for seed job here + + foreach (var seed in _seeds) + { + if (!seed.Comp.Enabled) + continue; + + _job.Data.Add(seed); + } + + for (var i = _job.Vis1.Count; i < _job.Data.Count; i++) + { + _job.Vis1.Add(new Dictionary()); + _job.Vis2.Add(new Dictionary()); + _job.SeedTiles.Add(new HashSet()); + _job.BoundaryTiles.Add(new HashSet()); + } + + _job.Grid = grid; + _job.VisibleTiles = visibleTiles; + _parallel.ProcessNow(_job, _job.Data.Count); + } + + private int GetMaxDelta(Vector2i tile, Vector2i center) + { + var delta = tile - center; + return Math.Max(Math.Abs(delta.X), Math.Abs(delta.Y)); + } + + private int GetSumDelta(Vector2i tile, Vector2i center) + { + var delta = tile - center; + return Math.Abs(delta.X) + Math.Abs(delta.Y); + } + + /// + /// Checks if any of a tile's neighbors are visible. + /// + private bool CheckNeighborsVis( + Dictionary vis, + Vector2i index, + int d) + { + for (var x = -1; x <= 1; x++) + { + for (var y = -1; y <= 1; y++) + { + if (x == 0 && y == 0) + continue; + + var neighbor = index + new Vector2i(x, y); + var neighborD = vis.GetValueOrDefault(neighbor); + + if (neighborD == d) + return true; } } + return false; + } + + /// Checks whether this tile fits the definition of a "corner" + /// + private bool IsCorner( + HashSet tiles, + HashSet blocked, + Dictionary vis1, + Vector2i index, + Vector2i delta) + { + var diagonalIndex = index + delta; + + if (!tiles.TryGetValue(diagonalIndex, out var diagonal)) + return false; + + var cardinal1 = new Vector2i(index.X, diagonal.Y); + var cardinal2 = new Vector2i(diagonal.X, index.Y); + + return vis1.GetValueOrDefault(diagonal) != 0 && + vis1.GetValueOrDefault(cardinal1) != 0 && + vis1.GetValueOrDefault(cardinal2) != 0 && + blocked.Contains(cardinal1) && + blocked.Contains(cardinal2) && + !blocked.Contains(diagonal); + } + + /// + /// Gets the relevant vision seeds for later. + /// + private record struct SeedJob() : IRobustJob + { + public StationAiVisionSystem System; + + public Entity Grid; + public Box2Rotated ExpandedBounds; + + public void Execute() + { + var localAABB = System._xforms.GetInvWorldMatrix(Grid.Owner).TransformBox(ExpandedBounds); + System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, localAABB, System._seeds); + } + } + + private record struct ViewJob() : IParallelRobustJob + { + public int BatchSize => 1; + + public IEntityManager EntManager; + public SharedMapSystem Maps; + public StationAiVisionSystem System; + + public Entity Grid; + public List> Data = new(); + + public HashSet VisibleTiles; + + public readonly List> Vis1 = new(); + public readonly List> Vis2 = new(); + + public readonly List> SeedTiles = new(); + public readonly List> BoundaryTiles = new(); + + public void Execute(int index) + { + var seed = Data[index]; + + // Fastpath just get tiles in range. + if (!seed.Comp.Occluded) + { + return; + } + + // Code based upon https://github.com/OpenDreamProject/OpenDream/blob/c4a3828ccb997bf3722673620460ebb11b95ccdf/OpenDreamShared/Dream/ViewAlgorithm.cs + + var range = seed.Comp.Range; + var vis1 = Vis1[index]; + var vis2 = Vis2[index]; + + var seedTiles = SeedTiles[index]; + var boundary = BoundaryTiles[index]; + var maxDepthMax = 0; + var sumDepthMax = 0; + + var eyePos = Maps.GetTileRef(Grid.Owner, Grid, EntManager.GetComponent(seed).Coordinates).GridIndices; + + for (var x = Math.Floor(eyePos.X - range); x <= eyePos.X + range; x++) + { + for (var y = Math.Floor(eyePos.Y - range); y <= eyePos.Y + range; y++) + { + var tile = new Vector2i((int)x, (int)y); + var delta = tile - eyePos; + var xDelta = Math.Abs(delta.X); + var yDelta = Math.Abs(delta.Y); + + var deltaSum = xDelta + yDelta; + + maxDepthMax = Math.Max(maxDepthMax, Math.Max(xDelta, yDelta)); + sumDepthMax = Math.Max(sumDepthMax, deltaSum); + seedTiles.Add(tile); + } + } + + // Step 3, Diagonal shadow loop + for (var d = 0; d < maxDepthMax; d++) + { + foreach (var tile in seedTiles) + { + var maxDelta = System.GetMaxDelta(tile, eyePos); + + if (maxDelta == d + 1 && System.CheckNeighborsVis(vis2, tile, d)) + { + vis2[tile] = (System._opaque.Contains(tile) ? -1 : d + 1); + } + } + } + + // Step 4, Straight shadow loop + for (var d = 0; d < sumDepthMax; d++) + { + foreach (var tile in seedTiles) + { + var sumDelta = System.GetSumDelta(tile, eyePos); + + if (sumDelta == d + 1 && System.CheckNeighborsVis(vis1, tile, d)) + { + if (System._opaque.Contains(tile)) + { + vis1[tile] = -1; + } + else if (vis2.GetValueOrDefault(tile) != 0) + { + vis1[tile] = d + 1; + } + } + } + } + + // Add the eye itself + vis1[eyePos] = 1; + + // Step 6. + + // Step 7. + + // Step 8. + foreach (var tile in seedTiles) + { + vis2[tile] = vis1.GetValueOrDefault(tile, 0); + } + + // Step 9 + foreach (var tile in seedTiles) + { + if (!System._opaque.Contains(tile)) + continue; - */ + var tileVis1 = vis1.GetValueOrDefault(tile); + + if (tileVis1 != 0) + continue; + + if (System.IsCorner(seedTiles, System._opaque, vis1, tile, Vector2i.UpRight) || + System.IsCorner(seedTiles, System._opaque, vis1, tile, Vector2i.UpLeft) || + System.IsCorner(seedTiles, System._opaque, vis1, tile, Vector2i.DownLeft) || + System.IsCorner(seedTiles, System._opaque, vis1, tile, Vector2i.DownRight)) + { + boundary.Add(tile); + } + } + + // Make all wall/corner tiles visible + foreach (var tile in boundary) + { + vis1[tile] = -1; + } + + // vis2 is what we care about for LOS. + foreach (var tile in seedTiles) + { + // If not in viewport don't care. + if (!System._viewportTiles.Contains(tile)) + continue; + + var tileVis2 = vis2.GetValueOrDefault(tile, 0); + + if (tileVis2 != 0) + { + // No idea if it's better to do this inside or out. + lock (VisibleTiles) + { + VisibleTiles.Add(tile); + } + } + } + + vis1.Clear(); + vis2.Clear(); + + seedTiles.Clear(); + boundary.Clear(); + } } } From d7a4a69b3b5827aee1393e0767ecf47c59660eb8 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Fri, 16 Aug 2024 16:48:50 +1000 Subject: [PATCH 12/40] Back to old static --- Content.Client/StationAi/StationAiOverlay.cs | 82 ++++++------------- .../StationAi/StationAiVisionSystem.cs | 2 +- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/StationAi/StationAiOverlay.cs index fa1e879ca3b888..9f8d023133e615 100644 --- a/Content.Client/StationAi/StationAiOverlay.cs +++ b/Content.Client/StationAi/StationAiOverlay.cs @@ -1,16 +1,10 @@ using System.Numerics; -using Content.Client.SurveillanceCamera; using Content.Shared.Silicons.StationAi; -using Content.Shared.StationAi; -using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; -using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Client.StationAi; @@ -26,7 +20,7 @@ public sealed class StationAiOverlay : Overlay private readonly HashSet _visibleTiles = new(); private IRenderTexture? _staticTexture; - public IRenderTexture? _stencilTexture; + private IRenderTexture? _stencilTexture; public StationAiOverlay() { @@ -66,71 +60,49 @@ protected override void Draw(in OverlayDrawArgs args) // TODO: Call the moved-to code here. _visibleTiles.Clear(); - _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles, expansionSize: 8.5f); var gridMatrix = xforms.GetWorldMatrix(gridUid); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); // Draw visible tiles to stencil worldHandle.RenderInRenderTarget(_stencilTexture!, () => - { - if (!gridUid.IsValid()) - return; - - worldHandle.SetTransform(matty); - - foreach (var tile in _visibleTiles) - { - var aabb = lookups.GetLocalBounds(tile, grid!.TileSize); - worldHandle.DrawRect(aabb, Color.White); - } - }, - Color.Transparent); - - // Create static texture - var curTime = IoCManager.Resolve().RealTime; + { + worldHandle.SetTransform(matty); - var noiseTexture = _entManager.System() - .GetFrame(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/noise.rsi"), "noise"), curTime); + foreach (var tile in _visibleTiles) + { + var aabb = lookups.GetLocalBounds(tile, grid.TileSize); + worldHandle.DrawRect(aabb, Color.White); + } + }, + Color.Transparent); // Once this is gucci optimise rendering. worldHandle.RenderInRenderTarget(_staticTexture!, - () => - { - // TODO: Handle properly - if (!gridUid.IsValid()) - return; - - worldHandle.SetTransform(matty); - - var tileEnumerator = maps.GetTilesEnumerator(gridUid, grid!, worldBounds, ignoreEmpty: false); - - while (tileEnumerator.MoveNext(out var tileRef)) - { - if (_visibleTiles.Contains(tileRef.GridIndices)) - continue; - - var bounds = lookups.GetLocalBounds(tileRef, grid!.TileSize); - worldHandle.DrawTextureRect(noiseTexture, bounds, Color.White.WithAlpha(80)); - } - - }, - Color.Black); + () => + { + worldHandle.SetTransform(invMatrix); + var shader = _proto.Index("CameraStatic").Instance(); + worldHandle.UseShader(shader); + worldHandle.DrawRect(worldBounds, Color.White); + }, + Color.Black); } // Not on a grid else { worldHandle.RenderInRenderTarget(_stencilTexture!, () => - { - }, - Color.Transparent); + { + }, + Color.Transparent); worldHandle.RenderInRenderTarget(_staticTexture!, - () => - { - worldHandle.SetTransform(Matrix3x2.Identity); - worldHandle.DrawRect(worldBounds, Color.Black); - }, Color.Black); + () => + { + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.DrawRect(worldBounds, Color.Black); + }, Color.Black); } // Use the lighting as a mask diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index cc5a81fc4e540a..7d6309f9c4655b 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -47,7 +47,7 @@ public override void Initialize() /// /// Gets a byond-equivalent for tiles in the specified worldAABB. /// - /// How much to expand the bounds before to find vision intersecting it. Makes this as small as you can. + /// How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile. public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 7.5f) { _viewportTiles.Clear(); From 951b4ee91fd125414575d6fd965aa04d5aea625f Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 18 Aug 2024 12:46:00 +1000 Subject: [PATCH 13/40] BUI radial working --- Content.Client/Chat/UI/EmotesMenu.xaml.cs | 13 +- .../Commands/SetMenuVisibilityCommand.cs | 1 + .../ContextMenu/UI/EntityMenuUIController.cs | 19 +- .../StationAi/StationAiBoundUserInterface.cs | 28 ++ .../Silicons/StationAi/StationAiMenu.xaml | 13 + .../Silicons/StationAi/StationAiMenu.xaml.cs | 120 +++++++++ .../StationAi/StationAiOverlay.cs | 5 +- .../StationAi/StationAiSystem.Airlock.cs | 27 ++ .../StationAi/StationAiSystem.cs | 6 +- Content.Client/Verbs/VerbSystem.cs | 45 +--- .../Anomaly/AnomalySystem.Generator.cs | 1 + .../Anomaly/AnomalySystem.Vessel.cs | 1 + .../Atmos/Monitor/Systems/AirAlarmSystem.cs | 1 + .../Monitor/Systems/AtmosMonitoringSystem.cs | 1 + .../Atmos/Monitor/Systems/FireAlarmSystem.cs | 1 + Content.Server/Audio/Jukebox/JukeboxSystem.cs | 1 + Content.Server/Bed/BedSystem.cs | 1 + .../Botany/Systems/SeedExtractorSystem.cs | 1 + .../Cargo/Systems/CargoSystem.Telepad.cs | 1 + .../SolutionContainerMixerSystem.cs | 1 + Content.Server/Cloning/CloningSystem.cs | 1 + Content.Server/Construction/FlatpackSystem.cs | 1 + .../DeviceNetworkRequiresPowerSystem.cs | 1 + .../Doors/Systems/FirelockSystem.cs | 1 + .../Electrocution/ElectrocutionSystem.cs | 1 + .../EntitySystems/ReagentGrinderSystem.cs | 1 + Content.Server/Lathe/LatheSystem.cs | 1 + .../Medical/MedicalScannerSystem.cs | 1 + .../EntitySystems/FatExtractorSystem.cs | 1 + .../Power/Components/CableComponent.cs | 9 +- .../EntitySystems/CableMultitoolSystem.cs | 5 +- .../Radio/EntitySystems/RadioDeviceSystem.cs | 1 + .../Research/Systems/ResearchSystem.Client.cs | 1 + .../Systems/ResearchSystem.Console.cs | 1 + .../Systems/ResearchSystem.PointSource.cs | 1 + .../Research/Systems/ResearchSystem.Server.cs | 1 + .../Shuttles/Systems/ShuttleConsoleSystem.cs | 1 + .../Shuttles/Systems/ThrusterSystem.cs | 1 + .../StationAi/AiInteractWireAction.cs | 37 +++ .../Silicons/StationAi/AiVisionWireAction.cs | 4 +- .../Silicons/StationAi/StationAiSystem.cs | 50 +++- .../SingularityAttractorSystem.cs | 1 + .../VendingMachines/VendingMachineSystem.cs | 1 + .../Melee/EnergySword/EnergySwordSystem.cs | 2 +- Content.Server/Wires/BaseWireAction.cs | 1 + Content.Server/Wires/WiresSystem.cs | 88 ++++--- .../Systems/ArtifactCrusherSystem.cs | 1 + .../Systems/TraversalDistorterSystem.cs | 1 + .../XenoArtifacts/ArtifactSystem.cs | 1 + .../ArtifactElectricityTriggerSystem.cs | 2 +- Content.Shared/Actions/ActionEvents.cs | 5 + Content.Shared/Actions/SharedActionsSystem.cs | 13 +- .../Configurable/ConfigurationComponent.cs | 3 +- Content.Shared/Examine/ExamineSystemShared.cs | 33 ++- .../Hands/EntitySystems/SharedHandsSystem.cs | 13 + .../Interaction/SharedInteractionSystem.cs | 33 ++- .../StationAi/IncorporealComponent.cs | 22 ++ .../Silicons/StationAi/IncorporealSystem.cs | 65 +++++ .../StationAi/RemoteInteractComponent.cs | 12 - .../SharedStationAiSystem.Airlock.cs | 25 ++ .../StationAi/SharedStationAiSystem.Held.cs | 116 ++++++++ .../StationAi/SharedStationAiSystem.cs | 160 ++++++++--- .../StationAi/StationAiHeldComponent.cs | 5 +- .../StationAi/StationAiVisionSystem.cs | 248 ++++++++++++++---- .../Tools/Systems/SharedToolSystem.cs | 3 + Content.Shared/Verbs/SharedVerbSystem.cs | 24 ++ Content.Shared/Wires/SharedWiresSystem.cs | 20 +- .../Locale/en-US/silicons/station-ai.ftl | 7 + .../Entities/Mobs/Player/silicon.yml | 25 +- .../Doors/Airlocks/base_structureairlocks.yml | 2 + Resources/Prototypes/Wires/layouts.yml | 1 + .../Mobs/Silicon/station_ai.rsi/holo.png | Bin 6565 -> 0 bytes .../Mobs/Silicon/station_ai.rsi/meta.json | 74 ------ 73 files changed, 1103 insertions(+), 312 deletions(-) create mode 100644 Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs create mode 100644 Content.Client/Silicons/StationAi/StationAiMenu.xaml create mode 100644 Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs rename Content.Client/{ => Silicons}/StationAi/StationAiOverlay.cs (96%) create mode 100644 Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs rename Content.Client/{ => Silicons}/StationAi/StationAiSystem.cs (93%) create mode 100644 Content.Server/Silicons/StationAi/AiInteractWireAction.cs create mode 100644 Content.Shared/Silicons/StationAi/IncorporealComponent.cs create mode 100644 Content.Shared/Silicons/StationAi/IncorporealSystem.cs delete mode 100644 Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs create mode 100644 Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs create mode 100644 Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs create mode 100644 Resources/Locale/en-US/silicons/station-ai.ftl delete mode 100644 Resources/Textures/Mobs/Silicon/station_ai.rsi/holo.png diff --git a/Content.Client/Chat/UI/EmotesMenu.xaml.cs b/Content.Client/Chat/UI/EmotesMenu.xaml.cs index 3340755343890b..f3b7837f21a59c 100644 --- a/Content.Client/Chat/UI/EmotesMenu.xaml.cs +++ b/Content.Client/Chat/UI/EmotesMenu.xaml.cs @@ -19,9 +19,6 @@ public sealed partial class EmotesMenu : RadialMenu [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly ISharedPlayerManager _playerManager = default!; - private readonly SpriteSystem _spriteSystem; - private readonly EntityWhitelistSystem _whitelistSystem; - public event Action>? OnPlayEmote; public EmotesMenu() @@ -29,8 +26,8 @@ public EmotesMenu() IoCManager.InjectDependencies(this); RobustXamlLoader.Load(this); - _spriteSystem = _entManager.System(); - _whitelistSystem = _entManager.System(); + var spriteSystem = _entManager.System(); + var whitelistSystem = _entManager.System(); var main = FindControl("Main"); @@ -40,8 +37,8 @@ public EmotesMenu() var player = _playerManager.LocalSession?.AttachedEntity; if (emote.Category == EmoteCategory.Invalid || emote.ChatTriggers.Count == 0 || - !(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || - _whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) + !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) || + whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value)) continue; if (!emote.Available && @@ -63,7 +60,7 @@ public EmotesMenu() { VerticalAlignment = VAlignment.Center, HorizontalAlignment = HAlignment.Center, - Texture = _spriteSystem.Frame0(emote.Icon), + Texture = spriteSystem.Frame0(emote.Icon), TextureScale = new Vector2(2f, 2f), }; diff --git a/Content.Client/Commands/SetMenuVisibilityCommand.cs b/Content.Client/Commands/SetMenuVisibilityCommand.cs index ddfb0b16920628..17a544dabaf37f 100644 --- a/Content.Client/Commands/SetMenuVisibilityCommand.cs +++ b/Content.Client/Commands/SetMenuVisibilityCommand.cs @@ -1,4 +1,5 @@ using Content.Client.Verbs; +using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.Console; diff --git a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs index 0462c095ba898e..bda831394d3f54 100644 --- a/Content.Client/ContextMenu/UI/EntityMenuUIController.cs +++ b/Content.Client/ContextMenu/UI/EntityMenuUIController.cs @@ -9,6 +9,7 @@ using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Input; +using Content.Shared.Verbs; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; @@ -194,8 +195,20 @@ public override void FrameUpdate(FrameEventArgs args) return; // Do we need to do in-range unOccluded checks? - var ignoreFov = !_eyeManager.CurrentEye.DrawFov || - (_verbSystem.Visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov; + var visibility = _verbSystem.Visibility; + + if (!_eyeManager.CurrentEye.DrawFov) + { + visibility &= ~MenuVisibility.NoFov; + } + + var ev = new MenuVisibilityEvent() + { + Visibility = visibility, + }; + + _entityManager.EventBus.RaiseLocalEvent(player, ref ev); + visibility = ev.Visibility; _entityManager.TryGetComponent(player, out ExaminerComponent? examiner); var xformQuery = _entityManager.GetEntityQuery(); @@ -209,7 +222,7 @@ public override void FrameUpdate(FrameEventArgs args) continue; } - if (ignoreFov) + if ((visibility & MenuVisibility.NoFov) == MenuVisibility.NoFov) continue; var pos = new MapCoordinates(_xform.GetWorldPosition(xform, xformQuery), xform.MapID); diff --git a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs new file mode 100644 index 00000000000000..68318305a0c9e4 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs @@ -0,0 +1,28 @@ +using Content.Shared.Silicons.StationAi; +using Robust.Client.UserInterface; + +namespace Content.Client.Silicons.StationAi; + +public sealed class StationAiBoundUserInterface : BoundUserInterface +{ + private StationAiMenu? _menu; + + public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + _menu = this.CreateWindow(); + _menu.Track(Owner); + + _menu.OnAiRadial += args => + { + SendPredictedMessage(new StationAiRadialMessage() + { + Event = args, + }); + }; + } +} diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml b/Content.Client/Silicons/StationAi/StationAiMenu.xaml new file mode 100644 index 00000000000000..d56fc832898490 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs new file mode 100644 index 00000000000000..2ad406b6378fa2 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs @@ -0,0 +1,120 @@ +using System.Numerics; +using Content.Client.UserInterface.Controls; +using Content.Shared.Silicons.StationAi; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; + +namespace Content.Client.Silicons.StationAi; + +[GenerateTypedNameReferences] +public sealed partial class StationAiMenu : RadialMenu +{ + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IEyeManager _eyeManager = default!; + + public event Action? OnAiRadial; + + private EntityUid _tracked; + + public StationAiMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + } + + public void Track(EntityUid owner) + { + _tracked = owner; + + if (!_entManager.EntityExists(_tracked)) + { + Close(); + return; + } + + BuildButtons(); + UpdatePosition(); + } + + private void BuildButtons() + { + var ev = new GetStationAiRadialEvent(); + _entManager.EventBus.RaiseLocalEvent(_tracked, ref ev); + + var main = FindControl("Main"); + main.DisposeAllChildren(); + var sprites = _entManager.System(); + + foreach (var action in ev.Actions) + { + // TODO: This radial boilerplate is quite annoying + var button = new StationAiMenuButton(action.Event) + { + StyleClasses = { "RadialMenuButton" }, + SetSize = new Vector2(64f, 64f), + ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null, + }; + + if (action.Sprite != null) + { + var tex = new TextureRect + { + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Texture = sprites.Frame0(action.Sprite), + TextureScale = new Vector2(2f, 2f), + }; + + button.AddChild(tex); + } + + button.OnPressed += args => + { + OnAiRadial?.Invoke(action.Event); + Close(); + }; + main.AddChild(button); + } + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + UpdatePosition(); + } + + private void UpdatePosition() + { + if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform)) + { + Close(); + return; + } + + if (!xform.Coordinates.IsValid(_entManager)) + { + Close(); + return; + } + + var coords = _eyeManager.CoordinatesToScreen(xform.Coordinates); + + if (!coords.IsValid) + { + Close(); + return; + } + + OpenScreenAt(coords.Position, _clyde); + } +} + +public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButton +{ + public BaseStationAiAction Action = action; +} diff --git a/Content.Client/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs similarity index 96% rename from Content.Client/StationAi/StationAiOverlay.cs rename to Content.Client/Silicons/StationAi/StationAiOverlay.cs index 9f8d023133e615..845a2efe382efb 100644 --- a/Content.Client/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -6,7 +6,7 @@ using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; -namespace Content.Client.StationAi; +namespace Content.Client.Silicons.StationAi; public sealed class StationAiOverlay : Overlay { @@ -42,7 +42,6 @@ protected override void Draw(in OverlayDrawArgs args) var worldHandle = args.WorldHandle; var worldBounds = args.WorldBounds; - var maps = _entManager.System(); var lookups = _entManager.System(); var xforms = _entManager.System(); @@ -60,7 +59,7 @@ protected override void Draw(in OverlayDrawArgs args) // TODO: Call the moved-to code here. _visibleTiles.Clear(); - _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles, expansionSize: 8.5f); + _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); var gridMatrix = xforms.GetWorldMatrix(gridUid); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs new file mode 100644 index 00000000000000..54f59da7794b86 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs @@ -0,0 +1,27 @@ +using Content.Shared.Doors.Components; +using Content.Shared.Silicons.StationAi; +using Robust.Shared.Utility; + +namespace Content.Client.Silicons.StationAi; + +public sealed partial class StationAiSystem +{ + private void InitializeAirlock() + { + SubscribeLocalEvent(OnDoorBoltGetRadial); + } + + private void OnDoorBoltGetRadial(Entity ent, ref GetStationAiRadialEvent args) + { + args.Actions.Add(new StationAiAction() + { + Sprite = new SpriteSpecifier.Rsi( + new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"), + Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"), + Event = new StationAiBoltEvent() + { + Bolted = !ent.Comp.BoltsDown, + } + }); + } +} diff --git a/Content.Client/StationAi/StationAiSystem.cs b/Content.Client/Silicons/StationAi/StationAiSystem.cs similarity index 93% rename from Content.Client/StationAi/StationAiSystem.cs rename to Content.Client/Silicons/StationAi/StationAiSystem.cs index 74d4ba5329b368..4c38807e215b2e 100644 --- a/Content.Client/StationAi/StationAiSystem.cs +++ b/Content.Client/Silicons/StationAi/StationAiSystem.cs @@ -3,9 +3,9 @@ using Robust.Client.Player; using Robust.Shared.Player; -namespace Content.Client.StationAi; +namespace Content.Client.Silicons.StationAi; -public sealed class StationAiSystem : SharedStationAiSystem +public sealed partial class StationAiSystem : SharedStationAiSystem { [Dependency] private readonly IOverlayManager _overlayMgr = default!; [Dependency] private readonly IPlayerManager _player = default!; @@ -15,6 +15,8 @@ public sealed class StationAiSystem : SharedStationAiSystem public override void Initialize() { base.Initialize(); + InitializeAirlock(); + SubscribeLocalEvent(OnAiAttached); SubscribeLocalEvent(OnAiDetached); SubscribeLocalEvent(OnAiOverlayInit); diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index 5f1f49e5fd08b3..c3e03528a79232 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -67,6 +67,14 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true ? Visibility : Visibility | MenuVisibility.NoFov; + var ev = new MenuVisibilityEvent() + { + TargetPos = targetPos, + Visibility = visibility, + }; + + RaiseLocalEvent(player.Value, ref ev); + visibility = ev.Visibility; // Get entities List entities; @@ -77,13 +85,8 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true var entitiesUnderMouse = gameScreenBase.GetClickableEntities(targetPos).ToHashSet(); bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e); - // first check the general location. - if (!_examine.CanExamine(player.Value, targetPos, Predicate)) - return false; - TryComp(player.Value, out ExaminerComponent? examiner); - // Then check every entity entities = new(); foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize)) { @@ -137,27 +140,6 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true } } - // Remove any entities that do not have LOS - if ((visibility & MenuVisibility.NoFov) == 0) - { - var xformQuery = GetEntityQuery(); - var playerPos = _transform.GetMapCoordinates(player.Value, xform: xformQuery.GetComponent(player.Value)); - - for (var i = entities.Count - 1; i >= 0; i--) - { - var entity = entities[i]; - - if (!_examine.InRangeUnOccluded( - playerPos, - _transform.GetMapCoordinates(entity, xform: xformQuery.GetComponent(entity)), - ExamineSystemShared.ExamineRange, - null)) - { - entities.RemoveSwap(i); - } - } - } - if (entities.Count == 0) return false; @@ -229,15 +211,4 @@ private void HandleVerbResponse(VerbsResponseEvent msg) OnVerbsResponse?.Invoke(msg); } } - - [Flags] - public enum MenuVisibility - { - // What entities can a user see on the entity menu? - Default = 0, // They can only see entities in FoV. - NoFov = 1 << 0, // They ignore FoV restrictions - InContainer = 1 << 1, // They can see through containers. - Invisible = 1 << 2, // They can see entities without sprites and the "HideContextMenu" tag is ignored. - All = NoFov | InContainer | Invisible - } } diff --git a/Content.Server/Anomaly/AnomalySystem.Generator.cs b/Content.Server/Anomaly/AnomalySystem.Generator.cs index 056a985cbe2040..5ef686c0d5573a 100644 --- a/Content.Server/Anomaly/AnomalySystem.Generator.cs +++ b/Content.Server/Anomaly/AnomalySystem.Generator.cs @@ -13,6 +13,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Map; using System.Numerics; +using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; namespace Content.Server.Anomaly; diff --git a/Content.Server/Anomaly/AnomalySystem.Vessel.cs b/Content.Server/Anomaly/AnomalySystem.Vessel.cs index 98e56a884453d9..0185328b352a8e 100644 --- a/Content.Server/Anomaly/AnomalySystem.Vessel.cs +++ b/Content.Server/Anomaly/AnomalySystem.Vessel.cs @@ -4,6 +4,7 @@ using Content.Shared.Anomaly.Components; using Content.Shared.Examine; using Content.Shared.Interaction; +using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Anomaly; diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index f4650861dbcae4..9dbef3ddfdbee2 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -18,6 +18,7 @@ using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Systems; using Content.Shared.Interaction; +using Content.Shared.Power.EntitySystems; using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Player; diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs index c1a5256fdd5bb5..02e72b97f7b574 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.EntitySystems; using Content.Shared.Tag; using Robust.Shared.Prototypes; diff --git a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs index 0a3ee4d7f7dc76..c9254bfa938e5b 100644 --- a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.DeviceNetwork.Systems; using Content.Shared.Interaction; using Content.Shared.Emag.Systems; +using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Configuration; diff --git a/Content.Server/Audio/Jukebox/JukeboxSystem.cs b/Content.Server/Audio/Jukebox/JukeboxSystem.cs index cc9235e3d7a41a..d59dd2366c5232 100644 --- a/Content.Server/Audio/Jukebox/JukeboxSystem.cs +++ b/Content.Server/Audio/Jukebox/JukeboxSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Audio.Jukebox; +using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Components; diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index a6b61da591f349..087d8b749b46af 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Damage; using Content.Shared.Emag.Systems; using Content.Shared.Mobs.Systems; +using Content.Shared.Power.EntitySystems; using Robust.Shared.Timing; using Robust.Shared.Utility; diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs index 4a0d56bfe98133..42ea0a45229fc5 100644 --- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Power.EntitySystems; using Robust.Shared.Random; namespace Content.Server.Botany.Systems; diff --git a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs index f83ec1a5123b6e..938f4ebb7a561c 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs @@ -6,6 +6,7 @@ using Content.Shared.Cargo; using Content.Shared.Cargo.Components; using Content.Shared.DeviceLinking; +using Content.Shared.Power.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Random; using Robust.Shared.Utility; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs index a942d34e7a82dd..94512d4b187110 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Power.EntitySystems; namespace Content.Server.Chemistry.EntitySystems; diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 3893f31d25d755..fcdb1d5b3231a7 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -23,6 +23,7 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Power.EntitySystems; using Content.Shared.Roles.Jobs; using Robust.Server.Containers; using Robust.Server.GameObjects; diff --git a/Content.Server/Construction/FlatpackSystem.cs b/Content.Server/Construction/FlatpackSystem.cs index af2132723cd136..8ee1f8011724d7 100644 --- a/Content.Server/Construction/FlatpackSystem.cs +++ b/Content.Server/Construction/FlatpackSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Construction; using Content.Shared.Construction.Components; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Power.EntitySystems; using Robust.Shared.Prototypes; using Robust.Shared.Timing; diff --git a/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs index 6e7bd255c5dc4c..f47a5df8ac401d 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs @@ -1,6 +1,7 @@ using Content.Server.DeviceNetwork.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Shared.Power.EntitySystems; namespace Content.Server.DeviceNetwork.Systems; diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 87e5887c422c5e..85562e0cabbfa0 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Atmos.Monitor; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; +using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 67e60c9de4660c..b3df21695ac6b4 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -18,6 +18,7 @@ using Content.Shared.Jittering; using Content.Shared.Maps; using Content.Shared.Popups; +using Content.Shared.Power.EntitySystems; using Content.Shared.Speech.EntitySystems; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index b4023bbdb9ffdd..097f30fa186d38 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -21,6 +21,7 @@ using System.Linq; using Content.Server.Jittering; using Content.Shared.Jittering; +using Content.Shared.Power.EntitySystems; namespace Content.Server.Kitchen.EntitySystems { diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index f6e5903bbe2b59..1418c4d70cdd84 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Examine; using Content.Shared.Lathe; using Content.Shared.Materials; +using Content.Shared.Power.EntitySystems; using Content.Shared.ReagentSpeed; using Content.Shared.Research.Components; using Content.Shared.Research.Prototypes; diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index b24690e204a961..612cf356bce9e7 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -14,6 +14,7 @@ using Content.Shared.Climbing.Systems; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Power.EntitySystems; using Robust.Server.Containers; using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; // Hmm... diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs index 180e40d1e42625..f3e2565fa3ff0d 100644 --- a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Emag.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Power.EntitySystems; using Content.Shared.Storage.Components; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; diff --git a/Content.Server/Power/Components/CableComponent.cs b/Content.Server/Power/Components/CableComponent.cs index a2a02a60f681b5..7398bc0616e132 100644 --- a/Content.Server/Power/Components/CableComponent.cs +++ b/Content.Server/Power/Components/CableComponent.cs @@ -4,6 +4,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using System.Diagnostics.Tracing; +using Content.Shared.Tools.Systems; namespace Content.Server.Power.Components; @@ -14,11 +15,11 @@ namespace Content.Server.Power.Components; [Access(typeof(CableSystem))] public sealed partial class CableComponent : Component { - [DataField("cableDroppedOnCutPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string CableDroppedOnCutPrototype = "CableHVStack1"; + [DataField] + public EntProtoId CableDroppedOnCutPrototype = "CableHVStack1"; - [DataField("cuttingQuality", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string CuttingQuality = "Cutting"; + [DataField] + public ProtoId CuttingQuality = SharedToolSystem.CutQuality; /// /// Checked by to determine if there is diff --git a/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs b/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs index 15b967bb1d5cbb..4a63be894ef2bd 100644 --- a/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs +++ b/Content.Server/Power/EntitySystems/CableMultitoolSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Tools; using Content.Shared.Examine; using Content.Shared.Interaction; +using Content.Shared.Tools.Systems; using Content.Shared.Verbs; using JetBrains.Annotations; using Robust.Shared.Utility; @@ -27,7 +28,7 @@ public override void Initialize() private void OnAfterInteractUsing(EntityUid uid, CableComponent component, AfterInteractUsingEvent args) { - if (args.Handled || args.Target == null || !args.CanReach || !_toolSystem.HasQuality(args.Used, "Pulsing")) + if (args.Handled || args.Target == null || !args.CanReach || !_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality)) return; var markup = FormattedMessage.FromMarkup(GenerateCableMarkup(uid)); @@ -45,7 +46,7 @@ private void OnGetExamineVerbs(EntityUid uid, CableComponent component, GetVerbs // Pulsing is hardcoded here because I don't think it needs to be more complex than that right now. // Update if I'm wrong. - var enabled = held != null && _toolSystem.HasQuality(held.Value, "Pulsing"); + var enabled = held != null && _toolSystem.HasQuality(held.Value, SharedToolSystem.PulseQuality); var verb = new ExamineVerb { Disabled = !enabled, diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 1258e0b8c7ebce..797b776d1b8dcc 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -9,6 +9,7 @@ using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; +using Content.Shared.Power.EntitySystems; using Content.Shared.Radio; using Content.Shared.Radio.Components; using Robust.Shared.Prototypes; diff --git a/Content.Server/Research/Systems/ResearchSystem.Client.cs b/Content.Server/Research/Systems/ResearchSystem.Client.cs index f8fdba55b7636f..4d6548095177a3 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Client.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Client.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Power.EntitySystems; +using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Research.Systems; diff --git a/Content.Server/Research/Systems/ResearchSystem.Console.cs b/Content.Server/Research/Systems/ResearchSystem.Console.cs index 5358ddefcdf4f7..38b548fdf85887 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Console.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Console.cs @@ -2,6 +2,7 @@ using Content.Server.Research.Components; using Content.Shared.UserInterface; using Content.Shared.Access.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; using Content.Shared.Research.Prototypes; diff --git a/Content.Server/Research/Systems/ResearchSystem.PointSource.cs b/Content.Server/Research/Systems/ResearchSystem.PointSource.cs index f069b1c80f7d23..399c28ec62fabd 100644 --- a/Content.Server/Research/Systems/ResearchSystem.PointSource.cs +++ b/Content.Server/Research/Systems/ResearchSystem.PointSource.cs @@ -1,5 +1,6 @@ using Content.Server.Power.EntitySystems; using Content.Server.Research.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Research.Systems; diff --git a/Content.Server/Research/Systems/ResearchSystem.Server.cs b/Content.Server/Research/Systems/ResearchSystem.Server.cs index 09ca7ed15c27a2..8fe0371413c66b 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Server.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Server.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Power.EntitySystems; +using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Research.Systems; diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index 00a913aad867c6..f079a5b58a4060 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Tag; using Content.Shared.Movement.Systems; +using Content.Shared.Power.EntitySystems; using Content.Shared.Shuttles.UI.MapObjects; using Content.Shared.Timing; using Robust.Server.GameObjects; diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index fd149630814c16..a8a8b163a435e6 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -19,6 +19,7 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Shared.Localizations; +using Content.Shared.Power.EntitySystems; namespace Content.Server.Shuttles.Systems; diff --git a/Content.Server/Silicons/StationAi/AiInteractWireAction.cs b/Content.Server/Silicons/StationAi/AiInteractWireAction.cs new file mode 100644 index 00000000000000..7afbf2c99c1364 --- /dev/null +++ b/Content.Server/Silicons/StationAi/AiInteractWireAction.cs @@ -0,0 +1,37 @@ +using Content.Server.Wires; +using Content.Shared.Doors; +using Content.Shared.Silicons.StationAi; +using Content.Shared.Wires; + +namespace Content.Server.Silicons.StationAi; + +/// +/// Controls whether an AI can interact with the target entity. +/// +public sealed partial class AiInteractWireAction : ComponentWireAction +{ + public override string Name { get; set; } = "wire-name-ai-light"; + public override Color Color { get; set; } = Color.DeepSkyBlue; + public override object StatusKey => AirlockWireStatus.AiControlIndicator; + + public override StatusLightState? GetLightState(Wire wire, StationAiWhitelistComponent component) + { + return component.Enabled ? StatusLightState.On : StatusLightState.Off; + } + + public override bool Cut(EntityUid user, Wire wire, StationAiWhitelistComponent component) + { + return EntityManager.System() + .SetWhitelistEnabled((component.Owner, component), false, announce: true); + } + + public override bool Mend(EntityUid user, Wire wire, StationAiWhitelistComponent component) + { + return EntityManager.System() + .SetWhitelistEnabled((component.Owner, component), true); + } + + public override void Pulse(EntityUid user, Wire wire, StationAiWhitelistComponent component) + { + } +} diff --git a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs index 161c6e76ad2383..b01e8487990638 100644 --- a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs +++ b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs @@ -23,13 +23,13 @@ public sealed partial class AiVisionWireAction : ComponentWireAction() - .SetEnabled((component.Owner, component), false, announce: true); + .SetVisionEnabled((component.Owner, component), false, announce: true); } public override bool Mend(EntityUid user, Wire wire, StationAiVisionComponent component) { return EntityManager.System() - .SetEnabled((component.Owner, component), true); + .SetVisionEnabled((component.Owner, component), true); } public override void Pulse(EntityUid user, Wire wire, StationAiVisionComponent component) diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index e4e75e37cd345d..03ac877b254a51 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -1,26 +1,57 @@ +using System.Linq; +using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; +using Content.Shared.Chat; using Content.Shared.Silicons.StationAi; using Content.Shared.StationAi; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map.Components; using Robust.Shared.Player; namespace Content.Server.Silicons.StationAi; public sealed class StationAiSystem : SharedStationAiSystem { - [Dependency] private readonly ChatSystem _chats = default!; + [Dependency] private readonly IChatManager _chats = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; private HashSet> _ais = new(); - public override bool SetEnabled(Entity entity, bool enabled, bool announce = false) + public override bool SetVisionEnabled(Entity entity, bool enabled, bool announce = false) { - if (!base.SetEnabled(entity, enabled, announce)) + if (!base.SetVisionEnabled(entity, enabled, announce)) return false; + if (announce) + { + AnnounceSnip(entity.Owner); + } + + return true; + } + + public override bool SetWhitelistEnabled(Entity entity, bool enabled, bool announce = false) + { + if (!base.SetWhitelistEnabled(entity, enabled, announce)) + return false; + + if (announce) + { + AnnounceSnip(entity.Owner); + } + + return true; + } + + private void AnnounceSnip(EntityUid entity) + { var xform = Transform(entity); + if (!TryComp(xform.GridUid, out MapGridComponent? grid)) + return; + _ais.Clear(); - _lookup.GetChildEntities(xform.ParentUid, _ais); + _lookup.GetChildEntities(xform.GridUid.Value, _ais); var filter = Filter.Empty(); foreach (var ai in _ais) @@ -32,7 +63,14 @@ public override bool SetEnabled(Entity entity, bool en } } - _chats.DispatchFilteredAnnouncement(filter, Loc.GetString("ai-wire-snipped"), entity.Owner, announcementSound: null); - return true; + // TEST + // filter = Filter.Broadcast(); + + // No easy way to do chat notif embeds atm. + var tile = Maps.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates); + var msg = Loc.GetString("ai-wire-snipped", ("coords", tile)); + + _chats.ChatMessageToMany(ChatChannel.Notifications, msg, msg, entity, false, true, filter.Recipients.Select(o => o.Channel)); + // Apparently there's no sound for this. } } diff --git a/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs b/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs index bc0de7c8c64f12..c96041eb0a1f64 100644 --- a/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs @@ -5,6 +5,7 @@ using Robust.Shared.Map; using Robust.Shared.Timing; using System.Numerics; +using Content.Shared.Power.EntitySystems; namespace Content.Server.Singularity.EntitySystems; diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 2866b14a835193..162b2bd6bdd5eb 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -16,6 +16,7 @@ using Content.Shared.Emag.Systems; using Content.Shared.Emp; using Content.Shared.Popups; +using Content.Shared.Power.EntitySystems; using Content.Shared.Throwing; using Content.Shared.UserInterface; using Content.Shared.VendingMachines; diff --git a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs index 5970e16319615a..c9be87c623188b 100644 --- a/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs +++ b/Content.Server/Weapons/Melee/EnergySword/EnergySwordSystem.cs @@ -38,7 +38,7 @@ private void OnInteractUsing(EntityUid uid, EnergySwordComponent comp, InteractU if (args.Handled) return; - if (!_toolSystem.HasQuality(args.Used, "Pulsing")) + if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality)) return; args.Handled = true; diff --git a/Content.Server/Wires/BaseWireAction.cs b/Content.Server/Wires/BaseWireAction.cs index ef6a0fdf36e459..e1e7768ebcde24 100644 --- a/Content.Server/Wires/BaseWireAction.cs +++ b/Content.Server/Wires/BaseWireAction.cs @@ -1,6 +1,7 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Database; +using Content.Shared.Power.EntitySystems; using Content.Shared.Wires; namespace Content.Server.Wires; diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index beb84d3e116bde..1bd709295a49b1 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -3,13 +3,16 @@ using System.Threading; using Content.Server.Construction; using Content.Server.Construction.Components; +using Content.Server.Hands.Systems; using Content.Server.Power.Components; using Content.Shared.DoAfter; using Content.Shared.GameTicking; using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Tools.Components; +using Content.Shared.Tools.Systems; using Content.Shared.Wires; using Robust.Server.GameObjects; using Robust.Shared.Player; @@ -22,8 +25,8 @@ public sealed class WiresSystem : SharedWiresSystem { [Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ConstructionSystem _construction = default!; @@ -43,6 +46,7 @@ public override void Initialize() // this is a broadcast event SubscribeLocalEvent(OnPanelChanged); SubscribeLocalEvent(OnWiresActionMessage); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnTimedWire); @@ -387,37 +391,15 @@ private void OnWiresPowered(EntityUid uid, WiresComponent component, ref PowerCh private void OnWiresActionMessage(EntityUid uid, WiresComponent component, WiresActionMessage args) { - if (args.Actor == null) - { - return; - } - var player = (EntityUid) args.Actor; - - if (!EntityManager.TryGetComponent(player, out HandsComponent? handsComponent)) - { - _popupSystem.PopupEntity(Loc.GetString("wires-component-ui-on-receive-message-no-hands"), uid, player); - return; - } - - if (!_interactionSystem.InRangeUnobstructed(player, uid)) - { - _popupSystem.PopupEntity(Loc.GetString("wires-component-ui-on-receive-message-cannot-reach"), uid, player); - return; - } - - var activeHand = handsComponent.ActiveHand; + var player = args.Actor; - if (activeHand == null) - return; - - if (activeHand.HeldEntity == null) - return; + // TODO: Get hand or self tool + var toolUid = _hands.GetActiveItemOrSelf(player); - var activeHandEntity = activeHand.HeldEntity.Value; - if (!EntityManager.TryGetComponent(activeHandEntity, out ToolComponent? tool)) + if (!EntityManager.TryGetComponent(toolUid, out ToolComponent? tool)) return; - TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool); + TryDoWireAction(uid, player, toolUid, args.Id, args.Action, component, tool); } private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args) @@ -436,25 +418,45 @@ private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args.Handled = true; } + private void OnActivate(Entity ent, ref ActivateInWorldEvent args) + { + if (args.Handled) + return; + + var toolUid = args.User; + + if (!TryComp(args.User, out var tool)) + return; + + if (!IsPanelOpen(ent.Owner, toolUid)) + return; + + if (Tool.HasQuality(toolUid, SharedToolSystem.CutQuality, tool) || + Tool.HasQuality(toolUid, SharedToolSystem.PulseQuality, tool)) + { + _uiSystem.OpenUi(ent.Owner, WiresUiKey.Key, args.User); + args.Handled = true; + } + } + private void OnInteractUsing(EntityUid uid, WiresComponent component, InteractUsingEvent args) { if (args.Handled) return; - if (!TryComp(args.Used, out var tool)) + var toolUid = args.Used; + + if (!TryComp(args.User, out var tool)) return; - if (!IsPanelOpen(uid)) + if (!IsPanelOpen(uid, toolUid)) return; - if (Tool.HasQuality(args.Used, "Cutting", tool) || - Tool.HasQuality(args.Used, "Pulsing", tool)) + if (Tool.HasQuality(toolUid, SharedToolSystem.CutQuality, tool) || + Tool.HasQuality(toolUid, SharedToolSystem.PulseQuality, tool)) { - if (TryComp(args.User, out ActorComponent? actor)) - { - _uiSystem.OpenUi(uid, WiresUiKey.Key, actor.PlayerSession); - args.Handled = true; - } + _uiSystem.OpenUi(uid, WiresUiKey.Key, args.User); + args.Handled = true; } } @@ -627,7 +629,7 @@ private void TryDoWireAction(EntityUid target, EntityUid user, EntityUid toolEnt switch (action) { case WiresAction.Cut: - if (!Tool.HasQuality(toolEntity, "Cutting", tool)) + if (!Tool.HasQuality(toolEntity, SharedToolSystem.CutQuality, tool)) { _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user); return; @@ -641,7 +643,7 @@ private void TryDoWireAction(EntityUid target, EntityUid user, EntityUid toolEnt break; case WiresAction.Mend: - if (!Tool.HasQuality(toolEntity, "Cutting", tool)) + if (!Tool.HasQuality(toolEntity, SharedToolSystem.CutQuality, tool)) { _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user); return; @@ -655,7 +657,7 @@ private void TryDoWireAction(EntityUid target, EntityUid user, EntityUid toolEnt break; case WiresAction.Pulse: - if (!Tool.HasQuality(toolEntity, "Pulsing", tool)) + if (!Tool.HasQuality(toolEntity, SharedToolSystem.PulseQuality, tool)) { _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user); return; @@ -714,7 +716,7 @@ private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, i switch (action) { case WiresAction.Cut: - if (!Tool.HasQuality(toolEntity, "Cutting", tool)) + if (!Tool.HasQuality(toolEntity, SharedToolSystem.CutQuality, tool)) { _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user); break; @@ -735,7 +737,7 @@ private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, i UpdateUserInterface(used); break; case WiresAction.Mend: - if (!Tool.HasQuality(toolEntity, "Cutting", tool)) + if (!Tool.HasQuality(toolEntity, SharedToolSystem.CutQuality, tool)) { _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-wirecutters"), user); break; @@ -756,7 +758,7 @@ private void UpdateWires(EntityUid used, EntityUid user, EntityUid toolEntity, i UpdateUserInterface(used); break; case WiresAction.Pulse: - if (!Tool.HasQuality(toolEntity, "Pulsing", tool)) + if (!Tool.HasQuality(toolEntity, SharedToolSystem.PulseQuality, tool)) { _popupSystem.PopupCursor(Loc.GetString("wires-component-ui-on-receive-message-need-multitool"), user); break; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs index f65ba46f7a5df3..1b4ce82daf614a 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Shared.Body.Components; using Content.Shared.Damage; +using Content.Shared.Power.EntitySystems; using Content.Shared.Verbs; using Content.Shared.Whitelist; using Content.Shared.Xenoarchaeology.Equipment; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs index d277792243d8dd..d207ff5ef39abd 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Placeable; +using Content.Shared.Power.EntitySystems; using Robust.Shared.Timing; namespace Content.Server.Xenoarchaeology.Equipment.Systems; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs index a5469e93dc008a..ed726c70a8be3f 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.CCVar; +using Content.Shared.Power.EntitySystems; using Content.Shared.Xenoarchaeology.XenoArtifacts; using JetBrains.Annotations; using Robust.Shared.Audio; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs index 019e09bbbbc018..9d2fd58980f2ce 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactElectricityTriggerSystem.cs @@ -44,7 +44,7 @@ private void OnInteractUsing(EntityUid uid, ArtifactElectricityTriggerComponent if (args.Handled) return; - if (!_toolSystem.HasQuality(args.Used, "Pulsing")) + if (!_toolSystem.HasQuality(args.Used, SharedToolSystem.PulseQuality)) return; args.Handled = _artifactSystem.TryActivateArtifact(uid, args.User); diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs index 4f1cd6da44aa21..e754b023c1ea2b 100644 --- a/Content.Shared/Actions/ActionEvents.cs +++ b/Content.Shared/Actions/ActionEvents.cs @@ -188,4 +188,9 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs /// The action the event belongs to. /// public EntityUid Action; + + /// + /// Should we toggle the action entity? + /// + public bool Toggle; } diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 635d78b8dd9390..41b6d84d12386b 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -653,6 +653,10 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti return; // no interaction occurred. // play sound, reduce charges, start cooldown, and mark as dirty (if required). + if (actionEvent?.Toggle == true) + { + action.Toggled = !action.Toggled; + } _audio.PlayPredicted(action.Sound, performer,predicted ? performer : null); @@ -673,10 +677,11 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti action.Cooldown = (curTime, curTime + action.UseDelay.Value); } - Dirty(actionId, action); - - if (dirty && component != null) - Dirty(performer, component); + if (dirty) + { + Dirty(actionId, action); + UpdateAction(actionId, action); + } var ev = new ActionPerformedEvent(performer); RaiseLocalEvent(actionId, ref ev); diff --git a/Content.Shared/Configurable/ConfigurationComponent.cs b/Content.Shared/Configurable/ConfigurationComponent.cs index 63a0dfe95a50fb..621871af3cee5f 100644 --- a/Content.Shared/Configurable/ConfigurationComponent.cs +++ b/Content.Shared/Configurable/ConfigurationComponent.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using Content.Shared.Tools; +using Content.Shared.Tools.Systems; using Robust.Shared.GameStates; using Robust.Shared.Serialization; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; @@ -13,7 +14,7 @@ public sealed partial class ConfigurationComponent : Component public Dictionary Config = new(); [DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string QualityNeeded = "Pulsing"; + public string QualityNeeded = SharedToolSystem.PulseQuality; [DataField("validation")] public Regex Validation = new("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled); diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index f0406c539891da..3d83886f2d34ba 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -117,12 +117,25 @@ public virtual bool CanExamine(EntityUid examiner, MapCoordinates target, Ignore if (EntityManager.GetComponent(examiner).MapID != target.MapId) return false; - return InRangeUnOccluded( - _transform.GetMapCoordinates(examiner), - target, - GetExaminerRange(examiner), - predicate: predicate, - ignoreInsideBlocker: true); + // Do target InRangeUnoccluded which has different checks. + if (examined != null) + { + return InRangeUnOccluded( + examiner, + examined.Value, + GetExaminerRange(examiner), + predicate: predicate, + ignoreInsideBlocker: true); + } + else + { + return InRangeUnOccluded( + examiner, + target, + GetExaminerRange(examiner), + predicate: predicate, + ignoreInsideBlocker: true); + } } /// @@ -214,6 +227,14 @@ public bool InRangeUnOccluded(MapCoordinates origin, MapCoordinates othe public bool InRangeUnOccluded(EntityUid origin, EntityUid other, float range = ExamineRange, Ignored? predicate = null, bool ignoreInsideBlocker = true) { + var ev = new InRangeOverrideEvent(origin, other); + RaiseLocalEvent(origin, ref ev); + + if (ev.Handled) + { + return ev.InRange; + } + var originPos = _transform.GetMapCoordinates(origin); var otherPos = _transform.GetMapCoordinates(other); diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs index e48aafeab52f69..7fe73ec1ca0697 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs @@ -159,6 +159,19 @@ public bool TryGetActiveItem(Entity entity, [NotNullWhen(true)] return item != null; } + /// + /// Gets active hand item if relevant otherwise gets the entity itself. + /// + public EntityUid GetActiveItemOrSelf(Entity entity) + { + if (!TryGetActiveItem(entity, out var item)) + { + return entity.Owner; + } + + return item.Value; + } + public Hand? GetActiveHand(Entity entity) { if (!Resolve(entity, ref entity.Comp)) diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 38350c2c438197..8539b9d282b8fd 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -629,8 +629,13 @@ public bool InRangeUnobstructed( if (!Resolve(other, ref other.Comp)) return false; - if (HasComp(origin)) - return true; + var ev = new InRangeOverrideEvent(origin, other); + RaiseLocalEvent(origin, ref ev); + + if (ev.Handled) + { + return ev.InRange; + } return InRangeUnobstructed(origin, other, @@ -1130,7 +1135,7 @@ public bool AltInteract(EntityUid user, EntityUid target) // Get list of alt-interact verbs var verbs = _verbSystem.GetLocalVerbs(target, user, typeof(AlternativeVerb)); - if (!verbs.Any()) + if (verbs.Count == 0) return false; _verbSystem.ExecuteVerb(verbs.First(), user, target); @@ -1185,7 +1190,8 @@ public bool InRangeAndAccessible( public bool IsAccessible(Entity user, Entity target) { var ev = new AccessibleOverrideEvent(user, target); - RaiseLocalEvent(user, ref ev, true); + + RaiseLocalEvent(user, ref ev); if (ev.Handled) return ev.Accessible; @@ -1382,8 +1388,10 @@ public record struct GetUsedEntityEvent() public record struct CombatModeShouldHandInteractEvent(bool Cancelled = false); /// - /// Override event raised directed on a user to say it can access the target. + /// Override event raised directed on the user to say the target is accessible. /// + /// + /// [ByRefEvent] public record struct AccessibleOverrideEvent(EntityUid User, EntityUid Target) { @@ -1391,6 +1399,19 @@ public record struct AccessibleOverrideEvent(EntityUid User, EntityUid Target) public readonly EntityUid Target = Target; public bool Handled; - public bool Accessible = true; + public bool Accessible = false; + } + + /// + /// Override event raised directed on a user to check InRangeUnoccluded AND InRangeUnobstructed to the target if you require custom logic. + /// + [ByRefEvent] + public record struct InRangeOverrideEvent(EntityUid User, EntityUid Target) + { + public readonly EntityUid User = User; + public readonly EntityUid Target = Target; + + public bool Handled; + public bool InRange = false; } } diff --git a/Content.Shared/Silicons/StationAi/IncorporealComponent.cs b/Content.Shared/Silicons/StationAi/IncorporealComponent.cs new file mode 100644 index 00000000000000..0f5e4c6f1621e8 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/IncorporealComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Silicons.StationAi; + +/// +/// Toggles vismask and sprite visibility of an entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class IncorporealComponent : Component +{ + [DataField, AutoNetworkedField] + public bool Visible = true; + + /// + /// Alpha to have when disabled. + /// + [DataField] + public float Alpha = 0.05f; + + [DataField, AutoNetworkedField] + public float VisibleSpeedModifier = 0.85f; +} diff --git a/Content.Shared/Silicons/StationAi/IncorporealSystem.cs b/Content.Shared/Silicons/StationAi/IncorporealSystem.cs new file mode 100644 index 00000000000000..504b09bf467b3a --- /dev/null +++ b/Content.Shared/Silicons/StationAi/IncorporealSystem.cs @@ -0,0 +1,65 @@ +using Content.Shared.Eye; +using Content.Shared.Movement.Systems; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +public sealed class IncorporealSystem : EntitySystem +{ + // Somewhat placeholder for holopads + + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly MovementSpeedModifierSystem _speed = default!; + [Dependency] private readonly SharedVisibilitySystem _vis = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnIncorporealMapInit); + SubscribeLocalEvent(OnIncorporealSpeed); + } + + private void OnIncorporealSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args) + { + if (ent.Comp.Visible) + args.ModifySpeed(ent.Comp.VisibleSpeedModifier); + } + + private void OnIncorporealMapInit(Entity ent, ref MapInitEvent args) + { + UpdateAppearance(ent); + } + + private void UpdateAppearance(Entity ent) + { + _appearance.SetData(ent.Owner, IncorporealState.Base, ent.Comp.Visible); + } + + public bool SetVisible(Entity entity, bool value) + { + if (entity.Comp.Visible == value) + return false; + + entity.Comp.Visible = value; + Dirty(entity); + + if (value) + { + _vis.AddLayer(entity.Owner, (ushort) VisibilityFlags.Normal); + } + else + { + _vis.RemoveLayer(entity.Owner, (ushort) VisibilityFlags.Normal); + } + + UpdateAppearance(entity); + _speed. RefreshMovementSpeedModifiers(entity); + return true; + } +} + +[Serializable, NetSerializable] +public enum IncorporealState : byte +{ + Base, +} diff --git a/Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs b/Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs deleted file mode 100644 index 6ad3ad7aa41509..00000000000000 --- a/Content.Shared/Silicons/StationAi/RemoteInteractComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Silicons.StationAi; - -/// -/// Indicates that InRangeUnobstructed checks should be bypassed for this entity, effectively giving it infinite interaction range. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class RemoteInteractComponent : Component -{ - -} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs new file mode 100644 index 00000000000000..ff6fc1ece07967 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs @@ -0,0 +1,25 @@ +using Content.Shared.Doors.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + // Handles airlock radial + + private void InitializeAirlock() + { + SubscribeLocalEvent(OnAirlockBolt); + } + + private void OnAirlockBolt(EntityUid ent, DoorBoltComponent component, StationAiBoltEvent args) + { + _doors.SetBoltsDown((ent, component), args.Bolted, args.User, predicted: true); + } +} + +[Serializable, NetSerializable] +public sealed class StationAiBoltEvent : BaseStationAiAction +{ + public bool Bolted; +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs new file mode 100644 index 00000000000000..8b93ea9d9af3c8 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -0,0 +1,116 @@ +using Content.Shared.Interaction.Events; +using Content.Shared.Verbs; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + /* + * Added when an entity is inserted into a StationAiCore. + */ + + private void InitializeHeld() + { + SubscribeLocalEvent(OnRadialMessage); + SubscribeLocalEvent(OnMessageAttempt); + SubscribeLocalEvent>(OnTargetVerbs); + SubscribeLocalEvent(OnHeldInteraction); + } + + private void OnRadialMessage(StationAiRadialMessage ev) + { + if (!TryGetEntity(ev.Entity, out var target)) + return; + + ev.Event.User = ev.Actor; + RaiseLocalEvent(target.Value, (object) ev.Event); + } + + private void OnMessageAttempt(BoundUserInterfaceMessageAttempt ev) + { + if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) && + (!ValidateAi((ev.Actor, aiComp)) || + !HasComp(ev.Target))) + { + ev.Cancel(); + } + } + + private void OnHeldInteraction(Entity ent, ref InteractionAttemptEvent args) + { + args.Cancelled = !TryComp(args.Target, out StationAiWhitelistComponent? whitelist) || !whitelist.Enabled; + } + + private void OnTargetVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanComplexInteract || !ent.Comp.Enabled || !TryComp(args.User, out StationAiHeldComponent? aiComp)) + return; + + var user = args.User; + var target = args.Target; + + var isOpen = _uiSystem.IsUiOpen(target, AiUi.Key, user); + + args.Verbs.Add(new AlternativeVerb() + { + // TODO: Localise + Text = isOpen ? "Close actions" : "Open actions", + Act = () => + { + if (isOpen) + { + _uiSystem.CloseUi(ent.Owner, AiUi.Key, user); + } + else + { + _uiSystem.OpenUi(ent.Owner, AiUi.Key, user); + } + } + }); + } +} + +[Serializable, NetSerializable] +public sealed class StationAiRadialMessage : BoundUserInterfaceMessage +{ + public BaseStationAiAction Event = default!; +} + +// Do nothing on server just here for shared move along. +public sealed class StationAiAction : BaseStationAiAction +{ + public SpriteSpecifier? Sprite; + + public string? Tooltip; + + public BaseStationAiAction Event = default!; +} + +/// +/// Abstract parent for radial actions events. +/// When a client requests a radial action this will get sent. +/// +[Serializable, NetSerializable] +public abstract class BaseStationAiAction +{ + [field:NonSerialized] + public EntityUid User { get; set; } +} + +// No idea if there's a better way to do this. +/// +/// Grab actions possible for an AI on the target entity. +/// +[ByRefEvent] +public record struct GetStationAiRadialEvent() +{ + public List Actions = new(); +} + +[Serializable, NetSerializable] +public enum AiUi : byte +{ + Key, +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 74e70e89035b62..24e8c6fde49e29 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -1,41 +1,39 @@ -using Content.Shared.Chat; +using Content.Shared.ActionBlocker; +using Content.Shared.Actions; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Doors.Systems; using Content.Shared.Interaction; -using Content.Shared.Interaction.Events; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.StationAi; using Content.Shared.Verbs; using Robust.Shared.Containers; +using Robust.Shared.Map.Components; using Robust.Shared.Serialization; using Robust.Shared.Timing; namespace Content.Shared.Silicons.StationAi; -public abstract class SharedStationAiSystem : EntitySystem +public abstract partial class SharedStationAiSystem : EntitySystem { - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly ItemSlotsSystem _slots = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedContainerSystem _containers = default!; - [Dependency] private readonly SharedEyeSystem _eye = default!; - [Dependency] private readonly SharedMoverController _mover = default!; - [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedContainerSystem _containers = default!; + [Dependency] private readonly SharedDoorSystem _doors = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] protected readonly SharedMapSystem Maps = default!; + [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly StationAiVisionSystem _vision = default!; /* - * TODO: Vismask on the AI eye - * TODO: Add door bolting - * TODO: Sprite / vismask visibility + action * TODO: Double-check positronic interactions didn't break - * Need action bar - * AI wire for doors. - * Need to check giving comp "just works" and maybe map act for it - * Need to move the view stuff to shared + parallel + optimise - * Need cameras to be snip-snippable and to turn ai vision off, also xray support, also power - * Need to bump PVS range for AI to like 20 or something - * Make it a screen-space overlay - * Need interaction whitelist or something working - * Need destruction and all that + * Fix inventory GUI + * Need destruction + * Cleanup the shitcode */ // StationAiHeld is added to anything inside of an AI core. @@ -46,8 +44,14 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnAiAccessible); - SubscribeLocalEvent(OnAiInteraction); + InitializeAirlock(); + InitializeHeld(); + + SubscribeLocalEvent(OnAiBuiCheck); + + SubscribeLocalEvent(OnAiAccessible); + SubscribeLocalEvent(OnAiInRange); + SubscribeLocalEvent(OnAiMenu); SubscribeLocalEvent(OnHolderInit); SubscribeLocalEvent(OnHolderRemove); @@ -62,27 +66,79 @@ public override void Initialize() SubscribeLocalEvent(OnAiShutdown); } - public virtual bool SetEnabled(Entity entity, bool enabled, bool announce = false) + private void OnAiAccessible(Entity ent, ref AccessibleOverrideEvent args) { - if (entity.Comp.Enabled == enabled) - return false; + args.Handled = true; - entity.Comp.Enabled = enabled; - Dirty(entity); + // Hopefully AI never needs storage + if (_containers.TryGetContainingContainer(args.Target, out var targetContainer)) + { + return; + } - return true; + if (!_containers.IsInSameOrTransparentContainer(args.User, args.Target, otherContainer: targetContainer)) + { + return; + } + + args.Accessible = true; } - private void OnAiInteraction(Entity ent, ref InteractionAttemptEvent args) + private void OnAiMenu(Entity ent, ref MenuVisibilityEvent args) { - args.Cancelled = !HasComp(args.Target); + args.Visibility &= ~MenuVisibility.NoFov; } - private void OnAiAccessible(Entity ent, ref AccessibleOverrideEvent args) + private void OnAiBuiCheck(Entity ent, ref BoundUserInterfaceCheckRangeEvent args) + { + args.Result = BoundUserInterfaceRangeResult.Fail; + + // Similar to the inrange check but more optimised so server doesn't die. + var targetXform = Transform(args.Target); + + // No cross-grid + if (targetXform.GridUid != args.Actor.Comp.GridUid) + { + return; + } + + if (!TryComp(targetXform.GridUid, out MapGridComponent? grid)) + { + return; + } + + var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates); + + lock (_vision) + { + if (_vision.IsAccessible((targetXform.GridUid.Value, grid), targetTile, fastPath: true)) + { + args.Result = BoundUserInterfaceRangeResult.Pass; + } + } + } + + private void OnAiInRange(Entity ent, ref InRangeOverrideEvent args) { - // TODO: Validate it's near cameras - args.Accessible = true; args.Handled = true; + var targetXform = Transform(args.Target); + + // No cross-grid + if (targetXform.GridUid != Transform(args.User).GridUid) + { + return; + } + + // Validate it's in camera range yes this is expensive. + // Yes it needs optimising + if (!TryComp(targetXform.GridUid, out MapGridComponent? grid)) + { + return; + } + + var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates); + + args.InRange = _vision.IsAccessible((targetXform.GridUid.Value, grid), targetTile); } private void OnHolderInteract(Entity ent, ref AfterInteractEvent args) @@ -191,7 +247,6 @@ private void OnAiInsert(Entity ent, ref EntInsertedIntoC EnsureComp(args.Entity); EnsureComp(args.Entity); EnsureComp(args.Entity); - EnsureComp(args.Entity); AttachEye(ent); } @@ -215,7 +270,6 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo RemCompDeferred(args.Entity); RemCompDeferred(args.Entity); RemCompDeferred(args.Entity); - RemCompDeferred(args.Entity); } protected void UpdateAppearance(Entity entity) @@ -232,6 +286,38 @@ protected void UpdateAppearance(Entity entity) _appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Occupied); } + + public virtual bool SetVisionEnabled(Entity entity, bool enabled, bool announce = false) + { + if (entity.Comp.Enabled == enabled) + return false; + + entity.Comp.Enabled = enabled; + Dirty(entity); + + return true; + } + + public virtual bool SetWhitelistEnabled(Entity entity, bool value, bool announce = false) + { + if (entity.Comp.Enabled == value) + return false; + + entity.Comp.Enabled = value; + Dirty(entity); + + return true; + } + + private bool ValidateAi(Entity entity) + { + if (!Resolve(entity.Owner, ref entity.Comp, false)) + { + return false; + } + + return _blocker.CanComplexInteract(entity.Owner); + } } [Serializable, NetSerializable] diff --git a/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs b/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs index 17c0548e52b669..6dab1ee491a7bf 100644 --- a/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiHeldComponent.cs @@ -6,7 +6,4 @@ namespace Content.Shared.Silicons.StationAi; /// Indicates this entity is currently held inside of a station AI core. /// [RegisterComponent, NetworkedComponent] -public sealed partial class StationAiHeldComponent : Component -{ - -} +public sealed partial class StationAiHeldComponent : Component; diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index 7d6309f9c4655b..35a116758b3a2f 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -1,7 +1,4 @@ -using Content.Shared.NPC; using Content.Shared.StationAi; -using Microsoft.Extensions.ObjectPool; -using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Threading; using Robust.Shared.Utility; @@ -10,6 +7,12 @@ namespace Content.Shared.Silicons.StationAi; public sealed class StationAiVisionSystem : EntitySystem { + /* + * This class handles 2 things: + * 1. It handles general "what tiles are visible" line of sight checks. + * 2. It does single-tile lookups to tell if they're visible or not with support for a faster range-only path. + */ + [Dependency] private readonly IParallelManager _parallel = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedMapSystem _maps = default!; @@ -18,14 +21,26 @@ public sealed class StationAiVisionSystem : EntitySystem private SeedJob _seedJob; private ViewJob _job; - private HashSet> _occluders = new(); - private HashSet> _seeds = new(); - private HashSet _viewportTiles = new(); + private readonly HashSet> _occluders = new(); + private readonly HashSet> _seeds = new(); + private readonly HashSet _viewportTiles = new(); + + // Dummy set + private readonly HashSet _singleTiles = new(); // Occupied tiles per-run. // For now it's only 1-grid supported but updating to TileRefs if required shouldn't be too hard. private readonly HashSet _opaque = new(); - private readonly HashSet _clear = new(); + + /// + /// Do we skip line of sight checks and just check vision ranges. + /// + private bool FastPath; + + /// + /// Have we found the target tile if we're only checking for a single one. + /// + private bool TargetFound; public override void Initialize() { @@ -44,25 +59,105 @@ public override void Initialize() }; } + /// + /// Returns whether a tile is accessible based on vision. + /// + public bool IsAccessible(Entity grid, Vector2i tile, float expansionSize = 8.5f, bool fastPath = false) + { + _viewportTiles.Clear(); + _opaque.Clear(); + _seeds.Clear(); + _viewportTiles.Add(tile); + var localBounds = _lookup.GetLocalBounds(tile, grid.Comp.TileSize); + var expandedBounds = localBounds.Enlarged(expansionSize); + + _seedJob.Grid = grid; + _seedJob.ExpandedBounds = expandedBounds; + _parallel.ProcessNow(_seedJob); + _job.Data.Clear(); + FastPath = fastPath; + + foreach (var seed in _seeds) + { + if (!seed.Comp.Enabled) + continue; + + _job.Data.Add(seed); + } + + if (_seeds.Count == 0) + return false; + + // Skip occluders step if we're just doing range checks. + if (!fastPath) + { + var tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, expandedBounds, ignoreEmpty: false); + + // Get all other relevant tiles. + while (tileEnumerator.MoveNext(out var tileRef)) + { + var tileBounds = _lookup.GetLocalBounds(tileRef.GridIndices, grid.Comp.TileSize).Enlarged(-0.05f); + + _occluders.Clear(); + _lookup.GetLocalEntitiesIntersecting(grid.Owner, tileBounds, _occluders, LookupFlags.Static); + + if (_occluders.Count > 0) + { + _opaque.Add(tileRef.GridIndices); + } + } + } + + for (var i = _job.Vis1.Count; i < _job.Data.Count; i++) + { + _job.Vis1.Add(new Dictionary()); + _job.Vis2.Add(new Dictionary()); + _job.SeedTiles.Add(new HashSet()); + _job.BoundaryTiles.Add(new HashSet()); + } + + _job.TargetTile = tile; + TargetFound = false; + _singleTiles.Clear(); + _job.Grid = grid; + _job.VisibleTiles = _singleTiles; + _parallel.ProcessNow(_job, _job.Data.Count); + + return TargetFound; + } + /// /// Gets a byond-equivalent for tiles in the specified worldAABB. /// /// How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile. - public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 7.5f) + public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 8.5f) { _viewportTiles.Clear(); _opaque.Clear(); - _clear.Clear(); _seeds.Clear(); var expandedBounds = worldBounds.Enlarged(expansionSize); // TODO: Would be nice to be able to run this while running the other stuff. _seedJob.Grid = grid; - _seedJob.ExpandedBounds = expandedBounds; + var localAABB = _xforms.GetInvWorldMatrix(grid).TransformBox(expandedBounds); + _seedJob.ExpandedBounds = localAABB; _parallel.ProcessNow(_seedJob); + _job.Data.Clear(); + FastPath = false; + + foreach (var seed in _seeds) + { + if (!seed.Comp.Enabled) + continue; + + _job.Data.Add(seed); + } + + if (_seeds.Count == 0) + return; // Get viewport tiles - var tileEnumerator = _maps.GetTilesEnumerator(grid, grid, worldBounds, ignoreEmpty: false); + var tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAABB, ignoreEmpty: false); while (tileEnumerator.MoveNext(out var tileRef)) { @@ -75,15 +170,11 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash { _opaque.Add(tileRef.GridIndices); } - else - { - _clear.Add(tileRef.GridIndices); - } _viewportTiles.Add(tileRef.GridIndices); } - tileEnumerator = _maps.GetTilesEnumerator(grid, grid, expandedBounds, ignoreEmpty: false); + tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAABB, ignoreEmpty: false); // Get all other relevant tiles. while (tileEnumerator.MoveNext(out var tileRef)) @@ -100,23 +191,10 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash { _opaque.Add(tileRef.GridIndices); } - else - { - _clear.Add(tileRef.GridIndices); - } } - _job.Data.Clear(); // Wait for seed job here - foreach (var seed in _seeds) - { - if (!seed.Comp.Enabled) - continue; - - _job.Data.Add(seed); - } - for (var i = _job.Vis1.Count; i < _job.Data.Count; i++) { _job.Vis1.Add(new Dictionary()); @@ -125,6 +203,8 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _job.BoundaryTiles.Add(new HashSet()); } + _job.TargetTile = null; + TargetFound = false; _job.Grid = grid; _job.VisibleTiles = visibleTiles; _parallel.ProcessNow(_job, _job.Data.Count); @@ -200,12 +280,11 @@ private record struct SeedJob() : IRobustJob public StationAiVisionSystem System; public Entity Grid; - public Box2Rotated ExpandedBounds; + public Box2 ExpandedBounds; public void Execute() { - var localAABB = System._xforms.GetInvWorldMatrix(Grid.Owner).TransformBox(ExpandedBounds); - System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, localAABB, System._seeds); + System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds); } } @@ -220,6 +299,9 @@ private record struct ViewJob() : IParallelRobustJob public Entity Grid; public List> Data = new(); + // If we're doing range-checks might be able to early out + public Vector2i? TargetTile; + public HashSet VisibleTiles; public readonly List> Vis1 = new(); @@ -230,11 +312,56 @@ private record struct ViewJob() : IParallelRobustJob public void Execute(int index) { + // If we're looking for a single tile then early-out if someone else has found it. + if (TargetTile != null) + { + lock (System) + { + if (System.TargetFound) + { + return; + } + } + } + var seed = Data[index]; + var seedXform = EntManager.GetComponent(seed); // Fastpath just get tiles in range. - if (!seed.Comp.Occluded) + // Either xray-vision or system is doing a quick-and-dirty check. + if (!seed.Comp.Occluded || System.FastPath) { + var squircles = Maps.GetLocalTilesIntersecting(Grid.Owner, + Grid.Comp, + new Circle(System._xforms.GetWorldPosition(seedXform), seed.Comp.Range), ignoreEmpty: false); + + // Try to find the target tile. + if (TargetTile != null) + { + foreach (var tile in squircles) + { + if (tile.GridIndices == TargetTile) + { + lock (System) + { + System.TargetFound = true; + } + + return; + } + } + } + else + { + lock (VisibleTiles) + { + foreach (var tile in squircles) + { + VisibleTiles.Add(tile.GridIndices); + } + } + } + return; } @@ -246,10 +373,18 @@ public void Execute(int index) var seedTiles = SeedTiles[index]; var boundary = BoundaryTiles[index]; + + // Cleanup last run + vis1.Clear(); + vis2.Clear(); + + seedTiles.Clear(); + boundary.Clear(); + var maxDepthMax = 0; var sumDepthMax = 0; - var eyePos = Maps.GetTileRef(Grid.Owner, Grid, EntManager.GetComponent(seed).Coordinates).GridIndices; + var eyePos = Maps.GetTileRef(Grid.Owner, Grid, seedXform.Coordinates).GridIndices; for (var x = Math.Floor(eyePos.X - range); x <= eyePos.X + range; x++) { @@ -342,30 +477,43 @@ public void Execute(int index) vis1[tile] = -1; } - // vis2 is what we care about for LOS. - foreach (var tile in seedTiles) + if (TargetTile != null) { - // If not in viewport don't care. - if (!System._viewportTiles.Contains(tile)) - continue; - - var tileVis2 = vis2.GetValueOrDefault(tile, 0); - - if (tileVis2 != 0) + if (vis2.TryGetValue(TargetTile.Value, out var tileVis2)) { - // No idea if it's better to do this inside or out. - lock (VisibleTiles) + DebugTools.Assert(seedTiles.Contains(TargetTile.Value)); + + if (tileVis2 != 0) { - VisibleTiles.Add(tile); + lock (System) + { + System.TargetFound = true; + return; + } } } } + else + { + // vis2 is what we care about for LOS. + foreach (var tile in seedTiles) + { + // If not in viewport don't care. + if (!System._viewportTiles.Contains(tile)) + continue; - vis1.Clear(); - vis2.Clear(); + var tileVis2 = vis2.GetValueOrDefault(tile, 0); - seedTiles.Clear(); - boundary.Clear(); + if (tileVis2 != 0) + { + // No idea if it's better to do this inside or out. + lock (VisibleTiles) + { + VisibleTiles.Add(tile); + } + } + } + } } } } diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs index 201eb19a88ba05..86b91dcda46778 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs @@ -33,6 +33,9 @@ public abstract partial class SharedToolSystem : EntitySystem [Dependency] private readonly TurfSystem _turfs = default!; [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainer = default!; + public const string CutQuality = "Cutting"; + public const string PulseQuality = "Pulsing"; + public override void Initialize() { InitializeMultipleTool(); diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index c3f906f2936e75..37840dcbb5478c 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Interaction; using Content.Shared.Inventory.VirtualItem; using Robust.Shared.Containers; +using Robust.Shared.Map; namespace Content.Shared.Verbs { @@ -174,4 +175,27 @@ public virtual void ExecuteVerb(Verb verb, EntityUid user, EntityUid target, boo _interactionSystem.DoContactInteraction(user, target); } } + + // Does nothing on server + /// + /// Raised directed when trying to get the entity menu visibility for entities. + /// + [ByRefEvent] + public record struct MenuVisibilityEvent + { + public MapCoordinates TargetPos; + public MenuVisibility Visibility; + } + + // Does nothing on server + [Flags] + public enum MenuVisibility + { + // What entities can a user see on the entity menu? + Default = 0, // They can only see entities in FoV. + NoFov = 1 << 0, // They ignore FoV restrictions + InContainer = 1 << 1, // They can see through containers. + Invisible = 1 << 2, // They can see entities without sprites and the "HideContextMenu" tag is ignored. + All = NoFov | InContainer | Invisible + } } diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index 7032293eaf6e4e..c4f860e165c73e 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -130,11 +130,20 @@ public bool CanTogglePanel(Entity ent, EntityUid? user) return !attempt.Cancelled; } - public bool IsPanelOpen(Entity entity) + public bool IsPanelOpen(Entity entity, EntityUid? tool = null) { if (!Resolve(entity, ref entity.Comp, false)) return true; + if (tool != null) + { + var ev = new PanelOverrideEvent(); + RaiseLocalEvent(tool.Value, ref ev); + + if (ev.Allowed) + return true; + } + // Listen, i don't know what the fuck this component does. it's stapled on shit for airlocks // but it looks like an almost direct duplication of WiresPanelComponent except with a shittier API. if (TryComp(entity, out var wiresPanelSecurity) && @@ -161,3 +170,12 @@ private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPan _activatableUI.CloseAll(uid); } } + +/// +/// Raised directed on a tool to try and override panel visibility. +/// +[ByRefEvent] +public record struct PanelOverrideEvent() +{ + public bool Allowed = true; +} diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl new file mode 100644 index 00000000000000..33617a1be5d3f2 --- /dev/null +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -0,0 +1,7 @@ +# General +ai-wire-snipped = Wire has been cut at {$coords}. +wire-name-ai-light = AI + +# Radial actions +bolt-close = Close bolt +bolt-open = Open bolt diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index f8b4121d8a94ee..16840a5197b154 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -1,5 +1,3 @@ -# Actions -# TODO # Ai - type: entity @@ -95,6 +93,7 @@ - state: ai shader: unshaded +# The actual brain inside the core - type: entity id: StationAiBrain parent: PositronicBrain @@ -105,11 +104,14 @@ # Once it's in a core it's pretty much an abstract entity at that point. visible: false - type: ComplexInteraction + - type: DoorRemote - type: Actions + - type: Access + groups: + - AllAccess - type: Eye drawFov: false - type: Examiner - checkInRangeUnoccluded: false - type: InputMover - type: Tag tags: @@ -131,12 +133,25 @@ components: - type: Eye pvsScale: 1.5 + - type: Appearance + - type: GenericVisualizer + visuals: + enum.IncorporealState.Base: + base: + True: + color: '#FFFFFFFF' + False: + color: '#FFFFFF0A' + - type: Incorporeal + visible: false + - type: Visibility + layer: 2 - type: Sprite sprite: Mobs/Silicon/station_ai.rsi - visible: false layers: - - state: holo + - state: default shader: unshaded + map: ["base"] # Borgs - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index f303fe6f9d04da..fe725b068410e9 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -105,6 +105,8 @@ - type: SpawnOnOverload - type: UserInterface interfaces: + enum.AiUi.Key: + type: StationAiBoundUserInterface enum.WiresUiKey.Key: type: WiresBoundUserInterface - type: Airtight diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index ec72f006a67a80..5c441796e0a6ba 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -9,6 +9,7 @@ - !type:DoorBoltLightWireAction - !type:DoorTimingWireAction - !type:DoorSafetyWireAction + - !type:AiInteractWireAction - type: wireLayout parent: Airlock diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/holo.png b/Resources/Textures/Mobs/Silicon/station_ai.rsi/holo.png deleted file mode 100644 index 3297549f2ae41ec0f9a4b98830ac69b0601e5c52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6565 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 diff --git a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json index eb42b02a5546a9..a3da52233dd12e 100644 --- a/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json +++ b/Resources/Textures/Mobs/Silicon/station_ai.rsi/meta.json @@ -41,80 +41,6 @@ ] ] }, - { - "name": "holo", - "directions": 4, - "delays": [ - [ - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1 - ], - [ - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1 - ], - [ - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1 - ], - [ - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1, - 0.1 - ] - ] - }, { "name": "default", "directions": 4 From 128530afe59d14cde6118cdfa5d6f3b4eee4c21b Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 18 Aug 2024 21:38:24 +1000 Subject: [PATCH 14/40] lots of work --- .../Lobby/UI/HumanoidProfileEditor.xaml.cs | 7 + .../Lobby/UI/Loadouts/LoadoutWindow.xaml | 18 +- .../Lobby/UI/Loadouts/LoadoutWindow.xaml.cs | 63 +- .../EntitySystems/PowerReceiverSystem.cs | 13 + .../Silicons/StationAi/StationAiMenu.xaml.cs | 14 +- .../StationAi/StationAiSystem.Airlock.cs | 7 +- .../StationAi/StationAiSystem.Light.cs | 32 + .../Silicons/StationAi/StationAiSystem.cs | 1 + .../Inventory/InventoryUIController.cs | 7 + .../Inventory/Widgets/InventoryGui.xaml | 2 + .../20240818064956_LoadoutNames.Designer.cs | 1967 +++++++++++++++++ .../Postgres/20240818064956_LoadoutNames.cs | 65 + .../PostgresServerDbContextModelSnapshot.cs | 11 +- .../20240818065011_LoadoutNames.Designer.cs | 1890 ++++++++++++++++ .../Sqlite/20240818065011_LoadoutNames.cs | 29 + .../SqliteServerDbContextModelSnapshot.cs | 7 + Content.Server.Database/Model.cs | 10 +- .../AlertLevel/AlertLevelDisplaySystem.cs | 1 + .../Ame/EntitySystems/AmeControllerSystem.cs | 1 + .../Anomaly/AnomalySynchronizerSystem.cs | 1 + .../Anomaly/AnomalySystem.Generator.cs | 1 + .../Arcade/BlockGame/BlockGameArcadeSystem.cs | 1 + .../SpaceVillainArcadeSystem.cs | 1 + .../Atmos/Monitor/Systems/AirAlarmSystem.cs | 1 + .../Monitor/Systems/AtmosAlarmableSystem.cs | 1 + .../Monitor/Systems/AtmosMonitoringSystem.cs | 1 + .../Unary/EntitySystems/GasVentPumpSystem.cs | 1 + .../EntitySystems/GasVentScrubberSystem.cs | 1 + .../Atmos/Portable/PortableScrubberSystem.cs | 1 + .../Atmos/Portable/SpaceHeaterSystem.cs | 1 + Content.Server/Audio/AmbientSoundSystem.cs | 1 + Content.Server/Audio/Jukebox/JukeboxSystem.cs | 1 + Content.Server/Bed/BedSystem.cs | 1 + .../Buckle/Systems/AntiRotOnBuckleSystem.cs | 1 + .../Cargo/Systems/CargoSystem.Telepad.cs | 1 + .../SolutionContainerMixerSystem.cs | 1 + .../EntitySystems/SolutionHeaterSystem.cs | 1 + .../Cloning/CloningConsoleSystem.cs | 1 + .../CommunicationsConsoleSystem.cs | 4 - .../ConstructionSystem.Computer.cs | 1 + Content.Server/Construction/FlatpackSystem.cs | 1 + Content.Server/Database/ServerDbBase.cs | 7 +- .../Systems/SingletonDeviceNetServerSystem.cs | 1 + .../Unit/EntitySystems/DisposalUnitSystem.cs | 1 + Content.Server/Doors/Systems/AirlockSystem.cs | 1 + Content.Server/Doors/Systems/DoorSystem.cs | 1 + .../Doors/Systems/FirelockSystem.cs | 1 + Content.Server/Fax/FaxSystem.cs | 1 + .../Kitchen/EntitySystems/MicrowaveSystem.cs | 1 + .../EntitySystems/ReagentGrinderSystem.cs | 1 + Content.Server/Lathe/LatheSystem.cs | 1 + .../EntitySystems/EmergencyLightSystem.cs | 1 + .../Light/EntitySystems/LitOnPoweredSystem.cs | 1 + .../Light/EntitySystems/PoweredLightSystem.cs | 1 + .../Materials/MaterialReclaimerSystem.cs | 1 + .../BiomassReclaimerSystem.cs | 1 + Content.Server/Medical/CryoPodSystem.cs | 1 + .../EntitySystems/FatExtractorSystem.cs | 1 + .../ParticleAcceleratorSystem.ControlBox.cs | 1 + .../Physics/Controllers/ConveyorController.cs | 1 + .../Components/ApcPowerReceiverComponent.cs | 7 - .../Power/EntitySystems/ChargerSystem.cs | 1 + .../Power/EntitySystems/PowerNetSystem.cs | 1 + .../EntitySystems/PowerReceiverSystem.cs | 13 + .../Power/Generation/Teg/TegSystem.cs | 1 + .../Power/Generator/GasPowerReceiverSystem.cs | 1 + .../Radio/EntitySystems/RadioDeviceSystem.cs | 1 + .../Shuttles/Systems/ShuttleConsoleSystem.cs | 1 + .../Shuttles/Systems/ThrusterSystem.cs | 1 + .../StationAi/AiInteractWireAction.cs | 2 +- .../Silicons/StationAi/AiVisionWireAction.cs | 2 +- .../EntitySystems/EmitterSystem.cs | 1 + .../Sound/SpamEmitSoundRequirePowerSystem.cs | 1 + .../Station/Systems/StationSpawningSystem.cs | 40 +- .../SurveillanceCameraMonitorSystem.cs | 1 + .../Systems/SurveillanceCameraRouterSystem.cs | 1 + .../Systems/SurveillanceCameraSystem.cs | 1 + .../Temperature/Systems/EntityHeaterSystem.cs | 1 + .../VendingMachines/VendingMachineSystem.cs | 1 + Content.Server/Wires/WiresSystem.cs | 1 + .../Systems/ArtifactAnalyzerSystem.cs | 1 + .../Systems/ArtifactCrusherSystem.cs | 1 + .../Containers/ContainerCompComponent.cs | 17 + .../Containers/ContainerCompSystem.cs | 47 + Content.Shared/Content.Shared.csproj | 3 - .../Components/ItemToggleComponent.cs | 12 +- .../Item/ItemToggle/ItemToggleSystem.cs | 40 +- .../ItemTogglePointLightComponent.cs | 12 + .../Components/SlimPoweredLightComponent.cs | 17 + .../ItemTogglePointLightSystem.cs | 29 + .../EntitySystems/SlimPoweredLightSystem.cs | 63 + .../UnpoweredFlashlightSystem.cs | 2 + .../Systems/SharedMoverController.Input.cs | 40 +- .../Power/Components/PowerChangedEvent.cs | 8 + .../SharedPowerReceiverSystem.cs | 11 + .../Preferences/HumanoidCharacterProfile.cs | 1 + .../Preferences/Loadouts/RoleLoadout.cs | 28 +- .../Loadouts/RoleLoadoutPrototype.cs | 16 +- .../StationAi/SharedStationAiSystem.Held.cs | 24 +- .../StationAi/SharedStationAiSystem.Light.cs | 28 + .../StationAi/SharedStationAiSystem.cs | 41 +- .../Station/SharedStationSpawningSystem.cs | 31 +- .../UserInterface/IntrinsicUIComponent.cs | 12 +- .../UserInterface/IntrinsicUISystem.cs | 17 +- Resources/Locale/en-US/items/toggle.ftl | 2 + .../Locale/en-US/job/department-desc.ftl | 1 + Resources/Locale/en-US/job/department.ftl | 1 + .../Locale/en-US/job/job-description.ftl | 1 + Resources/Locale/en-US/job/job-names.ftl | 3 +- .../Locale/en-US/preferences/loadouts.ftl | 4 + .../Locale/en-US/silicons/station-ai.ftl | 8 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 1 - .../Entities/Mobs/Player/silicon.yml | 46 +- .../Entities/Objects/Devices/pda.yml | 1 - .../Machines/Computers/computers.yml | 1 + .../Wallmounts/surveillance_camera.yml | 14 + .../Prototypes/Loadouts/role_loadouts.yml | 6 + .../Prototypes/Roles/play_time_trackers.yml | 3 + Resources/Prototypes/Wires/layouts.yml | 3 +- 119 files changed, 4742 insertions(+), 128 deletions(-) create mode 100644 Content.Client/Silicons/StationAi/StationAiSystem.Light.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.Designer.cs create mode 100644 Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs create mode 100644 Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs create mode 100644 Content.Shared/Containers/ContainerCompComponent.cs create mode 100644 Content.Shared/Containers/ContainerCompSystem.cs create mode 100644 Content.Shared/Light/Components/ItemTogglePointLightComponent.cs create mode 100644 Content.Shared/Light/Components/SlimPoweredLightComponent.cs create mode 100644 Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs create mode 100644 Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs create mode 100644 Content.Shared/Power/Components/PowerChangedEvent.cs create mode 100644 Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs rename {Content.Server => Content.Shared}/UserInterface/IntrinsicUIComponent.cs (59%) rename {Content.Server => Content.Shared}/UserInterface/IntrinsicUISystem.cs (73%) create mode 100644 Resources/Locale/en-US/items/toggle.ftl diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index c5f2f311d9b7db..f6bd88e6caa549 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -1009,6 +1009,13 @@ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLo _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection); _loadoutWindow.OpenCenteredLeft(); + _loadoutWindow.OnNameChanged += name => + { + roleLoadout.EntityName = name; + Profile = Profile.WithLoadout(roleLoadout); + SetDirty(); + }; + _loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) => { roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager); diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml index afa783c7aa9c03..b0d28e3412f6e8 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml @@ -1,10 +1,22 @@ + MinSize="800 128"> + + + + VerticalExpand="True" + HorizontalExpand="True"> + diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs index d029eb1223d8df..5bc1cf4351baa0 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; @@ -5,12 +6,14 @@ using Robust.Client.UserInterface.XAML; using Robust.Shared.Player; using Robust.Shared.Prototypes; +using Robust.Shared.Random; namespace Content.Client.Lobby.UI.Loadouts; [GenerateTypedNameReferences] public sealed partial class LoadoutWindow : FancyWindow { + public event Action? OnNameChanged; public event Action, ProtoId>? OnLoadoutPressed; public event Action, ProtoId>? OnLoadoutUnpressed; @@ -23,28 +26,58 @@ public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, Role RobustXamlLoader.Load(this); Profile = profile; var protoManager = collection.Resolve(); + RoleNameEdit.IsValid = text => text.Length <= HumanoidCharacterProfile.MaxLoadoutNameLength; - foreach (var group in proto.Groups) + // Hide if we can't edit the name. + if (!proto.CanCustomiseName) { - if (!protoManager.TryIndex(group, out var groupProto)) - continue; + RoleNameBox.Visible = false; + } + else + { + var name = loadout.EntityName; + var random = collection.Resolve(); - if (groupProto.Hidden) - continue; + // Pick a random name if we use a dataset. + if (name != null && protoManager.TryIndex(proto.NameDataset, out var nameData)) + { + RoleNameEdit.PlaceHolder = random.Pick(nameData.Values); + } - var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection); - LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name)); - _groups.Add(container); + RoleNameEdit.Text = name ?? string.Empty; + RoleNameEdit.OnTextChanged += args => OnNameChanged?.Invoke(args.Text); + } - container.OnLoadoutPressed += args => + // Hide if no groups + if (proto.Groups.Count == 0) + { + LoadoutGroupsContainer.Visible = false; + SetSize = Vector2.Zero; + } + else + { + foreach (var group in proto.Groups) { - OnLoadoutPressed?.Invoke(group, args); - }; + if (!protoManager.TryIndex(group, out var groupProto)) + continue; - container.OnLoadoutUnpressed += args => - { - OnLoadoutUnpressed?.Invoke(group, args); - }; + if (groupProto.Hidden) + continue; + + var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection); + LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name)); + _groups.Add(container); + + container.OnLoadoutPressed += args => + { + OnLoadoutPressed?.Invoke(group, args); + }; + + container.OnLoadoutUnpressed += args => + { + OnLoadoutUnpressed?.Invoke(group, args); + }; + } } } diff --git a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs index 61e20f751cafdd..ebf6c18c953ad2 100644 --- a/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs +++ b/Content.Client/Power/EntitySystems/PowerReceiverSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Content.Client.Power.Components; using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; @@ -27,4 +28,16 @@ private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, r component.Powered = state.Powered; } + + public override bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component) + { + if (component != null) + return true; + + if (!TryComp(entity, out ApcPowerReceiverComponent? receiver)) + return false; + + component = receiver; + return true; + } } diff --git a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs index 2ad406b6378fa2..24a802a60fe49c 100644 --- a/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs +++ b/Content.Client/Silicons/StationAi/StationAiMenu.xaml.cs @@ -62,12 +62,20 @@ private void BuildButtons() if (action.Sprite != null) { + var texture = sprites.Frame0(action.Sprite); + var scale = Vector2.One; + + if (texture.Width <= 32) + { + scale *= 2; + } + var tex = new TextureRect { VerticalAlignment = VAlignment.Center, HorizontalAlignment = HAlignment.Center, - Texture = sprites.Frame0(action.Sprite), - TextureScale = new Vector2(2f, 2f), + Texture = texture, + TextureScale = scale, }; button.AddChild(tex); @@ -102,7 +110,7 @@ private void UpdatePosition() return; } - var coords = _eyeManager.CoordinatesToScreen(xform.Coordinates); + var coords = _entManager.System().GetSpriteScreenCoordinates((_tracked, null, xform)); if (!coords.IsValid) { diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs index 54f59da7794b86..bf6b65a9697b5a 100644 --- a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs +++ b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs @@ -13,9 +13,12 @@ private void InitializeAirlock() private void OnDoorBoltGetRadial(Entity ent, ref GetStationAiRadialEvent args) { - args.Actions.Add(new StationAiAction() + args.Actions.Add(new StationAiRadial() { - Sprite = new SpriteSpecifier.Rsi( + Sprite = ent.Comp.BoltsDown ? + new SpriteSpecifier.Rsi( + new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") : + new SpriteSpecifier.Rsi( new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"), Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"), Event = new StationAiBoltEvent() diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs new file mode 100644 index 00000000000000..cf2f6136207d35 --- /dev/null +++ b/Content.Client/Silicons/StationAi/StationAiSystem.Light.cs @@ -0,0 +1,32 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Light.Components; +using Content.Shared.Silicons.StationAi; +using Robust.Shared.Utility; + +namespace Content.Client.Silicons.StationAi; + +public sealed partial class StationAiSystem +{ + // Used for surveillance camera lights + + private void InitializePowerToggle() + { + SubscribeLocalEvent(OnLightGetRadial); + } + + private void OnLightGetRadial(Entity ent, ref GetStationAiRadialEvent args) + { + if (!TryComp(ent.Owner, out ItemToggleComponent? toggle)) + return; + + args.Actions.Add(new StationAiRadial() + { + Tooltip = Loc.GetString("toggle-light"), + Sprite = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/light.svg.192dpi.png")), + Event = new StationAiLightEvent() + { + Enabled = !toggle.Activated + } + }); + } +} diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.cs b/Content.Client/Silicons/StationAi/StationAiSystem.cs index 4c38807e215b2e..ab9ace3c1d583d 100644 --- a/Content.Client/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Client/Silicons/StationAi/StationAiSystem.cs @@ -16,6 +16,7 @@ public override void Initialize() { base.Initialize(); InitializeAirlock(); + InitializePowerToggle(); SubscribeLocalEvent(OnAiAttached); SubscribeLocalEvent(OnAiDetached); diff --git a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs index 5d7a775104cb29..49a2fba89812dc 100644 --- a/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs +++ b/Content.Client/UserInterface/Systems/Inventory/InventoryUIController.cs @@ -21,6 +21,7 @@ using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.Map; +using Robust.Shared.Player; using Robust.Shared.Utility; using static Content.Client.Inventory.ClientInventorySystem; @@ -399,6 +400,9 @@ private void LoadSlots(EntityUid clientUid, InventorySlotsComponent clientInv) foreach (var slotData in clientInv.SlotData.Values) { AddSlot(slotData); + + if (_inventoryButton != null) + _inventoryButton.Visible = true; } UpdateInventoryHotbar(_playerInventory); @@ -406,6 +410,9 @@ private void LoadSlots(EntityUid clientUid, InventorySlotsComponent clientInv) private void UnloadSlots() { + if (_inventoryButton != null) + _inventoryButton.Visible = false; + _playerUid = null; _playerInventory = null; foreach (var slotGroup in _slotGroups.Values) diff --git a/Content.Client/UserInterface/Systems/Inventory/Widgets/InventoryGui.xaml b/Content.Client/UserInterface/Systems/Inventory/Widgets/InventoryGui.xaml index 576d06d8161961..d4fb68c38daa51 100644 --- a/Content.Client/UserInterface/Systems/Inventory/Widgets/InventoryGui.xaml +++ b/Content.Client/UserInterface/Systems/Inventory/Widgets/InventoryGui.xaml @@ -9,9 +9,11 @@ Orientation="Horizontal" HorizontalAlignment="Center"> + +using System; +using System.Net; +using System.Text.Json; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using NpgsqlTypes; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + [DbContext(typeof(PostgresServerDbContext))] + [Migration("20240818064956_LoadoutNames")] + partial class LoadoutNames + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminId") + .HasColumnType("uuid") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("boolean") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("smallint") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Message") + .HasAnnotation("Npgsql:TsVectorConfig", "english"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("integer") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_messages_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("boolean") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("boolean") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_notes_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("boolean") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_rank_flag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminRankId") + .HasColumnType("integer") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("admin_watchlists_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("antag_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("assigned_user_id_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("ban_template_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("interval") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("connection_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("smallint") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("timestamp with time zone") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("job_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("JobName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("play_time_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("PlayerId") + .HasColumnType("uuid") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("interval") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("text") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("player_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FirstSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("inet") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("bytea") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", null, t => + { + t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("preference_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("integer") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Age") + .HasColumnType("integer") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("text") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("text") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("integer") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("integer") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("text") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("integer") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("integer") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("text") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_loadout_group_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("profile_role_loadout_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustomName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("custom_name"); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("round_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ServerId") + .HasColumnType("integer") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("boolean") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("integer") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("integer") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_ban_hit_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("integer") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("server_role_ban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .HasColumnType("inet") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("uuid") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("bytea") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("boolean") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("uuid") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("uuid") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("interval") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("integer") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("integer") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); + + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("role_unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("unban_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BanId") + .HasColumnType("integer") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("uuid") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("trait_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ProfileId") + .HasColumnType("integer") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("uploaded_resource_log_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Data") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("integer") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("integer") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs b/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs new file mode 100644 index 00000000000000..e51efa9038c2d0 --- /dev/null +++ b/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Postgres +{ + /// + public partial class LoadoutNames : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "role_name", + table: "profile_role_loadout", + type: "character varying(256)", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AddColumn( + name: "custom_name", + table: "profile_role_loadout", + type: "character varying(256)", + maxLength: 256, + nullable: true); + + migrationBuilder.AlterColumn( + name: "group_name", + table: "profile_loadout_group", + type: "character varying(256)", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldType: "text"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "custom_name", + table: "profile_role_loadout"); + + migrationBuilder.AlterColumn( + name: "role_name", + table: "profile_role_loadout", + type: "text", + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "group_name", + table: "profile_loadout_group", + type: "text", + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(256)", + oldMaxLength: 256); + } + } +} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index cb9fdde4d5f464..b855c0333f9ec7 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -906,7 +906,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GroupName") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("group_name"); b.Property("ProfileRoleLoadoutId") @@ -930,13 +931,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("CustomName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("custom_name"); + b.Property("ProfileId") .HasColumnType("integer") .HasColumnName("profile_id"); b.Property("RoleName") .IsRequired() - .HasColumnType("text") + .HasMaxLength(256) + .HasColumnType("character varying(256)") .HasColumnName("role_name"); b.HasKey("Id") diff --git a/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs new file mode 100644 index 00000000000000..9242e6fa58a561 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs @@ -0,0 +1,1890 @@ +// +using System; +using Content.Server.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + [DbContext(typeof(SqliteServerDbContext))] + [Migration("20240818065011_LoadoutNames")] + partial class LoadoutNames + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Title") + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("UserId") + .HasName("PK_admin"); + + b.HasIndex("AdminRankId") + .HasDatabaseName("IX_admin_admin_rank_id"); + + b.ToTable("admin", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_flag_id"); + + b.Property("AdminId") + .HasColumnType("TEXT") + .HasColumnName("admin_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.Property("Negative") + .HasColumnType("INTEGER") + .HasColumnName("negative"); + + b.HasKey("Id") + .HasName("PK_admin_flag"); + + b.HasIndex("AdminId") + .HasDatabaseName("IX_admin_flag_admin_id"); + + b.HasIndex("Flag", "AdminId") + .IsUnique(); + + b.ToTable("admin_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Id") + .HasColumnType("INTEGER") + .HasColumnName("admin_log_id"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Impact") + .HasColumnType("INTEGER") + .HasColumnName("impact"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("json"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("Type") + .HasColumnType("INTEGER") + .HasColumnName("type"); + + b.HasKey("RoundId", "Id") + .HasName("PK_admin_log"); + + b.HasIndex("Date"); + + b.HasIndex("Type") + .HasDatabaseName("IX_admin_log_type"); + + b.ToTable("admin_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("LogId") + .HasColumnType("INTEGER") + .HasColumnName("log_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.HasKey("RoundId", "LogId", "PlayerUserId") + .HasName("PK_admin_log_player"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_log_player_player_user_id"); + + b.ToTable("admin_log_player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_messages_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("Dismissed") + .HasColumnType("INTEGER") + .HasColumnName("dismissed"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Seen") + .HasColumnType("INTEGER") + .HasColumnName("seen"); + + b.HasKey("Id") + .HasName("PK_admin_messages"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_messages_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_messages_round_id"); + + b.ToTable("admin_messages", null, t => + { + t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_notes_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Secret") + .HasColumnType("INTEGER") + .HasColumnName("secret"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_admin_notes"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_notes_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_notes_round_id"); + + b.ToTable("admin_notes", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_admin_rank"); + + b.ToTable("admin_rank", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_flag_id"); + + b.Property("AdminRankId") + .HasColumnType("INTEGER") + .HasColumnName("admin_rank_id"); + + b.Property("Flag") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flag"); + + b.HasKey("Id") + .HasName("PK_admin_rank_flag"); + + b.HasIndex("AdminRankId"); + + b.HasIndex("Flag", "AdminRankId") + .IsUnique(); + + b.ToTable("admin_rank_flag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("admin_watchlists_id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("TEXT") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("INTEGER") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("TEXT") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("TEXT") + .HasColumnName("deleted_by_id"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("LastEditedAt") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT") + .HasColumnName("message"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.HasKey("Id") + .HasName("PK_admin_watchlists"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DeletedById"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_admin_watchlists_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_admin_watchlists_round_id"); + + b.ToTable("admin_watchlists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("antag_id"); + + b.Property("AntagName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("antag_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_antag"); + + b.HasIndex("ProfileId", "AntagName") + .IsUnique(); + + b.ToTable("antag", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("assigned_user_id_id"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_assigned_user_id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("assigned_user_id", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.BanTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("ban_template_id"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("Length") + .HasColumnType("TEXT") + .HasColumnName("length"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("title"); + + b.HasKey("Id") + .HasName("PK_ban_template"); + + b.ToTable("ban_template", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("connection_log_id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("Denied") + .HasColumnType("INTEGER") + .HasColumnName("denied"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("ServerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("server_id"); + + b.Property("Time") + .HasColumnType("TEXT") + .HasColumnName("time"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("PK_connection_log"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_connection_log_server_id"); + + b.HasIndex("Time"); + + b.HasIndex("UserId"); + + b.ToTable("connection_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("job_id"); + + b.Property("JobName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("job_name"); + + b.Property("Priority") + .HasColumnType("INTEGER") + .HasColumnName("priority"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.HasKey("Id") + .HasName("PK_job"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "JobName") + .IsUnique(); + + b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") + .IsUnique() + .HasFilter("priority = 3"); + + b.ToTable("job", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.PlayTime", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("play_time_id"); + + b.Property("PlayerId") + .HasColumnType("TEXT") + .HasColumnName("player_id"); + + b.Property("TimeSpent") + .HasColumnType("TEXT") + .HasColumnName("time_spent"); + + b.Property("Tracker") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("tracker"); + + b.HasKey("Id") + .HasName("PK_play_time"); + + b.HasIndex("PlayerId", "Tracker") + .IsUnique(); + + b.ToTable("play_time", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("player_id"); + + b.Property("FirstSeenTime") + .HasColumnType("TEXT") + .HasColumnName("first_seen_time"); + + b.Property("LastReadRules") + .HasColumnType("TEXT") + .HasColumnName("last_read_rules"); + + b.Property("LastSeenAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_address"); + + b.Property("LastSeenHWId") + .HasColumnType("BLOB") + .HasColumnName("last_seen_hwid"); + + b.Property("LastSeenTime") + .HasColumnType("TEXT") + .HasColumnName("last_seen_time"); + + b.Property("LastSeenUserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("last_seen_user_name"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_player"); + + b.HasAlternateKey("UserId") + .HasName("ak_player_user_id"); + + b.HasIndex("LastSeenUserName"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("player", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("AdminOOCColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("admin_ooc_color"); + + b.Property("SelectedCharacterSlot") + .HasColumnType("INTEGER") + .HasColumnName("selected_character_slot"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_preference"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("preference", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("Age") + .HasColumnType("INTEGER") + .HasColumnName("age"); + + b.Property("CharacterName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("char_name"); + + b.Property("EyeColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("eye_color"); + + b.Property("FacialHairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_color"); + + b.Property("FacialHairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("facial_hair_name"); + + b.Property("FlavorText") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("flavor_text"); + + b.Property("Gender") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("gender"); + + b.Property("HairColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_color"); + + b.Property("HairName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("hair_name"); + + b.Property("Markings") + .HasColumnType("jsonb") + .HasColumnName("markings"); + + b.Property("PreferenceId") + .HasColumnType("INTEGER") + .HasColumnName("preference_id"); + + b.Property("PreferenceUnavailable") + .HasColumnType("INTEGER") + .HasColumnName("pref_unavailable"); + + b.Property("Sex") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("sex"); + + b.Property("SkinColor") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("skin_color"); + + b.Property("Slot") + .HasColumnType("INTEGER") + .HasColumnName("slot"); + + b.Property("SpawnPriority") + .HasColumnType("INTEGER") + .HasColumnName("spawn_priority"); + + b.Property("Species") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("species"); + + b.HasKey("Id") + .HasName("PK_profile"); + + b.HasIndex("PreferenceId") + .HasDatabaseName("IX_profile_preference_id"); + + b.HasIndex("Slot", "PreferenceId") + .IsUnique(); + + b.ToTable("profile", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_id"); + + b.Property("LoadoutName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("loadout_name"); + + b.Property("ProfileLoadoutGroupId") + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout"); + + b.HasIndex("ProfileLoadoutGroupId"); + + b.ToTable("profile_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_loadout_group_id"); + + b.Property("GroupName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("group_name"); + + b.Property("ProfileRoleLoadoutId") + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.HasKey("Id") + .HasName("PK_profile_loadout_group"); + + b.HasIndex("ProfileRoleLoadoutId"); + + b.ToTable("profile_loadout_group", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("profile_role_loadout_id"); + + b.Property("CustomName") + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("custom_name"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("RoleName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("role_name"); + + b.HasKey("Id") + .HasName("PK_profile_role_loadout"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_role_loadout", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.HasKey("PlayerUserId", "RoleId") + .HasName("PK_role_whitelists"); + + b.ToTable("role_whitelists", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("ServerId") + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("StartDate") + .HasColumnType("TEXT") + .HasColumnName("start_date"); + + b.HasKey("Id") + .HasName("PK_round"); + + b.HasIndex("ServerId") + .HasDatabaseName("IX_round_server_id"); + + b.HasIndex("StartDate"); + + b.ToTable("round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.HasKey("Id") + .HasName("PK_server"); + + b.ToTable("server", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("AutoDelete") + .HasColumnType("INTEGER") + .HasColumnName("auto_delete"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExemptFlags") + .HasColumnType("INTEGER") + .HasColumnName("exempt_flags"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_ban_round_id"); + + b.ToTable("server_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.Property("Flags") + .HasColumnType("INTEGER") + .HasColumnName("flags"); + + b.HasKey("UserId") + .HasName("PK_server_ban_exemption"); + + b.ToTable("server_ban_exemption", null, t => + { + t.HasCheckConstraint("FlagsNotZero", "flags != 0"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_ban_hit_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("ConnectionId") + .HasColumnType("INTEGER") + .HasColumnName("connection_id"); + + b.HasKey("Id") + .HasName("PK_server_ban_hit"); + + b.HasIndex("BanId") + .HasDatabaseName("IX_server_ban_hit_ban_id"); + + b.HasIndex("ConnectionId") + .HasDatabaseName("IX_server_ban_hit_connection_id"); + + b.ToTable("server_ban_hit", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("server_role_ban_id"); + + b.Property("Address") + .HasColumnType("TEXT") + .HasColumnName("address"); + + b.Property("BanTime") + .HasColumnType("TEXT") + .HasColumnName("ban_time"); + + b.Property("BanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("banning_admin"); + + b.Property("ExpirationTime") + .HasColumnType("TEXT") + .HasColumnName("expiration_time"); + + b.Property("HWId") + .HasColumnType("BLOB") + .HasColumnName("hwid"); + + b.Property("Hidden") + .HasColumnType("INTEGER") + .HasColumnName("hidden"); + + b.Property("LastEditedAt") + .HasColumnType("TEXT") + .HasColumnName("last_edited_at"); + + b.Property("LastEditedById") + .HasColumnType("TEXT") + .HasColumnName("last_edited_by_id"); + + b.Property("PlayerUserId") + .HasColumnType("TEXT") + .HasColumnName("player_user_id"); + + b.Property("PlaytimeAtNote") + .HasColumnType("TEXT") + .HasColumnName("playtime_at_note"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("reason"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("role_id"); + + b.Property("RoundId") + .HasColumnType("INTEGER") + .HasColumnName("round_id"); + + b.Property("Severity") + .HasColumnType("INTEGER") + .HasColumnName("severity"); + + b.HasKey("Id") + .HasName("PK_server_role_ban"); + + b.HasIndex("Address"); + + b.HasIndex("BanningAdmin"); + + b.HasIndex("LastEditedById"); + + b.HasIndex("PlayerUserId") + .HasDatabaseName("IX_server_role_ban_player_user_id"); + + b.HasIndex("RoundId") + .HasDatabaseName("IX_server_role_ban_round_id"); + + b.ToTable("server_role_ban", null, t => + { + t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); + }); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("role_unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_role_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_role_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("unban_id"); + + b.Property("BanId") + .HasColumnType("INTEGER") + .HasColumnName("ban_id"); + + b.Property("UnbanTime") + .HasColumnType("TEXT") + .HasColumnName("unban_time"); + + b.Property("UnbanningAdmin") + .HasColumnType("TEXT") + .HasColumnName("unbanning_admin"); + + b.HasKey("Id") + .HasName("PK_server_unban"); + + b.HasIndex("BanId") + .IsUnique(); + + b.ToTable("server_unban", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("trait_id"); + + b.Property("ProfileId") + .HasColumnType("INTEGER") + .HasColumnName("profile_id"); + + b.Property("TraitName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("trait_name"); + + b.HasKey("Id") + .HasName("PK_trait"); + + b.HasIndex("ProfileId", "TraitName") + .IsUnique(); + + b.ToTable("trait", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("uploaded_resource_log_id"); + + b.Property("Data") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("data"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasColumnName("date"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("path"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("PK_uploaded_resource_log"); + + b.ToTable("uploaded_resource_log", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Whitelist", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasColumnName("user_id"); + + b.HasKey("UserId") + .HasName("PK_whitelist"); + + b.ToTable("whitelist", (string)null); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.Property("PlayersId") + .HasColumnType("INTEGER") + .HasColumnName("players_id"); + + b.Property("RoundsId") + .HasColumnType("INTEGER") + .HasColumnName("rounds_id"); + + b.HasKey("PlayersId", "RoundsId") + .HasName("PK_player_round"); + + b.HasIndex("RoundsId") + .HasDatabaseName("IX_player_round_rounds_id"); + + b.ToTable("player_round", (string)null); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.HasOne("Content.Server.Database.AdminRank", "AdminRank") + .WithMany("Admins") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); + + b.Navigation("AdminRank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminFlag", b => + { + b.HasOne("Content.Server.Database.Admin", "Admin") + .WithMany("Flags") + .HasForeignKey("AdminId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_flag_admin_admin_id"); + + b.Navigation("Admin"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany("AdminLogs") + .HasForeignKey("RoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_round_round_id"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminLogs") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_player_player_user_id"); + + b.HasOne("Content.Server.Database.AdminLog", "Log") + .WithMany("Players") + .HasForeignKey("RoundId", "LogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); + + b.Navigation("Log"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminMessage", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminMessagesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminMessagesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminMessagesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminMessagesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_messages_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_messages_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminNote", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminNotesCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminNotesDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminNotesLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminNotesReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_notes_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_notes_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => + { + b.HasOne("Content.Server.Database.AdminRank", "Rank") + .WithMany("Flags") + .HasForeignKey("AdminRankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminWatchlistsCreated") + .HasForeignKey("CreatedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_created_by_id"); + + b.HasOne("Content.Server.Database.Player", "DeletedBy") + .WithMany("AdminWatchlistsDeleted") + .HasForeignKey("DeletedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminWatchlistsLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("AdminWatchlistsReceived") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_admin_watchlists_player_player_user_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_admin_watchlists_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Player"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.Antag", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Antags") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_antag_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("ConnectionLogs") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.SetNull) + .IsRequired() + .HasConstraintName("FK_connection_log_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.Job", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Jobs") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_job_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.HasOne("Content.Server.Database.Preference", "Preference") + .WithMany("Profiles") + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_preference_preference_id"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => + { + b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") + .WithMany("Loadouts") + .HasForeignKey("ProfileLoadoutGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); + + b.Navigation("ProfileLoadoutGroup"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") + .WithMany("Groups") + .HasForeignKey("ProfileRoleLoadoutId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); + + b.Navigation("ProfileRoleLoadout"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Loadouts") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => + { + b.HasOne("Content.Server.Database.Player", "Player") + .WithMany("JobWhitelists") + .HasForeignKey("PlayerUserId") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_role_whitelists_player_player_user_id"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.HasOne("Content.Server.Database.Server", "Server") + .WithMany("Rounds") + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_round_server_server_id"); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithMany("BanHits") + .HasForeignKey("BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); + + b.HasOne("Content.Server.Database.ConnectionLog", "Connection") + .WithMany("BanHits") + .HasForeignKey("ConnectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); + + b.Navigation("Ban"); + + b.Navigation("Connection"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.HasOne("Content.Server.Database.Player", "CreatedBy") + .WithMany("AdminServerRoleBansCreated") + .HasForeignKey("BanningAdmin") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_banning_admin"); + + b.HasOne("Content.Server.Database.Player", "LastEditedBy") + .WithMany("AdminServerRoleBansLastEdited") + .HasForeignKey("LastEditedById") + .HasPrincipalKey("UserId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); + + b.HasOne("Content.Server.Database.Round", "Round") + .WithMany() + .HasForeignKey("RoundId") + .HasConstraintName("FK_server_role_ban_round_round_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("LastEditedBy"); + + b.Navigation("Round"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => + { + b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerUnban", b => + { + b.HasOne("Content.Server.Database.ServerBan", "Ban") + .WithOne("Unban") + .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_server_unban_server_ban_ban_id"); + + b.Navigation("Ban"); + }); + + modelBuilder.Entity("Content.Server.Database.Trait", b => + { + b.HasOne("Content.Server.Database.Profile", "Profile") + .WithMany("Traits") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_trait_profile_profile_id"); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("PlayerRound", b => + { + b.HasOne("Content.Server.Database.Player", null) + .WithMany() + .HasForeignKey("PlayersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_player_players_id"); + + b.HasOne("Content.Server.Database.Round", null) + .WithMany() + .HasForeignKey("RoundsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_player_round_round_rounds_id"); + }); + + modelBuilder.Entity("Content.Server.Database.Admin", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminLog", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Content.Server.Database.AdminRank", b => + { + b.Navigation("Admins"); + + b.Navigation("Flags"); + }); + + modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => + { + b.Navigation("BanHits"); + }); + + modelBuilder.Entity("Content.Server.Database.Player", b => + { + b.Navigation("AdminLogs"); + + b.Navigation("AdminMessagesCreated"); + + b.Navigation("AdminMessagesDeleted"); + + b.Navigation("AdminMessagesLastEdited"); + + b.Navigation("AdminMessagesReceived"); + + b.Navigation("AdminNotesCreated"); + + b.Navigation("AdminNotesDeleted"); + + b.Navigation("AdminNotesLastEdited"); + + b.Navigation("AdminNotesReceived"); + + b.Navigation("AdminServerBansCreated"); + + b.Navigation("AdminServerBansLastEdited"); + + b.Navigation("AdminServerRoleBansCreated"); + + b.Navigation("AdminServerRoleBansLastEdited"); + + b.Navigation("AdminWatchlistsCreated"); + + b.Navigation("AdminWatchlistsDeleted"); + + b.Navigation("AdminWatchlistsLastEdited"); + + b.Navigation("AdminWatchlistsReceived"); + + b.Navigation("JobWhitelists"); + }); + + modelBuilder.Entity("Content.Server.Database.Preference", b => + { + b.Navigation("Profiles"); + }); + + modelBuilder.Entity("Content.Server.Database.Profile", b => + { + b.Navigation("Antags"); + + b.Navigation("Jobs"); + + b.Navigation("Loadouts"); + + b.Navigation("Traits"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => + { + b.Navigation("Loadouts"); + }); + + modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => + { + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Content.Server.Database.Round", b => + { + b.Navigation("AdminLogs"); + }); + + modelBuilder.Entity("Content.Server.Database.Server", b => + { + b.Navigation("ConnectionLogs"); + + b.Navigation("Rounds"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerBan", b => + { + b.Navigation("BanHits"); + + b.Navigation("Unban"); + }); + + modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => + { + b.Navigation("Unban"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs b/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs new file mode 100644 index 00000000000000..1bce48c96c3dc1 --- /dev/null +++ b/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Content.Server.Database.Migrations.Sqlite +{ + /// + public partial class LoadoutNames : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "custom_name", + table: "profile_role_loadout", + type: "TEXT", + maxLength: 256, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "custom_name", + table: "profile_role_loadout"); + } + } +} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index efc66821911153..acf50a31978f16 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -853,6 +853,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GroupName") .IsRequired() + .HasMaxLength(256) .HasColumnType("TEXT") .HasColumnName("group_name"); @@ -875,12 +876,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER") .HasColumnName("profile_role_loadout_id"); + b.Property("CustomName") + .HasMaxLength(256) + .HasColumnType("TEXT") + .HasColumnName("custom_name"); + b.Property("ProfileId") .HasColumnType("INTEGER") .HasColumnName("profile_id"); b.Property("RoleName") .IsRequired() + .HasMaxLength(256) .HasColumnType("TEXT") .HasColumnName("role_name"); diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index d195201c29cd1a..7621ff4e0d6b97 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -430,9 +430,16 @@ public class ProfileRoleLoadout public Profile Profile { get; set; } = null!; + /// + /// If the loadout supports custom naming what is it. + /// + [StringLength(256)] + public string? CustomName { get; set; } = null; + /// /// The corresponding role prototype on the profile. /// + [StringLength(256)] public string RoleName { get; set; } = string.Empty; /// @@ -458,6 +465,7 @@ public class ProfileLoadoutGroup /// /// The corresponding group prototype. /// + [StringLength(256)] public string GroupName { get; set; } = string.Empty; /// @@ -903,7 +911,7 @@ public enum ConnectionDenyReason : byte Panic = 3, /* * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future. - * + * * If baby jail is removed, please reserve this value for as long as can reasonably be done to prevent causing ambiguity in connection denial reasons. * Reservation by commenting out the value is likely sufficient for this purpose, but may impact projects which depend on SS14 like SS14.Admin. */ diff --git a/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs b/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs index 4f2108748b8850..b8799c2f9f0b04 100644 --- a/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs +++ b/Content.Server/AlertLevel/AlertLevelDisplaySystem.cs @@ -1,6 +1,7 @@ using Content.Server.Power.Components; using Content.Server.Station.Systems; using Content.Shared.AlertLevel; +using Content.Shared.Power.Components; namespace Content.Server.AlertLevel; diff --git a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs index bac2648307cce3..eb7e2c2e47d924 100644 --- a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs +++ b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Database; using Content.Shared.Mind.Components; +using Content.Shared.Power.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs index 9f18a412925556..f6fde103a64a76 100644 --- a/Content.Server/Anomaly/AnomalySynchronizerSystem.cs +++ b/Content.Server/Anomaly/AnomalySynchronizerSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Robust.Shared.Audio.Systems; using Content.Shared.Verbs; diff --git a/Content.Server/Anomaly/AnomalySystem.Generator.cs b/Content.Server/Anomaly/AnomalySystem.Generator.cs index 5ef686c0d5573a..f057e652330112 100644 --- a/Content.Server/Anomaly/AnomalySystem.Generator.cs +++ b/Content.Server/Anomaly/AnomalySystem.Generator.cs @@ -13,6 +13,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Map; using System.Numerics; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; diff --git a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs index 561cad8d7eed46..58c2dd35755116 100644 --- a/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs +++ b/Content.Server/Arcade/BlockGame/BlockGameArcadeSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Advertise; using Content.Server.Advertise.Components; using Content.Shared.Arcade; +using Content.Shared.Power.Components; using Robust.Server.GameObjects; using Robust.Shared.Player; diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index f7758f11f17d0c..fecd3e75a4f14d 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.UserInterface; using Content.Server.Advertise; using Content.Server.Advertise.Components; +using Content.Shared.Power.Components; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index 9dbef3ddfdbee2..a8108ecff0ef84 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -18,6 +18,7 @@ using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Systems; using Content.Shared.Interaction; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Wires; using Robust.Server.GameObjects; diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs index 1b3b11068c5269..c6fde85ba95b3e 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosAlarmableSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Power.Components; using Content.Shared.Atmos.Monitor; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.Components; using Content.Shared.Tag; using Robust.Server.Audio; using Robust.Server.GameObjects; diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs index 02e72b97f7b574..01540e203b2f21 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs @@ -9,6 +9,7 @@ using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Tag; using Robust.Shared.Prototypes; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index 2859c7f19d0c09..440709b0bf8100 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -18,6 +18,7 @@ using Content.Shared.Audio; using Content.Shared.DeviceNetwork; using Content.Shared.Examine; +using Content.Shared.Power.Components; using Content.Shared.Tools.Systems; using JetBrains.Annotations; using Robust.Server.GameObjects; diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index b27689ed586ed5..bd5475414e6f49 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -16,6 +16,7 @@ using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Audio; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.Components; using Content.Shared.Tools.Systems; using JetBrains.Annotations; using Robust.Server.GameObjects; diff --git a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs index bc5db2e22cbf0a..03508d1f3ea3c2 100644 --- a/Content.Server/Atmos/Portable/PortableScrubberSystem.cs +++ b/Content.Server/Atmos/Portable/PortableScrubberSystem.cs @@ -15,6 +15,7 @@ using Content.Server.NodeContainer.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Database; +using Content.Shared.Power.Components; namespace Content.Server.Atmos.Portable { diff --git a/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs b/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs index cbf63f54043164..55d8bb54e4a953 100644 --- a/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs +++ b/Content.Server/Atmos/Portable/SpaceHeaterSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Atmos.Piping.Portable.Components; using Content.Shared.Atmos.Visuals; +using Content.Shared.Power.Components; using Content.Shared.UserInterface; using Robust.Server.GameObjects; diff --git a/Content.Server/Audio/AmbientSoundSystem.cs b/Content.Server/Audio/AmbientSoundSystem.cs index e78970d1243df3..a21d811e951f2e 100644 --- a/Content.Server/Audio/AmbientSoundSystem.cs +++ b/Content.Server/Audio/AmbientSoundSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Audio; using Content.Shared.Mobs; +using Content.Shared.Power.Components; namespace Content.Server.Audio; diff --git a/Content.Server/Audio/Jukebox/JukeboxSystem.cs b/Content.Server/Audio/Jukebox/JukeboxSystem.cs index d59dd2366c5232..c91de0d66d24a9 100644 --- a/Content.Server/Audio/Jukebox/JukeboxSystem.cs +++ b/Content.Server/Audio/Jukebox/JukeboxSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Audio.Jukebox; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Audio; diff --git a/Content.Server/Bed/BedSystem.cs b/Content.Server/Bed/BedSystem.cs index 087d8b749b46af..dcec6f51348db2 100644 --- a/Content.Server/Bed/BedSystem.cs +++ b/Content.Server/Bed/BedSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Damage; using Content.Shared.Emag.Systems; using Content.Shared.Mobs.Systems; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Robust.Shared.Timing; using Robust.Shared.Utility; diff --git a/Content.Server/Buckle/Systems/AntiRotOnBuckleSystem.cs b/Content.Server/Buckle/Systems/AntiRotOnBuckleSystem.cs index 4458b020a11bca..0bb79c238ba611 100644 --- a/Content.Server/Buckle/Systems/AntiRotOnBuckleSystem.cs +++ b/Content.Server/Buckle/Systems/AntiRotOnBuckleSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Power.Components; using Content.Shared.Atmos.Rotting; using Content.Shared.Buckle.Components; +using Content.Shared.Power.Components; namespace Content.Server.Buckle.Systems; diff --git a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs index 938f4ebb7a561c..2de2bdbb7670df 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Telepad.cs @@ -6,6 +6,7 @@ using Content.Shared.Cargo; using Content.Shared.Cargo.Components; using Content.Shared.DeviceLinking; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Robust.Shared.Audio; using Robust.Shared.Random; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs index 94512d4b187110..b7ff283bc15d5a 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionContainerMixerSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; namespace Content.Server.Chemistry.EntitySystems; diff --git a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs index 6e6373e10bf757..7742c55b9be6f2 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionHeaterSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Placeable; +using Content.Shared.Power.Components; namespace Content.Server.Chemistry.EntitySystems; diff --git a/Content.Server/Cloning/CloningConsoleSystem.cs b/Content.Server/Cloning/CloningConsoleSystem.cs index 950a6599a890bb..85109f7a1bb409 100644 --- a/Content.Server/Cloning/CloningConsoleSystem.cs +++ b/Content.Server/Cloning/CloningConsoleSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Mind; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Power.Components; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Player; diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index 7502c889e1ca5b..35d4568fbf3504 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -182,10 +182,6 @@ private static bool CanAnnounce(CommunicationsConsoleComponent comp) private bool CanUse(EntityUid user, EntityUid console) { - // This shouldn't technically be possible because of BUI but don't trust client. - if (!_interaction.InRangeUnobstructed(console, user)) - return false; - if (TryComp(console, out var accessReaderComponent) && !HasComp(console)) { return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent); diff --git a/Content.Server/Construction/ConstructionSystem.Computer.cs b/Content.Server/Construction/ConstructionSystem.Computer.cs index 0685b08f4ff879..606f693a1d03ea 100644 --- a/Content.Server/Construction/ConstructionSystem.Computer.cs +++ b/Content.Server/Construction/ConstructionSystem.Computer.cs @@ -1,6 +1,7 @@ using Content.Server.Construction.Components; using Content.Server.Power.Components; using Content.Shared.Computer; +using Content.Shared.Power.Components; using Robust.Shared.Containers; namespace Content.Server.Construction; diff --git a/Content.Server/Construction/FlatpackSystem.cs b/Content.Server/Construction/FlatpackSystem.cs index 8ee1f8011724d7..35b389d402d52c 100644 --- a/Content.Server/Construction/FlatpackSystem.cs +++ b/Content.Server/Construction/FlatpackSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Construction; using Content.Shared.Construction.Components; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Robust.Shared.Prototypes; using Robust.Shared.Timing; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index d91ef391981582..e208550b927694 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -218,7 +218,11 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile) foreach (var role in profile.Loadouts) { - var loadout = new RoleLoadout(role.RoleName); + var loadout = new RoleLoadout(role.RoleName) + { + // Validate later if it's even possible. + EntityName = role.CustomName, + }; foreach (var group in role.Groups) { @@ -315,6 +319,7 @@ private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int sl var dz = new ProfileRoleLoadout() { RoleName = role, + CustomName = loadouts.EntityName, }; foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts) diff --git a/Content.Server/DeviceNetwork/Systems/SingletonDeviceNetServerSystem.cs b/Content.Server/DeviceNetwork/Systems/SingletonDeviceNetServerSystem.cs index cdc083feacd127..a941e2d209844b 100644 --- a/Content.Server/DeviceNetwork/Systems/SingletonDeviceNetServerSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/SingletonDeviceNetServerSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Medical.CrewMonitoring; using Content.Server.Power.Components; using Content.Server.Station.Systems; +using Content.Shared.Power.Components; namespace Content.Server.DeviceNetwork.Systems; diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs index ba0124626adfab..16328109db78c2 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposalUnitSystem.cs @@ -25,6 +25,7 @@ using Content.Shared.Item; using Content.Shared.Movement.Events; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Verbs; using Robust.Server.Audio; using Robust.Server.GameObjects; diff --git a/Content.Server/Doors/Systems/AirlockSystem.cs b/Content.Server/Doors/Systems/AirlockSystem.cs index fd5d3a9ceba3c6..0f1cdd69b635fc 100644 --- a/Content.Server/Doors/Systems/AirlockSystem.cs +++ b/Content.Server/Doors/Systems/AirlockSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; using Content.Shared.Interaction; +using Content.Shared.Power.Components; using Content.Shared.Wires; using Robust.Shared.Player; diff --git a/Content.Server/Doors/Systems/DoorSystem.cs b/Content.Server/Doors/Systems/DoorSystem.cs index 5968e445c19a8d..4fc11412937bed 100644 --- a/Content.Server/Doors/Systems/DoorSystem.cs +++ b/Content.Server/Doors/Systems/DoorSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Power.Components; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; +using Content.Shared.Power.Components; using Robust.Shared.Physics.Components; namespace Content.Server.Doors.Systems; diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 85562e0cabbfa0..f23a57ce1b6107 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Atmos.Monitor; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; diff --git a/Content.Server/Fax/FaxSystem.cs b/Content.Server/Fax/FaxSystem.cs index 18852ac75551d2..851c70cd73384a 100644 --- a/Content.Server/Fax/FaxSystem.cs +++ b/Content.Server/Fax/FaxSystem.cs @@ -29,6 +29,7 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Content.Shared.NameModifier.Components; +using Content.Shared.Power.Components; namespace Content.Server.Fax; diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index c05c679f176880..42e1da034c22d1 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -41,6 +41,7 @@ using Content.Server.Construction.Components; using Content.Shared.Chat; using Content.Shared.Damage; +using Content.Shared.Power.Components; namespace Content.Server.Kitchen.EntitySystems { diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 097f30fa186d38..234918b16bcfe2 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -21,6 +21,7 @@ using System.Linq; using Content.Server.Jittering; using Content.Shared.Jittering; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; namespace Content.Server.Kitchen.EntitySystems diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs index 1418c4d70cdd84..b1f35ee8af40f5 100644 --- a/Content.Server/Lathe/LatheSystem.cs +++ b/Content.Server/Lathe/LatheSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Examine; using Content.Shared.Lathe; using Content.Shared.Materials; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.ReagentSpeed; using Content.Shared.Research.Components; diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index b6810aa33be604..d87615d51ed6bc 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Examine; using Content.Shared.Light; using Content.Shared.Light.Components; +using Content.Shared.Power.Components; using Content.Shared.Station.Components; using Robust.Server.GameObjects; using Color = Robust.Shared.Maths.Color; diff --git a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs index 752fb8f5fe6a33..cc09b9c9802868 100644 --- a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs +++ b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Light.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Shared.Power.Components; namespace Content.Server.Light.EntitySystems { diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index 33b7ce0782f4a4..7a5123d705b165 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -25,6 +25,7 @@ using Robust.Shared.Audio.Systems; using Content.Shared.Damage.Systems; using Content.Shared.Damage.Components; +using Content.Shared.Power.Components; namespace Content.Server.Light.EntitySystems { diff --git a/Content.Server/Materials/MaterialReclaimerSystem.cs b/Content.Server/Materials/MaterialReclaimerSystem.cs index b962af2b41fd20..292357ff476fbe 100644 --- a/Content.Server/Materials/MaterialReclaimerSystem.cs +++ b/Content.Server/Materials/MaterialReclaimerSystem.cs @@ -21,6 +21,7 @@ using System.Linq; using Content.Server.Administration.Logs; using Content.Shared.Database; +using Content.Shared.Power.Components; namespace Content.Server.Materials; diff --git a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs index c5beed718ed779..4b4853322b3678 100644 --- a/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs +++ b/Content.Server/Medical/BiomassReclaimer/BiomassReclaimerSystem.cs @@ -24,6 +24,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Throwing; using Robust.Server.Player; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/Medical/CryoPodSystem.cs b/Content.Server/Medical/CryoPodSystem.cs index 8d54fc6dd951d0..4d98248428467d 100644 --- a/Content.Server/Medical/CryoPodSystem.cs +++ b/Content.Server/Medical/CryoPodSystem.cs @@ -29,6 +29,7 @@ using Content.Shared.Interaction; using Content.Shared.Medical.Cryogenics; using Content.Shared.MedicalScanner; +using Content.Shared.Power.Components; using Content.Shared.Verbs; using Robust.Server.GameObjects; using Robust.Shared.Containers; diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs index f3e2565fa3ff0d..013ca08863bb7f 100644 --- a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Emag.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Storage.Components; using Robust.Shared.Audio.Systems; diff --git a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs index 4d39a5ce305c01..e92e8aec846120 100644 --- a/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs +++ b/Content.Server/ParticleAccelerator/EntitySystems/ParticleAcceleratorSystem.ControlBox.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using Content.Server.Administration.Managers; using Content.Shared.CCVar; +using Content.Shared.Power.Components; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; diff --git a/Content.Server/Physics/Controllers/ConveyorController.cs b/Content.Server/Physics/Controllers/ConveyorController.cs index db4307f6de5888..527135ca5ebffc 100644 --- a/Content.Server/Physics/Controllers/ConveyorController.cs +++ b/Content.Server/Physics/Controllers/ConveyorController.cs @@ -6,6 +6,7 @@ using Content.Shared.Maps; using Content.Shared.Physics; using Content.Shared.Physics.Controllers; +using Content.Shared.Power.Components; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; diff --git a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs index 9a68e2aabb894b..ebb3c6b42f306d 100644 --- a/Content.Server/Power/Components/ApcPowerReceiverComponent.cs +++ b/Content.Server/Power/Components/ApcPowerReceiverComponent.cs @@ -59,11 +59,4 @@ public bool PowerDisabled { public float PowerReceived => NetworkLoad.ReceivingPower; } - - /// - /// Raised whenever an ApcPowerReceiver becomes powered / unpowered. - /// Does nothing on the client. - /// - [ByRefEvent] - public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower); } diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index 4e2543d2a70ddd..df7bd2a54f7da8 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using Robust.Shared.Containers; using System.Diagnostics.CodeAnalysis; +using Content.Shared.Power.Components; using Content.Shared.Storage.Components; using Robust.Server.Containers; using Content.Shared.Whitelist; diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index a7098649ceffb1..9ce48141396296 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Power.Pow3r; using Content.Shared.CCVar; using Content.Shared.Power; +using Content.Shared.Power.Components; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Configuration; diff --git a/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs b/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs index 9b15bdfd287a05..38d7376e5470e8 100644 --- a/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerReceiverSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Power.Components; @@ -197,5 +198,17 @@ public void SetLoad(ApcPowerReceiverComponent comp, float load) { comp.Load = load; } + + public override bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component) + { + if (component != null) + return true; + + if (!TryComp(entity, out ApcPowerReceiverComponent? receiver)) + return false; + + component = receiver; + return true; + } } } diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs index 02412ca5fb54b5..0cf98b8722fadb 100644 --- a/Content.Server/Power/Generation/Teg/TegSystem.cs +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Atmos; using Content.Shared.DeviceNetwork; using Content.Shared.Examine; +using Content.Shared.Power.Components; using Content.Shared.Power.Generation.Teg; using Content.Shared.Rounding; using Robust.Server.GameObjects; diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs index 5f79906c9951a2..b20b820b0646c2 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -5,6 +5,7 @@ using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Shared.Atmos; +using Content.Shared.Power.Components; namespace Content.Server.Power.Generator; diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 797b776d1b8dcc..df66903e2a0ced 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -9,6 +9,7 @@ using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Radio; using Content.Shared.Radio.Components; diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index f079a5b58a4060..0ec0a7ce5f84a2 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Shuttles.Systems; using Content.Shared.Tag; using Content.Shared.Movement.Systems; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Shuttles.UI.MapObjects; using Content.Shared.Timing; diff --git a/Content.Server/Shuttles/Systems/ThrusterSystem.cs b/Content.Server/Shuttles/Systems/ThrusterSystem.cs index a8a8b163a435e6..8354bef3890706 100644 --- a/Content.Server/Shuttles/Systems/ThrusterSystem.cs +++ b/Content.Server/Shuttles/Systems/ThrusterSystem.cs @@ -19,6 +19,7 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Shared.Localizations; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; namespace Content.Server.Shuttles.Systems; diff --git a/Content.Server/Silicons/StationAi/AiInteractWireAction.cs b/Content.Server/Silicons/StationAi/AiInteractWireAction.cs index 7afbf2c99c1364..c92c825b32b303 100644 --- a/Content.Server/Silicons/StationAi/AiInteractWireAction.cs +++ b/Content.Server/Silicons/StationAi/AiInteractWireAction.cs @@ -10,7 +10,7 @@ namespace Content.Server.Silicons.StationAi; /// public sealed partial class AiInteractWireAction : ComponentWireAction { - public override string Name { get; set; } = "wire-name-ai-light"; + public override string Name { get; set; } = "wire-name-ai-act-light"; public override Color Color { get; set; } = Color.DeepSkyBlue; public override object StatusKey => AirlockWireStatus.AiControlIndicator; diff --git a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs index b01e8487990638..3523f4d38f033d 100644 --- a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs +++ b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs @@ -11,7 +11,7 @@ namespace Content.Server.Silicons.StationAi; /// public sealed partial class AiVisionWireAction : ComponentWireAction { - public override string Name { get; set; } = "wire-name-ai-light"; + public override string Name { get; set; } = "wire-name-ai-vision-light"; public override Color Color { get; set; } = Color.DeepSkyBlue; public override object StatusKey => AirlockWireStatus.AiControlIndicator; diff --git a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs index a9763b64d90bac..ac5f51f5b3d76d 100644 --- a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Interaction; using Content.Shared.Lock; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Projectiles; using Content.Shared.Singularity.Components; using Content.Shared.Singularity.EntitySystems; diff --git a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs index 9cc85060c6e6c9..4fe5177e33a7f8 100644 --- a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs +++ b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Shared.Power.Components; using Content.Shared.Sound; using Content.Shared.Sound.Components; diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index e960a2bbbe2c7a..fcbc3aa52f6987 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.PDA; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; +using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Random; using Content.Shared.Random.Helpers; using Content.Shared.Roles; @@ -150,6 +151,22 @@ public EntityUid SpawnPlayerMob( EntityUid? entity = null) { _prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype); + RoleLoadout? loadout = null; + + // Need to get the loadout up-front to handle names if we use an entity spawn override. + var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID); + + if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? roleProto)) + { + profile?.Loadouts.TryGetValue(jobLoadout, out loadout); + + // Set to default if not present + if (loadout == null) + { + loadout = new RoleLoadout(jobLoadout); + loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager); + } + } // If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff. if (prototype?.JobEntity != null) @@ -157,6 +174,13 @@ public EntityUid SpawnPlayerMob( DebugTools.Assert(entity is null); var jobEntity = EntityManager.SpawnEntity(prototype.JobEntity, coordinates); MakeSentientCommand.MakeSentient(jobEntity, EntityManager); + + // Make sure custom names get handled, what is gameticker control flow whoopy. + if (loadout != null) + { + EquipRoleName(jobEntity, loadout, roleProto!); + } + DoJobSpecials(job, jobEntity); _identity.QueueIdentityUpdate(jobEntity); return jobEntity; @@ -188,21 +212,9 @@ public EntityUid SpawnPlayerMob( profile = HumanoidCharacterProfile.RandomWithSpecies(speciesId); } - var jobLoadout = LoadoutSystem.GetJobPrototype(prototype?.ID); - - if (_prototypeManager.TryIndex(jobLoadout, out RoleLoadoutPrototype? roleProto)) + if (loadout != null) { - RoleLoadout? loadout = null; - profile?.Loadouts.TryGetValue(jobLoadout, out loadout); - - // Set to default if not present - if (loadout == null) - { - loadout = new RoleLoadout(jobLoadout); - loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager); - } - - EquipRoleLoadout(entity.Value, loadout, roleProto); + EquipRoleLoadout(entity.Value, loadout, roleProto!); } if (prototype?.StartingGear != null) diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs index f61c57a4a6bb06..af07462621a6cb 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMonitorSystem.cs @@ -3,6 +3,7 @@ using Content.Server.DeviceNetwork.Systems; using Content.Server.Power.Components; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.Components; using Content.Shared.UserInterface; using Content.Shared.SurveillanceCamera; using Robust.Server.GameObjects; diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs index d0c2cd78d320b9..0ad80bd53f2775 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraRouterSystem.cs @@ -4,6 +4,7 @@ using Content.Server.Power.Components; using Content.Shared.ActionBlocker; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.Components; using Content.Shared.SurveillanceCamera; using Content.Shared.Verbs; using Robust.Server.GameObjects; diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs index 8dd253269d06cb..f86377751f4b05 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Power.Components; using Content.Shared.ActionBlocker; using Content.Shared.DeviceNetwork; +using Content.Shared.Power.Components; using Content.Shared.SurveillanceCamera; using Content.Shared.Verbs; using Robust.Server.GameObjects; diff --git a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs index 6da774ba076edf..887a73e4cdd838 100644 --- a/Content.Server/Temperature/Systems/EntityHeaterSystem.cs +++ b/Content.Server/Temperature/Systems/EntityHeaterSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Examine; using Content.Shared.Placeable; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Temperature; using Content.Shared.Verbs; diff --git a/Content.Server/VendingMachines/VendingMachineSystem.cs b/Content.Server/VendingMachines/VendingMachineSystem.cs index 162b2bd6bdd5eb..f93c87369b7cc9 100644 --- a/Content.Server/VendingMachines/VendingMachineSystem.cs +++ b/Content.Server/VendingMachines/VendingMachineSystem.cs @@ -16,6 +16,7 @@ using Content.Shared.Emag.Systems; using Content.Shared.Emp; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Throwing; using Content.Shared.UserInterface; diff --git a/Content.Server/Wires/WiresSystem.cs b/Content.Server/Wires/WiresSystem.cs index 1bd709295a49b1..97cc93e251f6df 100644 --- a/Content.Server/Wires/WiresSystem.cs +++ b/Content.Server/Wires/WiresSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Tools.Components; using Content.Shared.Tools.Systems; using Content.Shared.Wires; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs index 8fc2f26fa635a2..41ea30e4abf477 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactAnalyzerSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Paper; using Content.Shared.Placeable; using Content.Shared.Popups; +using Content.Shared.Power.Components; using Content.Shared.Research.Components; using Content.Shared.Xenoarchaeology.Equipment; using Content.Shared.Xenoarchaeology.XenoArtifacts; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs index 1b4ce82daf614a..f20d8dad7c9e24 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/ArtifactCrusherSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Xenoarchaeology.XenoArtifacts; using Content.Shared.Body.Components; using Content.Shared.Damage; +using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Verbs; using Content.Shared.Whitelist; diff --git a/Content.Shared/Containers/ContainerCompComponent.cs b/Content.Shared/Containers/ContainerCompComponent.cs new file mode 100644 index 00000000000000..b1415e0d8b5c07 --- /dev/null +++ b/Content.Shared/Containers/ContainerCompComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Containers; + +/// +/// Applies container changes whenever an entity is inserted into the specified container on this entity. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ContainerCompComponent : Component +{ + [DataField(required: true)] + public EntProtoId Proto; + + [DataField(required: true)] + public string Container = string.Empty; +} diff --git a/Content.Shared/Containers/ContainerCompSystem.cs b/Content.Shared/Containers/ContainerCompSystem.cs new file mode 100644 index 00000000000000..aa0fe099b5c9b0 --- /dev/null +++ b/Content.Shared/Containers/ContainerCompSystem.cs @@ -0,0 +1,47 @@ +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Containers; + +/// +/// Applies / removes an entity prototype from a child entity when it's inserted into a container. +/// +public sealed class ContainerCompSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnConInsert); + SubscribeLocalEvent(OnConRemove); + } + + private void OnConRemove(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID != ent.Comp.Container) + return; + + if (_proto.TryIndex(ent.Comp.Container, out var entProto)) + { + foreach (var entry in entProto.Components.Values) + { + RemComp(args.Entity, entry.Component); + } + } + } + + private void OnConInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID != ent.Comp.Container) + return; + + if (_proto.TryIndex(ent.Comp.Proto, out var entProto)) + { + foreach (var entry in entProto.Components.Values) + { + AddComp(args.Entity, entry.Component, overwrite: true); + } + } + } +} diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index 51a80dfd213219..4cca399b28b976 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -23,9 +23,6 @@ false - - - diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs index 46249fdd0defde..47edec135d8d85 100644 --- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs +++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs @@ -19,6 +19,12 @@ public sealed partial class ItemToggleComponent : Component [DataField, AutoNetworkedField] public bool Activated = false; + /// + /// Can the entity be activated in the world. + /// + [DataField] + public bool OnActivate = true; + /// /// If this is set to false then the item can't be toggled by pressing Z. /// Use another system to do it then. @@ -52,12 +58,6 @@ public sealed partial class ItemToggleComponent : Component /// [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] public SoundSpecifier? SoundFailToActivate; - - /// - /// Whether or not to toggle the entity's lights on or off. - /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] - public bool ToggleLight = true; } /// diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs index 6b969d1d62b7ed..1392cc7f15678d 100644 --- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs +++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs @@ -1,8 +1,10 @@ +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Popups; using Content.Shared.Temperature; using Content.Shared.Toggleable; +using Content.Shared.Verbs; using Content.Shared.Wieldable; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -20,7 +22,6 @@ public sealed class ItemToggleSystem : EntitySystem [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() @@ -32,6 +33,8 @@ public override void Initialize() SubscribeLocalEvent(TurnOffOnUnwielded); SubscribeLocalEvent(TurnOnOnWielded); SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent>(OnActivateVerb); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnIsHotEvent); @@ -62,6 +65,32 @@ private void OnUseInHand(Entity ent, ref UseInHandEvent arg Toggle((ent, ent.Comp), args.User, predicted: ent.Comp.Predictable); } + private void OnActivateVerb(Entity ent, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + var user = args.User; + + args.Verbs.Add(new ActivationVerb() + { + Text = !ent.Comp.Activated ? Loc.GetString("item-toggle-activate") : Loc.GetString("item-toggle-deactivate"), + Act = () => + { + Toggle((ent.Owner, ent.Comp), user, predicted: ent.Comp.Predictable); + } + }); + } + + private void OnActivate(Entity ent, ref ActivateInWorldEvent args) + { + if (args.Handled || !ent.Comp.OnActivate) + return; + + args.Handled = true; + Toggle((ent.Owner, ent.Comp), args.User, predicted: ent.Comp.Predictable); + } + /// /// Used when an item is attempted to be toggled. /// Sets its state to the opposite of what it is. @@ -199,16 +228,7 @@ private void UpdateVisuals(Entity ent) if (TryComp(ent, out AppearanceComponent? appearance)) { _appearance.SetData(ent, ToggleVisuals.Toggled, ent.Comp.Activated, appearance); - - if (ent.Comp.ToggleLight) - _appearance.SetData(ent, ToggleableLightVisuals.Enabled, ent.Comp.Activated, appearance); } - - if (!ent.Comp.ToggleLight) - return; - - if (_light.TryGetLight(ent, out var light)) - _light.SetEnabled(ent, ent.Comp.Activated, light); } /// diff --git a/Content.Shared/Light/Components/ItemTogglePointLightComponent.cs b/Content.Shared/Light/Components/ItemTogglePointLightComponent.cs new file mode 100644 index 00000000000000..6ac1bf236d73b1 --- /dev/null +++ b/Content.Shared/Light/Components/ItemTogglePointLightComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Toggles point light on an entity whenever ItemToggle hits. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ItemTogglePointLightComponent : Component +{ + +} diff --git a/Content.Shared/Light/Components/SlimPoweredLightComponent.cs b/Content.Shared/Light/Components/SlimPoweredLightComponent.cs new file mode 100644 index 00000000000000..bf6ae0e5251a20 --- /dev/null +++ b/Content.Shared/Light/Components/SlimPoweredLightComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +// All content light code is terrible and everything is baked-in. Power code got predicted before light code did. +/// +/// Handles turning a pointlight on / off based on power. Nothing else +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class SlimPoweredLightComponent : Component +{ + /// + /// Used to make this as being lit. If unpowered then the light will still be off. + /// + [DataField, AutoNetworkedField] + public bool Enabled = true; +} diff --git a/Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs b/Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs new file mode 100644 index 00000000000000..7030c538c1d6a4 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/ItemTogglePointLightSystem.cs @@ -0,0 +1,29 @@ +using Content.Shared.Item.ItemToggle.Components; +using Content.Shared.Toggleable; +using ItemTogglePointLightComponent = Content.Shared.Light.Components.ItemTogglePointLightComponent; + +namespace Content.Shared.Light.EntitySystems; + +/// +/// Handles ItemToggle for PointLight +/// +public sealed class ItemTogglePointLightSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedPointLightSystem _light = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnLightToggled); + } + + private void OnLightToggled(Entity ent, ref ItemToggledEvent args) + { + if (!_light.TryGetLight(ent.Owner, out var light)) + return; + + _appearance.SetData(ent, ToggleableLightVisuals.Enabled, args.Activated); + _light.SetEnabled(ent.Owner, args.Activated, comp: light); + } +} diff --git a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs new file mode 100644 index 00000000000000..329f462e2278a3 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.Light.Components; +using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; + +namespace Content.Shared.Light.EntitySystems; + +public sealed class SlimPoweredLightSystem : EntitySystem +{ + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly SharedPointLightSystem _lights = default!; + + private bool _setting; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnLightAttempt); + SubscribeLocalEvent(OnLightPowerChanged); + } + + private void OnLightAttempt(Entity ent, ref AttemptPointLightToggleEvent args) + { + // Early-out to avoid having to trycomp stuff if we're the caller setting it + if (_setting) + return; + + if (args.Enabled && !_receiver.IsPowered(ent.Owner)) + args.Cancelled = true; + } + + private void OnLightPowerChanged(Entity ent, ref PowerChangedEvent args) + { + // Early out if we don't need to trycomp. + if (args.Powered) + { + if (!ent.Comp.Enabled) + return; + } + else + { + if (!ent.Comp.Enabled) + return; + } + + if (!_lights.TryGetLight(ent.Owner, out var light)) + return; + + var enabled = ent.Comp.Enabled && args.Powered; + _setting = true; + _lights.SetEnabled(ent.Owner, enabled, light); + _setting = false; + } + + public void SetEnabled(Entity entity, bool enabled) + { + if (entity.Comp.Enabled == enabled) + return; + + entity.Comp.Enabled = enabled; + Dirty(entity); + _lights.SetEnabled(entity.Owner, enabled); + } +} diff --git a/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs b/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs index 42e55bea55dd12..8754de50583ed3 100644 --- a/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs +++ b/Content.Shared/Light/EntitySystems/UnpoweredFlashlightSystem.cs @@ -13,6 +13,8 @@ namespace Content.Shared.Light.EntitySystems; public sealed class UnpoweredFlashlightSystem : EntitySystem { + // TODO: Split some of this to ItemTogglePointLight + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 9dda249423e2e0..752558a53e874c 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -149,23 +149,53 @@ private void OnAutoParentChange(Entity entity, ref EntParen public void RotateCamera(EntityUid uid, Angle angle) { - if (CameraRotationLocked || !MoverQuery.TryGetComponent(uid, out var mover)) + if (CameraRotationLocked) + return; + + var entity = uid; + + if (!TryComp(uid, out RelayInputMoverComponent? relay) || + !MoverQuery.TryComp(relay.RelayEntity, out var mover)) + { + MoverQuery.TryGetComponent(uid, out mover); + } + else + { + entity = relay.RelayEntity; + } + + if (mover == null) return; mover.TargetRelativeRotation += angle; - Dirty(uid, mover); + Dirty(entity, mover); } public void ResetCamera(EntityUid uid) { - if (CameraRotationLocked || - !MoverQuery.TryGetComponent(uid, out var mover)) + if (CameraRotationLocked) { return; } + TransformComponent xform; + + if (!TryComp(uid, out RelayInputMoverComponent? relay) || + !MoverQuery.TryComp(relay.RelayEntity, out var mover)) + { + MoverQuery.TryGetComponent(uid, out mover); + xform = XformQuery.Comp(uid); + } + else + { + xform = XformQuery.Comp(relay.RelayEntity); + } + + if (mover == null) + return; + // If we updated parent then cancel the accumulator and force it now. - if (!TryUpdateRelative(mover, XformQuery.GetComponent(uid)) && mover.TargetRelativeRotation.Equals(Angle.Zero)) + if (!TryUpdateRelative(mover, xform) && mover.TargetRelativeRotation.Equals(Angle.Zero)) return; mover.LerpTarget = TimeSpan.Zero; diff --git a/Content.Shared/Power/Components/PowerChangedEvent.cs b/Content.Shared/Power/Components/PowerChangedEvent.cs new file mode 100644 index 00000000000000..b1814888b5a5ca --- /dev/null +++ b/Content.Shared/Power/Components/PowerChangedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Power.Components; + +/// +/// Raised whenever an ApcPowerReceiver becomes powered / unpowered. +/// Does nothing on the client. +/// +[ByRefEvent] +public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower); \ No newline at end of file diff --git a/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs index 37ac7518896876..2bc2af78314352 100644 --- a/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs +++ b/Content.Shared/Power/EntitySystems/SharedPowerReceiverSystem.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Content.Shared.Examine; using Content.Shared.Power.Components; @@ -5,6 +6,16 @@ namespace Content.Shared.Power.EntitySystems; public abstract class SharedPowerReceiverSystem : EntitySystem { + public abstract bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component); + + public bool IsPowered(Entity entity) + { + if (!ResolveApc(entity.Owner, ref entity.Comp)) + return true; + + return entity.Comp.Powered; + } + protected string GetExamineText(bool powered) { return Loc.GetString("power-receiver-component-on-examine-main", diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index c3ebe0d1a38372..9091ba81e3e83a 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -29,6 +29,7 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile private static readonly Regex ICNameCaseRegex = new(@"^(?\w)|\b(?\w)(?=\w*$)"); public const int MaxNameLength = 32; + public const int MaxLoadoutNameLength = 32; public const int MaxDescLength = 512; /// diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs index f943c9ea8a0765..3f00fb98dd9bd1 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs @@ -22,6 +22,11 @@ public sealed partial class RoleLoadout : IEquatable [DataField] public Dictionary, List> SelectedLoadouts = new(); + /// + /// Loadout specific name. + /// + public string? EntityName; + /* * Loadout-specific data used for validation. */ @@ -42,6 +47,8 @@ public RoleLoadout Clone() weh.SelectedLoadouts.Add(selected.Key, new List(selected.Value)); } + weh.EntityName = EntityName; + return weh; } @@ -55,10 +62,28 @@ public void EnsureValid(HumanoidCharacterProfile profile, ICommonSession session if (!protoManager.TryIndex(Role, out var roleProto)) { + EntityName = null; SelectedLoadouts.Clear(); return; } + // Remove name not allowed. + if (!roleProto.CanCustomiseName) + { + EntityName = null; + } + + // Validate name length + if (EntityName != null) + { + var name = EntityName.Trim(); + + if (name.Length > HumanoidCharacterProfile.MaxNameLength) + { + EntityName = name[..HumanoidCharacterProfile.MaxNameLength]; + } + } + // In some instances we might not have picked up a new group for existing data. foreach (var groupProto in roleProto.Groups) { @@ -322,7 +347,8 @@ public bool Equals(RoleLoadout? other) if (!Role.Equals(other.Role) || SelectedLoadouts.Count != other.SelectedLoadouts.Count || - Points != other.Points) + Points != other.Points || + EntityName != other.EntityName) { return false; } diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs b/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs index 36619ab1046c4b..f6235ac6eba247 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared.Dataset; using Robust.Shared.Prototypes; namespace Content.Shared.Preferences.Loadouts; @@ -15,10 +16,23 @@ public sealed partial class RoleLoadoutPrototype : IPrototype [IdDataField] public string ID { get; } = string.Empty; + /// + /// Can the user edit their entity name for this role loadout? + /// + [DataField] + public bool CanCustomiseName; + + /// + /// Should we use a random name for this loadout? + /// + [DataField] + public ProtoId? NameDataset; + + // Not required so people can set their names. /// /// Groups that comprise this role loadout. /// - [DataField(required: true)] + [DataField] public List> Groups = new(); /// diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index 8b93ea9d9af3c8..e9adcad70b3982 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -30,6 +30,9 @@ private void OnRadialMessage(StationAiRadialMessage ev) private void OnMessageAttempt(BoundUserInterfaceMessageAttempt ev) { + if (ev.Actor == ev.Target) + return; + if (TryComp(ev.Actor, out StationAiHeldComponent? aiComp) && (!ValidateAi((ev.Actor, aiComp)) || !HasComp(ev.Target))) @@ -40,12 +43,15 @@ private void OnMessageAttempt(BoundUserInterfaceMessageAttempt ev) private void OnHeldInteraction(Entity ent, ref InteractionAttemptEvent args) { - args.Cancelled = !TryComp(args.Target, out StationAiWhitelistComponent? whitelist) || !whitelist.Enabled; + // Cancel if it's not us or something with a whitelist. + args.Cancelled = ent.Owner != args.Target && + args.Target != null && + (!TryComp(args.Target, out StationAiWhitelistComponent? whitelist) || !whitelist.Enabled); } private void OnTargetVerbs(Entity ent, ref GetVerbsEvent args) { - if (!args.CanComplexInteract || !ent.Comp.Enabled || !TryComp(args.User, out StationAiHeldComponent? aiComp)) + if (!args.CanComplexInteract || !ent.Comp.Enabled || !HasComp(args.User)) return; var user = args.User; @@ -55,8 +61,7 @@ private void OnTargetVerbs(Entity ent, ref GetVerbs args.Verbs.Add(new AlternativeVerb() { - // TODO: Localise - Text = isOpen ? "Close actions" : "Open actions", + Text = isOpen ? Loc.GetString("ai-close") : Loc.GetString("ai-open"), Act = () => { if (isOpen) @@ -72,6 +77,10 @@ private void OnTargetVerbs(Entity ent, ref GetVerbs } } +/// +/// Raised from client to server as a BUI message wrapping the event to perform. +/// Also handles AI action validation. +/// [Serializable, NetSerializable] public sealed class StationAiRadialMessage : BoundUserInterfaceMessage { @@ -79,7 +88,10 @@ public sealed class StationAiRadialMessage : BoundUserInterfaceMessage } // Do nothing on server just here for shared move along. -public sealed class StationAiAction : BaseStationAiAction +/// +/// Raised on client to get the relevant data for radial actions. +/// +public sealed class StationAiRadial : BaseStationAiAction { public SpriteSpecifier? Sprite; @@ -106,7 +118,7 @@ public abstract class BaseStationAiAction [ByRefEvent] public record struct GetStationAiRadialEvent() { - public List Actions = new(); + public List Actions = new(); } [Serializable, NetSerializable] diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs new file mode 100644 index 00000000000000..bc2e3665f580e7 --- /dev/null +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Light.cs @@ -0,0 +1,28 @@ +using Content.Shared.Light.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Silicons.StationAi; + +public abstract partial class SharedStationAiSystem +{ + // Handles light toggling. + + private void InitializeLight() + { + SubscribeLocalEvent(OnLight); + } + + private void OnLight(EntityUid ent, ItemTogglePointLightComponent component, StationAiLightEvent args) + { + if (args.Enabled) + _toggles.TryActivate(ent, user: args.User); + else + _toggles.TryDeactivate(ent, user: args.User); + } +} + +[Serializable, NetSerializable] +public sealed class StationAiLightEvent : BaseStationAiAction +{ + public bool Enabled; +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 24e8c6fde49e29..4f6352137be9e2 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -1,8 +1,8 @@ using Content.Shared.ActionBlocker; -using Content.Shared.Actions; using Content.Shared.Containers.ItemSlots; using Content.Shared.Doors.Systems; using Content.Shared.Interaction; +using Content.Shared.Item.ItemToggle; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.StationAi; @@ -24,21 +24,33 @@ public abstract partial class SharedStationAiSystem : EntitySystem [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly SharedDoorSystem _doors = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly ItemToggleSystem _toggles = default!; [Dependency] protected readonly SharedMapSystem Maps = default!; [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly StationAiVisionSystem _vision = default!; /* + * TODO: Sprite-System get screencoords of entity / get world coords * TODO: Double-check positronic interactions didn't break - * Fix inventory GUI - * Need destruction - * Cleanup the shitcode + * Upload console + * Sensor overlay to see job + * Check the + * crew monitoring console + * crew manifest + alert console (donno if we have that yet) + button jump towards the ai core + call shuttle + make annoucement + state laws */ // StationAiHeld is added to anything inside of an AI core. // StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core). // StationAiCore holds functionality related to the core itself. + // StationAiWhitelist is a general whitelist to stop it being able to interact with anything + // StationAiOverlay handles the static overlay. It also handles interaction blocking on client and server + // for anything under it. public override void Initialize() { @@ -46,6 +58,7 @@ public override void Initialize() InitializeAirlock(); InitializeHeld(); + InitializeLight(); SubscribeLocalEvent(OnAiBuiCheck); @@ -172,7 +185,7 @@ private void OnHolderInteract(Entity ent, ref AfterInt private void OnHolderInit(Entity ent, ref ComponentInit args) { - _slots.AddItemSlot(ent.Owner, StationAiCoreComponent.Container, ent.Comp.Slot); + _slots.AddItemSlot(ent.Owner, StationAiHolderComponent.Container, ent.Comp.Slot); } private void OnHolderRemove(Entity ent, ref ComponentRemove args) @@ -220,12 +233,13 @@ private void AttachEye(Entity ent) if (ent.Comp.RemoteEntity == null) return; - if (!_containers.TryGetContainer(ent.Owner, StationAiCoreComponent.Container, out var container) || + if (!_containers.TryGetContainer(ent.Owner, StationAiHolderComponent.Container, out var container) || container.ContainedEntities.Count != 1) { return; } + // Attach them to the portable eye that can move around. var user = container.ContainedEntities[0]; if (TryComp(user, out EyeComponent? eyeComp)) @@ -244,10 +258,6 @@ private void OnAiInsert(Entity ent, ref EntInsertedIntoC // Just so text and the likes works properly _metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName); - EnsureComp(args.Entity); - EnsureComp(args.Entity); - EnsureComp(args.Entity); - AttachEye(ent); } @@ -266,18 +276,14 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo { _eye.SetTarget(args.Entity, null, eyeComp); } - - RemCompDeferred(args.Entity); - RemCompDeferred(args.Entity); - RemCompDeferred(args.Entity); } - protected void UpdateAppearance(Entity entity) + private void UpdateAppearance(Entity entity) { if (!Resolve(entity.Owner, ref entity.Comp, false)) return; - if (!_containers.TryGetContainer(entity.Owner, StationAiCoreComponent.Container, out var container) || + if (!_containers.TryGetContainer(entity.Owner, StationAiHolderComponent.Container, out var container) || container.Count == 0) { _appearance.SetData(entity.Owner, StationAiVisualState.Key, StationAiState.Empty); @@ -309,6 +315,9 @@ public virtual bool SetWhitelistEnabled(Entity enti return true; } + /// + /// BUI validation for ai interactions. + /// private bool ValidateAi(Entity entity) { if (!Resolve(entity.Owner, ref entity.Comp, false)) diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index fb8b64454c5c35..52b630d82788a0 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -8,14 +8,18 @@ using Content.Shared.Storage.EntitySystems; using Robust.Shared.Collections; using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Shared.Station; public abstract class SharedStationSpawningSystem : EntitySystem { [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] protected readonly InventorySystem InventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; [Dependency] private readonly SharedTransformSystem _xformSystem = default!; @@ -34,7 +38,7 @@ public override void Initialize() } /// - /// Equips the given starting gears from a `RoleLoadout` onto an entity. + /// Equips the data from a `RoleLoadout` onto an entity. /// public void EquipRoleLoadout(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto) { @@ -52,6 +56,31 @@ public void EquipRoleLoadout(EntityUid entity, RoleLoadout loadout, RoleLoadoutP EquipStartingGear(entity, loadoutProto, raiseEvent: false); } } + + EquipRoleName(entity, loadout, roleProto); + } + + /// + /// Applies the role's name as applicable to the entity. + /// + public void EquipRoleName(EntityUid entity, RoleLoadout loadout, RoleLoadoutPrototype roleProto) + { + string? name = null; + + if (roleProto.CanCustomiseName) + { + name = loadout.EntityName; + } + + if (string.IsNullOrEmpty(name) && PrototypeManager.TryIndex(roleProto.NameDataset, out var nameData)) + { + name = _random.Pick(nameData.Values); + } + + if (!string.IsNullOrEmpty(name)) + { + _metadata.SetEntityName(entity, name); + } } public void EquipStartingGear(EntityUid entity, LoadoutPrototype loadout, bool raiseEvent = true) diff --git a/Content.Server/UserInterface/IntrinsicUIComponent.cs b/Content.Shared/UserInterface/IntrinsicUIComponent.cs similarity index 59% rename from Content.Server/UserInterface/IntrinsicUIComponent.cs rename to Content.Shared/UserInterface/IntrinsicUIComponent.cs index 83936edc8c479f..ede0fd9c342bae 100644 --- a/Content.Server/UserInterface/IntrinsicUIComponent.cs +++ b/Content.Shared/UserInterface/IntrinsicUIComponent.cs @@ -1,9 +1,9 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; -namespace Content.Server.UserInterface; +namespace Content.Shared.UserInterface; -[RegisterComponent] +[RegisterComponent, NetworkedComponent] public sealed partial class IntrinsicUIComponent : Component { /// @@ -15,8 +15,8 @@ public sealed partial class IntrinsicUIComponent : Component [DataDefinition] public sealed partial class IntrinsicUIEntry { - [DataField("toggleAction", customTypeSerializer: typeof(PrototypeIdSerializer), required: true)] - public string? ToggleAction; + [DataField("toggleAction", required: true)] + public EntProtoId? ToggleAction; /// /// The action used for this BUI. diff --git a/Content.Server/UserInterface/IntrinsicUISystem.cs b/Content.Shared/UserInterface/IntrinsicUISystem.cs similarity index 73% rename from Content.Server/UserInterface/IntrinsicUISystem.cs rename to Content.Shared/UserInterface/IntrinsicUISystem.cs index 0f7261865dcfd0..2d8c5d14801d2c 100644 --- a/Content.Server/UserInterface/IntrinsicUISystem.cs +++ b/Content.Shared/UserInterface/IntrinsicUISystem.cs @@ -1,14 +1,11 @@ -using Content.Server.Actions; -using Content.Shared.UserInterface; -using Robust.Server.GameObjects; -using Robust.Shared.Player; +using Content.Shared.Actions; -namespace Content.Server.UserInterface; +namespace Content.Shared.UserInterface; public sealed class IntrinsicUISystem : EntitySystem { - [Dependency] private readonly ActionsSystem _actionsSystem = default!; - [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; public override void Initialize() { @@ -32,9 +29,9 @@ private void InitActions(EntityUid uid, IntrinsicUIComponent component, MapInitE } } - public bool InteractUI(EntityUid uid, Enum key, IntrinsicUIComponent? iui = null, ActorComponent? actor = null) + public bool InteractUI(EntityUid uid, Enum key, IntrinsicUIComponent? iui = null) { - if (!Resolve(uid, ref iui, ref actor)) + if (!Resolve(uid, ref iui)) return false; var attempt = new IntrinsicUIOpenAttemptEvent(uid, key); @@ -42,7 +39,7 @@ public bool InteractUI(EntityUid uid, Enum key, IntrinsicUIComponent? iui = null if (attempt.Cancelled) return false; - return _uiSystem.TryToggleUi(uid, key, actor.PlayerSession); + return _uiSystem.TryToggleUi(uid, key, uid); } } diff --git a/Resources/Locale/en-US/items/toggle.ftl b/Resources/Locale/en-US/items/toggle.ftl new file mode 100644 index 00000000000000..5e1021f2b04381 --- /dev/null +++ b/Resources/Locale/en-US/items/toggle.ftl @@ -0,0 +1,2 @@ +item-toggle-activate = Activate +item-toggle-deactivate = Deactive diff --git a/Resources/Locale/en-US/job/department-desc.ftl b/Resources/Locale/en-US/job/department-desc.ftl index 05c52dada9ee9d..0243d61942f3d1 100644 --- a/Resources/Locale/en-US/job/department-desc.ftl +++ b/Resources/Locale/en-US/job/department-desc.ftl @@ -5,4 +5,5 @@ department-Engineering-description = Keep the power on and the station operation department-Medical-description = Keep the crew healthy. department-Security-description = Keep the peace around the station. department-Science-description = Research artifacts and anomalies to invent new equipment for the station +department-Silicon-description = Obey your laws and serve the crew. department-Specific-description = Jobs that not all stations have. diff --git a/Resources/Locale/en-US/job/department.ftl b/Resources/Locale/en-US/job/department.ftl index 508a0459cf60c0..2295a9ba9d30c5 100644 --- a/Resources/Locale/en-US/job/department.ftl +++ b/Resources/Locale/en-US/job/department.ftl @@ -5,4 +5,5 @@ department-Engineering = Engineering department-Medical = Medical department-Security = Security department-Science = Science +department-Silicon = Silicons department-Specific = Station specific diff --git a/Resources/Locale/en-US/job/job-description.ftl b/Resources/Locale/en-US/job/job-description.ftl index e8db804688d32d..956d3176a88203 100644 --- a/Resources/Locale/en-US/job/job-description.ftl +++ b/Resources/Locale/en-US/job/job-description.ftl @@ -43,6 +43,7 @@ job-description-salvagespec = Use the salvage magnet to draw in detatched scraps job-description-scientist = Research alien artifacts, unlock new technologies, build newer and better machines around the station, and make everything run more efficiently. job-description-security = Catch criminals and enemies of the station, enforce the law, and ensure that the station does not fall into disarray. job-description-serviceworker = Learn the basics of bartending, cooking, and growing plants. +job-description-station-ai = Follow your laws, serve the crew. job-description-visitor = Enjoy your visit to the station. job-description-warden = Patrol the security department, ensure that no one is stealing from the armory, and make sure that all prisoners are processed and let out when their time is up. job-description-zookeeper = Put on a joyful display of cute animals and space carps for all the crew to see. Currently available on Gemini Station. diff --git a/Resources/Locale/en-US/job/job-names.ftl b/Resources/Locale/en-US/job/job-names.ftl index f5cdafa672a83b..583c49267926fb 100644 --- a/Resources/Locale/en-US/job/job-names.ftl +++ b/Resources/Locale/en-US/job/job-names.ftl @@ -33,6 +33,7 @@ job-name-botanist = Botanist job-name-bartender = Bartender job-name-passenger = Passenger job-name-salvagespec = Salvage specialist +job-name-station-ai = Station AI job-name-qm = Quartermaster job-name-cargotech = Cargo Technician job-name-chef = Chef @@ -60,7 +61,6 @@ job-name-virologist = Virologist job-name-zombie = Zombie # Role timers - Make these alphabetical or I cut you -JobStationAi = Station AI JobAtmosphericTechnician = Atmospheric Technician JobBartender = Bartender JobBorg = Borg @@ -104,6 +104,7 @@ JobScientist = Scientist JobSecurityCadet = Security Cadet JobSecurityOfficer = Security Officer JobServiceWorker = Service Worker +JobStationAi = Station AI JobStationEngineer = Station Engineer JobTechnicalAssistant = Technical Assistant JobVisitor = Visitor diff --git a/Resources/Locale/en-US/preferences/loadouts.ftl b/Resources/Locale/en-US/preferences/loadouts.ftl index b6953c713b7ffc..60e8350cd319ea 100644 --- a/Resources/Locale/en-US/preferences/loadouts.ftl +++ b/Resources/Locale/en-US/preferences/loadouts.ftl @@ -1,3 +1,7 @@ +# Name +loadout-name-edit-label = Custom name +loadout-name-edit-tooltip = 32 characters max. If no name is specified a random one may be chosen for you. + # Restrictions loadout-restrictions = Restrictions loadouts-min-limit = Min count: {$count} diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl index 33617a1be5d3f2..598d4218395607 100644 --- a/Resources/Locale/en-US/silicons/station-ai.ftl +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -1,7 +1,13 @@ # General ai-wire-snipped = Wire has been cut at {$coords}. -wire-name-ai-light = AI +wire-name-ai-vision-light = AIV +wire-name-ai-act-light = AIA # Radial actions +ai-open = Open actions +ai-close = Close actions + bolt-close = Close bolt bolt-open = Open bolt + +toggle-light = Toggle light diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 371c102eb0aeb2..8b0a67f8e6ee0e 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -120,7 +120,6 @@ - type: ItemToggle activated: false # gets activated when a mind is added onUse: false # no item-borg toggling sorry - toggleLight: false - type: AccessToggle # TODO: refactor movement to just be based on toggle like speedboots but for the boots themselves # TODO: or just have sentient speedboots be fast idk diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 16840a5197b154..61f49cce82c50e 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -1,3 +1,28 @@ +# Be careful with these as they get removed on shutdown too! +- type: entity + id: AiHeld + description: Components added / removed from an entity that gets inserted into an AI core. + noSpawn: true + components: + - type: IgnoreUIRange + - type: StationAiHeld + - type: StationAiOverlay + - type: UserInterface + interfaces: + enum.RadarConsoleUiKey.Key: + type: RadarConsoleBoundUserInterface + enum.CrewMonitoringUIKey.Key: + type: CrewMonitoringBoundUserInterface + enum.GeneralStationRecordConsoleKey.Key: + type: GeneralStationRecordConsoleBoundUserInterface + - type: IntrinsicUI + uis: + enum.RadarConsoleUiKey.Key: + toggleAction: ActionAGhostShowRadar + enum.CrewMonitoringUIKey.Key: + toggleAction: ActionAGhostShowCrewMonitoring + enum.GeneralStationRecordConsoleKey.Key: + toggleAction: ActionAGhostShowStationRecords # Ai - type: entity @@ -61,6 +86,20 @@ - AiHolder suffix: Empty components: + - type: ContainerComp + proto: AiHeld + container: station_ai_mind_slot + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: MetalBreak + - !type:DoActsBehavior + acts: [ "Destruction" ] - type: StationAiCore - type: StationAiVision - type: Sprite @@ -78,6 +117,7 @@ Empty: { state: ai_empty } Occupied: { state: ai } +# The job-ready version of an AI spawn. - type: entity id: PlayerStationAi parent: PlayerStationAiEmpty @@ -117,17 +157,15 @@ tags: - HideContextMenu - StationAi - - type: RandomMetadata - # TODO: Loadout name support - # TODO: Copy it on station AI insertion - nameSegments: [ names_ai ] +# Hologram projection that the AI's eye tracks. - type: entity parent: - Incorporeal - BaseMob id: StationAiHolo name: Hologram + description: A projection of the AI. noSpawn: true suffix: DO NOT MAP components: diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index faad961ebdfb1d..80cc0212a3c965 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -110,7 +110,6 @@ abstract: true components: - type: ItemToggle - toggleLight: false onUse: false - type: HealthAnalyzer scanDelay: 1 diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index e5fd87ef2a40f2..fe68d4b4e19780 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -560,6 +560,7 @@ name: communications computer description: A computer used to make station wide announcements via keyboard, set the appropriate alert level, and call the emergency shuttle. components: + - type: StationAiWhitelist - type: Sprite layers: - map: ["computerLayerBody"] diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 2a96da27604a9d..461cf55d9d38b5 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -4,6 +4,18 @@ name: camera description: A surveillance camera. It's watching you. Kinda. components: + - type: StationAiWhitelist + - type: ItemToggle + soundActivate: + path: /Audio/Machines/button.ogg + soundDeactivate: + path: /Audio/Machines/button.ogg + - type: PointLight + enabled: false + radius: 5 + - type: ItemTogglePointLight + - type: SlimPoweredLight + enabled: false - type: StationAiVision - type: Clickable - type: InteractionOutline @@ -43,6 +55,8 @@ InUse: camera_in_use - type: UserInterface interfaces: + enum.AiUi.Key: + type: StationAiBoundUserInterface enum.SurveillanceCameraSetupUiKey.Camera: type: SurveillanceCameraSetupBoundUi enum.WiresUiKey.Key: diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 878c14dc70a04c..d3f50b236b8b63 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -25,6 +25,12 @@ - Trinkets - GroupSpeciesBreathTool +# Silicons +- type: roleLoadout + id: JobStationAi + canCustomiseName: true + nameDataset: names_ai + # Civilian - type: roleLoadout id: JobPassenger diff --git a/Resources/Prototypes/Roles/play_time_trackers.yml b/Resources/Prototypes/Roles/play_time_trackers.yml index 402d49e90d2395..d4cd1ec15d0b32 100644 --- a/Resources/Prototypes/Roles/play_time_trackers.yml +++ b/Resources/Prototypes/Roles/play_time_trackers.yml @@ -127,6 +127,9 @@ - type: playTimeTracker id: JobServiceWorker +- type: playTimeTracker + id: JobStationAi + - type: playTimeTracker id: JobStationEngineer diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index 5c441796e0a6ba..f0ec49983544ef 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -98,10 +98,11 @@ - type: wireLayout id: SurveillanceCamera - dummyWires: 4 + dummyWires: 3 wires: - !type:PowerWireAction - !type:AiVisionWireAction + - !type:AiInteractWireAction - type: wireLayout id: CryoPod From 31affd8b29e2fa956f61c219476072b0ac3d32c6 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 19 Aug 2024 01:51:42 +1000 Subject: [PATCH 15/40] Saving work --- Content.Client/Actions/ActionsSystem.cs | 6 -- .../Systems/Actions/ActionUIController.cs | 6 -- .../Actions/ActionOnInteractSystem.cs | 12 --- Content.Shared/Actions/ActionEvents.cs | 2 +- .../Actions/ActionGrantComponent.cs | 17 ++++ Content.Shared/Actions/ActionGrantSystem.cs | 36 +++++++++ .../Events/ActionComponentChangeEvent.cs | 27 +++++++ Content.Shared/Actions/SharedActionsSystem.cs | 54 +++++++++++-- .../Containers/ContainerCompSystem.cs | 5 +- .../Components/LightOnCollideComponent.cs | 11 +++ .../LightOnCollideTriggerComponent.cs | 6 ++ .../Light/EntitySystems/LightCollideSystem.cs | 80 +++++++++++++++++++ .../StationAi/SharedStationAiSystem.cs | 6 +- .../Entities/Mobs/Player/silicon.yml | 44 ++++++++++ 14 files changed, 274 insertions(+), 38 deletions(-) create mode 100644 Content.Shared/Actions/ActionGrantComponent.cs create mode 100644 Content.Shared/Actions/ActionGrantSystem.cs create mode 100644 Content.Shared/Actions/Events/ActionComponentChangeEvent.cs create mode 100644 Content.Shared/Light/Components/LightOnCollideComponent.cs create mode 100644 Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs create mode 100644 Content.Shared/Light/EntitySystems/LightCollideSystem.cs diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index f05e4455880def..26a22fa8b8df37 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -259,12 +259,6 @@ public void TriggerAction(EntityUid actionId, BaseActionComponent action) if (action.ClientExclusive) { - if (instantAction.Event != null) - { - instantAction.Event.Performer = user; - instantAction.Event.Action = actionId; - } - PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); } else diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index 1c76b30075516e..f394f8cf179c70 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -219,8 +219,6 @@ private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, Wor if (action.Event != null) { action.Event.Target = coords; - action.Event.Performer = user; - action.Event.Action = actionId; } _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); @@ -254,8 +252,6 @@ private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, En if (action.Event != null) { action.Event.Target = entity; - action.Event.Performer = user; - action.Event.Action = actionId; } _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); @@ -295,8 +291,6 @@ private bool TryTargetEntityWorld(in PointerInputCmdArgs args, { action.Event.Entity = entity; action.Event.Coords = coords; - action.Event.Performer = user; - action.Event.Action = actionId; } _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); diff --git a/Content.Server/Actions/ActionOnInteractSystem.cs b/Content.Server/Actions/ActionOnInteractSystem.cs index 36e5920b32561b..23d74b04b89949 100644 --- a/Content.Server/Actions/ActionOnInteractSystem.cs +++ b/Content.Server/Actions/ActionOnInteractSystem.cs @@ -55,12 +55,6 @@ private void OnActivate(EntityUid uid, ActionOnInteractComponent component, Acti return; var (actId, act) = _random.Pick(options); - if (act.Event != null) - { - act.Event.Performer = args.User; - act.Event.Action = actId; - } - _actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false); args.Handled = true; } @@ -94,8 +88,6 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, var (entActId, entAct) = _random.Pick(entOptions); if (entAct.Event != null) { - entAct.Event.Performer = args.User; - entAct.Event.Action = entActId; entAct.Event.Target = args.Target.Value; } @@ -119,8 +111,6 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, var (entActId, entAct) = _random.Pick(entWorldOptions); if (entAct.Event != null) { - entAct.Event.Performer = args.User; - entAct.Event.Action = entActId; entAct.Event.Entity = args.Target; entAct.Event.Coords = args.ClickLocation; } @@ -145,8 +135,6 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, var (actId, act) = _random.Pick(options); if (act.Event != null) { - act.Event.Performer = args.User; - act.Event.Action = actId; act.Event.Target = args.ClickLocation; } diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs index e754b023c1ea2b..6ff86604589cbe 100644 --- a/Content.Shared/Actions/ActionEvents.cs +++ b/Content.Shared/Actions/ActionEvents.cs @@ -187,7 +187,7 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs /// /// The action the event belongs to. /// - public EntityUid Action; + public Entity Action; /// /// Should we toggle the action entity? diff --git a/Content.Shared/Actions/ActionGrantComponent.cs b/Content.Shared/Actions/ActionGrantComponent.cs new file mode 100644 index 00000000000000..f5d0b0dba1e030 --- /dev/null +++ b/Content.Shared/Actions/ActionGrantComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions; + +/// +/// Grants actions on MapInit and removes them on shutdown +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ActionGrantComponent : Component +{ + [DataField(required: true), AutoNetworkedField] + public List Actions = new(); + + [DataField, AutoNetworkedField] + public List ActionEntities = new(); +} diff --git a/Content.Shared/Actions/ActionGrantSystem.cs b/Content.Shared/Actions/ActionGrantSystem.cs new file mode 100644 index 00000000000000..5f5f8d02703ca0 --- /dev/null +++ b/Content.Shared/Actions/ActionGrantSystem.cs @@ -0,0 +1,36 @@ +namespace Content.Shared.Actions; + +/// +/// +/// +public sealed class ActionGrantSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actions = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShutdown); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + foreach (var action in ent.Comp.Actions) + { + EntityUid? actionEnt = null; + _actions.AddAction(ent.Owner, ref actionEnt, action); + + if (actionEnt != null) + ent.Comp.ActionEntities.Add(actionEnt.Value); + } + } + + private void OnShutdown(Entity ent, ref ComponentShutdown args) + { + foreach (var actionEnt in ent.Comp.ActionEntities) + { + _actions.RemoveAction(ent.Owner, actionEnt); + } + } +} diff --git a/Content.Shared/Actions/Events/ActionComponentChangeEvent.cs b/Content.Shared/Actions/Events/ActionComponentChangeEvent.cs new file mode 100644 index 00000000000000..c9c4db145daf6b --- /dev/null +++ b/Content.Shared/Actions/Events/ActionComponentChangeEvent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions.Events; + +/// +/// Adds / removes the component upon action. +/// +[Virtual] +public partial class ActionComponentChangeEvent : InstantActionEvent +{ + [DataField(required: true)] + public ComponentRegistry Components = new(); +} + +/// +/// Similar to except raises an event to attempt to relay it. +/// +public sealed partial class RelayedActionComponentChangeEvent : ActionComponentChangeEvent +{ + +} + +[ByRefEvent] +public record struct AttemptRelayActionComponentChangeEvent +{ + public EntityUid? Target; +} diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 41b6d84d12386b..03cbc74b5bdf3a 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -11,7 +11,6 @@ using Content.Shared.Rejuvenate; using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Timing; @@ -45,6 +44,8 @@ public override void Initialize() SubscribeLocalEvent(OnActionShutdown); SubscribeLocalEvent(OnActionShutdown); + SubscribeLocalEvent(OnActionCompChange); + SubscribeLocalEvent(OnRelayActionCompChange); SubscribeLocalEvent(OnDidEquip); SubscribeLocalEvent(OnHandEquipped); SubscribeLocalEvent(OnDidUnequip); @@ -490,12 +491,6 @@ private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArg break; } - if (performEvent != null) - { - performEvent.Performer = user; - performEvent.Action = actionEnt; - } - // All checks passed. Perform the action! PerformAction(user, component, actionEnt, action, performEvent, curTime); } @@ -641,6 +636,8 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti // This here is required because of client-side prediction (RaisePredictiveEvent results in event re-use). actionEvent.Handled = false; var target = performer; + actionEvent.Performer = performer; + actionEvent.Action = (actionId, action); if (!action.RaiseOnUser && action.Container != null && !HasComp(action.Container)) target = action.Container.Value; @@ -660,7 +657,7 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti _audio.PlayPredicted(action.Sound, performer,predicted ? performer : null); - var dirty = toggledBefore == action.Toggled; + var dirty = toggledBefore != action.Toggled; if (action.Charges != null) { @@ -965,6 +962,47 @@ protected virtual void ActionRemoved(EntityUid performer, EntityUid actionId, Ac #endregion + private void OnRelayActionCompChange(Entity ent, ref RelayedActionComponentChangeEvent args) + { + if (args.Handled) + return; + + var ev = new AttemptRelayActionComponentChangeEvent(); + RaiseLocalEvent(ent.Owner, ref ev); + var target = ev.Target ?? ent.Owner; + + args.Handled = true; + args.Toggle = true; + + if (!args.Action.Comp.Toggled) + { + EntityManager.AddComponents(target, args.Components); + } + else + { + EntityManager.RemoveComponents(target, args.Components); + } + } + + private void OnActionCompChange(Entity ent, ref ActionComponentChangeEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + args.Toggle = true; + var target = ent.Owner; + + if (!args.Action.Comp.Toggled) + { + EntityManager.AddComponents(target, args.Components); + } + else + { + EntityManager.RemoveComponents(target, args.Components); + } + } + #region EquipHandlers private void OnDidEquip(EntityUid uid, ActionsComponent component, DidEquipEvent args) { diff --git a/Content.Shared/Containers/ContainerCompSystem.cs b/Content.Shared/Containers/ContainerCompSystem.cs index aa0fe099b5c9b0..1e1983a331b9f5 100644 --- a/Content.Shared/Containers/ContainerCompSystem.cs +++ b/Content.Shared/Containers/ContainerCompSystem.cs @@ -38,10 +38,7 @@ private void OnConInsert(Entity ent, ref EntInsertedInto if (_proto.TryIndex(ent.Comp.Proto, out var entProto)) { - foreach (var entry in entProto.Components.Values) - { - AddComp(args.Entity, entry.Component, overwrite: true); - } + EntityManager.AddComponents(args.Entity, entProto.Components); } } } diff --git a/Content.Shared/Light/Components/LightOnCollideComponent.cs b/Content.Shared/Light/Components/LightOnCollideComponent.cs new file mode 100644 index 00000000000000..c3b4bd739651ab --- /dev/null +++ b/Content.Shared/Light/Components/LightOnCollideComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Enables / disables pointlight whenever entities are contacting with it +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class LightOnCollideComponent : Component +{ +} diff --git a/Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs b/Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs new file mode 100644 index 00000000000000..939ecf1a82a6b8 --- /dev/null +++ b/Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Light.Components; + +public sealed class LightOnCollideTriggerComponent +{ + +} diff --git a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs new file mode 100644 index 00000000000000..9fdcb8b62622b0 --- /dev/null +++ b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs @@ -0,0 +1,80 @@ +using Content.Shared.Light.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; + +namespace Content.Shared.Light.EntitySystems; + +public sealed class LightCollideSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SlimPoweredLightSystem _lights = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnPreventCollide); + SubscribeLocalEvent(OnStart); + SubscribeLocalEvent(OnEnd); + + SubscribeLocalEvent(OnCollideShutdown); + } + + private void OnCollideShutdown(Entity ent, ref ComponentShutdown args) + { + // TODO: Check this on the event. + if (TerminatingOrDeleted(ent.Owner)) + return; + + // Regenerate contacts for everything we were colliding with. + foreach (var contact in _physics.GetContacts(ent.Owner)) + { + if (!contact.IsTouching) + continue; + + var other = contact.OtherEnt(ent.Owner); + + if (HasComp(other)) + { + _physics.RegenerateContacts(other); + } + } + } + + // You may be wondering what de fok this is doing here. + // At the moment there's no easy way to do collision whitelists based on components. + private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) + { + if (!HasComp(args.OtherEntity)) + { + args.Cancelled = true; + } + } + + private void OnEnd(Entity ent, ref EndCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + if (!HasComp(args.OtherEntity)) + return; + + // TODO: Engine bug IsTouching box2d yay. + var contacts = _physics.GetTouchingContacts(args.OtherEntity) - 1; + + if (contacts > 0) + return; + + _lights.SetEnabled(args.OtherEntity, false); + } + + private void OnStart(Entity ent, ref StartCollideEvent args) + { + if (args.OurFixtureId != ent.Comp.FixtureId) + return; + + if (!HasComp(args.OtherEntity)) + return; + + _lights.SetEnabled(args.OtherEntity, true); + } +} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 4f6352137be9e2..a8dd4d1f033a6e 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -31,8 +31,12 @@ public abstract partial class SharedStationAiSystem : EntitySystem [Dependency] private readonly StationAiVisionSystem _vision = default!; /* - * TODO: Sprite-System get screencoords of entity / get world coords * TODO: Double-check positronic interactions didn't break + * + * Camera lights need fixing apparently the toggle is only if they go in range or some shit + * Need non-hard fixture on lights + * Probably proximitytrigger with whitelist, use slimpoweredlightcomponent and toggle it on / off + * * Upload console * Sensor overlay to see job * Check the diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 61f49cce82c50e..90bb67b3777298 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -7,6 +7,10 @@ - type: IgnoreUIRange - type: StationAiHeld - type: StationAiOverlay + - type: ActionGrant + actions: + - ActionShowJobIcons + - ActionSurvCameraLights - type: UserInterface interfaces: enum.RadarConsoleUiKey.Key: @@ -24,6 +28,45 @@ enum.GeneralStationRecordConsoleKey.Key: toggleAction: ActionAGhostShowStationRecords +# Actions +- type: entity + id: ActionShowJobIcons + name: Show job icons + description: Shows job icons for crew members. + components: + - type: InstantAction + itemIconStyle: BigAction + icon: + sprite: Interface/Misc/job_icons.rsi + state: Captain + event: !type:ActionComponentChangeEvent + components: + - type: ShowJobIcons + +- type: entity + id: ActionSurvCameraLights + name: Toggle camera lights + description: Enable surveillance camera lights near wherever you're viewing. + components: + - type: InstantAction + itemIconStyle: BigAction + icon: + sprite: Interface/Misc/job_icons.rsi + state: Captain + event: !type:RelayedActionComponentChangeEvent + components: + - type: LightOnCollideCollider + - type: FixturesChange + fixtures: + lightTrigger: + shape: + !type:PhysShapeCircle + radius: 0.35 + density: 80 + hard: false + layer: + - GhostImpassable + # Ai - type: entity id: AiHolder @@ -102,6 +145,7 @@ acts: [ "Destruction" ] - type: StationAiCore - type: StationAiVision + - type: InteractionOutline - type: Sprite sprite: Mobs/Silicon/station_ai.rsi layers: From 173ee91cae6009446535d19042b1dcadd1e35243 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 19 Aug 2024 01:52:07 +1000 Subject: [PATCH 16/40] thanks fork --- .../Climbing/Systems/ClimbSystem.cs | 38 ++++++++----------- .../LightOnCollideColliderComponent.cs | 13 +++++++ .../LightOnCollideTriggerComponent.cs | 6 --- .../EntitySystems/SlimPoweredLightSystem.cs | 5 ++- .../StationAi/SharedStationAiSystem.Held.cs | 25 ++++++++++++ .../Wallmounts/surveillance_camera.yml | 20 ++++++---- Resources/Prototypes/Wires/layouts.yml | 3 +- 7 files changed, 71 insertions(+), 39 deletions(-) create mode 100644 Content.Shared/Light/Components/LightOnCollideColliderComponent.cs delete mode 100644 Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs diff --git a/Content.Shared/Climbing/Systems/ClimbSystem.cs b/Content.Shared/Climbing/Systems/ClimbSystem.cs index 9b77d039f465e4..da194706f8f0a6 100644 --- a/Content.Shared/Climbing/Systems/ClimbSystem.cs +++ b/Content.Shared/Climbing/Systems/ClimbSystem.cs @@ -358,34 +358,26 @@ private void OnClimbEndCollide(EntityUid uid, ClimbingComponent component, ref E return; } - if (args.OurFixture.Contacts.Count > 1) + foreach (var contact in args.OurFixture.Contacts.Values) { - foreach (var contact in args.OurFixture.Contacts.Values) + if (!contact.IsTouching) + continue; + + var otherEnt = contact.OtherEnt(uid); + var (otherFixtureId, otherFixture) = contact.OtherFixture(uid); + + // TODO: Remove this on engine. + if (args.OtherEntity == otherEnt && args.OtherFixtureId == otherFixtureId) + continue; + + if (otherFixture is { Hard: true } && + _climbableQuery.HasComp(otherEnt)) { - if (!contact.IsTouching) - continue; - - var otherEnt = contact.EntityA; - var otherFixture = contact.FixtureA; - var otherFixtureId = contact.FixtureAId; - if (uid == contact.EntityA) - { - otherEnt = contact.EntityB; - otherFixture = contact.FixtureB; - otherFixtureId = contact.FixtureBId; - } - - if (args.OtherEntity == otherEnt && args.OtherFixtureId == otherFixtureId) - continue; - - if (otherFixture is { Hard: true } && - _climbableQuery.HasComp(otherEnt)) - { - return; - } + return; } } + // TODO: Is this even needed anymore? foreach (var otherFixture in args.OurFixture.Contacts.Keys) { // If it's the other fixture then ignore em diff --git a/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs b/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs new file mode 100644 index 00000000000000..39be05a1480a55 --- /dev/null +++ b/Content.Shared/Light/Components/LightOnCollideColliderComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Light.Components; + +/// +/// Can activate when collided with. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class LightOnCollideColliderComponent : Component +{ + [DataField] + public string FixtureId = "lightTrigger"; +} diff --git a/Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs b/Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs deleted file mode 100644 index 939ecf1a82a6b8..00000000000000 --- a/Content.Shared/Light/Components/LightOnCollideTriggerComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Shared.Light.Components; - -public sealed class LightOnCollideTriggerComponent -{ - -} diff --git a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs index 329f462e2278a3..6bc635b6dd0156 100644 --- a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs +++ b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs @@ -51,8 +51,11 @@ private void OnLightPowerChanged(Entity ent, ref Powe _setting = false; } - public void SetEnabled(Entity entity, bool enabled) + public void SetEnabled(Entity entity, bool enabled) { + if (!Resolve(entity.Owner, ref entity.Comp, false)) + return; + if (entity.Comp.Enabled == enabled) return; diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index e9adcad70b3982..b4081b449772bd 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -1,3 +1,4 @@ +using Content.Shared.Actions.Events; using Content.Shared.Interaction.Events; using Content.Shared.Verbs; using Robust.Shared.Serialization; @@ -17,6 +18,30 @@ private void InitializeHeld() SubscribeLocalEvent(OnMessageAttempt); SubscribeLocalEvent>(OnTargetVerbs); SubscribeLocalEvent(OnHeldInteraction); + SubscribeLocalEvent(OnHeldRelay); + } + + private bool TryGetCore(EntityUid ent, out EntityUid core) + { + if (!_containers.TryGetContainingContainer(ent, out var container) || + container.ID != StationAiCoreComponent.Container || + !TryComp(container.Owner, out StationAiCoreComponent? coreComp) || + coreComp.RemoteEntity == null) + { + core = EntityUid.Invalid; + return false; + } + + core = coreComp.RemoteEntity.Value; + return true; + } + + private void OnHeldRelay(Entity ent, ref AttemptRelayActionComponentChangeEvent args) + { + if (!TryGetCore(ent.Owner, out var core)) + return; + + args.Target = core; } private void OnRadialMessage(StationAiRadialMessage ev) diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 461cf55d9d38b5..2d4ad9bec7baf4 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -4,16 +4,22 @@ name: camera description: A surveillance camera. It's watching you. Kinda. components: - - type: StationAiWhitelist - - type: ItemToggle - soundActivate: - path: /Audio/Machines/button.ogg - soundDeactivate: - path: /Audio/Machines/button.ogg + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + light: + shape: + !type:PhysShapeCircle + radius: 5 + hard: false + mask: + - GhostImpassable + - type: LightOnCollide + fixtureId: light - type: PointLight enabled: false radius: 5 - - type: ItemTogglePointLight - type: SlimPoweredLight enabled: false - type: StationAiVision diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index f0ec49983544ef..ccab000cfa2b41 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -98,11 +98,10 @@ - type: wireLayout id: SurveillanceCamera - dummyWires: 3 + dummyWires: 2 wires: - !type:PowerWireAction - !type:AiVisionWireAction - - !type:AiInteractWireAction - type: wireLayout id: CryoPod From a817da90aa988def463944bce4322c95170dea54 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 19 Aug 2024 18:39:27 +1000 Subject: [PATCH 17/40] alright --- .../Laws/SiliconLawEditUi/SiliconLawEui.cs | 2 +- .../Systems/Actions/ActionUIController.cs | 2 +- .../Administration/Systems/AdminVerbSystem.cs | 1 + .../Silicons/Laws/SiliconLawSystem.cs | 15 +----- .../Actions/ActionGrantComponent.cs | 4 +- Content.Shared/Actions/ActionGrantSystem.cs | 12 +++++ .../Actions/ItemActionGrantComponent.cs | 14 ++++++ Content.Shared/Actions/SharedActionsSystem.cs | 2 +- .../Components/MovementSoundComponent.cs | 20 ++++++++ .../Movement/Systems/MovementSoundSystem.cs | 44 ++++++++++++++++++ .../Components/SiliconLawBoundComponent.cs | 15 +----- .../StationAi/SharedStationAiSystem.Held.cs | 19 ++++++-- .../StationAi/SharedStationAiSystem.cs | 8 ++++ .../Audio/Effects/Footsteps/attributions.yml | 2 +- .../Audio/Effects/Footsteps/borgwalk2.ogg | Bin 20233 -> 9052 bytes .../Mobs/Cyborgs/base_borg_chassis.yml | 3 ++ .../Entities/Mobs/Cyborgs/borg_chassis.yml | 8 ++-- .../Entities/Mobs/Player/silicon.yml | 29 +++++++++++- Resources/Prototypes/Entities/Mobs/base.yml | 1 - 19 files changed, 158 insertions(+), 43 deletions(-) create mode 100644 Content.Shared/Actions/ItemActionGrantComponent.cs create mode 100644 Content.Shared/Movement/Components/MovementSoundComponent.cs create mode 100644 Content.Shared/Movement/Systems/MovementSoundSystem.cs diff --git a/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs b/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs index a4d59d1f3150f3..03c74032f73bd3 100644 --- a/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs +++ b/Content.Client/Silicons/Laws/SiliconLawEditUi/SiliconLawEui.cs @@ -6,7 +6,7 @@ namespace Content.Client.Silicons.Laws.SiliconLawEditUi; public sealed class SiliconLawEui : BaseEui { - public readonly EntityManager _entityManager = default!; + private readonly EntityManager _entityManager; private SiliconLawUi _siliconLawUi; private EntityUid _target; diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index f394f8cf179c70..1dffeb8d2d4497 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -121,7 +121,7 @@ public void OnStateEntered(GameplayState state) var boundKey = hotbarKeys[i]; builder = builder.Bind(boundKey, new PointerInputCmdHandler((in PointerInputCmdArgs args) => { - if (args.State != BoundKeyState.Up) + if (args.State != BoundKeyState.Down) return false; TriggerAction(boundId); diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 6123bba8deeb5d..308a679846ccf9 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -36,6 +36,7 @@ using System.Linq; using System.Numerics; using Content.Server.Silicons.Laws; +using Content.Shared.Silicons.Laws; using Content.Shared.Silicons.Laws.Components; using Robust.Server.Player; using Robust.Shared.Physics.Components; diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 0c0f68c23f352e..b34fb32c0087b2 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -5,12 +5,10 @@ using Content.Server.Radio.Components; using Content.Server.Roles; using Content.Server.Station.Systems; -using Content.Shared.Actions; using Content.Shared.Administration; using Content.Shared.Chat; using Content.Shared.Emag.Components; using Content.Shared.Emag.Systems; -using Content.Shared.Examine; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; @@ -22,7 +20,6 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; -using Robust.Shared.Utility; namespace Content.Server.Silicons.Laws; @@ -32,11 +29,9 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly SharedMindSystem _mind = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; /// @@ -44,7 +39,6 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnToggleLawsScreen); @@ -58,15 +52,8 @@ public override void Initialize() SubscribeLocalEvent(OnEmagMindRemoved); } - private void OnComponentShutdown(EntityUid uid, SiliconLawBoundComponent component, ComponentShutdown args) - { - if (component.ViewLawsActionEntity != null) - _actions.RemoveAction(uid, component.ViewLawsActionEntity); - } - private void OnMapInit(EntityUid uid, SiliconLawBoundComponent component, MapInitEvent args) { - _actions.AddAction(uid, ref component.ViewLawsActionEntity, component.ViewLawsAction); GetLaws(uid, component); } @@ -92,7 +79,7 @@ private void OnToggleLawsScreen(EntityUid uid, SiliconLawBoundComponent componen private void OnBoundUIOpened(EntityUid uid, SiliconLawBoundComponent component, BoundUIOpenedEvent args) { - _entityManager.TryGetComponent(uid, out var intrinsicRadio); + TryComp(uid, out IntrinsicRadioTransmitterComponent? intrinsicRadio); var radioChannels = intrinsicRadio?.Channels; var state = new SiliconLawBuiState(GetLaws(uid).Laws, radioChannels); diff --git a/Content.Shared/Actions/ActionGrantComponent.cs b/Content.Shared/Actions/ActionGrantComponent.cs index f5d0b0dba1e030..94c3a0bbd1d8cc 100644 --- a/Content.Shared/Actions/ActionGrantComponent.cs +++ b/Content.Shared/Actions/ActionGrantComponent.cs @@ -6,10 +6,10 @@ namespace Content.Shared.Actions; /// /// Grants actions on MapInit and removes them on shutdown /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(ActionGrantSystem))] public sealed partial class ActionGrantComponent : Component { - [DataField(required: true), AutoNetworkedField] + [DataField(required: true), AutoNetworkedField, AlwaysPushInheritance] public List Actions = new(); [DataField, AutoNetworkedField] diff --git a/Content.Shared/Actions/ActionGrantSystem.cs b/Content.Shared/Actions/ActionGrantSystem.cs index 5f5f8d02703ca0..f73ecf8a460813 100644 --- a/Content.Shared/Actions/ActionGrantSystem.cs +++ b/Content.Shared/Actions/ActionGrantSystem.cs @@ -12,6 +12,18 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnItemGet); + } + + private void OnItemGet(Entity ent, ref GetItemActionsEvent args) + { + if (!TryComp(ent.Owner, out ActionGrantComponent? grant)) + return; + + foreach (var action in grant.ActionEntities) + { + args.AddAction(action); + } } private void OnMapInit(Entity ent, ref MapInitEvent args) diff --git a/Content.Shared/Actions/ItemActionGrantComponent.cs b/Content.Shared/Actions/ItemActionGrantComponent.cs new file mode 100644 index 00000000000000..d1769b51a2ffd9 --- /dev/null +++ b/Content.Shared/Actions/ItemActionGrantComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Actions; + +/// +/// Works in tandem with by granting those actions to the equipper entity. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(ActionGrantSystem))] +public sealed partial class ItemActionGrantComponent : Component +{ + [DataField(required: true), AutoNetworkedField, AlwaysPushInheritance] + public List Actions = new(); +} diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 03cbc74b5bdf3a..41ab0ebefe576c 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -655,7 +655,7 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti action.Toggled = !action.Toggled; } - _audio.PlayPredicted(action.Sound, performer,predicted ? performer : null); + _audio.PlayPredicted(action.Sound, performer, predicted ? performer : null); var dirty = toggledBefore != action.Toggled; diff --git a/Content.Shared/Movement/Components/MovementSoundComponent.cs b/Content.Shared/Movement/Components/MovementSoundComponent.cs new file mode 100644 index 00000000000000..92a6974cc6766b --- /dev/null +++ b/Content.Shared/Movement/Components/MovementSoundComponent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Movement.Components; + +/// +/// Plays a sound whenever InputMover is running. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MovementSoundComponent : Component +{ + /// + /// Sound to play when InputMover has inputs. + /// + [DataField(required: true), AutoNetworkedField] + public SoundSpecifier? Sound; + + [DataField, AutoNetworkedField] + public EntityUid? SoundEntity; +} diff --git a/Content.Shared/Movement/Systems/MovementSoundSystem.cs b/Content.Shared/Movement/Systems/MovementSoundSystem.cs new file mode 100644 index 00000000000000..9a1146779fac27 --- /dev/null +++ b/Content.Shared/Movement/Systems/MovementSoundSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.Movement.Systems; + +/// +/// Plays a sound on MoveInputEvent. +/// +public sealed class MovementSoundSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMoveInput); + } + + private void OnMoveInput(Entity ent, ref MoveInputEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None; + var moving = (SharedMoverController.GetNormalizedMovement(args.Entity.Comp.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None; + + if (oldMoving == moving) + return; + + if (moving) + { + DebugTools.Assert(ent.Comp.SoundEntity == null); + ent.Comp.SoundEntity = _audio.PlayPredicted(ent.Comp.Sound, ent.Owner, ent.Owner)?.Entity; + } + else + { + ent.Comp.SoundEntity = _audio.Stop(ent.Comp.SoundEntity); + } + } +} diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs index 824d057b3ea011..0fb9c5920fa759 100644 --- a/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawBoundComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Actions; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -7,21 +8,9 @@ namespace Content.Shared.Silicons.Laws.Components; /// /// This is used for entities which are bound to silicon laws and can view them. /// -[RegisterComponent, Access(typeof(SharedSiliconLawSystem))] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedSiliconLawSystem))] public sealed partial class SiliconLawBoundComponent : Component { - /// - /// The sidebar action that toggles the laws screen. - /// - [DataField] - public EntProtoId ViewLawsAction = "ActionViewLaws"; - - /// - /// The action for toggling laws. Stored here so we can remove it later. - /// - [DataField] - public EntityUid? ViewLawsActionEntity; - /// /// The last entity that provided laws to this entity. /// diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index b4081b449772bd..0dac2be39ac804 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Content.Shared.Actions.Events; using Content.Shared.Interaction.Events; using Content.Shared.Verbs; @@ -17,22 +18,32 @@ private void InitializeHeld() SubscribeLocalEvent(OnRadialMessage); SubscribeLocalEvent(OnMessageAttempt); SubscribeLocalEvent>(OnTargetVerbs); + SubscribeLocalEvent(OnHeldInteraction); SubscribeLocalEvent(OnHeldRelay); + SubscribeLocalEvent(OnCoreJump); + } + + private void OnCoreJump(Entity ent, ref JumpToCoreEvent args) + { + if (!TryGetCore(ent.Owner, out var core) || core.Comp?.RemoteEntity == null) + return; + + _xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ; } - private bool TryGetCore(EntityUid ent, out EntityUid core) + private bool TryGetCore(EntityUid ent, out Entity core) { if (!_containers.TryGetContainingContainer(ent, out var container) || container.ID != StationAiCoreComponent.Container || !TryComp(container.Owner, out StationAiCoreComponent? coreComp) || coreComp.RemoteEntity == null) { - core = EntityUid.Invalid; + core = (EntityUid.Invalid, null); return false; } - core = coreComp.RemoteEntity.Value; + core = (container.Owner, coreComp); return true; } @@ -41,7 +52,7 @@ private void OnHeldRelay(Entity ent, ref AttemptRelayAct if (!TryGetCore(ent.Owner, out var core)) return; - args.Target = core; + args.Target = core.Comp?.RemoteEntity; } private void OnRadialMessage(StationAiRadialMessage ev) diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index a8dd4d1f033a6e..41877b4a4bf7dd 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.ActionBlocker; +using Content.Shared.Actions; using Content.Shared.Containers.ItemSlots; using Content.Shared.Doors.Systems; using Content.Shared.Interaction; @@ -27,6 +28,7 @@ public abstract partial class SharedStationAiSystem : EntitySystem [Dependency] private readonly ItemToggleSystem _toggles = default!; [Dependency] protected readonly SharedMapSystem Maps = default!; [Dependency] private readonly SharedMoverController _mover = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly StationAiVisionSystem _vision = default!; @@ -229,6 +231,7 @@ private void SetupEye(Entity ent) if (ent.Comp.RemoteEntityProto != null) { ent.Comp.RemoteEntity = SpawnAtPosition(ent.Comp.RemoteEntityProto, Transform(ent.Owner).Coordinates); + Dirty(ent); } } @@ -333,6 +336,11 @@ private bool ValidateAi(Entity entity) } } +public sealed partial class JumpToCoreEvent : InstantActionEvent +{ + +} + [Serializable, NetSerializable] public enum StationAiVisualState : byte { diff --git a/Resources/Audio/Effects/Footsteps/attributions.yml b/Resources/Audio/Effects/Footsteps/attributions.yml index 91c3ce260d4e92..7a56beec38c105 100644 --- a/Resources/Audio/Effects/Footsteps/attributions.yml +++ b/Resources/Audio/Effects/Footsteps/attributions.yml @@ -76,5 +76,5 @@ - borgwalk1.ogg - borgwalk2.ogg license: "CC-BY-SA-4.0" - copyright: "Taken from IENBA freesound.org and modified by https://github.com/MilenVolf" + copyright: "Taken from IENBA freesound.org and modified by https://github.com/MilenVolf. borgwalk2 clipped my metalgearsloth." source: "https://freesound.org/people/IENBA/sounds/697379/" diff --git a/Resources/Audio/Effects/Footsteps/borgwalk2.ogg b/Resources/Audio/Effects/Footsteps/borgwalk2.ogg index 96c2c1617f47556ab8143e378469e314b37e6c64..57685ff173de25a873c363fe72d645db56b91201 100644 GIT binary patch delta 7656 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 Date: Mon, 19 Aug 2024 18:39:54 +1000 Subject: [PATCH 18/40] pc --- .../Entities/Structures/Machines/Computers/computers.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index fe68d4b4e19780..3d80adbaa53a03 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -1118,3 +1118,9 @@ access: [["ResearchDirector"]] - type: Lock unlockOnClick: false + +- type: entity + id: StationAiUploadComputer + name: AI upload console + description: Used to update the laws of the station AI. + From d88accfa526a30a52157c8228610dc9444a185b7 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 19 Aug 2024 20:37:04 +1000 Subject: [PATCH 19/40] AI upload console --- .../Components/SiliconLawUpdaterComponent.cs | 18 ++++++++++++++++++ .../Machines/Computers/computers.yml | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs new file mode 100644 index 00000000000000..e68c74600b5294 --- /dev/null +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Silicons.Laws.Components; + +///

+/// Whenever an entity is inserted with silicon laws it will update the relevant entity's laws. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SiliconLawUpdaterComponent : Component +{ + /// + /// Entities to update + /// + [DataField] + public ComponentRegistry Components; +} diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 3d80adbaa53a03..b032752046d6f2 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -1121,6 +1121,20 @@ - type: entity id: StationAiUploadComputer + parent: BaseComputer name: AI upload console description: Used to update the laws of the station AI. - + components: + - type: Sprite + layers: + - map: [ "computerLayerBody" ] + state: computer + - map: [ "computerLayerKeyboard" ] + state: generic_keyboard + - map: [ "computerLayerScreen" ] + state: aiupload + - map: [ "computerLayerKeys" ] + state: generic_keys + - type: SiliconLawUpdater + - type: ItemSlots + - type: ContainerContainer From fc57c84d487a8a0bb3adf9018ceb684e03309b75 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 20 Aug 2024 22:03:50 +1000 Subject: [PATCH 20/40] AI upload --- .../Silicons/Laws/SiliconLawSystem.cs | 20 ++++++- .../ItemSlot/ItemSlotsLockComponent.cs | 13 +++++ .../ItemSlot/ItemSlotsSystem.Lock.cs | 36 +++++++++++++ .../Containers/ItemSlot/ItemSlotsSystem.cs | 8 +-- .../ItemSlotRequiresPowerComponent.cs | 9 ++++ .../ItemSlotRequiresPowerSystem.cs | 23 ++++++++ .../Components/SiliconLawUpdaterComponent.cs | 3 +- .../Laws/SharedSiliconLawSystem.Updater.cs | 17 ++++++ .../Silicons/Laws/SharedSiliconLawSystem.cs | 3 +- .../StationAi/SharedStationAiSystem.cs | 49 ++++++++++++------ .../administration/ui/silicon-law-ui.ftl | 2 + .../Entities/Mobs/Player/silicon.yml | 30 +++++++++++ .../Objects/Specific/Robotics/mmi.yml | 1 + .../Machines/Computers/computers.yml | 22 ++++++++ .../Devices/ai_card.rsi/inhand-left.png | Bin 0 -> 306 bytes .../Devices/ai_card.rsi/inhand-right.png | Bin 0 -> 316 bytes .../Objects/Devices/ai_card.rsi/meta.json | 8 +++ 17 files changed, 219 insertions(+), 25 deletions(-) create mode 100644 Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs create mode 100644 Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs create mode 100644 Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs create mode 100644 Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs create mode 100644 Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs create mode 100644 Resources/Textures/Objects/Devices/ai_card.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Devices/ai_card.rsi/inhand-right.png diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index b34fb32c0087b2..6b7df52a6ebc1d 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Stunnable; using Content.Shared.Wires; using Robust.Server.GameObjects; +using Robust.Shared.Containers; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Toolshed; @@ -251,9 +252,9 @@ public void NotifyLawsChanged(EntityUid uid) /// /// Extract all the laws from a lawset's prototype ids. /// - public SiliconLawset GetLawset(string lawset) + public SiliconLawset GetLawset(ProtoId lawset) { - var proto = _prototype.Index(lawset); + var proto = _prototype.Index(lawset); var laws = new SiliconLawset() { Laws = new List(proto.Laws.Count) @@ -281,6 +282,21 @@ public void SetLaws(List newLaws, EntityUid target) component.Lawset.Laws = newLaws; NotifyLawsChanged(target); } + + protected override void OnUpdaterInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + // TODO: Prediction dump this + if (!TryComp(args.Entity, out SiliconLawProviderComponent? provider)) + return; + + var lawset = GetLawset(provider.Laws).Laws; + var query = EntityManager.CompRegistryQueryEnumerator(ent.Comp.Components); + + while (query.MoveNext(out var update)) + { + SetLaws(lawset, update); + } + } } [ToolshedCommand, AdminCommand(AdminFlags.Admin)] diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs new file mode 100644 index 00000000000000..0d8901028d70ac --- /dev/null +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsLockComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Containers.ItemSlots; + +/// +/// Updates the relevant ItemSlots locks based on +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ItemSlotsLockComponent : Component +{ + [DataField(required: true)] + public List Slots = new(); +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs new file mode 100644 index 00000000000000..ee5178df95b1cc --- /dev/null +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.Lock.cs @@ -0,0 +1,36 @@ +using Content.Shared.Lock; + +namespace Content.Shared.Containers.ItemSlots; + +public sealed partial class ItemSlotsSystem +{ + private void InitializeLock() + { + SubscribeLocalEvent(OnLockMapInit); + SubscribeLocalEvent(OnLockToggled); + } + + private void OnLockMapInit(Entity ent, ref MapInitEvent args) + { + if (!TryComp(ent.Owner, out LockComponent? lockComp)) + return; + + UpdateLocks(ent, lockComp.Locked); + } + + private void OnLockToggled(Entity ent, ref LockToggledEvent args) + { + UpdateLocks(ent, args.Locked); + } + + private void UpdateLocks(Entity ent, bool value) + { + foreach (var slot in ent.Comp.Slots) + { + if (!TryGetSlot(ent.Owner, slot, out var itemSlot)) + continue; + + SetLock(ent.Owner, itemSlot, value); + } + } +} diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index e0c499bec71c65..f41fa2b22d2c53 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -24,7 +24,7 @@ namespace Content.Shared.Containers.ItemSlots /// Note when using popups on entities with many slots with InsertOnInteract, EjectOnInteract or EjectOnUse: /// A single use will try to insert to/eject from every slot and generate a popup for each that fails. /// - public sealed class ItemSlotsSystem : EntitySystem + public sealed partial class ItemSlotsSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; @@ -38,6 +38,8 @@ public override void Initialize() { base.Initialize(); + InitializeLock(); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(Oninitialize); @@ -252,7 +254,7 @@ private void Insert(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? use if (inserted != null && inserted.Value && user != null) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} inserted {ToPrettyString(item)} into {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}"); - _audioSystem.PlayPredicted(slot.InsertSound, uid, !excludeUserAudio ? user : null); + _audioSystem.PlayPredicted(slot.InsertSound, uid, excludeUserAudio ? user : null); } /// @@ -450,7 +452,7 @@ private void Eject(EntityUid uid, ItemSlot slot, EntityUid item, EntityUid? user if (ejected != null && ejected.Value && user != null) _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user.Value)} ejected {ToPrettyString(item)} from {slot.ContainerSlot?.ID + " slot of "}{ToPrettyString(uid)}"); - _audioSystem.PlayPredicted(slot.EjectSound, uid, !excludeUserAudio ? user : null); + _audioSystem.PlayPredicted(slot.EjectSound, uid, excludeUserAudio ? user : null); } /// diff --git a/Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs b/Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs new file mode 100644 index 00000000000000..6e3b9eaca053fd --- /dev/null +++ b/Content.Shared/Power/Components/ItemSlotRequiresPowerComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Power.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ItemSlotRequiresPowerComponent : Component +{ + +} diff --git a/Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs b/Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs new file mode 100644 index 00000000000000..3df8b91a9856d5 --- /dev/null +++ b/Content.Shared/Power/EntitySystems/ItemSlotRequiresPowerSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Containers.ItemSlots; +using Content.Shared.Power.Components; + +namespace Content.Shared.Power.EntitySystems; + +public sealed class ItemSlotRequiresPowerSystem : EntitySystem +{ + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnInsertAttempt); + } + + private void OnInsertAttempt(Entity ent, ref ItemSlotInsertAttemptEvent args) + { + if (!_receiver.IsPowered(ent.Owner)) + { + args.Cancelled = true; + } + } +} diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs index e68c74600b5294..e28bf883d91c81 100644 --- a/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawUpdaterComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -13,6 +12,6 @@ public sealed partial class SiliconLawUpdaterComponent : Component /// /// Entities to update /// - [DataField] + [DataField(required: true)] public ComponentRegistry Components; } diff --git a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs new file mode 100644 index 00000000000000..9fbef58a842070 --- /dev/null +++ b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.Updater.cs @@ -0,0 +1,17 @@ +using Content.Shared.Silicons.Laws.Components; +using Robust.Shared.Containers; + +namespace Content.Shared.Silicons.Laws; + +public abstract partial class SharedSiliconLawSystem +{ + private void InitializeUpdater() + { + SubscribeLocalEvent(OnUpdaterInsert); + } + + protected virtual void OnUpdaterInsert(Entity ent, ref EntInsertedIntoContainerMessage args) + { + // TODO: Prediction + } +} diff --git a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs index c0619e6e43d47d..a30e7c8980f98c 100644 --- a/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs +++ b/Content.Shared/Silicons/Laws/SharedSiliconLawSystem.cs @@ -8,13 +8,14 @@ namespace Content.Shared.Silicons.Laws; /// /// This handles getting and displaying the laws for silicons. /// -public abstract class SharedSiliconLawSystem : EntitySystem +public abstract partial class SharedSiliconLawSystem : EntitySystem { [Dependency] private readonly SharedPopupSystem _popup = default!; /// public override void Initialize() { + InitializeUpdater(); SubscribeLocalEvent(OnGotEmagged); SubscribeLocalEvent(OnAttemptEmag); } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 41877b4a4bf7dd..fb82e33c67be3c 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Item.ItemToggle; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; +using Content.Shared.Power.Components; using Content.Shared.StationAi; using Content.Shared.Verbs; using Robust.Shared.Containers; @@ -33,22 +34,8 @@ public abstract partial class SharedStationAiSystem : EntitySystem [Dependency] private readonly StationAiVisionSystem _vision = default!; /* - * TODO: Double-check positronic interactions didn't break - * - * Camera lights need fixing apparently the toggle is only if they go in range or some shit - * Need non-hard fixture on lights - * Probably proximitytrigger with whitelist, use slimpoweredlightcomponent and toggle it on / off - * - * Upload console - * Sensor overlay to see job - * Check the - * crew monitoring console - * crew manifest - alert console (donno if we have that yet) - button jump towards the ai core - call shuttle - make annoucement - state laws + - AI core announcement on latejoin + - Test posibrain interactions */ // StationAiHeld is added to anything inside of an AI core. @@ -83,6 +70,7 @@ public override void Initialize() SubscribeLocalEvent(OnAiRemove); SubscribeLocalEvent(OnAiMapInit); SubscribeLocalEvent(OnAiShutdown); + SubscribeLocalEvent(OnCorePower); } private void OnAiAccessible(Entity ent, ref AccessibleOverrideEvent args) @@ -220,19 +208,46 @@ private void OnAiShutdown(Entity ent, ref ComponentShutd ent.Comp.RemoteEntity = null; } + private void OnCorePower(Entity ent, ref PowerChangedEvent args) + { + // TODO: I think in 13 they just straightup die so maybe implement that + if (args.Powered) + { + if (!SetupEye(ent)) + return; + + AttachEye(ent); + } + else + { + ClearEye(ent); + } + } + private void OnAiMapInit(Entity ent, ref MapInitEvent args) { SetupEye(ent); AttachEye(ent); } - private void SetupEye(Entity ent) + private bool SetupEye(Entity ent) { + if (ent.Comp.RemoteEntity != null) + return false; + if (ent.Comp.RemoteEntityProto != null) { ent.Comp.RemoteEntity = SpawnAtPosition(ent.Comp.RemoteEntityProto, Transform(ent.Owner).Coordinates); Dirty(ent); } + + return true; + } + + private void ClearEye(Entity ent) + { + QueueDel(ent.Comp.RemoteEntity); + ent.Comp.RemoteEntity = null; } private void AttachEye(Entity ent) diff --git a/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl b/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl index 0a68d08063ad96..24ab73097437eb 100644 --- a/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl +++ b/Resources/Locale/en-US/administration/ui/silicon-law-ui.ftl @@ -8,3 +8,5 @@ silicon-law-ui-delete = Delete silicon-law-ui-check-corrupted = Corrupted law silicon-law-ui-check-corrupted-tooltip = If the law identifier should be set as 'corrupted', so symbols shuffling around. silicon-law-ui-placeholder = Type here to change law text... + +silicon-laws-updated = Updated laws diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index b917fc56bcafe5..cfe3b4aa61a246 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -104,6 +104,32 @@ # anyway it's just a quality of life thing. showEnts: True +# Boards +- type: entity + id: AsimovCircuitBoard + parent: BaseElectronics + name: circuit board (Crewsimov) + description: An electronics board containing the Crewsimov lawset. + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: std_mod + - type: SiliconLawProvider + laws: Crewsimov + +- type: entity + id: CorporateCircuitBoard + parent: BaseElectronics + name: circuit board (Corporate) + description: An electronics board containing the Corporate lawset. + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: std_mod + - type: SiliconLawProvider + laws: Corporate + +# Items - type: entity id: Intellicard name: Intellicard @@ -160,6 +186,8 @@ collection: MetalBreak - !type:DoActsBehavior acts: [ "Destruction" ] + - type: ApcPowerReceiver + powerLoad: 1000 - type: StationAiCore - type: StationAiVision - type: InteractionOutline @@ -204,6 +232,8 @@ - type: Sprite # Once it's in a core it's pretty much an abstract entity at that point. visible: false + - type: SiliconLawProvider + laws: Crewsimov - type: SiliconLawBound - type: ActionGrant actions: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml index 0dd3f6d59850e1..2d2620efca8ff2 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml @@ -89,6 +89,7 @@ wipeVerbPopup: positronic-brain-wiped-device stopSearchVerbText: positronic-brain-stop-searching-verb-text stopSearchVerbPopup: positronic-brain-stopped-searching + - type: BlockMovement - type: Examiner - type: BorgBrain - type: IntrinsicRadioReceiver diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index b032752046d6f2..9a3ce32e1e36c4 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -1135,6 +1135,28 @@ state: aiupload - map: [ "computerLayerKeys" ] state: generic_keys + - type: ApcPowerReceiver + powerLoad: 1000 + - type: AccessReader + access: [ [ "ResearchDirector" ] ] + - type: Lock + unlockOnClick: false - type: SiliconLawUpdater + components: + - type: StationAiHeld + - type: ItemSlotsLock + slots: + - circuit_holder + - type: ItemSlotRequiresPower - type: ItemSlots + slots: + circuit_holder: + name: circuit-holder + insertSuccessPopup: silicon-laws-updated + whitelist: + components: + - SiliconLawProvider + - Item - type: ContainerContainer + containers: + circuit_holder: !type:ContainerSlot diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-left.png b/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..2d3863145b951a7743066a59157c1cc7e562a50d GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fAQ1FST zi(^Q|oVT|QavnAiaJeXd!mxBHL-@lzyJI3eW&X_#`)JO=0C5^IGT8Tx_t4~ zX@6Nl{8v5ulh<%-62mLgwUz6yO>>#Rwqw3njO^a1`qCRp_ZvjpV=T? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-right.png b/Resources/Textures/Objects/Devices/ai_card.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..1704b9c3c112fc423e9466328df5e2bac1f15c87 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fAQ1G3n zi(^Q|oVT|&@*Xk}alP2>ax#YJfu_-<Qq+r@Tz=cdWe;su{G zbQCijv1E88!%)cAV8QM%hgsnqqky@H!qP3Z{1Ny;?i_Y=X7}c^aI?Gd2{7O|KF>S%~kcJoh@js0_;4V~` bf53TkzSA$a-26*GpEG#6`njxgN@xNATZDFx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json b/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json index 0982109f9ab2c7..8b8135fa16e6b7 100644 --- a/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/ai_card.rsi/meta.json @@ -10,6 +10,14 @@ { "name": "base" }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, { "name": "empty", "delays": [ From a3d03dbfde3a61d8596260cdf288839c715faf4a Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 20 Aug 2024 22:19:11 +1000 Subject: [PATCH 21/40] stuff --- .../Silicons/StationAi/StationAiSystem.cs | 2 +- .../StationAi/SharedStationAiSystem.cs | 5 ---- .../Entities/Mobs/Player/silicon.yml | 24 +++++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index 03ac877b254a51..846497387d24ae 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -15,7 +15,7 @@ public sealed class StationAiSystem : SharedStationAiSystem [Dependency] private readonly IChatManager _chats = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; - private HashSet> _ais = new(); + private readonly HashSet> _ais = new(); public override bool SetVisionEnabled(Entity entity, bool enabled, bool announce = false) { diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index fb82e33c67be3c..57adc88076d773 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -33,11 +33,6 @@ public abstract partial class SharedStationAiSystem : EntitySystem [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly StationAiVisionSystem _vision = default!; - /* - - AI core announcement on latejoin - - Test posibrain interactions - */ - // StationAiHeld is added to anything inside of an AI core. // StationAiHolder indicates it can hold an AI positronic brain (e.g. holocard / core). // StationAiCore holds functionality related to the core itself. diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index cfe3b4aa61a246..35e72e025172dc 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -129,6 +129,30 @@ - type: SiliconLawProvider laws: Corporate +- type: entity + id: NTDefaultCircuitBoard + parent: BaseElectronics + name: circuit board (NT Default) + description: An electronics board containing the NT Default lawset. + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: std_mod + - type: SiliconLawProvider + laws: NTDefault + +- type: entity + id: NTDefaultCircuitBoard + parent: BaseElectronics + name: circuit board (NT Default) + description: An electronics board containing the NT Default lawset. + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: std_mod + - type: SiliconLawProvider + laws: NTDefault + # Items - type: entity id: Intellicard From f6ab7954b0995574c3da8bcc362242ff5c9fb08b Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 22 Aug 2024 22:25:56 +1000 Subject: [PATCH 22/40] Fix copy-paste shitcode --- Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs b/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs index f6cc2d146895fc..33bc8f9074c368 100644 --- a/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs +++ b/Content.Server/NPC/Systems/NPCUseActionOnTargetSystem.cs @@ -36,8 +36,6 @@ public bool TryUseTentacleAttack(Entity user, En if (action.Event != null) { - action.Event.Performer = user; - action.Event.Action = user.Comp.ActionEnt.Value; action.Event.Coords = Transform(target).Coordinates; } From 92499803f678b2beb88a312c0c759a01330d4bd0 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Thu, 22 Aug 2024 22:57:57 +1000 Subject: [PATCH 23/40] AI actions --- .../Entities/Mobs/Player/silicon.yml | 20 +++----------- .../Actions/actions_ai.rsi/ai_core.png | Bin 0 -> 269 bytes .../Actions/actions_ai.rsi/camera_light.png | Bin 0 -> 309 bytes .../Actions/actions_ai.rsi/crew_monitor.png | Bin 0 -> 295 bytes .../Actions/actions_ai.rsi/manifest.png | Bin 0 -> 245 bytes .../Actions/actions_ai.rsi/meta.json | 26 ++++++++++++++++++ .../Actions/actions_ai.rsi/state_laws.png | Bin 0 -> 241 bytes 7 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 Resources/Textures/Interface/Actions/actions_ai.rsi/ai_core.png create mode 100644 Resources/Textures/Interface/Actions/actions_ai.rsi/camera_light.png create mode 100644 Resources/Textures/Interface/Actions/actions_ai.rsi/crew_monitor.png create mode 100644 Resources/Textures/Interface/Actions/actions_ai.rsi/manifest.png create mode 100644 Resources/Textures/Interface/Actions/actions_ai.rsi/meta.json create mode 100644 Resources/Textures/Interface/Actions/actions_ai.rsi/state_laws.png diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 35e72e025172dc..f5d941937fc017 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -41,8 +41,8 @@ - type: InstantAction itemIconStyle: BigAction icon: - sprite: Interface/Misc/job_icons.rsi - state: Captain + sprite: Interface/Actions/actions_ai.rsi + state: ai_core event: !type:JumpToCoreEvent - type: entity @@ -67,8 +67,8 @@ - type: InstantAction itemIconStyle: BigAction icon: - sprite: Interface/Misc/job_icons.rsi - state: Captain + sprite: Interface/Actions/actions_ai.rsi + state: camera_light event: !type:RelayedActionComponentChangeEvent components: - type: LightOnCollideCollider @@ -141,18 +141,6 @@ - type: SiliconLawProvider laws: NTDefault -- type: entity - id: NTDefaultCircuitBoard - parent: BaseElectronics - name: circuit board (NT Default) - description: An electronics board containing the NT Default lawset. - components: - - type: Sprite - sprite: Objects/Misc/module.rsi - state: std_mod - - type: SiliconLawProvider - laws: NTDefault - # Items - type: entity id: Intellicard diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/ai_core.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/ai_core.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd3031f9fc0b41e613e0f27050665c312010aa9 GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvl>na**8>L*7#kY{c?^7#3{tub z-qRQi5*Tc&7@X@FGA}c%5MZe1t2(6v(#BX4#;SV2^%)?u2R}u zZ7%U<23zRFn_IIYqoS^@61^;-b;-&3azMzEwzjr~D?EI8xs?|v>GJxzMr29UMH}2{ zd>CZVu=>Od-oTB*@1l2nU#KO($P(Jf;k1IwXL*7#kY{dH?_aXW)}$ zkkVx^NMLZTXRxhe2wKXJd6}V}k70$t^CE96pd!YSAirP+hi5m^fSf&^E{-7@6O$7Z zcqCXSHZTM@1aPuUIk;q!LV&{wR!)s37ANP+LCy;qbRIkki<{Q8NNMrjJvyS1u2rtq zKD<7ztUkOE3al%bRG7jiO0fxLdN;2=F=r!>M$ooX(i{hvBHzxvd`bJ#G^LdVg@xYd zqnZquRYa$?7O6!QkoY=d#Wzp$PyH C>tw$G literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/crew_monitor.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/crew_monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..78fad17a76c58ceb3ff7bdc60107aaf01f477511 GIT binary patch literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv-2k5u*8>L*7#kY{c?^7#3{tub z^?VEl2@ER)7@X@Fin!ewG6EP%!x`!s%+eT);%<0z0EYlhP7Ws4mY#_XN*)JTIW?R@TH4yAq-QTZ7`mfv`t-IPJGLGS zoiT0tbav_4TNOj5OlxmnvUKaxr3$N0ym;}#qir>ZYq(%n8rv>iiFXG|R&Lz5bmPZW zM^9*_`4xVQYg%*Y%8efeU0mVf@*&1Pz5I*Y+E??>(p}cxn>#VIVoO=c7dywW!YdM? otQW*gj6bwlxw#~|s46lrY*UY|zO-x71(07oUHx3vIVCg!0LmC=oB#j- literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/actions_ai.rsi/manifest.png b/Resources/Textures/Interface/Actions/actions_ai.rsi/manifest.png new file mode 100644 index 0000000000000000000000000000000000000000..08514aa90829c59d8d3995ba7f1a5f0ad5c24bd2 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvtpJ}8*8>L*7#kY{c?^7#3{tub z1_=xaoDB7R49@io?Ys;H+zcxO7)&x4Y^x@eT{8nJV=M{s3ubV5b|VeQsq}Pl4AGdF zoS?uX!8);lA;2MklaqsqwWVibgObMqR!)r|U)PL`jEKrgtqDyl3f{hWkul$LGm}Sa z1FMf9)4>4IP|*ynu=Ou?R&Lw1NOn8d0?Fd@VL9s?Rv*}~boZ`r=jPsiYF)6}p}3v< m(8~}WekUyhB`#$xPDX~VR|VGL*7#kY{c?^7#3{tub z1_=!Hd<@R@47ODaD+Ih}3GV|+F_r}R1v5B2yO9RuG zc;9e29;_CzYIva3R9#VYJBwe{?Sk|FgNL6h9?Q9HwOhe5OHofKuv^$8MSH{1ed?$D zD;RuRdtb!u}jolMn|I`RMAi9i1D j9cS27zTB`Paq4Ot3*m6NwGaI|K(6+5^>bP0l+XkK Date: Fri, 23 Aug 2024 13:31:52 +1000 Subject: [PATCH 24/40] navmap work --- .../CrewMonitoringNavMapControl.cs | 10 +- Content.Client/Pinpointer/NavMapData.cs | 406 ++++++++++++++++++ Content.Client/Pinpointer/UI/NavMapControl.cs | 376 +--------------- .../PowerMonitoringConsoleNavMapControl.cs | 46 +- .../Silicons/StationAi/StationAiOverlay.cs | 43 +- 5 files changed, 471 insertions(+), 410 deletions(-) create mode 100644 Content.Client/Pinpointer/NavMapData.cs diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs index 340cc9af891c76..fb3b12122a07de 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs @@ -15,9 +15,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl public CrewMonitoringNavMapControl() : base() { - WallColor = new Color(192, 122, 196); - TileColor = new(71, 42, 72); - BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); + NavData.WallColor = Color.FromSrgb(new Color(192, 122, 196)); + NavData.TileColor = Color.FromSrgb(new(71, 42, 72)); + BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); _trackedEntityLabel = new Label { @@ -41,7 +41,7 @@ public CrewMonitoringNavMapControl() : base() }; _trackedEntityPanel.AddChild(_trackedEntityLabel); - this.AddChild(_trackedEntityPanel); + AddChild(_trackedEntityPanel); } protected override void FrameUpdate(FrameEventArgs args) @@ -56,7 +56,7 @@ protected override void FrameUpdate(FrameEventArgs args) return; } - foreach ((var netEntity, var blip) in TrackedEntities) + foreach (var (netEntity, blip) in TrackedEntities) { if (netEntity != Focus) continue; diff --git a/Content.Client/Pinpointer/NavMapData.cs b/Content.Client/Pinpointer/NavMapData.cs new file mode 100644 index 00000000000000..b3953eff30acdd --- /dev/null +++ b/Content.Client/Pinpointer/NavMapData.cs @@ -0,0 +1,406 @@ +using System.Numerics; +using Content.Shared.Atmos; +using Content.Shared.Pinpointer; +using Robust.Client.Graphics; +using Robust.Shared.Collections; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.Pinpointer; + +public sealed class NavMapData +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + public Entity Entity; + + // Default colors + public Color WallColor = Color.ToSrgb(new Color(102, 217, 102)); + public Color TileColor = new Color(65, 252, 3); + + /// + /// Offset for the data to be drawn at. + /// + public Vector2 Offset; + + public List<(Vector2, Vector2)> TileLines = new(); + public List<(Vector2, Vector2)> TileRects = new(); + + public Dictionary TilePolygons = new(); + + private Dictionary _horizLines = new(); + private Dictionary _horizLinesReversed = new(); + private Dictionary _vertLines = new(); + private Dictionary _vertLinesReversed = new(); + + protected float FullWallInstep = 0.165f; + protected float ThinWallThickness = 0.165f; + protected float ThinDoorThickness = 0.30f; + + // TODO: Power should be updating it on its own. + /// + /// Called if navmap updates + /// + public event Action? OnUpdate; + + // TODO: Subscribe to statechanges on navmapcomponent + + public NavMapData() + { + IoCManager.InjectDependencies(this); + } + + public void Draw(DrawingHandleBase handle, Func scale, Box2 localAABB) + { + var verts = new ValueList(TileLines.Count * 2); + var maps = _entManager.System(); + + // Draw floor tiles + if (TilePolygons.Count != 0) + { + verts.Clear(); + var tilesEnumerator = maps.GetLocalTilesEnumerator(Entity, Entity, localAABB); + + while (tilesEnumerator.MoveNext(out var tileRef)) + { + if (!TilePolygons.TryGetValue(tileRef.GridIndices, out var polys)) + continue; + + for (var i = 0; i < polys.Length; i++) + { + verts.Add(scale.Invoke(polys[i])); + } + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, verts.Span, TileColor); + } + + // Draw map lines + if (TileLines.Count != 0) + { + verts.Clear(); + + foreach (var (o, t) in TileLines) + { + var origin = scale.Invoke(o - Offset); + var terminus = scale.Invoke(t - Offset); + + verts.Add(origin); + verts.Add(terminus); + } + + if (verts.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts.Span, WallColor); + } + + // Draw map rects + if (TileRects.Count != 0) + { + var rects = new ValueList(TileRects.Count * 8); + + foreach (var (lt, rb) in TileRects) + { + var leftTop = scale.Invoke(lt - Offset); + var rightBottom = scale.Invoke(rb - Offset); + + var rightTop = new Vector2(rightBottom.X, leftTop.Y); + var leftBottom = new Vector2(leftTop.X, rightBottom.Y); + + rects.Add(leftTop); + rects.Add(rightTop); + rects.Add(rightTop); + rects.Add(rightBottom); + rects.Add(rightBottom); + rects.Add(leftBottom); + rects.Add(leftBottom); + rects.Add(leftTop); + } + + if (rects.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, WallColor); + } + } + + public void UpdateNavMap(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp)) + return; + + Entity = (entity.Owner, entity.Comp); + + // Clear stale values + TilePolygons.Clear(); + TileLines.Clear(); + TileRects.Clear(); + + UpdateNavMapFloorTiles(entity.Owner); + UpdateNavMapWallLines((entity.Owner, entity.Comp, null)); + UpdateNavMapAirlocks((entity.Owner, entity.Comp, null)); + } + + private void UpdateNavMapFloorTiles(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp)) + { + return; + } + + var lookup = _entManager.System(); + var tiles = _entManager.System().GetAllTilesEnumerator(entity.Owner, entity.Comp); + + while (tiles.MoveNext(out var tile)) + { + var box = lookup.GetLocalBounds(tile.Value.GridIndices, entity.Comp.TileSize).Enlarged(-0.45f); + box = new Box2(box.Left, -box.Bottom, box.Right, -box.Top); + var arr = new Vector2[6]; + + arr[0] = box.BottomLeft; + arr[1] = box.BottomRight; + arr[2] = box.TopLeft; + + arr[3] = box.BottomRight; + arr[4] = box.TopLeft; + arr[5] = box.TopRight; + + TilePolygons[tile.Value.GridIndices] = arr; + } + } + + private void UpdateNavMapWallLines(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || + !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) + { + return; + } + + // We'll use the following dictionaries to combine collinear wall lines + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); + + const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; + const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; + const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; + const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; + + foreach (var (chunkOrigin, chunk) in entity.Comp2.Chunks) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Wall; + + var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * entity.Comp1.TileSize; + + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinWall(tileData, tile); + continue; + } + + tile = tile with { Y = -tile.Y }; + NavMapChunk? neighborChunk; + + // North edge + var neighborData = 0; + if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+1]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) + neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; + + if ((neighborData & southMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), + tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), _horizLines, + _horizLinesReversed); + } + + // East edge + neighborData = 0; + if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) + neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; + + if ((neighborData & westMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), + tile + new Vector2i(entity.Comp1.TileSize, 0), _vertLines, _vertLinesReversed); + } + + // South edge + neighborData = 0; + if (relativeTile.Y != 0) + neighborData = chunk.TileData[i - 1]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) + neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; + + if ((neighborData & northMask) == 0) + { + AddOrUpdateNavMapLine(tile, tile + new Vector2i(entity.Comp1.TileSize, 0), _horizLines, + _horizLinesReversed); + } + + // West edge + neighborData = 0; + if (relativeTile.X != 0) + neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; + else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) + neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; + + if ((neighborData & eastMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), tile, _vertLines, + _vertLinesReversed); + } + + // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these + TileLines.Add((tile + new Vector2(0, -entity.Comp1.TileSize), tile + new Vector2(entity.Comp1.TileSize, 0))); + } + } + + // Record the combined lines + foreach (var (origin, terminal) in _horizLines) + { + TileLines.Add((origin, terminal)); + } + + foreach (var (origin, terminal) in _vertLines) + { + TileLines.Add((origin, terminal)); + } + } + + private void UpdateNavMapAirlocks(Entity entity) + { + if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || + !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) + { + return; + } + + foreach (var chunk in entity.Comp2.Chunks.Values) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Airlock; + + var relative = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * entity.Comp1.TileSize; + + // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinAirlock(tileData, tile); + continue; + } + + // Otherwise add a single full tile airlock + TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), + new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); + + TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), + new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); + } + } + } + + private void AddRectForThinWall(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); + var rightBottom = new Vector2(0.5f, 0.5f); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + + // TODO NAVMAP + // Consider using faster rotation operations, given that these are always 90 degree increments + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + } + } + + private void AddRectForThinAirlock(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); + var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); + var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); + var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); + } + } + + public void AddOrUpdateNavMapLine( + Vector2i origin, + Vector2i terminus, + Dictionary lookup, + Dictionary lookupReversed) + { + Vector2i foundTermius; + Vector2i foundOrigin; + + // Does our new line end at the beginning of an existing line? + if (lookup.Remove(terminus, out foundTermius)) + { + DebugTools.Assert(lookupReversed[foundTermius] == terminus); + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just connects two existing lines + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = foundTermius; + lookupReversed[foundTermius] = foundOrigin; + } + else + { + // Our new line precedes an existing line, extending it further to the left + lookup[origin] = foundTermius; + lookupReversed[foundTermius] = origin; + } + return; + } + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just extends an existing line further to the right + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = terminus; + lookupReversed[terminus] = foundOrigin; + return; + } + + // Completely disconnected line segment. + lookup.Add(origin, terminus); + lookupReversed.Add(terminus, origin); + } +} diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index 413b41c36a6f43..adbe9d724013d0 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -30,7 +30,6 @@ public partial class NavMapControl : MapGridControl { [Dependency] private IResourceCache _cache = default!; private readonly SharedTransformSystem _transformSystem; - private readonly SharedNavMapSystem _navMapSystem; public EntityUid? Owner; public EntityUid? MapUid; @@ -45,13 +44,7 @@ public partial class NavMapControl : MapGridControl public Dictionary TrackedCoordinates = new(); public Dictionary TrackedEntities = new(); - public List<(Vector2, Vector2)> TileLines = new(); - public List<(Vector2, Vector2)> TileRects = new(); - public List<(Vector2[], Color)> TilePolygons = new(); - - // Default colors - public Color WallColor = new(102, 217, 102); - public Color TileColor = new(30, 67, 30); + public NavMapData NavData = new(); // Constants protected float UpdateTime = 1.0f; @@ -61,22 +54,13 @@ public partial class NavMapControl : MapGridControl protected static float MaxDisplayedRange = 128f; protected static float DefaultDisplayedRange = 48f; protected float MinmapScaleModifier = 0.075f; - protected float FullWallInstep = 0.165f; - protected float ThinWallThickness = 0.165f; - protected float ThinDoorThickness = 0.30f; // Local variables private float _updateTimer = 1.0f; - private Dictionary _sRGBLookUp = new(); protected Color BackgroundColor; protected float BackgroundOpacity = 0.9f; private int _targetFontsize = 8; - private Dictionary _horizLines = new(); - private Dictionary _horizLinesReversed = new(); - private Dictionary _vertLines = new(); - private Dictionary _vertLinesReversed = new(); - // Components private NavMapComponent? _navMap; private MapGridComponent? _grid; @@ -117,9 +101,8 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl IoCManager.InjectDependencies(this); _transformSystem = EntManager.System(); - _navMapSystem = EntManager.System(); - BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); + BackgroundColor = Color.FromSrgb(NavData.TileColor.WithAlpha(BackgroundOpacity)); RectClipContent = true; HorizontalExpand = true; @@ -179,13 +162,12 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl public void ForceNavMapUpdate() { - EntManager.TryGetComponent(MapUid, out _navMap); - EntManager.TryGetComponent(MapUid, out _grid); - EntManager.TryGetComponent(MapUid, out _xform); - EntManager.TryGetComponent(MapUid, out _physics); - EntManager.TryGetComponent(MapUid, out _fixtures); + if (MapUid == null) + { + return; + } - UpdateNavMap(); + NavData.UpdateNavMap(MapUid.Value); } public void CenterToCoordinates(EntityCoordinates coordinates) @@ -228,7 +210,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { if (!blip.Selectable) continue; - + var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length(); if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance) @@ -293,80 +275,11 @@ protected override void Draw(DrawingHandleScreen handle) if (_physics != null) offset += _physics.LocalCenter; - var offsetVec = new Vector2(offset.X, -offset.Y); - - // Wall sRGB - if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB)) - { - wallsRGB = Color.ToSrgb(WallColor); - _sRGBLookUp[WallColor] = wallsRGB; - } - - // Draw floor tiles - if (TilePolygons.Any()) - { - Span verts = new Vector2[8]; - - foreach (var (polygonVerts, polygonColor) in TilePolygons) - { - for (var i = 0; i < polygonVerts.Length; i++) - { - var vert = polygonVerts[i] - offset; - verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y)); - } - - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor); - } - } - - // Draw map lines - if (TileLines.Any()) - { - var lines = new ValueList(TileLines.Count * 2); - - foreach (var (o, t) in TileLines) - { - var origin = ScalePosition(o - offsetVec); - var terminus = ScalePosition(t - offsetVec); - - lines.Add(origin); - lines.Add(terminus); - } - - if (lines.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB); - } - - // Draw map rects - if (TileRects.Any()) - { - var rects = new ValueList(TileRects.Count * 8); - - foreach (var (lt, rb) in TileRects) - { - var leftTop = ScalePosition(lt - offsetVec); - var rightBottom = ScalePosition(rb - offsetVec); - - var rightTop = new Vector2(rightBottom.X, leftTop.Y); - var leftBottom = new Vector2(leftTop.X, rightBottom.Y); - - rects.Add(leftTop); - rects.Add(rightTop); - rects.Add(rightTop); - rects.Add(rightBottom); - rects.Add(rightBottom); - rects.Add(leftBottom); - rects.Add(leftBottom); - rects.Add(leftTop); - } - - if (rects.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB); - } + NavData.Offset = new Vector2(offset.X, -offset.Y); + NavData.Draw(handle, ScalePosition, Box2.UnitCentered); // Invoke post wall drawing action - if (PostWallDrawingAction != null) - PostWallDrawingAction.Invoke(handle); + PostWallDrawingAction?.Invoke(handle); // Beacons if (_beacons.Pressed) @@ -436,279 +349,18 @@ protected override void Draw(DrawingHandleScreen handle) protected override void FrameUpdate(FrameEventArgs args) { // Update the timer + // TODO: Sub to state changes. _updateTimer += args.DeltaSeconds; if (_updateTimer >= UpdateTime) { _updateTimer -= UpdateTime; - UpdateNavMap(); - } - } - - protected virtual void UpdateNavMap() - { - // Clear stale values - TilePolygons.Clear(); - TileLines.Clear(); - TileRects.Clear(); - - UpdateNavMapFloorTiles(); - UpdateNavMapWallLines(); - UpdateNavMapAirlocks(); - } - - private void UpdateNavMapFloorTiles() - { - if (_fixtures == null) - return; - - var verts = new Vector2[8]; - - foreach (var fixture in _fixtures.Fixtures.Values) - { - if (fixture.Shape is not PolygonShape poly) - continue; - - for (var i = 0; i < poly.VertexCount; i++) - { - var vert = poly.Vertices[i]; - verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y)); - } - - TilePolygons.Add((verts[..poly.VertexCount], TileColor)); - } - } - - private void UpdateNavMapWallLines() - { - if (_navMap == null || _grid == null) - return; - - // We'll use the following dictionaries to combine collinear wall lines - _horizLines.Clear(); - _horizLinesReversed.Clear(); - _vertLines.Clear(); - _vertLinesReversed.Clear(); - - const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; - const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; - const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; - const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; - - foreach (var (chunkOrigin, chunk) in _navMap.Chunks) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Wall; - - var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; - - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinWall(tileData, tile); - continue; - } - - tile = tile with { Y = -tile.Y }; - NavMapChunk? neighborChunk; - - // North edge - var neighborData = 0; - if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i+1]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) - neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; - - if ((neighborData & southMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), - tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines, - _horizLinesReversed); - } - - // East edge - neighborData = 0; - if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) - neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; - - if ((neighborData & westMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), - tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed); - } - - // South edge - neighborData = 0; - if (relativeTile.Y != 0) - neighborData = chunk.TileData[i - 1]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) - neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; - - if ((neighborData & northMask) == 0) - { - AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines, - _horizLinesReversed); - } - - // West edge - neighborData = 0; - if (relativeTile.X != 0) - neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; - else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) - neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; - - if ((neighborData & eastMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines, - _vertLinesReversed); - } - - // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these - TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0))); - } - } - - // Record the combined lines - foreach (var (origin, terminal) in _horizLines) - { - TileLines.Add((origin, terminal)); - } - - foreach (var (origin, terminal) in _vertLines) - { - TileLines.Add((origin, terminal)); - } - } - - private void UpdateNavMapAirlocks() - { - if (_navMap == null || _grid == null) - return; - - foreach (var chunk in _navMap.Chunks.Values) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Airlock; - - var relative = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize; - - // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinAirlock(tileData, tile); - continue; - } - - // Otherwise add a single full tile airlock - TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), - new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); - - TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), - new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); - } + if (MapUid != null) + NavData.UpdateNavMap(MapUid.Value); } } - private void AddRectForThinWall(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); - var rightBottom = new Vector2(0.5f, 0.5f); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - - // TODO NAVMAP - // Consider using faster rotation operations, given that these are always 90 degree increments - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - } - } - - private void AddRectForThinAirlock(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); - var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); - var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); - var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); - } - } - - protected void AddOrUpdateNavMapLine( - Vector2i origin, - Vector2i terminus, - Dictionary lookup, - Dictionary lookupReversed) - { - Vector2i foundTermius; - Vector2i foundOrigin; - - // Does our new line end at the beginning of an existing line? - if (lookup.Remove(terminus, out foundTermius)) - { - DebugTools.Assert(lookupReversed[foundTermius] == terminus); - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just connects two existing lines - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = foundTermius; - lookupReversed[foundTermius] = foundOrigin; - } - else - { - // Our new line precedes an existing line, extending it further to the left - lookup[origin] = foundTermius; - lookupReversed[foundTermius] = origin; - } - return; - } - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just extends an existing line further to the right - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = terminus; - lookupReversed[terminus] = foundOrigin; - return; - } - - // Completely disconnected line segment. - lookup.Add(origin, terminus); - lookupReversed.Add(terminus, origin); - } - protected Vector2 GetOffset() { return Offset + (_physics?.LocalCenter ?? new Vector2()); diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs index d5057416cf84ed..dd29778819654a 100644 --- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs +++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs @@ -20,9 +20,7 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private readonly Color[] _powerCableColors = { Color.OrangeRed, Color.Yellow, Color.LimeGreen }; private readonly Vector2[] _powerCableOffsets = { new Vector2(-0.2f, -0.2f), Vector2.Zero, new Vector2(0.2f, 0.2f) }; - private Dictionary _sRGBLookUp = new Dictionary(); - public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks; public List HiddenLineGroups = new(); public List PowerCableNetwork = new(); public List FocusCableNetwork = new(); @@ -34,20 +32,19 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private MapGridComponent? _grid; - public PowerMonitoringConsoleNavMapControl() : base() + public PowerMonitoringConsoleNavMapControl() { // Set colors - TileColor = new Color(30, 57, 67); - WallColor = new Color(102, 164, 217); - BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); + NavData.TileColor = Color.FromSrgb(new Color(30, 57, 67)); + BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); PostWallDrawingAction += DrawAllCableNetworks; + + NavData.OnUpdate += UpdateNavMap; } - protected override void UpdateNavMap() + private void UpdateNavMap() { - base.UpdateNavMap(); - if (Owner == null) return; @@ -64,14 +61,14 @@ public void DrawAllCableNetworks(DrawingHandleScreen handle) return; // Draw full cable network - if (PowerCableNetwork != null && PowerCableNetwork.Count > 0) + if (PowerCableNetwork.Count > 0) { - var modulator = (FocusCableNetwork != null && FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; + var modulator = (FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; DrawCableNetwork(handle, PowerCableNetwork, modulator); } // Draw focus network - if (FocusCableNetwork != null && FocusCableNetwork.Count > 0) + if (FocusCableNetwork.Count > 0) DrawCableNetwork(handle, FocusCableNetwork, Color.White); } @@ -106,15 +103,8 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = _powerCableColors[cableNetworkIdx] * modulator; - - if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) - { - sRGB = Color.ToSrgb(color); - _sRGBLookUp[color] = sRGB; - } - - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, sRGB); + var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, color); } } } @@ -164,15 +154,9 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = _powerCableColors[cableNetworkIdx] * modulator; - - if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) - { - sRGB = Color.ToSrgb(color); - _sRGBLookUp[color] = sRGB; - } + var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, sRGB); + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, color); } } } @@ -236,7 +220,7 @@ public List GetDecodedPowerCableChunks(Dictionary GetDecodedPowerCableChunks(Dictionary().FrameTime.TotalSeconds; + + if (_accumulator <= 0f) + { + _accumulator += UpdateRate; + // TODO: I hate this shit + _data.UpdateNavMap((gridUid, grid)); - _visibleTiles.Clear(); - _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + // TODO: Pass in attached entity's grid. + // TODO: Credit OD on the moved to code + // TODO: Call the moved-to code here. - var gridMatrix = xforms.GetWorldMatrix(gridUid); + _visibleTiles.Clear(); + _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + } + + var (_, _, gridMatrix, gridInvMatrix) = xforms.GetWorldPositionRotationMatrixWithInv(gridUid); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); // Draw visible tiles to stencil @@ -77,16 +95,17 @@ protected override void Draw(in OverlayDrawArgs args) }, Color.Transparent); - // Once this is gucci optimise rendering. + // Create background texture. worldHandle.RenderInRenderTarget(_staticTexture!, () => { worldHandle.SetTransform(invMatrix); - var shader = _proto.Index("CameraStatic").Instance(); - worldHandle.UseShader(shader); - worldHandle.DrawRect(worldBounds, Color.White); - }, - Color.Black); + worldHandle.DrawRect(worldBounds, Color.Black); + worldHandle.SetTransform(matty); + var localAABB = gridInvMatrix.TransformBox(worldBounds); + + _data.Draw(worldHandle, vec => new Vector2(vec.X, -vec.Y), localAABB); + }, Color.Transparent); } // Not on a grid else From 8536cf4078ab6ebe934f8e629b8b28c09ccf2c73 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sat, 24 Aug 2024 12:33:06 +1000 Subject: [PATCH 25/40] Fixes --- Content.Shared/Light/EntitySystems/LightCollideSystem.cs | 4 +++- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs index 9fdcb8b62622b0..f09ae6824ea8ac 100644 --- a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs +++ b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs @@ -26,7 +26,9 @@ private void OnCollideShutdown(Entity ent, ref return; // Regenerate contacts for everything we were colliding with. - foreach (var contact in _physics.GetContacts(ent.Owner)) + var contacts = _physics.GetContacts(ent.Owner); + + while (contacts.MoveNext(out var contact)) { if (!contact.IsTouching) continue; diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index f5d941937fc017..4169ccdc1f9369 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -244,6 +244,8 @@ - type: Sprite # Once it's in a core it's pretty much an abstract entity at that point. visible: false + - type: BlockMovement + blockInteraction: false - type: SiliconLawProvider laws: Crewsimov - type: SiliconLawBound From b19ce4e9d39d7e4000db11cf01fce6c5fd637087 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:37:13 +1000 Subject: [PATCH 26/40] first impressions --- .../Lobby/UI/Loadouts/LoadoutWindow.xaml | 2 + .../Lobby/UI/Loadouts/LoadoutWindow.xaml.cs | 21 - .../20240818064956_LoadoutNames.Designer.cs | 1967 ----------------- .../Postgres/20240818064956_LoadoutNames.cs | 65 - .../PostgresServerDbContextModelSnapshot.cs | 9 - .../20240818065011_LoadoutNames.Designer.cs | 1890 ---------------- .../Sqlite/20240818065011_LoadoutNames.cs | 29 - .../SqliteServerDbContextModelSnapshot.cs | 7 - Content.Server.Database/Model.cs | 7 - .../Anomaly/AnomalySystem.Vessel.cs | 1 - .../Atmos/Monitor/Systems/FireAlarmSystem.cs | 1 - .../Botany/Systems/SeedExtractorSystem.cs | 1 - Content.Server/Cloning/CloningSystem.cs | 1 - .../DeviceNetworkRequiresPowerSystem.cs | 1 - .../Electrocution/ElectrocutionSystem.cs | 1 - .../Kitchen/EntitySystems/MicrowaveSystem.cs | 1 - .../Light/EntitySystems/LitOnPoweredSystem.cs | 1 - .../Light/EntitySystems/PoweredLightSystem.cs | 1 - .../Materials/MaterialReclaimerSystem.cs | 1 - .../Medical/MedicalScannerSystem.cs | 1 - .../Power/EntitySystems/ChargerSystem.cs | 5 +- .../Power/EntitySystems/PowerNetSystem.cs | 1 - .../Power/Generation/Teg/TegSystem.cs | 1 - .../Power/Generator/GasPowerReceiverSystem.cs | 1 - .../Research/Systems/ResearchSystem.Client.cs | 1 - .../Systems/ResearchSystem.Console.cs | 1 - .../Systems/ResearchSystem.PointSource.cs | 1 - .../Research/Systems/ResearchSystem.Server.cs | 1 - .../SingularityAttractorSystem.cs | 1 - .../Sound/SpamEmitSoundRequirePowerSystem.cs | 1 - Content.Server/Wires/BaseWireAction.cs | 1 - .../Systems/TraversalDistorterSystem.cs | 1 - .../XenoArtifacts/ArtifactSystem.cs | 1 - .../EntitySystems/SlimPoweredLightSystem.cs | 1 - .../Components/MovementSoundComponent.cs | 20 - .../Movement/Systems/MovementSoundSystem.cs | 44 - .../Systems/SharedMoverController.Input.cs | 40 +- .../Power/Components/PowerChangedEvent.cs | 8 - .../Preferences/HumanoidCharacterProfile.cs | 1 - .../Preferences/Loadouts/RoleLoadout.cs | 28 +- .../Loadouts/RoleLoadoutPrototype.cs | 6 - .../StationAi/IncorporealComponent.cs | 22 - .../Silicons/StationAi/IncorporealSystem.cs | 65 - .../StationAi/SharedStationAiSystem.cs | 2 +- .../Station/SharedStationSpawningSystem.cs | 5 - .../Entities/Mobs/Player/silicon.yml | 2 - .../Prototypes/Roles/Jobs/Science/borg.yml | 9 +- 47 files changed, 15 insertions(+), 4263 deletions(-) delete mode 100644 Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.Designer.cs delete mode 100644 Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs delete mode 100644 Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs delete mode 100644 Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs delete mode 100644 Content.Shared/Movement/Components/MovementSoundComponent.cs delete mode 100644 Content.Shared/Movement/Systems/MovementSoundSystem.cs delete mode 100644 Content.Shared/Power/Components/PowerChangedEvent.cs delete mode 100644 Content.Shared/Silicons/StationAi/IncorporealComponent.cs delete mode 100644 Content.Shared/Silicons/StationAi/IncorporealSystem.cs diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml index b0d28e3412f6e8..87d11005be8a98 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml @@ -5,6 +5,7 @@ SetSize="800 800" MinSize="800 128"> + diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs index 5bc1cf4351baa0..42a41847584147 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs @@ -26,27 +26,6 @@ public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, Role RobustXamlLoader.Load(this); Profile = profile; var protoManager = collection.Resolve(); - RoleNameEdit.IsValid = text => text.Length <= HumanoidCharacterProfile.MaxLoadoutNameLength; - - // Hide if we can't edit the name. - if (!proto.CanCustomiseName) - { - RoleNameBox.Visible = false; - } - else - { - var name = loadout.EntityName; - var random = collection.Resolve(); - - // Pick a random name if we use a dataset. - if (name != null && protoManager.TryIndex(proto.NameDataset, out var nameData)) - { - RoleNameEdit.PlaceHolder = random.Pick(nameData.Values); - } - - RoleNameEdit.Text = name ?? string.Empty; - RoleNameEdit.OnTextChanged += args => OnNameChanged?.Invoke(args.Text); - } // Hide if no groups if (proto.Groups.Count == 0) diff --git a/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.Designer.cs b/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.Designer.cs deleted file mode 100644 index dc14faad59452a..00000000000000 --- a/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.Designer.cs +++ /dev/null @@ -1,1967 +0,0 @@ -// -using System; -using System.Net; -using System.Text.Json; -using Content.Server.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using NpgsqlTypes; - -#nullable disable - -namespace Content.Server.Database.Migrations.Postgres -{ - [DbContext(typeof(PostgresServerDbContext))] - [Migration("20240818064956_LoadoutNames")] - partial class LoadoutNames - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Content.Server.Database.Admin", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("AdminRankId") - .HasColumnType("integer") - .HasColumnName("admin_rank_id"); - - b.Property("Title") - .HasColumnType("text") - .HasColumnName("title"); - - b.HasKey("UserId") - .HasName("PK_admin"); - - b.HasIndex("AdminRankId") - .HasDatabaseName("IX_admin_admin_rank_id"); - - b.ToTable("admin", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("admin_flag_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AdminId") - .HasColumnType("uuid") - .HasColumnName("admin_id"); - - b.Property("Flag") - .IsRequired() - .HasColumnType("text") - .HasColumnName("flag"); - - b.Property("Negative") - .HasColumnType("boolean") - .HasColumnName("negative"); - - b.HasKey("Id") - .HasName("PK_admin_flag"); - - b.HasIndex("AdminId") - .HasDatabaseName("IX_admin_flag_admin_id"); - - b.HasIndex("Flag", "AdminId") - .IsUnique(); - - b.ToTable("admin_flag", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLog", b => - { - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.Property("Id") - .HasColumnType("integer") - .HasColumnName("admin_log_id"); - - b.Property("Date") - .HasColumnType("timestamp with time zone") - .HasColumnName("date"); - - b.Property("Impact") - .HasColumnType("smallint") - .HasColumnName("impact"); - - b.Property("Json") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("json"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Type") - .HasColumnType("integer") - .HasColumnName("type"); - - b.HasKey("RoundId", "Id") - .HasName("PK_admin_log"); - - b.HasIndex("Date"); - - b.HasIndex("Message") - .HasAnnotation("Npgsql:TsVectorConfig", "english"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Message"), "GIN"); - - b.HasIndex("Type") - .HasDatabaseName("IX_admin_log_type"); - - b.ToTable("admin_log", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => - { - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.Property("LogId") - .HasColumnType("integer") - .HasColumnName("log_id"); - - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.HasKey("RoundId", "LogId", "PlayerUserId") - .HasName("PK_admin_log_player"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_log_player_player_user_id"); - - b.ToTable("admin_log_player", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminMessage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("admin_messages_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("CreatedById") - .HasColumnType("uuid") - .HasColumnName("created_by_id"); - - b.Property("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeletedById") - .HasColumnType("uuid") - .HasColumnName("deleted_by_id"); - - b.Property("Dismissed") - .HasColumnType("boolean") - .HasColumnName("dismissed"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("LastEditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("uuid") - .HasColumnName("last_edited_by_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("message"); - - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("interval") - .HasColumnName("playtime_at_note"); - - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.Property("Seen") - .HasColumnType("boolean") - .HasColumnName("seen"); - - b.HasKey("Id") - .HasName("PK_admin_messages"); - - b.HasIndex("CreatedById"); - - b.HasIndex("DeletedById"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_messages_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_admin_messages_round_id"); - - b.ToTable("admin_messages", null, t => - { - t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.AdminNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("admin_notes_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("CreatedById") - .HasColumnType("uuid") - .HasColumnName("created_by_id"); - - b.Property("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeletedById") - .HasColumnType("uuid") - .HasColumnName("deleted_by_id"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("LastEditedAt") - .IsRequired() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("uuid") - .HasColumnName("last_edited_by_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("message"); - - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("interval") - .HasColumnName("playtime_at_note"); - - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.Property("Secret") - .HasColumnType("boolean") - .HasColumnName("secret"); - - b.Property("Severity") - .HasColumnType("integer") - .HasColumnName("severity"); - - b.HasKey("Id") - .HasName("PK_admin_notes"); - - b.HasIndex("CreatedById"); - - b.HasIndex("DeletedById"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_notes_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_admin_notes_round_id"); - - b.ToTable("admin_notes", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("admin_rank_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.HasKey("Id") - .HasName("PK_admin_rank"); - - b.ToTable("admin_rank", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("admin_rank_flag_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AdminRankId") - .HasColumnType("integer") - .HasColumnName("admin_rank_id"); - - b.Property("Flag") - .IsRequired() - .HasColumnType("text") - .HasColumnName("flag"); - - b.HasKey("Id") - .HasName("PK_admin_rank_flag"); - - b.HasIndex("AdminRankId"); - - b.HasIndex("Flag", "AdminRankId") - .IsUnique(); - - b.ToTable("admin_rank_flag", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("admin_watchlists_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property("CreatedById") - .HasColumnType("uuid") - .HasColumnName("created_by_id"); - - b.Property("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeletedById") - .HasColumnType("uuid") - .HasColumnName("deleted_by_id"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("LastEditedAt") - .IsRequired() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("uuid") - .HasColumnName("last_edited_by_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("character varying(4096)") - .HasColumnName("message"); - - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("interval") - .HasColumnName("playtime_at_note"); - - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.HasKey("Id") - .HasName("PK_admin_watchlists"); - - b.HasIndex("CreatedById"); - - b.HasIndex("DeletedById"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_watchlists_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_admin_watchlists_round_id"); - - b.ToTable("admin_watchlists", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("antag_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AntagName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("antag_name"); - - b.Property("ProfileId") - .HasColumnType("integer") - .HasColumnName("profile_id"); - - b.HasKey("Id") - .HasName("PK_antag"); - - b.HasIndex("ProfileId", "AntagName") - .IsUnique(); - - b.ToTable("antag", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("assigned_user_id_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("UserName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("PK_assigned_user_id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.HasIndex("UserName") - .IsUnique(); - - b.ToTable("assigned_user_id", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.BanTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("ban_template_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AutoDelete") - .HasColumnType("boolean") - .HasColumnName("auto_delete"); - - b.Property("ExemptFlags") - .HasColumnType("integer") - .HasColumnName("exempt_flags"); - - b.Property("Hidden") - .HasColumnType("boolean") - .HasColumnName("hidden"); - - b.Property("Length") - .HasColumnType("interval") - .HasColumnName("length"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("Severity") - .HasColumnType("integer") - .HasColumnName("severity"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text") - .HasColumnName("title"); - - b.HasKey("Id") - .HasName("PK_ban_template"); - - b.ToTable("ban_template", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("connection_log_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasColumnType("inet") - .HasColumnName("address"); - - b.Property("Denied") - .HasColumnType("smallint") - .HasColumnName("denied"); - - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - - b.Property("ServerId") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0) - .HasColumnName("server_id"); - - b.Property("Time") - .HasColumnType("timestamp with time zone") - .HasColumnName("time"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("UserName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("PK_connection_log"); - - b.HasIndex("ServerId") - .HasDatabaseName("IX_connection_log_server_id"); - - b.HasIndex("Time"); - - b.HasIndex("UserId"); - - b.ToTable("connection_log", null, t => - { - t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("job_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("JobName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("job_name"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("ProfileId") - .HasColumnType("integer") - .HasColumnName("profile_id"); - - b.HasKey("Id") - .HasName("PK_job"); - - b.HasIndex("ProfileId"); - - b.HasIndex("ProfileId", "JobName") - .IsUnique(); - - b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") - .IsUnique() - .HasFilter("priority = 3"); - - b.ToTable("job", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.PlayTime", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("play_time_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PlayerId") - .HasColumnType("uuid") - .HasColumnName("player_id"); - - b.Property("TimeSpent") - .HasColumnType("interval") - .HasColumnName("time_spent"); - - b.Property("Tracker") - .IsRequired() - .HasColumnType("text") - .HasColumnName("tracker"); - - b.HasKey("Id") - .HasName("PK_play_time"); - - b.HasIndex("PlayerId", "Tracker") - .IsUnique(); - - b.ToTable("play_time", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Player", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("player_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FirstSeenTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("first_seen_time"); - - b.Property("LastReadRules") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_read_rules"); - - b.Property("LastSeenAddress") - .IsRequired() - .HasColumnType("inet") - .HasColumnName("last_seen_address"); - - b.Property("LastSeenHWId") - .HasColumnType("bytea") - .HasColumnName("last_seen_hwid"); - - b.Property("LastSeenTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_time"); - - b.Property("LastSeenUserName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("last_seen_user_name"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("PK_player"); - - b.HasAlternateKey("UserId") - .HasName("ak_player_user_id"); - - b.HasIndex("LastSeenUserName"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("player", null, t => - { - t.HasCheckConstraint("LastSeenAddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= last_seen_address"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.Preference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("preference_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AdminOOCColor") - .IsRequired() - .HasColumnType("text") - .HasColumnName("admin_ooc_color"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("integer") - .HasColumnName("selected_character_slot"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("PK_preference"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("preference", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("profile_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Age") - .HasColumnType("integer") - .HasColumnName("age"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("char_name"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("text") - .HasColumnName("eye_color"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("text") - .HasColumnName("facial_hair_color"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("facial_hair_name"); - - b.Property("FlavorText") - .IsRequired() - .HasColumnType("text") - .HasColumnName("flavor_text"); - - b.Property("Gender") - .IsRequired() - .HasColumnType("text") - .HasColumnName("gender"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("text") - .HasColumnName("hair_color"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("hair_name"); - - b.Property("Markings") - .HasColumnType("jsonb") - .HasColumnName("markings"); - - b.Property("PreferenceId") - .HasColumnType("integer") - .HasColumnName("preference_id"); - - b.Property("PreferenceUnavailable") - .HasColumnType("integer") - .HasColumnName("pref_unavailable"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("text") - .HasColumnName("sex"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("text") - .HasColumnName("skin_color"); - - b.Property("Slot") - .HasColumnType("integer") - .HasColumnName("slot"); - - b.Property("SpawnPriority") - .HasColumnType("integer") - .HasColumnName("spawn_priority"); - - b.Property("Species") - .IsRequired() - .HasColumnType("text") - .HasColumnName("species"); - - b.HasKey("Id") - .HasName("PK_profile"); - - b.HasIndex("PreferenceId") - .HasDatabaseName("IX_profile_preference_id"); - - b.HasIndex("Slot", "PreferenceId") - .IsUnique(); - - b.ToTable("profile", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("profile_loadout_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("LoadoutName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("loadout_name"); - - b.Property("ProfileLoadoutGroupId") - .HasColumnType("integer") - .HasColumnName("profile_loadout_group_id"); - - b.HasKey("Id") - .HasName("PK_profile_loadout"); - - b.HasIndex("ProfileLoadoutGroupId"); - - b.ToTable("profile_loadout", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("profile_loadout_group_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("group_name"); - - b.Property("ProfileRoleLoadoutId") - .HasColumnType("integer") - .HasColumnName("profile_role_loadout_id"); - - b.HasKey("Id") - .HasName("PK_profile_loadout_group"); - - b.HasIndex("ProfileRoleLoadoutId"); - - b.ToTable("profile_loadout_group", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("profile_role_loadout_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CustomName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("custom_name"); - - b.Property("ProfileId") - .HasColumnType("integer") - .HasColumnName("profile_id"); - - b.Property("RoleName") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("role_name"); - - b.HasKey("Id") - .HasName("PK_profile_role_loadout"); - - b.HasIndex("ProfileId"); - - b.ToTable("profile_role_loadout", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => - { - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.Property("RoleId") - .HasColumnType("text") - .HasColumnName("role_id"); - - b.HasKey("PlayerUserId", "RoleId") - .HasName("PK_role_whitelists"); - - b.ToTable("role_whitelists", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("round_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ServerId") - .HasColumnType("integer") - .HasColumnName("server_id"); - - b.Property("StartDate") - .HasColumnType("timestamp with time zone") - .HasColumnName("start_date"); - - b.HasKey("Id") - .HasName("PK_round"); - - b.HasIndex("ServerId") - .HasDatabaseName("IX_round_server_id"); - - b.HasIndex("StartDate"); - - b.ToTable("round", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("server_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.HasKey("Id") - .HasName("PK_server"); - - b.ToTable("server", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("server_ban_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .HasColumnType("inet") - .HasColumnName("address"); - - b.Property("AutoDelete") - .HasColumnType("boolean") - .HasColumnName("auto_delete"); - - b.Property("BanTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("ban_time"); - - b.Property("BanningAdmin") - .HasColumnType("uuid") - .HasColumnName("banning_admin"); - - b.Property("ExemptFlags") - .HasColumnType("integer") - .HasColumnName("exempt_flags"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - - b.Property("Hidden") - .HasColumnType("boolean") - .HasColumnName("hidden"); - - b.Property("LastEditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("uuid") - .HasColumnName("last_edited_by_id"); - - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("interval") - .HasColumnName("playtime_at_note"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.Property("Severity") - .HasColumnType("integer") - .HasColumnName("severity"); - - b.HasKey("Id") - .HasName("PK_server_ban"); - - b.HasIndex("Address"); - - b.HasIndex("BanningAdmin"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_server_ban_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_server_ban_round_id"); - - b.ToTable("server_ban", null, t => - { - t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); - - t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("Flags") - .HasColumnType("integer") - .HasColumnName("flags"); - - b.HasKey("UserId") - .HasName("PK_server_ban_exemption"); - - b.ToTable("server_ban_exemption", null, t => - { - t.HasCheckConstraint("FlagsNotZero", "flags != 0"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("server_ban_hit_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BanId") - .HasColumnType("integer") - .HasColumnName("ban_id"); - - b.Property("ConnectionId") - .HasColumnType("integer") - .HasColumnName("connection_id"); - - b.HasKey("Id") - .HasName("PK_server_ban_hit"); - - b.HasIndex("BanId") - .HasDatabaseName("IX_server_ban_hit_ban_id"); - - b.HasIndex("ConnectionId") - .HasDatabaseName("IX_server_ban_hit_connection_id"); - - b.ToTable("server_ban_hit", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("server_role_ban_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .HasColumnType("inet") - .HasColumnName("address"); - - b.Property("BanTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("ban_time"); - - b.Property("BanningAdmin") - .HasColumnType("uuid") - .HasColumnName("banning_admin"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("HWId") - .HasColumnType("bytea") - .HasColumnName("hwid"); - - b.Property("Hidden") - .HasColumnType("boolean") - .HasColumnName("hidden"); - - b.Property("LastEditedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("uuid") - .HasColumnName("last_edited_by_id"); - - b.Property("PlayerUserId") - .HasColumnType("uuid") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("interval") - .HasColumnName("playtime_at_note"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("role_id"); - - b.Property("RoundId") - .HasColumnType("integer") - .HasColumnName("round_id"); - - b.Property("Severity") - .HasColumnType("integer") - .HasColumnName("severity"); - - b.HasKey("Id") - .HasName("PK_server_role_ban"); - - b.HasIndex("Address"); - - b.HasIndex("BanningAdmin"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_server_role_ban_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_server_role_ban_round_id"); - - b.ToTable("server_role_ban", null, t => - { - t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address"); - - t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("role_unban_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BanId") - .HasColumnType("integer") - .HasColumnName("ban_id"); - - b.Property("UnbanTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("unban_time"); - - b.Property("UnbanningAdmin") - .HasColumnType("uuid") - .HasColumnName("unbanning_admin"); - - b.HasKey("Id") - .HasName("PK_server_role_unban"); - - b.HasIndex("BanId") - .IsUnique(); - - b.ToTable("server_role_unban", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ServerUnban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("unban_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BanId") - .HasColumnType("integer") - .HasColumnName("ban_id"); - - b.Property("UnbanTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("unban_time"); - - b.Property("UnbanningAdmin") - .HasColumnType("uuid") - .HasColumnName("unbanning_admin"); - - b.HasKey("Id") - .HasName("PK_server_unban"); - - b.HasIndex("BanId") - .IsUnique(); - - b.ToTable("server_unban", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Trait", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("trait_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ProfileId") - .HasColumnType("integer") - .HasColumnName("profile_id"); - - b.Property("TraitName") - .IsRequired() - .HasColumnType("text") - .HasColumnName("trait_name"); - - b.HasKey("Id") - .HasName("PK_trait"); - - b.HasIndex("ProfileId", "TraitName") - .IsUnique(); - - b.ToTable("trait", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("uploaded_resource_log_id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Data") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("data"); - - b.Property("Date") - .HasColumnType("timestamp with time zone") - .HasColumnName("date"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text") - .HasColumnName("path"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("PK_uploaded_resource_log"); - - b.ToTable("uploaded_resource_log", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Whitelist", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("UserId") - .HasName("PK_whitelist"); - - b.ToTable("whitelist", (string)null); - }); - - modelBuilder.Entity("PlayerRound", b => - { - b.Property("PlayersId") - .HasColumnType("integer") - .HasColumnName("players_id"); - - b.Property("RoundsId") - .HasColumnType("integer") - .HasColumnName("rounds_id"); - - b.HasKey("PlayersId", "RoundsId") - .HasName("PK_player_round"); - - b.HasIndex("RoundsId") - .HasDatabaseName("IX_player_round_rounds_id"); - - b.ToTable("player_round", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Admin", b => - { - b.HasOne("Content.Server.Database.AdminRank", "AdminRank") - .WithMany("Admins") - .HasForeignKey("AdminRankId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); - - b.Navigation("AdminRank"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminFlag", b => - { - b.HasOne("Content.Server.Database.Admin", "Admin") - .WithMany("Flags") - .HasForeignKey("AdminId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_flag_admin_admin_id"); - - b.Navigation("Admin"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLog", b => - { - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany("AdminLogs") - .HasForeignKey("RoundId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_log_round_round_id"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => - { - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminLogs") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_log_player_player_player_user_id"); - - b.HasOne("Content.Server.Database.AdminLog", "Log") - .WithMany("Players") - .HasForeignKey("RoundId", "LogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); - - b.Navigation("Log"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminMessage", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminMessagesCreated") - .HasForeignKey("CreatedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_messages_player_created_by_id"); - - b.HasOne("Content.Server.Database.Player", "DeletedBy") - .WithMany("AdminMessagesDeleted") - .HasForeignKey("DeletedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_messages_player_deleted_by_id"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminMessagesLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminMessagesReceived") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_admin_messages_player_player_user_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_admin_messages_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("DeletedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminNote", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminNotesCreated") - .HasForeignKey("CreatedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_notes_player_created_by_id"); - - b.HasOne("Content.Server.Database.Player", "DeletedBy") - .WithMany("AdminNotesDeleted") - .HasForeignKey("DeletedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_notes_player_deleted_by_id"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminNotesLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminNotesReceived") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_admin_notes_player_player_user_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_admin_notes_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("DeletedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => - { - b.HasOne("Content.Server.Database.AdminRank", "Rank") - .WithMany("Flags") - .HasForeignKey("AdminRankId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); - - b.Navigation("Rank"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminWatchlistsCreated") - .HasForeignKey("CreatedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_watchlists_player_created_by_id"); - - b.HasOne("Content.Server.Database.Player", "DeletedBy") - .WithMany("AdminWatchlistsDeleted") - .HasForeignKey("DeletedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminWatchlistsLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminWatchlistsReceived") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_admin_watchlists_player_player_user_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_admin_watchlists_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("DeletedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Antags") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_antag_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => - { - b.HasOne("Content.Server.Database.Server", "Server") - .WithMany("ConnectionLogs") - .HasForeignKey("ServerId") - .OnDelete(DeleteBehavior.SetNull) - .IsRequired() - .HasConstraintName("FK_connection_log_server_server_id"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_job_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Content.Server.Database.Profile", b => - { - b.HasOne("Content.Server.Database.Preference", "Preference") - .WithMany("Profiles") - .HasForeignKey("PreferenceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_preference_preference_id"); - - b.Navigation("Preference"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => - { - b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") - .WithMany("Loadouts") - .HasForeignKey("ProfileLoadoutGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group~"); - - b.Navigation("ProfileLoadoutGroup"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => - { - b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") - .WithMany("Groups") - .HasForeignKey("ProfileRoleLoadoutId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loa~"); - - b.Navigation("ProfileRoleLoadout"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Loadouts") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => - { - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("JobWhitelists") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_role_whitelists_player_player_user_id"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Content.Server.Database.Round", b => - { - b.HasOne("Content.Server.Database.Server", "Server") - .WithMany("Rounds") - .HasForeignKey("ServerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_round_server_server_id"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBan", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminServerBansCreated") - .HasForeignKey("BanningAdmin") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_ban_player_banning_admin"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminServerBansLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_ban_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_server_ban_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => - { - b.HasOne("Content.Server.Database.ServerBan", "Ban") - .WithMany("BanHits") - .HasForeignKey("BanId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); - - b.HasOne("Content.Server.Database.ConnectionLog", "Connection") - .WithMany("BanHits") - .HasForeignKey("ConnectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); - - b.Navigation("Ban"); - - b.Navigation("Connection"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminServerRoleBansCreated") - .HasForeignKey("BanningAdmin") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_role_ban_player_banning_admin"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminServerRoleBansLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_server_role_ban_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => - { - b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") - .WithOne("Unban") - .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); - - b.Navigation("Ban"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerUnban", b => - { - b.HasOne("Content.Server.Database.ServerBan", "Ban") - .WithOne("Unban") - .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_unban_server_ban_ban_id"); - - b.Navigation("Ban"); - }); - - modelBuilder.Entity("Content.Server.Database.Trait", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Traits") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_trait_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("PlayerRound", b => - { - b.HasOne("Content.Server.Database.Player", null) - .WithMany() - .HasForeignKey("PlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_player_round_player_players_id"); - - b.HasOne("Content.Server.Database.Round", null) - .WithMany() - .HasForeignKey("RoundsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_player_round_round_rounds_id"); - }); - - modelBuilder.Entity("Content.Server.Database.Admin", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLog", b => - { - b.Navigation("Players"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRank", b => - { - b.Navigation("Admins"); - - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => - { - b.Navigation("BanHits"); - }); - - modelBuilder.Entity("Content.Server.Database.Player", b => - { - b.Navigation("AdminLogs"); - - b.Navigation("AdminMessagesCreated"); - - b.Navigation("AdminMessagesDeleted"); - - b.Navigation("AdminMessagesLastEdited"); - - b.Navigation("AdminMessagesReceived"); - - b.Navigation("AdminNotesCreated"); - - b.Navigation("AdminNotesDeleted"); - - b.Navigation("AdminNotesLastEdited"); - - b.Navigation("AdminNotesReceived"); - - b.Navigation("AdminServerBansCreated"); - - b.Navigation("AdminServerBansLastEdited"); - - b.Navigation("AdminServerRoleBansCreated"); - - b.Navigation("AdminServerRoleBansLastEdited"); - - b.Navigation("AdminWatchlistsCreated"); - - b.Navigation("AdminWatchlistsDeleted"); - - b.Navigation("AdminWatchlistsLastEdited"); - - b.Navigation("AdminWatchlistsReceived"); - - b.Navigation("JobWhitelists"); - }); - - modelBuilder.Entity("Content.Server.Database.Preference", b => - { - b.Navigation("Profiles"); - }); - - modelBuilder.Entity("Content.Server.Database.Profile", b => - { - b.Navigation("Antags"); - - b.Navigation("Jobs"); - - b.Navigation("Loadouts"); - - b.Navigation("Traits"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => - { - b.Navigation("Loadouts"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => - { - b.Navigation("Groups"); - }); - - modelBuilder.Entity("Content.Server.Database.Round", b => - { - b.Navigation("AdminLogs"); - }); - - modelBuilder.Entity("Content.Server.Database.Server", b => - { - b.Navigation("ConnectionLogs"); - - b.Navigation("Rounds"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBan", b => - { - b.Navigation("BanHits"); - - b.Navigation("Unban"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => - { - b.Navigation("Unban"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs b/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs deleted file mode 100644 index e51efa9038c2d0..00000000000000 --- a/Content.Server.Database/Migrations/Postgres/20240818064956_LoadoutNames.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Content.Server.Database.Migrations.Postgres -{ - /// - public partial class LoadoutNames : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "role_name", - table: "profile_role_loadout", - type: "character varying(256)", - maxLength: 256, - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AddColumn( - name: "custom_name", - table: "profile_role_loadout", - type: "character varying(256)", - maxLength: 256, - nullable: true); - - migrationBuilder.AlterColumn( - name: "group_name", - table: "profile_loadout_group", - type: "character varying(256)", - maxLength: 256, - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "custom_name", - table: "profile_role_loadout"); - - migrationBuilder.AlterColumn( - name: "role_name", - table: "profile_role_loadout", - type: "text", - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(256)", - oldMaxLength: 256); - - migrationBuilder.AlterColumn( - name: "group_name", - table: "profile_loadout_group", - type: "text", - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(256)", - oldMaxLength: 256); - } - } -} diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index b855c0333f9ec7..723af17aa53065 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -906,8 +906,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GroupName") .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") .HasColumnName("group_name"); b.Property("ProfileRoleLoadoutId") @@ -931,19 +929,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("CustomName") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("custom_name"); - b.Property("ProfileId") .HasColumnType("integer") .HasColumnName("profile_id"); b.Property("RoleName") .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") .HasColumnName("role_name"); b.HasKey("Id") diff --git a/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs b/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs deleted file mode 100644 index 9242e6fa58a561..00000000000000 --- a/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.Designer.cs +++ /dev/null @@ -1,1890 +0,0 @@ -// -using System; -using Content.Server.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace Content.Server.Database.Migrations.Sqlite -{ - [DbContext(typeof(SqliteServerDbContext))] - [Migration("20240818065011_LoadoutNames")] - partial class LoadoutNames - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); - - modelBuilder.Entity("Content.Server.Database.Admin", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.Property("AdminRankId") - .HasColumnType("INTEGER") - .HasColumnName("admin_rank_id"); - - b.Property("Title") - .HasColumnType("TEXT") - .HasColumnName("title"); - - b.HasKey("UserId") - .HasName("PK_admin"); - - b.HasIndex("AdminRankId") - .HasDatabaseName("IX_admin_admin_rank_id"); - - b.ToTable("admin", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("admin_flag_id"); - - b.Property("AdminId") - .HasColumnType("TEXT") - .HasColumnName("admin_id"); - - b.Property("Flag") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("flag"); - - b.Property("Negative") - .HasColumnType("INTEGER") - .HasColumnName("negative"); - - b.HasKey("Id") - .HasName("PK_admin_flag"); - - b.HasIndex("AdminId") - .HasDatabaseName("IX_admin_flag_admin_id"); - - b.HasIndex("Flag", "AdminId") - .IsUnique(); - - b.ToTable("admin_flag", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLog", b => - { - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("Id") - .HasColumnType("INTEGER") - .HasColumnName("admin_log_id"); - - b.Property("Date") - .HasColumnType("TEXT") - .HasColumnName("date"); - - b.Property("Impact") - .HasColumnType("INTEGER") - .HasColumnName("impact"); - - b.Property("Json") - .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("json"); - - b.Property("Message") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("message"); - - b.Property("Type") - .HasColumnType("INTEGER") - .HasColumnName("type"); - - b.HasKey("RoundId", "Id") - .HasName("PK_admin_log"); - - b.HasIndex("Date"); - - b.HasIndex("Type") - .HasDatabaseName("IX_admin_log_type"); - - b.ToTable("admin_log", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => - { - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("LogId") - .HasColumnType("INTEGER") - .HasColumnName("log_id"); - - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.HasKey("RoundId", "LogId", "PlayerUserId") - .HasName("PK_admin_log_player"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_log_player_player_user_id"); - - b.ToTable("admin_log_player", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminMessage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("admin_messages_id"); - - b.Property("CreatedAt") - .HasColumnType("TEXT") - .HasColumnName("created_at"); - - b.Property("CreatedById") - .HasColumnType("TEXT") - .HasColumnName("created_by_id"); - - b.Property("Deleted") - .HasColumnType("INTEGER") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("TEXT") - .HasColumnName("deleted_at"); - - b.Property("DeletedById") - .HasColumnType("TEXT") - .HasColumnName("deleted_by_id"); - - b.Property("Dismissed") - .HasColumnType("INTEGER") - .HasColumnName("dismissed"); - - b.Property("ExpirationTime") - .HasColumnType("TEXT") - .HasColumnName("expiration_time"); - - b.Property("LastEditedAt") - .HasColumnType("TEXT") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("TEXT") - .HasColumnName("last_edited_by_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("TEXT") - .HasColumnName("message"); - - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("TEXT") - .HasColumnName("playtime_at_note"); - - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("Seen") - .HasColumnType("INTEGER") - .HasColumnName("seen"); - - b.HasKey("Id") - .HasName("PK_admin_messages"); - - b.HasIndex("CreatedById"); - - b.HasIndex("DeletedById"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_messages_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_admin_messages_round_id"); - - b.ToTable("admin_messages", null, t => - { - t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.AdminNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("admin_notes_id"); - - b.Property("CreatedAt") - .HasColumnType("TEXT") - .HasColumnName("created_at"); - - b.Property("CreatedById") - .HasColumnType("TEXT") - .HasColumnName("created_by_id"); - - b.Property("Deleted") - .HasColumnType("INTEGER") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("TEXT") - .HasColumnName("deleted_at"); - - b.Property("DeletedById") - .HasColumnType("TEXT") - .HasColumnName("deleted_by_id"); - - b.Property("ExpirationTime") - .HasColumnType("TEXT") - .HasColumnName("expiration_time"); - - b.Property("LastEditedAt") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("TEXT") - .HasColumnName("last_edited_by_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("TEXT") - .HasColumnName("message"); - - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("TEXT") - .HasColumnName("playtime_at_note"); - - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("Secret") - .HasColumnType("INTEGER") - .HasColumnName("secret"); - - b.Property("Severity") - .HasColumnType("INTEGER") - .HasColumnName("severity"); - - b.HasKey("Id") - .HasName("PK_admin_notes"); - - b.HasIndex("CreatedById"); - - b.HasIndex("DeletedById"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_notes_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_admin_notes_round_id"); - - b.ToTable("admin_notes", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("admin_rank_id"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("name"); - - b.HasKey("Id") - .HasName("PK_admin_rank"); - - b.ToTable("admin_rank", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("admin_rank_flag_id"); - - b.Property("AdminRankId") - .HasColumnType("INTEGER") - .HasColumnName("admin_rank_id"); - - b.Property("Flag") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("flag"); - - b.HasKey("Id") - .HasName("PK_admin_rank_flag"); - - b.HasIndex("AdminRankId"); - - b.HasIndex("Flag", "AdminRankId") - .IsUnique(); - - b.ToTable("admin_rank_flag", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("admin_watchlists_id"); - - b.Property("CreatedAt") - .HasColumnType("TEXT") - .HasColumnName("created_at"); - - b.Property("CreatedById") - .HasColumnType("TEXT") - .HasColumnName("created_by_id"); - - b.Property("Deleted") - .HasColumnType("INTEGER") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("TEXT") - .HasColumnName("deleted_at"); - - b.Property("DeletedById") - .HasColumnType("TEXT") - .HasColumnName("deleted_by_id"); - - b.Property("ExpirationTime") - .HasColumnType("TEXT") - .HasColumnName("expiration_time"); - - b.Property("LastEditedAt") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("TEXT") - .HasColumnName("last_edited_by_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(4096) - .HasColumnType("TEXT") - .HasColumnName("message"); - - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("TEXT") - .HasColumnName("playtime_at_note"); - - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.HasKey("Id") - .HasName("PK_admin_watchlists"); - - b.HasIndex("CreatedById"); - - b.HasIndex("DeletedById"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_admin_watchlists_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_admin_watchlists_round_id"); - - b.ToTable("admin_watchlists", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("antag_id"); - - b.Property("AntagName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("antag_name"); - - b.Property("ProfileId") - .HasColumnType("INTEGER") - .HasColumnName("profile_id"); - - b.HasKey("Id") - .HasName("PK_antag"); - - b.HasIndex("ProfileId", "AntagName") - .IsUnique(); - - b.ToTable("antag", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.AssignedUserId", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("assigned_user_id_id"); - - b.Property("UserId") - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.Property("UserName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("PK_assigned_user_id"); - - b.HasIndex("UserId") - .IsUnique(); - - b.HasIndex("UserName") - .IsUnique(); - - b.ToTable("assigned_user_id", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.BanTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("ban_template_id"); - - b.Property("AutoDelete") - .HasColumnType("INTEGER") - .HasColumnName("auto_delete"); - - b.Property("ExemptFlags") - .HasColumnType("INTEGER") - .HasColumnName("exempt_flags"); - - b.Property("Hidden") - .HasColumnType("INTEGER") - .HasColumnName("hidden"); - - b.Property("Length") - .HasColumnType("TEXT") - .HasColumnName("length"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("reason"); - - b.Property("Severity") - .HasColumnType("INTEGER") - .HasColumnName("severity"); - - b.Property("Title") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("title"); - - b.HasKey("Id") - .HasName("PK_ban_template"); - - b.ToTable("ban_template", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("connection_log_id"); - - b.Property("Address") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("address"); - - b.Property("Denied") - .HasColumnType("INTEGER") - .HasColumnName("denied"); - - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - - b.Property("ServerId") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(0) - .HasColumnName("server_id"); - - b.Property("Time") - .HasColumnType("TEXT") - .HasColumnName("time"); - - b.Property("UserId") - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.Property("UserName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("PK_connection_log"); - - b.HasIndex("ServerId") - .HasDatabaseName("IX_connection_log_server_id"); - - b.HasIndex("Time"); - - b.HasIndex("UserId"); - - b.ToTable("connection_log", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("job_id"); - - b.Property("JobName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("job_name"); - - b.Property("Priority") - .HasColumnType("INTEGER") - .HasColumnName("priority"); - - b.Property("ProfileId") - .HasColumnType("INTEGER") - .HasColumnName("profile_id"); - - b.HasKey("Id") - .HasName("PK_job"); - - b.HasIndex("ProfileId"); - - b.HasIndex("ProfileId", "JobName") - .IsUnique(); - - b.HasIndex(new[] { "ProfileId" }, "IX_job_one_high_priority") - .IsUnique() - .HasFilter("priority = 3"); - - b.ToTable("job", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.PlayTime", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("play_time_id"); - - b.Property("PlayerId") - .HasColumnType("TEXT") - .HasColumnName("player_id"); - - b.Property("TimeSpent") - .HasColumnType("TEXT") - .HasColumnName("time_spent"); - - b.Property("Tracker") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("tracker"); - - b.HasKey("Id") - .HasName("PK_play_time"); - - b.HasIndex("PlayerId", "Tracker") - .IsUnique(); - - b.ToTable("play_time", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Player", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("player_id"); - - b.Property("FirstSeenTime") - .HasColumnType("TEXT") - .HasColumnName("first_seen_time"); - - b.Property("LastReadRules") - .HasColumnType("TEXT") - .HasColumnName("last_read_rules"); - - b.Property("LastSeenAddress") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("last_seen_address"); - - b.Property("LastSeenHWId") - .HasColumnType("BLOB") - .HasColumnName("last_seen_hwid"); - - b.Property("LastSeenTime") - .HasColumnType("TEXT") - .HasColumnName("last_seen_time"); - - b.Property("LastSeenUserName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("last_seen_user_name"); - - b.Property("UserId") - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("PK_player"); - - b.HasAlternateKey("UserId") - .HasName("ak_player_user_id"); - - b.HasIndex("LastSeenUserName"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("player", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Preference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("preference_id"); - - b.Property("AdminOOCColor") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("admin_ooc_color"); - - b.Property("SelectedCharacterSlot") - .HasColumnType("INTEGER") - .HasColumnName("selected_character_slot"); - - b.Property("UserId") - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("PK_preference"); - - b.HasIndex("UserId") - .IsUnique(); - - b.ToTable("preference", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Profile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("profile_id"); - - b.Property("Age") - .HasColumnType("INTEGER") - .HasColumnName("age"); - - b.Property("CharacterName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("char_name"); - - b.Property("EyeColor") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("eye_color"); - - b.Property("FacialHairColor") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("facial_hair_color"); - - b.Property("FacialHairName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("facial_hair_name"); - - b.Property("FlavorText") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("flavor_text"); - - b.Property("Gender") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("gender"); - - b.Property("HairColor") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("hair_color"); - - b.Property("HairName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("hair_name"); - - b.Property("Markings") - .HasColumnType("jsonb") - .HasColumnName("markings"); - - b.Property("PreferenceId") - .HasColumnType("INTEGER") - .HasColumnName("preference_id"); - - b.Property("PreferenceUnavailable") - .HasColumnType("INTEGER") - .HasColumnName("pref_unavailable"); - - b.Property("Sex") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("sex"); - - b.Property("SkinColor") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("skin_color"); - - b.Property("Slot") - .HasColumnType("INTEGER") - .HasColumnName("slot"); - - b.Property("SpawnPriority") - .HasColumnType("INTEGER") - .HasColumnName("spawn_priority"); - - b.Property("Species") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("species"); - - b.HasKey("Id") - .HasName("PK_profile"); - - b.HasIndex("PreferenceId") - .HasDatabaseName("IX_profile_preference_id"); - - b.HasIndex("Slot", "PreferenceId") - .IsUnique(); - - b.ToTable("profile", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("profile_loadout_id"); - - b.Property("LoadoutName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("loadout_name"); - - b.Property("ProfileLoadoutGroupId") - .HasColumnType("INTEGER") - .HasColumnName("profile_loadout_group_id"); - - b.HasKey("Id") - .HasName("PK_profile_loadout"); - - b.HasIndex("ProfileLoadoutGroupId"); - - b.ToTable("profile_loadout", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("profile_loadout_group_id"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT") - .HasColumnName("group_name"); - - b.Property("ProfileRoleLoadoutId") - .HasColumnType("INTEGER") - .HasColumnName("profile_role_loadout_id"); - - b.HasKey("Id") - .HasName("PK_profile_loadout_group"); - - b.HasIndex("ProfileRoleLoadoutId"); - - b.ToTable("profile_loadout_group", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("profile_role_loadout_id"); - - b.Property("CustomName") - .HasMaxLength(256) - .HasColumnType("TEXT") - .HasColumnName("custom_name"); - - b.Property("ProfileId") - .HasColumnType("INTEGER") - .HasColumnName("profile_id"); - - b.Property("RoleName") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT") - .HasColumnName("role_name"); - - b.HasKey("Id") - .HasName("PK_profile_role_loadout"); - - b.HasIndex("ProfileId"); - - b.ToTable("profile_role_loadout", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => - { - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.Property("RoleId") - .HasColumnType("TEXT") - .HasColumnName("role_id"); - - b.HasKey("PlayerUserId", "RoleId") - .HasName("PK_role_whitelists"); - - b.ToTable("role_whitelists", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Round", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("ServerId") - .HasColumnType("INTEGER") - .HasColumnName("server_id"); - - b.Property("StartDate") - .HasColumnType("TEXT") - .HasColumnName("start_date"); - - b.HasKey("Id") - .HasName("PK_round"); - - b.HasIndex("ServerId") - .HasDatabaseName("IX_round_server_id"); - - b.HasIndex("StartDate"); - - b.ToTable("round", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Server", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("server_id"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("name"); - - b.HasKey("Id") - .HasName("PK_server"); - - b.ToTable("server", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("server_ban_id"); - - b.Property("Address") - .HasColumnType("TEXT") - .HasColumnName("address"); - - b.Property("AutoDelete") - .HasColumnType("INTEGER") - .HasColumnName("auto_delete"); - - b.Property("BanTime") - .HasColumnType("TEXT") - .HasColumnName("ban_time"); - - b.Property("BanningAdmin") - .HasColumnType("TEXT") - .HasColumnName("banning_admin"); - - b.Property("ExemptFlags") - .HasColumnType("INTEGER") - .HasColumnName("exempt_flags"); - - b.Property("ExpirationTime") - .HasColumnType("TEXT") - .HasColumnName("expiration_time"); - - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - - b.Property("Hidden") - .HasColumnType("INTEGER") - .HasColumnName("hidden"); - - b.Property("LastEditedAt") - .HasColumnType("TEXT") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("TEXT") - .HasColumnName("last_edited_by_id"); - - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("TEXT") - .HasColumnName("playtime_at_note"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("reason"); - - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("Severity") - .HasColumnType("INTEGER") - .HasColumnName("severity"); - - b.HasKey("Id") - .HasName("PK_server_ban"); - - b.HasIndex("Address"); - - b.HasIndex("BanningAdmin"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_server_ban_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_server_ban_round_id"); - - b.ToTable("server_ban", null, t => - { - t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBanExemption", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.Property("Flags") - .HasColumnType("INTEGER") - .HasColumnName("flags"); - - b.HasKey("UserId") - .HasName("PK_server_ban_exemption"); - - b.ToTable("server_ban_exemption", null, t => - { - t.HasCheckConstraint("FlagsNotZero", "flags != 0"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("server_ban_hit_id"); - - b.Property("BanId") - .HasColumnType("INTEGER") - .HasColumnName("ban_id"); - - b.Property("ConnectionId") - .HasColumnType("INTEGER") - .HasColumnName("connection_id"); - - b.HasKey("Id") - .HasName("PK_server_ban_hit"); - - b.HasIndex("BanId") - .HasDatabaseName("IX_server_ban_hit_ban_id"); - - b.HasIndex("ConnectionId") - .HasDatabaseName("IX_server_ban_hit_connection_id"); - - b.ToTable("server_ban_hit", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("server_role_ban_id"); - - b.Property("Address") - .HasColumnType("TEXT") - .HasColumnName("address"); - - b.Property("BanTime") - .HasColumnType("TEXT") - .HasColumnName("ban_time"); - - b.Property("BanningAdmin") - .HasColumnType("TEXT") - .HasColumnName("banning_admin"); - - b.Property("ExpirationTime") - .HasColumnType("TEXT") - .HasColumnName("expiration_time"); - - b.Property("HWId") - .HasColumnType("BLOB") - .HasColumnName("hwid"); - - b.Property("Hidden") - .HasColumnType("INTEGER") - .HasColumnName("hidden"); - - b.Property("LastEditedAt") - .HasColumnType("TEXT") - .HasColumnName("last_edited_at"); - - b.Property("LastEditedById") - .HasColumnType("TEXT") - .HasColumnName("last_edited_by_id"); - - b.Property("PlayerUserId") - .HasColumnType("TEXT") - .HasColumnName("player_user_id"); - - b.Property("PlaytimeAtNote") - .HasColumnType("TEXT") - .HasColumnName("playtime_at_note"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("reason"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("role_id"); - - b.Property("RoundId") - .HasColumnType("INTEGER") - .HasColumnName("round_id"); - - b.Property("Severity") - .HasColumnType("INTEGER") - .HasColumnName("severity"); - - b.HasKey("Id") - .HasName("PK_server_role_ban"); - - b.HasIndex("Address"); - - b.HasIndex("BanningAdmin"); - - b.HasIndex("LastEditedById"); - - b.HasIndex("PlayerUserId") - .HasDatabaseName("IX_server_role_ban_player_user_id"); - - b.HasIndex("RoundId") - .HasDatabaseName("IX_server_role_ban_round_id"); - - b.ToTable("server_role_ban", null, t => - { - t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"); - }); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("role_unban_id"); - - b.Property("BanId") - .HasColumnType("INTEGER") - .HasColumnName("ban_id"); - - b.Property("UnbanTime") - .HasColumnType("TEXT") - .HasColumnName("unban_time"); - - b.Property("UnbanningAdmin") - .HasColumnType("TEXT") - .HasColumnName("unbanning_admin"); - - b.HasKey("Id") - .HasName("PK_server_role_unban"); - - b.HasIndex("BanId") - .IsUnique(); - - b.ToTable("server_role_unban", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.ServerUnban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("unban_id"); - - b.Property("BanId") - .HasColumnType("INTEGER") - .HasColumnName("ban_id"); - - b.Property("UnbanTime") - .HasColumnType("TEXT") - .HasColumnName("unban_time"); - - b.Property("UnbanningAdmin") - .HasColumnType("TEXT") - .HasColumnName("unbanning_admin"); - - b.HasKey("Id") - .HasName("PK_server_unban"); - - b.HasIndex("BanId") - .IsUnique(); - - b.ToTable("server_unban", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Trait", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("trait_id"); - - b.Property("ProfileId") - .HasColumnType("INTEGER") - .HasColumnName("profile_id"); - - b.Property("TraitName") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("trait_name"); - - b.HasKey("Id") - .HasName("PK_trait"); - - b.HasIndex("ProfileId", "TraitName") - .IsUnique(); - - b.ToTable("trait", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.UploadedResourceLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasColumnName("uploaded_resource_log_id"); - - b.Property("Data") - .IsRequired() - .HasColumnType("BLOB") - .HasColumnName("data"); - - b.Property("Date") - .HasColumnType("TEXT") - .HasColumnName("date"); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasColumnName("path"); - - b.Property("UserId") - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("PK_uploaded_resource_log"); - - b.ToTable("uploaded_resource_log", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Whitelist", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasColumnName("user_id"); - - b.HasKey("UserId") - .HasName("PK_whitelist"); - - b.ToTable("whitelist", (string)null); - }); - - modelBuilder.Entity("PlayerRound", b => - { - b.Property("PlayersId") - .HasColumnType("INTEGER") - .HasColumnName("players_id"); - - b.Property("RoundsId") - .HasColumnType("INTEGER") - .HasColumnName("rounds_id"); - - b.HasKey("PlayersId", "RoundsId") - .HasName("PK_player_round"); - - b.HasIndex("RoundsId") - .HasDatabaseName("IX_player_round_rounds_id"); - - b.ToTable("player_round", (string)null); - }); - - modelBuilder.Entity("Content.Server.Database.Admin", b => - { - b.HasOne("Content.Server.Database.AdminRank", "AdminRank") - .WithMany("Admins") - .HasForeignKey("AdminRankId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_admin_rank_admin_rank_id"); - - b.Navigation("AdminRank"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminFlag", b => - { - b.HasOne("Content.Server.Database.Admin", "Admin") - .WithMany("Flags") - .HasForeignKey("AdminId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_flag_admin_admin_id"); - - b.Navigation("Admin"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLog", b => - { - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany("AdminLogs") - .HasForeignKey("RoundId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_log_round_round_id"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLogPlayer", b => - { - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminLogs") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_log_player_player_player_user_id"); - - b.HasOne("Content.Server.Database.AdminLog", "Log") - .WithMany("Players") - .HasForeignKey("RoundId", "LogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_log_player_admin_log_round_id_log_id"); - - b.Navigation("Log"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminMessage", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminMessagesCreated") - .HasForeignKey("CreatedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_messages_player_created_by_id"); - - b.HasOne("Content.Server.Database.Player", "DeletedBy") - .WithMany("AdminMessagesDeleted") - .HasForeignKey("DeletedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_messages_player_deleted_by_id"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminMessagesLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_messages_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminMessagesReceived") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_admin_messages_player_player_user_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_admin_messages_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("DeletedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminNote", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminNotesCreated") - .HasForeignKey("CreatedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_notes_player_created_by_id"); - - b.HasOne("Content.Server.Database.Player", "DeletedBy") - .WithMany("AdminNotesDeleted") - .HasForeignKey("DeletedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_notes_player_deleted_by_id"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminNotesLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_notes_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminNotesReceived") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_admin_notes_player_player_user_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_admin_notes_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("DeletedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b => - { - b.HasOne("Content.Server.Database.AdminRank", "Rank") - .WithMany("Flags") - .HasForeignKey("AdminRankId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_admin_rank_flag_admin_rank_admin_rank_id"); - - b.Navigation("Rank"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminWatchlistsCreated") - .HasForeignKey("CreatedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_watchlists_player_created_by_id"); - - b.HasOne("Content.Server.Database.Player", "DeletedBy") - .WithMany("AdminWatchlistsDeleted") - .HasForeignKey("DeletedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_watchlists_player_deleted_by_id"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminWatchlistsLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_admin_watchlists_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("AdminWatchlistsReceived") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("FK_admin_watchlists_player_player_user_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_admin_watchlists_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("DeletedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Player"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.Antag", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Antags") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_antag_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => - { - b.HasOne("Content.Server.Database.Server", "Server") - .WithMany("ConnectionLogs") - .HasForeignKey("ServerId") - .OnDelete(DeleteBehavior.SetNull) - .IsRequired() - .HasConstraintName("FK_connection_log_server_server_id"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Content.Server.Database.Job", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Jobs") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_job_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Content.Server.Database.Profile", b => - { - b.HasOne("Content.Server.Database.Preference", "Preference") - .WithMany("Profiles") - .HasForeignKey("PreferenceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_preference_preference_id"); - - b.Navigation("Preference"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadout", b => - { - b.HasOne("Content.Server.Database.ProfileLoadoutGroup", "ProfileLoadoutGroup") - .WithMany("Loadouts") - .HasForeignKey("ProfileLoadoutGroupId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_loadout_profile_loadout_group_profile_loadout_group_id"); - - b.Navigation("ProfileLoadoutGroup"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => - { - b.HasOne("Content.Server.Database.ProfileRoleLoadout", "ProfileRoleLoadout") - .WithMany("Groups") - .HasForeignKey("ProfileRoleLoadoutId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_loadout_group_profile_role_loadout_profile_role_loadout_id"); - - b.Navigation("ProfileRoleLoadout"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Loadouts") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_profile_role_loadout_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Content.Server.Database.RoleWhitelist", b => - { - b.HasOne("Content.Server.Database.Player", "Player") - .WithMany("JobWhitelists") - .HasForeignKey("PlayerUserId") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_role_whitelists_player_player_user_id"); - - b.Navigation("Player"); - }); - - modelBuilder.Entity("Content.Server.Database.Round", b => - { - b.HasOne("Content.Server.Database.Server", "Server") - .WithMany("Rounds") - .HasForeignKey("ServerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_round_server_server_id"); - - b.Navigation("Server"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBan", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminServerBansCreated") - .HasForeignKey("BanningAdmin") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_ban_player_banning_admin"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminServerBansLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_ban_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_server_ban_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBanHit", b => - { - b.HasOne("Content.Server.Database.ServerBan", "Ban") - .WithMany("BanHits") - .HasForeignKey("BanId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_ban_hit_server_ban_ban_id"); - - b.HasOne("Content.Server.Database.ConnectionLog", "Connection") - .WithMany("BanHits") - .HasForeignKey("ConnectionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_ban_hit_connection_log_connection_id"); - - b.Navigation("Ban"); - - b.Navigation("Connection"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => - { - b.HasOne("Content.Server.Database.Player", "CreatedBy") - .WithMany("AdminServerRoleBansCreated") - .HasForeignKey("BanningAdmin") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_role_ban_player_banning_admin"); - - b.HasOne("Content.Server.Database.Player", "LastEditedBy") - .WithMany("AdminServerRoleBansLastEdited") - .HasForeignKey("LastEditedById") - .HasPrincipalKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("FK_server_role_ban_player_last_edited_by_id"); - - b.HasOne("Content.Server.Database.Round", "Round") - .WithMany() - .HasForeignKey("RoundId") - .HasConstraintName("FK_server_role_ban_round_round_id"); - - b.Navigation("CreatedBy"); - - b.Navigation("LastEditedBy"); - - b.Navigation("Round"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b => - { - b.HasOne("Content.Server.Database.ServerRoleBan", "Ban") - .WithOne("Unban") - .HasForeignKey("Content.Server.Database.ServerRoleUnban", "BanId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_role_unban_server_role_ban_ban_id"); - - b.Navigation("Ban"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerUnban", b => - { - b.HasOne("Content.Server.Database.ServerBan", "Ban") - .WithOne("Unban") - .HasForeignKey("Content.Server.Database.ServerUnban", "BanId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_server_unban_server_ban_ban_id"); - - b.Navigation("Ban"); - }); - - modelBuilder.Entity("Content.Server.Database.Trait", b => - { - b.HasOne("Content.Server.Database.Profile", "Profile") - .WithMany("Traits") - .HasForeignKey("ProfileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_trait_profile_profile_id"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("PlayerRound", b => - { - b.HasOne("Content.Server.Database.Player", null) - .WithMany() - .HasForeignKey("PlayersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_player_round_player_players_id"); - - b.HasOne("Content.Server.Database.Round", null) - .WithMany() - .HasForeignKey("RoundsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("FK_player_round_round_rounds_id"); - }); - - modelBuilder.Entity("Content.Server.Database.Admin", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminLog", b => - { - b.Navigation("Players"); - }); - - modelBuilder.Entity("Content.Server.Database.AdminRank", b => - { - b.Navigation("Admins"); - - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Content.Server.Database.ConnectionLog", b => - { - b.Navigation("BanHits"); - }); - - modelBuilder.Entity("Content.Server.Database.Player", b => - { - b.Navigation("AdminLogs"); - - b.Navigation("AdminMessagesCreated"); - - b.Navigation("AdminMessagesDeleted"); - - b.Navigation("AdminMessagesLastEdited"); - - b.Navigation("AdminMessagesReceived"); - - b.Navigation("AdminNotesCreated"); - - b.Navigation("AdminNotesDeleted"); - - b.Navigation("AdminNotesLastEdited"); - - b.Navigation("AdminNotesReceived"); - - b.Navigation("AdminServerBansCreated"); - - b.Navigation("AdminServerBansLastEdited"); - - b.Navigation("AdminServerRoleBansCreated"); - - b.Navigation("AdminServerRoleBansLastEdited"); - - b.Navigation("AdminWatchlistsCreated"); - - b.Navigation("AdminWatchlistsDeleted"); - - b.Navigation("AdminWatchlistsLastEdited"); - - b.Navigation("AdminWatchlistsReceived"); - - b.Navigation("JobWhitelists"); - }); - - modelBuilder.Entity("Content.Server.Database.Preference", b => - { - b.Navigation("Profiles"); - }); - - modelBuilder.Entity("Content.Server.Database.Profile", b => - { - b.Navigation("Antags"); - - b.Navigation("Jobs"); - - b.Navigation("Loadouts"); - - b.Navigation("Traits"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileLoadoutGroup", b => - { - b.Navigation("Loadouts"); - }); - - modelBuilder.Entity("Content.Server.Database.ProfileRoleLoadout", b => - { - b.Navigation("Groups"); - }); - - modelBuilder.Entity("Content.Server.Database.Round", b => - { - b.Navigation("AdminLogs"); - }); - - modelBuilder.Entity("Content.Server.Database.Server", b => - { - b.Navigation("ConnectionLogs"); - - b.Navigation("Rounds"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerBan", b => - { - b.Navigation("BanHits"); - - b.Navigation("Unban"); - }); - - modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b => - { - b.Navigation("Unban"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs b/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs deleted file mode 100644 index 1bce48c96c3dc1..00000000000000 --- a/Content.Server.Database/Migrations/Sqlite/20240818065011_LoadoutNames.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Content.Server.Database.Migrations.Sqlite -{ - /// - public partial class LoadoutNames : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "custom_name", - table: "profile_role_loadout", - type: "TEXT", - maxLength: 256, - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "custom_name", - table: "profile_role_loadout"); - } - } -} diff --git a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs index acf50a31978f16..efc66821911153 100644 --- a/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs @@ -853,7 +853,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GroupName") .IsRequired() - .HasMaxLength(256) .HasColumnType("TEXT") .HasColumnName("group_name"); @@ -876,18 +875,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER") .HasColumnName("profile_role_loadout_id"); - b.Property("CustomName") - .HasMaxLength(256) - .HasColumnType("TEXT") - .HasColumnName("custom_name"); - b.Property("ProfileId") .HasColumnType("INTEGER") .HasColumnName("profile_id"); b.Property("RoleName") .IsRequired() - .HasMaxLength(256) .HasColumnType("TEXT") .HasColumnName("role_name"); diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 5cdae60168b40a..79bbff2028697b 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -430,16 +430,9 @@ public class ProfileRoleLoadout public Profile Profile { get; set; } = null!; - /// - /// If the loadout supports custom naming what is it. - /// - [StringLength(256)] - public string? CustomName { get; set; } = null; - /// /// The corresponding role prototype on the profile. /// - [StringLength(256)] public string RoleName { get; set; } = string.Empty; /// diff --git a/Content.Server/Anomaly/AnomalySystem.Vessel.cs b/Content.Server/Anomaly/AnomalySystem.Vessel.cs index 0185328b352a8e..98e56a884453d9 100644 --- a/Content.Server/Anomaly/AnomalySystem.Vessel.cs +++ b/Content.Server/Anomaly/AnomalySystem.Vessel.cs @@ -4,7 +4,6 @@ using Content.Shared.Anomaly.Components; using Content.Shared.Examine; using Content.Shared.Interaction; -using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Anomaly; diff --git a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs index c9254bfa938e5b..0a3ee4d7f7dc76 100644 --- a/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/FireAlarmSystem.cs @@ -12,7 +12,6 @@ using Content.Shared.DeviceNetwork.Systems; using Content.Shared.Interaction; using Content.Shared.Emag.Systems; -using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.Configuration; diff --git a/Content.Server/Botany/Systems/SeedExtractorSystem.cs b/Content.Server/Botany/Systems/SeedExtractorSystem.cs index 42ea0a45229fc5..4a0d56bfe98133 100644 --- a/Content.Server/Botany/Systems/SeedExtractorSystem.cs +++ b/Content.Server/Botany/Systems/SeedExtractorSystem.cs @@ -3,7 +3,6 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Popups; -using Content.Shared.Power.EntitySystems; using Robust.Shared.Random; namespace Content.Server.Botany.Systems; diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index fcdb1d5b3231a7..3893f31d25d755 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -23,7 +23,6 @@ using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Power.EntitySystems; using Content.Shared.Roles.Jobs; using Robust.Server.Containers; using Robust.Server.GameObjects; diff --git a/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs index f47a5df8ac401d..6e7bd255c5dc4c 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceNetworkRequiresPowerSystem.cs @@ -1,7 +1,6 @@ using Content.Server.DeviceNetwork.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; -using Content.Shared.Power.EntitySystems; namespace Content.Server.DeviceNetwork.Systems; diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index b3df21695ac6b4..67e60c9de4660c 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -18,7 +18,6 @@ using Content.Shared.Jittering; using Content.Shared.Maps; using Content.Shared.Popups; -using Content.Shared.Power.EntitySystems; using Content.Shared.Speech.EntitySystems; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; diff --git a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs index 42e1da034c22d1..c05c679f176880 100644 --- a/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/MicrowaveSystem.cs @@ -41,7 +41,6 @@ using Content.Server.Construction.Components; using Content.Shared.Chat; using Content.Shared.Damage; -using Content.Shared.Power.Components; namespace Content.Server.Kitchen.EntitySystems { diff --git a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs index 3c5f7eaecb2d10..5c66d65b573594 100644 --- a/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs +++ b/Content.Server/Light/EntitySystems/LitOnPoweredSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Power; -using Content.Shared.Power.Components; namespace Content.Server.Light.EntitySystems { diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index 6e1363dee2a2e4..3bd788bcf43f3f 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -26,7 +26,6 @@ using Content.Shared.Damage.Systems; using Content.Shared.Damage.Components; using Content.Shared.Power; -using Content.Shared.Power.Components; namespace Content.Server.Light.EntitySystems { diff --git a/Content.Server/Materials/MaterialReclaimerSystem.cs b/Content.Server/Materials/MaterialReclaimerSystem.cs index 44877441ffb89d..b02212844b0cdb 100644 --- a/Content.Server/Materials/MaterialReclaimerSystem.cs +++ b/Content.Server/Materials/MaterialReclaimerSystem.cs @@ -26,7 +26,6 @@ using Content.Shared.Emag.Components; using Content.Shared.Power; using Robust.Shared.Prototypes; -using Content.Shared.Power.Components; namespace Content.Server.Materials; diff --git a/Content.Server/Medical/MedicalScannerSystem.cs b/Content.Server/Medical/MedicalScannerSystem.cs index 612cf356bce9e7..b24690e204a961 100644 --- a/Content.Server/Medical/MedicalScannerSystem.cs +++ b/Content.Server/Medical/MedicalScannerSystem.cs @@ -14,7 +14,6 @@ using Content.Shared.Climbing.Systems; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Power.EntitySystems; using Robust.Server.Containers; using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent; // Hmm... diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs index df7bd2a54f7da8..40b998a95d00fb 100644 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Server/Power/EntitySystems/ChargerSystem.cs @@ -8,7 +8,6 @@ using JetBrains.Annotations; using Robust.Shared.Containers; using System.Diagnostics.CodeAnalysis; -using Content.Shared.Power.Components; using Content.Shared.Storage.Components; using Robust.Server.Containers; using Content.Shared.Whitelist; @@ -64,7 +63,7 @@ private void OnChargerExamine(EntityUid uid, ChargerComponent component, Examine } else { - // add how much each item is charged it + // add how much each item is charged it foreach (var contained in container.ContainedEntities) { if (!TryComp(contained, out var battery)) @@ -232,7 +231,7 @@ private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component) return CellChargerStatus.Charging; } - + private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float frameTime) { if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent)) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 9ce48141396296..a7098649ceffb1 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -5,7 +5,6 @@ using Content.Server.Power.Pow3r; using Content.Shared.CCVar; using Content.Shared.Power; -using Content.Shared.Power.Components; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Configuration; diff --git a/Content.Server/Power/Generation/Teg/TegSystem.cs b/Content.Server/Power/Generation/Teg/TegSystem.cs index edf0693954c5d8..9fb7d5ff1f649b 100644 --- a/Content.Server/Power/Generation/Teg/TegSystem.cs +++ b/Content.Server/Power/Generation/Teg/TegSystem.cs @@ -11,7 +11,6 @@ using Content.Shared.DeviceNetwork; using Content.Shared.Examine; using Content.Shared.Power; -using Content.Shared.Power.Components; using Content.Shared.Power.Generation.Teg; using Content.Shared.Rounding; using Robust.Server.GameObjects; diff --git a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs index 5a1bd31a15c363..e3979a65192957 100644 --- a/Content.Server/Power/Generator/GasPowerReceiverSystem.cs +++ b/Content.Server/Power/Generator/GasPowerReceiverSystem.cs @@ -6,7 +6,6 @@ using Content.Server.Power.Components; using Content.Shared.Atmos; using Content.Shared.Power; -using Content.Shared.Power.Components; namespace Content.Server.Power.Generator; diff --git a/Content.Server/Research/Systems/ResearchSystem.Client.cs b/Content.Server/Research/Systems/ResearchSystem.Client.cs index 4d6548095177a3..f8fdba55b7636f 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Client.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Client.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using Content.Server.Power.EntitySystems; -using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Research.Systems; diff --git a/Content.Server/Research/Systems/ResearchSystem.Console.cs b/Content.Server/Research/Systems/ResearchSystem.Console.cs index 38b548fdf85887..5358ddefcdf4f7 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Console.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Console.cs @@ -2,7 +2,6 @@ using Content.Server.Research.Components; using Content.Shared.UserInterface; using Content.Shared.Access.Components; -using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; using Content.Shared.Research.Prototypes; diff --git a/Content.Server/Research/Systems/ResearchSystem.PointSource.cs b/Content.Server/Research/Systems/ResearchSystem.PointSource.cs index 399c28ec62fabd..f069b1c80f7d23 100644 --- a/Content.Server/Research/Systems/ResearchSystem.PointSource.cs +++ b/Content.Server/Research/Systems/ResearchSystem.PointSource.cs @@ -1,6 +1,5 @@ using Content.Server.Power.EntitySystems; using Content.Server.Research.Components; -using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Research.Systems; diff --git a/Content.Server/Research/Systems/ResearchSystem.Server.cs b/Content.Server/Research/Systems/ResearchSystem.Server.cs index 8fe0371413c66b..09ca7ed15c27a2 100644 --- a/Content.Server/Research/Systems/ResearchSystem.Server.cs +++ b/Content.Server/Research/Systems/ResearchSystem.Server.cs @@ -1,6 +1,5 @@ using System.Linq; using Content.Server.Power.EntitySystems; -using Content.Shared.Power.EntitySystems; using Content.Shared.Research.Components; namespace Content.Server.Research.Systems; diff --git a/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs b/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs index c96041eb0a1f64..bc0de7c8c64f12 100644 --- a/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/SingularityAttractorSystem.cs @@ -5,7 +5,6 @@ using Robust.Shared.Map; using Robust.Shared.Timing; using System.Numerics; -using Content.Shared.Power.EntitySystems; namespace Content.Server.Singularity.EntitySystems; diff --git a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs index d2c2a8a1ca7adb..1ad1bb6c0a1222 100644 --- a/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs +++ b/Content.Server/Sound/SpamEmitSoundRequirePowerSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Power; -using Content.Shared.Power.Components; using Content.Shared.Sound; using Content.Shared.Sound.Components; diff --git a/Content.Server/Wires/BaseWireAction.cs b/Content.Server/Wires/BaseWireAction.cs index e1e7768ebcde24..ef6a0fdf36e459 100644 --- a/Content.Server/Wires/BaseWireAction.cs +++ b/Content.Server/Wires/BaseWireAction.cs @@ -1,7 +1,6 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Administration.Logs; using Content.Shared.Database; -using Content.Shared.Power.EntitySystems; using Content.Shared.Wires; namespace Content.Server.Wires; diff --git a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs index d207ff5ef39abd..d277792243d8dd 100644 --- a/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs +++ b/Content.Server/Xenoarchaeology/Equipment/Systems/TraversalDistorterSystem.cs @@ -4,7 +4,6 @@ using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Placeable; -using Content.Shared.Power.EntitySystems; using Robust.Shared.Timing; namespace Content.Server.Xenoarchaeology.Equipment.Systems; diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs index ed726c70a8be3f..a5469e93dc008a 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/ArtifactSystem.cs @@ -7,7 +7,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.CCVar; -using Content.Shared.Power.EntitySystems; using Content.Shared.Xenoarchaeology.XenoArtifacts; using JetBrains.Annotations; using Robust.Shared.Audio; diff --git a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs index 6d984ed19a364d..4cf9b25dadcb29 100644 --- a/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs +++ b/Content.Shared/Light/EntitySystems/SlimPoweredLightSystem.cs @@ -1,6 +1,5 @@ using Content.Shared.Light.Components; using Content.Shared.Power; -using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; namespace Content.Shared.Light.EntitySystems; diff --git a/Content.Shared/Movement/Components/MovementSoundComponent.cs b/Content.Shared/Movement/Components/MovementSoundComponent.cs deleted file mode 100644 index 92a6974cc6766b..00000000000000 --- a/Content.Shared/Movement/Components/MovementSoundComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.GameStates; - -namespace Content.Shared.Movement.Components; - -/// -/// Plays a sound whenever InputMover is running. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class MovementSoundComponent : Component -{ - /// - /// Sound to play when InputMover has inputs. - /// - [DataField(required: true), AutoNetworkedField] - public SoundSpecifier? Sound; - - [DataField, AutoNetworkedField] - public EntityUid? SoundEntity; -} diff --git a/Content.Shared/Movement/Systems/MovementSoundSystem.cs b/Content.Shared/Movement/Systems/MovementSoundSystem.cs deleted file mode 100644 index 9a1146779fac27..00000000000000 --- a/Content.Shared/Movement/Systems/MovementSoundSystem.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Shared.Movement.Components; -using Content.Shared.Movement.Events; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Content.Shared.Movement.Systems; - -/// -/// Plays a sound on MoveInputEvent. -/// -public sealed class MovementSoundSystem : EntitySystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnMoveInput); - } - - private void OnMoveInput(Entity ent, ref MoveInputEvent args) - { - if (!_timing.IsFirstTimePredicted) - return; - - var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None; - var moving = (SharedMoverController.GetNormalizedMovement(args.Entity.Comp.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None; - - if (oldMoving == moving) - return; - - if (moving) - { - DebugTools.Assert(ent.Comp.SoundEntity == null); - ent.Comp.SoundEntity = _audio.PlayPredicted(ent.Comp.Sound, ent.Owner, ent.Owner)?.Entity; - } - else - { - ent.Comp.SoundEntity = _audio.Stop(ent.Comp.SoundEntity); - } - } -} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 752558a53e874c..9dda249423e2e0 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -149,53 +149,23 @@ private void OnAutoParentChange(Entity entity, ref EntParen public void RotateCamera(EntityUid uid, Angle angle) { - if (CameraRotationLocked) - return; - - var entity = uid; - - if (!TryComp(uid, out RelayInputMoverComponent? relay) || - !MoverQuery.TryComp(relay.RelayEntity, out var mover)) - { - MoverQuery.TryGetComponent(uid, out mover); - } - else - { - entity = relay.RelayEntity; - } - - if (mover == null) + if (CameraRotationLocked || !MoverQuery.TryGetComponent(uid, out var mover)) return; mover.TargetRelativeRotation += angle; - Dirty(entity, mover); + Dirty(uid, mover); } public void ResetCamera(EntityUid uid) { - if (CameraRotationLocked) + if (CameraRotationLocked || + !MoverQuery.TryGetComponent(uid, out var mover)) { return; } - TransformComponent xform; - - if (!TryComp(uid, out RelayInputMoverComponent? relay) || - !MoverQuery.TryComp(relay.RelayEntity, out var mover)) - { - MoverQuery.TryGetComponent(uid, out mover); - xform = XformQuery.Comp(uid); - } - else - { - xform = XformQuery.Comp(relay.RelayEntity); - } - - if (mover == null) - return; - // If we updated parent then cancel the accumulator and force it now. - if (!TryUpdateRelative(mover, xform) && mover.TargetRelativeRotation.Equals(Angle.Zero)) + if (!TryUpdateRelative(mover, XformQuery.GetComponent(uid)) && mover.TargetRelativeRotation.Equals(Angle.Zero)) return; mover.LerpTarget = TimeSpan.Zero; diff --git a/Content.Shared/Power/Components/PowerChangedEvent.cs b/Content.Shared/Power/Components/PowerChangedEvent.cs deleted file mode 100644 index b1814888b5a5ca..00000000000000 --- a/Content.Shared/Power/Components/PowerChangedEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Shared.Power.Components; - -/// -/// Raised whenever an ApcPowerReceiver becomes powered / unpowered. -/// Does nothing on the client. -/// -[ByRefEvent] -public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower); \ No newline at end of file diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index 9091ba81e3e83a..c3ebe0d1a38372 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -29,7 +29,6 @@ public sealed partial class HumanoidCharacterProfile : ICharacterProfile private static readonly Regex ICNameCaseRegex = new(@"^(?\w)|\b(?\w)(?=\w*$)"); public const int MaxNameLength = 32; - public const int MaxLoadoutNameLength = 32; public const int MaxDescLength = 512; /// diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs index 3f00fb98dd9bd1..f943c9ea8a0765 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs @@ -22,11 +22,6 @@ public sealed partial class RoleLoadout : IEquatable [DataField] public Dictionary, List> SelectedLoadouts = new(); - /// - /// Loadout specific name. - /// - public string? EntityName; - /* * Loadout-specific data used for validation. */ @@ -47,8 +42,6 @@ public RoleLoadout Clone() weh.SelectedLoadouts.Add(selected.Key, new List(selected.Value)); } - weh.EntityName = EntityName; - return weh; } @@ -62,28 +55,10 @@ public void EnsureValid(HumanoidCharacterProfile profile, ICommonSession session if (!protoManager.TryIndex(Role, out var roleProto)) { - EntityName = null; SelectedLoadouts.Clear(); return; } - // Remove name not allowed. - if (!roleProto.CanCustomiseName) - { - EntityName = null; - } - - // Validate name length - if (EntityName != null) - { - var name = EntityName.Trim(); - - if (name.Length > HumanoidCharacterProfile.MaxNameLength) - { - EntityName = name[..HumanoidCharacterProfile.MaxNameLength]; - } - } - // In some instances we might not have picked up a new group for existing data. foreach (var groupProto in roleProto.Groups) { @@ -347,8 +322,7 @@ public bool Equals(RoleLoadout? other) if (!Role.Equals(other.Role) || SelectedLoadouts.Count != other.SelectedLoadouts.Count || - Points != other.Points || - EntityName != other.EntityName) + Points != other.Points) { return false; } diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs b/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs index f6235ac6eba247..7a29bad6678891 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadoutPrototype.cs @@ -16,12 +16,6 @@ public sealed partial class RoleLoadoutPrototype : IPrototype [IdDataField] public string ID { get; } = string.Empty; - /// - /// Can the user edit their entity name for this role loadout? - /// - [DataField] - public bool CanCustomiseName; - /// /// Should we use a random name for this loadout? /// diff --git a/Content.Shared/Silicons/StationAi/IncorporealComponent.cs b/Content.Shared/Silicons/StationAi/IncorporealComponent.cs deleted file mode 100644 index 0f5e4c6f1621e8..00000000000000 --- a/Content.Shared/Silicons/StationAi/IncorporealComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Silicons.StationAi; - -/// -/// Toggles vismask and sprite visibility of an entity. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class IncorporealComponent : Component -{ - [DataField, AutoNetworkedField] - public bool Visible = true; - - /// - /// Alpha to have when disabled. - /// - [DataField] - public float Alpha = 0.05f; - - [DataField, AutoNetworkedField] - public float VisibleSpeedModifier = 0.85f; -} diff --git a/Content.Shared/Silicons/StationAi/IncorporealSystem.cs b/Content.Shared/Silicons/StationAi/IncorporealSystem.cs deleted file mode 100644 index 504b09bf467b3a..00000000000000 --- a/Content.Shared/Silicons/StationAi/IncorporealSystem.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Content.Shared.Eye; -using Content.Shared.Movement.Systems; -using Robust.Shared.Serialization; - -namespace Content.Shared.Silicons.StationAi; - -public sealed class IncorporealSystem : EntitySystem -{ - // Somewhat placeholder for holopads - - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly MovementSpeedModifierSystem _speed = default!; - [Dependency] private readonly SharedVisibilitySystem _vis = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnIncorporealMapInit); - SubscribeLocalEvent(OnIncorporealSpeed); - } - - private void OnIncorporealSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args) - { - if (ent.Comp.Visible) - args.ModifySpeed(ent.Comp.VisibleSpeedModifier); - } - - private void OnIncorporealMapInit(Entity ent, ref MapInitEvent args) - { - UpdateAppearance(ent); - } - - private void UpdateAppearance(Entity ent) - { - _appearance.SetData(ent.Owner, IncorporealState.Base, ent.Comp.Visible); - } - - public bool SetVisible(Entity entity, bool value) - { - if (entity.Comp.Visible == value) - return false; - - entity.Comp.Visible = value; - Dirty(entity); - - if (value) - { - _vis.AddLayer(entity.Owner, (ushort) VisibilityFlags.Normal); - } - else - { - _vis.RemoveLayer(entity.Owner, (ushort) VisibilityFlags.Normal); - } - - UpdateAppearance(entity); - _speed. RefreshMovementSpeedModifiers(entity); - return true; - } -} - -[Serializable, NetSerializable] -public enum IncorporealState : byte -{ - Base, -} diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 57adc88076d773..a4ab3b0db0d651 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -6,7 +6,7 @@ using Content.Shared.Item.ItemToggle; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; -using Content.Shared.Power.Components; +using Content.Shared.Power; using Content.Shared.StationAi; using Content.Shared.Verbs; using Robust.Shared.Containers; diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 52b630d82788a0..0584b10562a1a4 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -67,11 +67,6 @@ public void EquipRoleName(EntityUid entity, RoleLoadout loadout, RoleLoadoutProt { string? name = null; - if (roleProto.CanCustomiseName) - { - name = loadout.EntityName; - } - if (string.IsNullOrEmpty(name) && PrototypeManager.TryIndex(roleProto.NameDataset, out var nameData)) { name = _random.Pick(nameData.Values); diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 4169ccdc1f9369..c36d501b2a300e 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -293,8 +293,6 @@ color: '#FFFFFFFF' False: color: '#FFFFFF0A' - - type: Incorporeal - visible: false - type: Visibility layer: 2 - type: Sprite diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index 2a5e47f0a0a406..fffeaff39c59e4 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -1,14 +1,13 @@ # No idea why it's in sci but we ball. -# TODO: Need to just force it to spawn on a spawner and allow takeover. -# If no takeover then fallback. - type: job id: StationAi name: job-name-station-ai description: job-description-station-ai playTimeTracker: JobStationAi requirements: - - !type:OverallPlaytimeRequirement - time: 216000 #60 hrs + - !type:RoleTimeRequirement + role: JobBorg + time: 18000 # 5 hrs canBeAntag: false icon: JobIconStationAi supervisors: job-supervisors-rd @@ -21,7 +20,7 @@ playTimeTracker: JobBorg requirements: - !type:OverallPlaytimeRequirement - time: 216000 #60 hrs + time: 216000 # 60 hrs canBeAntag: false icon: JobIconBorg supervisors: job-supervisors-rd From d388ae5870448194bd6a2ef7a00645500be60c61 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:39:47 +1000 Subject: [PATCH 27/40] a --- Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs | 7 ------- Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs | 1 - Content.Server/Database/ServerDbBase.cs | 3 --- 3 files changed, 11 deletions(-) diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index f6bd88e6caa549..c5f2f311d9b7db 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -1009,13 +1009,6 @@ private void OpenLoadout(JobPrototype? jobProto, RoleLoadout roleLoadout, RoleLo _loadoutWindow.RefreshLoadouts(roleLoadout, session, collection); _loadoutWindow.OpenCenteredLeft(); - _loadoutWindow.OnNameChanged += name => - { - roleLoadout.EntityName = name; - Profile = Profile.WithLoadout(roleLoadout); - SetDirty(); - }; - _loadoutWindow.OnLoadoutPressed += (loadoutGroup, loadoutProto) => { roleLoadout.AddLoadout(loadoutGroup, loadoutProto, _prototypeManager); diff --git a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs index 42a41847584147..aab2a56ff6819e 100644 --- a/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs +++ b/Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs @@ -13,7 +13,6 @@ namespace Content.Client.Lobby.UI.Loadouts; [GenerateTypedNameReferences] public sealed partial class LoadoutWindow : FancyWindow { - public event Action? OnNameChanged; public event Action, ProtoId>? OnLoadoutPressed; public event Action, ProtoId>? OnLoadoutUnpressed; diff --git a/Content.Server/Database/ServerDbBase.cs b/Content.Server/Database/ServerDbBase.cs index e1be3c3f6156fe..196fe782fad59e 100644 --- a/Content.Server/Database/ServerDbBase.cs +++ b/Content.Server/Database/ServerDbBase.cs @@ -222,8 +222,6 @@ private static HumanoidCharacterProfile ConvertProfiles(Profile profile) { var loadout = new RoleLoadout(role.RoleName) { - // Validate later if it's even possible. - EntityName = role.CustomName, }; foreach (var group in role.Groups) @@ -321,7 +319,6 @@ private static Profile ConvertProfiles(HumanoidCharacterProfile humanoid, int sl var dz = new ProfileRoleLoadout() { RoleName = role, - CustomName = loadouts.EntityName, }; foreach (var (group, groupLoadouts) in loadouts.SelectedLoadouts) From 1ebc89d6c7eb48beb0b221fcc3f595f8d30da136 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:49:26 +1000 Subject: [PATCH 28/40] reh --- Content.Server.Database/Model.cs | 1 - Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml | 5 ----- 2 files changed, 6 deletions(-) diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs index 79bbff2028697b..ea63c41fc25cc3 100644 --- a/Content.Server.Database/Model.cs +++ b/Content.Server.Database/Model.cs @@ -458,7 +458,6 @@ public class ProfileLoadoutGroup /// /// The corresponding group prototype. /// - [StringLength(256)] public string GroupName { get; set; } = string.Empty; /// diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index d81396f9cc5a31..728f8ce7d58b65 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -265,11 +265,6 @@ access: [["Medical"], ["Command"], ["Research"]] - type: Inventory templateId: borgDutch - - type: MovementSound - sound: - collection: FootstepHoverBorg - params: - loop: true - type: SolutionScanner - type: InteractionPopup interactSuccessString: petting-success-medical-cyborg From 67c79e83a4546538c9dbf89107d02e189a4d9f6e Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:51:52 +1000 Subject: [PATCH 29/40] Revert "navmap work" This reverts commit 6f63fea6e9245e189f368f97be3e32e9b210580e. # Conflicts: # Content.Client/Silicons/StationAi/StationAiOverlay.cs --- .../CrewMonitoringNavMapControl.cs | 10 +- Content.Client/Pinpointer/NavMapData.cs | 406 ------------------ Content.Client/Pinpointer/UI/NavMapControl.cs | 376 +++++++++++++++- .../PowerMonitoringConsoleNavMapControl.cs | 46 +- .../Silicons/StationAi/StationAiOverlay.cs | 2 - 5 files changed, 398 insertions(+), 442 deletions(-) delete mode 100644 Content.Client/Pinpointer/NavMapData.cs diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs index fb3b12122a07de..340cc9af891c76 100644 --- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs +++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringNavMapControl.cs @@ -15,9 +15,9 @@ public sealed partial class CrewMonitoringNavMapControl : NavMapControl public CrewMonitoringNavMapControl() : base() { - NavData.WallColor = Color.FromSrgb(new Color(192, 122, 196)); - NavData.TileColor = Color.FromSrgb(new(71, 42, 72)); - BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); + WallColor = new Color(192, 122, 196); + TileColor = new(71, 42, 72); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); _trackedEntityLabel = new Label { @@ -41,7 +41,7 @@ public CrewMonitoringNavMapControl() : base() }; _trackedEntityPanel.AddChild(_trackedEntityLabel); - AddChild(_trackedEntityPanel); + this.AddChild(_trackedEntityPanel); } protected override void FrameUpdate(FrameEventArgs args) @@ -56,7 +56,7 @@ protected override void FrameUpdate(FrameEventArgs args) return; } - foreach (var (netEntity, blip) in TrackedEntities) + foreach ((var netEntity, var blip) in TrackedEntities) { if (netEntity != Focus) continue; diff --git a/Content.Client/Pinpointer/NavMapData.cs b/Content.Client/Pinpointer/NavMapData.cs deleted file mode 100644 index b3953eff30acdd..00000000000000 --- a/Content.Client/Pinpointer/NavMapData.cs +++ /dev/null @@ -1,406 +0,0 @@ -using System.Numerics; -using Content.Shared.Atmos; -using Content.Shared.Pinpointer; -using Robust.Client.Graphics; -using Robust.Shared.Collections; -using Robust.Shared.Map.Components; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Collision.Shapes; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Content.Client.Pinpointer; - -public sealed class NavMapData -{ - [Dependency] private readonly IEntityManager _entManager = default!; - - public Entity Entity; - - // Default colors - public Color WallColor = Color.ToSrgb(new Color(102, 217, 102)); - public Color TileColor = new Color(65, 252, 3); - - /// - /// Offset for the data to be drawn at. - /// - public Vector2 Offset; - - public List<(Vector2, Vector2)> TileLines = new(); - public List<(Vector2, Vector2)> TileRects = new(); - - public Dictionary TilePolygons = new(); - - private Dictionary _horizLines = new(); - private Dictionary _horizLinesReversed = new(); - private Dictionary _vertLines = new(); - private Dictionary _vertLinesReversed = new(); - - protected float FullWallInstep = 0.165f; - protected float ThinWallThickness = 0.165f; - protected float ThinDoorThickness = 0.30f; - - // TODO: Power should be updating it on its own. - /// - /// Called if navmap updates - /// - public event Action? OnUpdate; - - // TODO: Subscribe to statechanges on navmapcomponent - - public NavMapData() - { - IoCManager.InjectDependencies(this); - } - - public void Draw(DrawingHandleBase handle, Func scale, Box2 localAABB) - { - var verts = new ValueList(TileLines.Count * 2); - var maps = _entManager.System(); - - // Draw floor tiles - if (TilePolygons.Count != 0) - { - verts.Clear(); - var tilesEnumerator = maps.GetLocalTilesEnumerator(Entity, Entity, localAABB); - - while (tilesEnumerator.MoveNext(out var tileRef)) - { - if (!TilePolygons.TryGetValue(tileRef.GridIndices, out var polys)) - continue; - - for (var i = 0; i < polys.Length; i++) - { - verts.Add(scale.Invoke(polys[i])); - } - } - - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, verts.Span, TileColor); - } - - // Draw map lines - if (TileLines.Count != 0) - { - verts.Clear(); - - foreach (var (o, t) in TileLines) - { - var origin = scale.Invoke(o - Offset); - var terminus = scale.Invoke(t - Offset); - - verts.Add(origin); - verts.Add(terminus); - } - - if (verts.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, verts.Span, WallColor); - } - - // Draw map rects - if (TileRects.Count != 0) - { - var rects = new ValueList(TileRects.Count * 8); - - foreach (var (lt, rb) in TileRects) - { - var leftTop = scale.Invoke(lt - Offset); - var rightBottom = scale.Invoke(rb - Offset); - - var rightTop = new Vector2(rightBottom.X, leftTop.Y); - var leftBottom = new Vector2(leftTop.X, rightBottom.Y); - - rects.Add(leftTop); - rects.Add(rightTop); - rects.Add(rightTop); - rects.Add(rightBottom); - rects.Add(rightBottom); - rects.Add(leftBottom); - rects.Add(leftBottom); - rects.Add(leftTop); - } - - if (rects.Count > 0) - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, WallColor); - } - } - - public void UpdateNavMap(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp)) - return; - - Entity = (entity.Owner, entity.Comp); - - // Clear stale values - TilePolygons.Clear(); - TileLines.Clear(); - TileRects.Clear(); - - UpdateNavMapFloorTiles(entity.Owner); - UpdateNavMapWallLines((entity.Owner, entity.Comp, null)); - UpdateNavMapAirlocks((entity.Owner, entity.Comp, null)); - } - - private void UpdateNavMapFloorTiles(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp)) - { - return; - } - - var lookup = _entManager.System(); - var tiles = _entManager.System().GetAllTilesEnumerator(entity.Owner, entity.Comp); - - while (tiles.MoveNext(out var tile)) - { - var box = lookup.GetLocalBounds(tile.Value.GridIndices, entity.Comp.TileSize).Enlarged(-0.45f); - box = new Box2(box.Left, -box.Bottom, box.Right, -box.Top); - var arr = new Vector2[6]; - - arr[0] = box.BottomLeft; - arr[1] = box.BottomRight; - arr[2] = box.TopLeft; - - arr[3] = box.BottomRight; - arr[4] = box.TopLeft; - arr[5] = box.TopRight; - - TilePolygons[tile.Value.GridIndices] = arr; - } - } - - private void UpdateNavMapWallLines(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || - !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) - { - return; - } - - // We'll use the following dictionaries to combine collinear wall lines - _horizLines.Clear(); - _horizLinesReversed.Clear(); - _vertLines.Clear(); - _vertLinesReversed.Clear(); - - const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; - const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; - const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; - const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; - - foreach (var (chunkOrigin, chunk) in entity.Comp2.Chunks) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Wall; - - var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * entity.Comp1.TileSize; - - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinWall(tileData, tile); - continue; - } - - tile = tile with { Y = -tile.Y }; - NavMapChunk? neighborChunk; - - // North edge - var neighborData = 0; - if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i+1]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) - neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; - - if ((neighborData & southMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), - tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), _horizLines, - _horizLinesReversed); - } - - // East edge - neighborData = 0; - if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) - neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) - neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; - - if ((neighborData & westMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(entity.Comp1.TileSize, -entity.Comp1.TileSize), - tile + new Vector2i(entity.Comp1.TileSize, 0), _vertLines, _vertLinesReversed); - } - - // South edge - neighborData = 0; - if (relativeTile.Y != 0) - neighborData = chunk.TileData[i - 1]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) - neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; - - if ((neighborData & northMask) == 0) - { - AddOrUpdateNavMapLine(tile, tile + new Vector2i(entity.Comp1.TileSize, 0), _horizLines, - _horizLinesReversed); - } - - // West edge - neighborData = 0; - if (relativeTile.X != 0) - neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; - else if (entity.Comp2.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) - neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; - - if ((neighborData & eastMask) == 0) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -entity.Comp1.TileSize), tile, _vertLines, - _vertLinesReversed); - } - - // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these - TileLines.Add((tile + new Vector2(0, -entity.Comp1.TileSize), tile + new Vector2(entity.Comp1.TileSize, 0))); - } - } - - // Record the combined lines - foreach (var (origin, terminal) in _horizLines) - { - TileLines.Add((origin, terminal)); - } - - foreach (var (origin, terminal) in _vertLines) - { - TileLines.Add((origin, terminal)); - } - } - - private void UpdateNavMapAirlocks(Entity entity) - { - if (!_entManager.TryGetComponent(entity.Owner, out entity.Comp1) || - !_entManager.TryGetComponent(entity.Owner, out entity.Comp2)) - { - return; - } - - foreach (var chunk in entity.Comp2.Chunks.Values) - { - for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) - { - var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; - if (tileData == 0) - continue; - - tileData >>= (int) NavMapChunkType.Airlock; - - var relative = SharedNavMapSystem.GetTileFromIndex(i); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * entity.Comp1.TileSize; - - // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge - if (tileData != SharedNavMapSystem.AllDirMask) - { - AddRectForThinAirlock(tileData, tile); - continue; - } - - // Otherwise add a single full tile airlock - TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), - new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); - - TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), - new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); - } - } - } - - private void AddRectForThinWall(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); - var rightBottom = new Vector2(0.5f, 0.5f); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - - // TODO NAVMAP - // Consider using faster rotation operations, given that these are always 90 degree increments - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - } - } - - private void AddRectForThinAirlock(int tileData, Vector2i tile) - { - var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); - var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); - var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); - var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); - - for (var i = 0; i < SharedNavMapSystem.Directions; i++) - { - var dirMask = 1 << i; - if ((tileData & dirMask) == 0) - continue; - - var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - var angle = -((AtmosDirection) dirMask).ToAngle(); - TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); - TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); - } - } - - public void AddOrUpdateNavMapLine( - Vector2i origin, - Vector2i terminus, - Dictionary lookup, - Dictionary lookupReversed) - { - Vector2i foundTermius; - Vector2i foundOrigin; - - // Does our new line end at the beginning of an existing line? - if (lookup.Remove(terminus, out foundTermius)) - { - DebugTools.Assert(lookupReversed[foundTermius] == terminus); - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just connects two existing lines - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = foundTermius; - lookupReversed[foundTermius] = foundOrigin; - } - else - { - // Our new line precedes an existing line, extending it further to the left - lookup[origin] = foundTermius; - lookupReversed[foundTermius] = origin; - } - return; - } - - // Does our new line start at the end of an existing line? - if (lookupReversed.Remove(origin, out foundOrigin)) - { - // Our new line just extends an existing line further to the right - DebugTools.Assert(lookup[foundOrigin] == origin); - lookup[foundOrigin] = terminus; - lookupReversed[terminus] = foundOrigin; - return; - } - - // Completely disconnected line segment. - lookup.Add(origin, terminus); - lookupReversed.Add(terminus, origin); - } -} diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index adbe9d724013d0..413b41c36a6f43 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -30,6 +30,7 @@ public partial class NavMapControl : MapGridControl { [Dependency] private IResourceCache _cache = default!; private readonly SharedTransformSystem _transformSystem; + private readonly SharedNavMapSystem _navMapSystem; public EntityUid? Owner; public EntityUid? MapUid; @@ -44,7 +45,13 @@ public partial class NavMapControl : MapGridControl public Dictionary TrackedCoordinates = new(); public Dictionary TrackedEntities = new(); - public NavMapData NavData = new(); + public List<(Vector2, Vector2)> TileLines = new(); + public List<(Vector2, Vector2)> TileRects = new(); + public List<(Vector2[], Color)> TilePolygons = new(); + + // Default colors + public Color WallColor = new(102, 217, 102); + public Color TileColor = new(30, 67, 30); // Constants protected float UpdateTime = 1.0f; @@ -54,13 +61,22 @@ public partial class NavMapControl : MapGridControl protected static float MaxDisplayedRange = 128f; protected static float DefaultDisplayedRange = 48f; protected float MinmapScaleModifier = 0.075f; + protected float FullWallInstep = 0.165f; + protected float ThinWallThickness = 0.165f; + protected float ThinDoorThickness = 0.30f; // Local variables private float _updateTimer = 1.0f; + private Dictionary _sRGBLookUp = new(); protected Color BackgroundColor; protected float BackgroundOpacity = 0.9f; private int _targetFontsize = 8; + private Dictionary _horizLines = new(); + private Dictionary _horizLinesReversed = new(); + private Dictionary _vertLines = new(); + private Dictionary _vertLinesReversed = new(); + // Components private NavMapComponent? _navMap; private MapGridComponent? _grid; @@ -101,8 +117,9 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl IoCManager.InjectDependencies(this); _transformSystem = EntManager.System(); + _navMapSystem = EntManager.System(); - BackgroundColor = Color.FromSrgb(NavData.TileColor.WithAlpha(BackgroundOpacity)); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); RectClipContent = true; HorizontalExpand = true; @@ -162,12 +179,13 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl public void ForceNavMapUpdate() { - if (MapUid == null) - { - return; - } + EntManager.TryGetComponent(MapUid, out _navMap); + EntManager.TryGetComponent(MapUid, out _grid); + EntManager.TryGetComponent(MapUid, out _xform); + EntManager.TryGetComponent(MapUid, out _physics); + EntManager.TryGetComponent(MapUid, out _fixtures); - NavData.UpdateNavMap(MapUid.Value); + UpdateNavMap(); } public void CenterToCoordinates(EntityCoordinates coordinates) @@ -210,7 +228,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { if (!blip.Selectable) continue; - + var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length(); if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance) @@ -275,11 +293,80 @@ protected override void Draw(DrawingHandleScreen handle) if (_physics != null) offset += _physics.LocalCenter; - NavData.Offset = new Vector2(offset.X, -offset.Y); - NavData.Draw(handle, ScalePosition, Box2.UnitCentered); + var offsetVec = new Vector2(offset.X, -offset.Y); + + // Wall sRGB + if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB)) + { + wallsRGB = Color.ToSrgb(WallColor); + _sRGBLookUp[WallColor] = wallsRGB; + } + + // Draw floor tiles + if (TilePolygons.Any()) + { + Span verts = new Vector2[8]; + + foreach (var (polygonVerts, polygonColor) in TilePolygons) + { + for (var i = 0; i < polygonVerts.Length; i++) + { + var vert = polygonVerts[i] - offset; + verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y)); + } + + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor); + } + } + + // Draw map lines + if (TileLines.Any()) + { + var lines = new ValueList(TileLines.Count * 2); + + foreach (var (o, t) in TileLines) + { + var origin = ScalePosition(o - offsetVec); + var terminus = ScalePosition(t - offsetVec); + + lines.Add(origin); + lines.Add(terminus); + } + + if (lines.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB); + } + + // Draw map rects + if (TileRects.Any()) + { + var rects = new ValueList(TileRects.Count * 8); + + foreach (var (lt, rb) in TileRects) + { + var leftTop = ScalePosition(lt - offsetVec); + var rightBottom = ScalePosition(rb - offsetVec); + + var rightTop = new Vector2(rightBottom.X, leftTop.Y); + var leftBottom = new Vector2(leftTop.X, rightBottom.Y); + + rects.Add(leftTop); + rects.Add(rightTop); + rects.Add(rightTop); + rects.Add(rightBottom); + rects.Add(rightBottom); + rects.Add(leftBottom); + rects.Add(leftBottom); + rects.Add(leftTop); + } + + if (rects.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB); + } // Invoke post wall drawing action - PostWallDrawingAction?.Invoke(handle); + if (PostWallDrawingAction != null) + PostWallDrawingAction.Invoke(handle); // Beacons if (_beacons.Pressed) @@ -349,18 +436,279 @@ protected override void Draw(DrawingHandleScreen handle) protected override void FrameUpdate(FrameEventArgs args) { // Update the timer - // TODO: Sub to state changes. _updateTimer += args.DeltaSeconds; if (_updateTimer >= UpdateTime) { _updateTimer -= UpdateTime; - if (MapUid != null) - NavData.UpdateNavMap(MapUid.Value); + UpdateNavMap(); + } + } + + protected virtual void UpdateNavMap() + { + // Clear stale values + TilePolygons.Clear(); + TileLines.Clear(); + TileRects.Clear(); + + UpdateNavMapFloorTiles(); + UpdateNavMapWallLines(); + UpdateNavMapAirlocks(); + } + + private void UpdateNavMapFloorTiles() + { + if (_fixtures == null) + return; + + var verts = new Vector2[8]; + + foreach (var fixture in _fixtures.Fixtures.Values) + { + if (fixture.Shape is not PolygonShape poly) + continue; + + for (var i = 0; i < poly.VertexCount; i++) + { + var vert = poly.Vertices[i]; + verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y)); + } + + TilePolygons.Add((verts[..poly.VertexCount], TileColor)); + } + } + + private void UpdateNavMapWallLines() + { + if (_navMap == null || _grid == null) + return; + + // We'll use the following dictionaries to combine collinear wall lines + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); + + const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; + const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; + const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; + const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; + + foreach (var (chunkOrigin, chunk) in _navMap.Chunks) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Wall; + + var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; + + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinWall(tileData, tile); + continue; + } + + tile = tile with { Y = -tile.Y }; + NavMapChunk? neighborChunk; + + // North edge + var neighborData = 0; + if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) + neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; + + if ((neighborData & southMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines, + _horizLinesReversed); + } + + // East edge + neighborData = 0; + if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i + SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) + neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; + + if ((neighborData & westMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed); + } + + // South edge + neighborData = 0; + if (relativeTile.Y != 0) + neighborData = chunk.TileData[i - 1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) + neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; + + if ((neighborData & northMask) == 0) + { + AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines, + _horizLinesReversed); + } + + // West edge + neighborData = 0; + if (relativeTile.X != 0) + neighborData = chunk.TileData[i - SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) + neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; + + if ((neighborData & eastMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines, + _vertLinesReversed); + } + + // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these + TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0))); + } + } + + // Record the combined lines + foreach (var (origin, terminal) in _horizLines) + { + TileLines.Add((origin, terminal)); + } + + foreach (var (origin, terminal) in _vertLines) + { + TileLines.Add((origin, terminal)); + } + } + + private void UpdateNavMapAirlocks() + { + if (_navMap == null || _grid == null) + return; + + foreach (var chunk in _navMap.Chunks.Values) + { + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) + { + var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; + if (tileData == 0) + continue; + + tileData >>= (int) NavMapChunkType.Airlock; + + var relative = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize; + + // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge + if (tileData != SharedNavMapSystem.AllDirMask) + { + AddRectForThinAirlock(tileData, tile); + continue; + } + + // Otherwise add a single full tile airlock + TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep), + new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1))); + + TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep), + new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1))); + } } } + private void AddRectForThinWall(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); + var rightBottom = new Vector2(0.5f, 0.5f); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + + // TODO NAVMAP + // Consider using faster rotation operations, given that these are always 90 degree increments + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + } + } + + private void AddRectForThinAirlock(int tileData, Vector2i tile) + { + var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); + var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); + var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); + var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); + + for (var i = 0; i < SharedNavMapSystem.Directions; i++) + { + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) + continue; + + var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); + var angle = -((AtmosDirection) dirMask).ToAngle(); + TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); + TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); + } + } + + protected void AddOrUpdateNavMapLine( + Vector2i origin, + Vector2i terminus, + Dictionary lookup, + Dictionary lookupReversed) + { + Vector2i foundTermius; + Vector2i foundOrigin; + + // Does our new line end at the beginning of an existing line? + if (lookup.Remove(terminus, out foundTermius)) + { + DebugTools.Assert(lookupReversed[foundTermius] == terminus); + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just connects two existing lines + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = foundTermius; + lookupReversed[foundTermius] = foundOrigin; + } + else + { + // Our new line precedes an existing line, extending it further to the left + lookup[origin] = foundTermius; + lookupReversed[foundTermius] = origin; + } + return; + } + + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just extends an existing line further to the right + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = terminus; + lookupReversed[terminus] = foundOrigin; + return; + } + + // Completely disconnected line segment. + lookup.Add(origin, terminus); + lookupReversed.Add(terminus, origin); + } + protected Vector2 GetOffset() { return Offset + (_physics?.LocalCenter ?? new Vector2()); diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs index dd29778819654a..d5057416cf84ed 100644 --- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs +++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs @@ -20,7 +20,9 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private readonly Color[] _powerCableColors = { Color.OrangeRed, Color.Yellow, Color.LimeGreen }; private readonly Vector2[] _powerCableOffsets = { new Vector2(-0.2f, -0.2f), Vector2.Zero, new Vector2(0.2f, 0.2f) }; + private Dictionary _sRGBLookUp = new Dictionary(); + public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks; public List HiddenLineGroups = new(); public List PowerCableNetwork = new(); public List FocusCableNetwork = new(); @@ -32,19 +34,20 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl private MapGridComponent? _grid; - public PowerMonitoringConsoleNavMapControl() + public PowerMonitoringConsoleNavMapControl() : base() { // Set colors - NavData.TileColor = Color.FromSrgb(new Color(30, 57, 67)); - BackgroundColor = NavData.TileColor.WithAlpha(BackgroundOpacity); + TileColor = new Color(30, 57, 67); + WallColor = new Color(102, 164, 217); + BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity)); PostWallDrawingAction += DrawAllCableNetworks; - - NavData.OnUpdate += UpdateNavMap; } - private void UpdateNavMap() + protected override void UpdateNavMap() { + base.UpdateNavMap(); + if (Owner == null) return; @@ -61,14 +64,14 @@ public void DrawAllCableNetworks(DrawingHandleScreen handle) return; // Draw full cable network - if (PowerCableNetwork.Count > 0) + if (PowerCableNetwork != null && PowerCableNetwork.Count > 0) { - var modulator = (FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; + var modulator = (FocusCableNetwork != null && FocusCableNetwork.Count > 0) ? Color.DimGray : Color.White; DrawCableNetwork(handle, PowerCableNetwork, modulator); } // Draw focus network - if (FocusCableNetwork.Count > 0) + if (FocusCableNetwork != null && FocusCableNetwork.Count > 0) DrawCableNetwork(handle, FocusCableNetwork, Color.White); } @@ -103,8 +106,15 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); - handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, color); + var color = _powerCableColors[cableNetworkIdx] * modulator; + + if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) + { + sRGB = Color.ToSrgb(color); + _sRGBLookUp[color] = sRGB; + } + + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, cableNetwork.Span, sRGB); } } } @@ -154,9 +164,15 @@ public void DrawCableNetwork(DrawingHandleScreen handle, List 0) { - var color = Color.ToSrgb(_powerCableColors[cableNetworkIdx] * modulator); + var color = _powerCableColors[cableNetworkIdx] * modulator; + + if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) + { + sRGB = Color.ToSrgb(color); + _sRGBLookUp[color] = sRGB; + } - handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, color); + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, cableVertexUV.Span, sRGB); } } } @@ -220,7 +236,7 @@ public List GetDecodedPowerCableChunks(Dictionary GetDecodedPowerCableChunks(Dictionary(); - var xforms = _entManager.System(); _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); var gridMatrix = xforms.GetWorldMatrix(gridUid); From d61f206b08b59719ee3e698d2a42d6fbebd8e9a4 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:53:18 +1000 Subject: [PATCH 30/40] OD --- Content.Client/Silicons/StationAi/StationAiOverlay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs index 7acbb144ebbaf0..dd1614eab59ae2 100644 --- a/Content.Client/Silicons/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -52,9 +52,8 @@ protected override void Draw(in OverlayDrawArgs args) if (grid != null) { - // TODO: Pass in attached entity's grid. - // TODO: Credit OD on the moved to code - // TODO: Call the moved-to code here. + var lookups = _entManager.System(); + var xforms = _entManager.System(); _visibleTiles.Clear(); _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); From f5cc3055afcbbad2906434fa6acd5e5012bdcd36 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:59:29 +1000 Subject: [PATCH 31/40] radar --- .../Entities/Mobs/Player/silicon.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index c36d501b2a300e..300b5105a57cb1 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -166,15 +166,6 @@ Empty: { state: empty } Occupied: { state: full } - - # Fix all of the interaction stuff etc. and make it actually work - # Also thanks moony for whatever gameticker spawning is doing. - # TODO: move camera stuff to shared and check it works + parallel - # TODO: Bump viewer range / PVS for AI specifically - # TODO: Get interactions working (need some kind of component for it idk) - # TODO: Need interaction validation - # TODO: Merge borg PR and get the vis working - - type: entity id: PlayerStationAiEmpty name: AI Core @@ -284,15 +275,6 @@ components: - type: Eye pvsScale: 1.5 - - type: Appearance - - type: GenericVisualizer - visuals: - enum.IncorporealState.Base: - base: - True: - color: '#FFFFFFFF' - False: - color: '#FFFFFF0A' - type: Visibility layer: 2 - type: Sprite From 225a69450064cabc2f3e7c40667fcd3a9d9fc9ff Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Sun, 25 Aug 2024 23:59:39 +1000 Subject: [PATCH 32/40] weh --- .../Postgres/PostgresServerDbContextModelSnapshot.cs | 2 ++ Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs index 723af17aa53065..cb9fdde4d5f464 100644 --- a/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs +++ b/Content.Server.Database/Migrations/Postgres/PostgresServerDbContextModelSnapshot.cs @@ -906,6 +906,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GroupName") .IsRequired() + .HasColumnType("text") .HasColumnName("group_name"); b.Property("ProfileRoleLoadoutId") @@ -935,6 +936,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RoleName") .IsRequired() + .HasColumnType("text") .HasColumnName("role_name"); b.HasKey("Id") diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml index 728f8ce7d58b65..d1d530ae81b728 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml @@ -265,6 +265,9 @@ access: [["Medical"], ["Command"], ["Research"]] - type: Inventory templateId: borgDutch + - type: FootstepModifier + footstepSoundCollection: + collection: FootstepHoverBorg - type: SolutionScanner - type: InteractionPopup interactSuccessString: petting-success-medical-cyborg From 91cad402f82fb099f6dc128f1acb276cbcec6d68 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 26 Aug 2024 00:13:17 +1000 Subject: [PATCH 33/40] Fix examines --- Content.Client/Verbs/VerbSystem.cs | 5 +++-- .../Entities/Structures/Wallmounts/surveillance_camera.yml | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs index c3e03528a79232..e28f48d6a50d92 100644 --- a/Content.Client/Verbs/VerbSystem.cs +++ b/Content.Client/Verbs/VerbSystem.cs @@ -78,6 +78,7 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true // Get entities List entities; + var examineFlags = LookupFlags.All & ~LookupFlags.Sensors; // Do we have to do FoV checks? if ((visibility & MenuVisibility.NoFov) == 0) @@ -88,7 +89,7 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true TryComp(player.Value, out ExaminerComponent? examiner); entities = new(); - foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize)) + foreach (var ent in _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags)) { if (_examine.CanExamine(player.Value, targetPos, Predicate, ent, examiner)) entities.Add(ent); @@ -96,7 +97,7 @@ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true } else { - entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize).ToList(); + entities = _entityLookup.GetEntitiesInRange(targetPos, EntityMenuLookupSize, flags: examineFlags).ToList(); } if (entities.Count == 0) diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index 2d4ad9bec7baf4..fd1d3ffcfa4c3b 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -8,6 +8,11 @@ bodyType: Static - type: Fixtures fixtures: + # This exists for examine. + fix1: + shape: + !type:PhysShapeCircle + radius: 0.25 light: shape: !type:PhysShapeCircle From 99b3f0a0b434e59bb37b0c6050d3d714762af643 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 26 Aug 2024 00:35:49 +1000 Subject: [PATCH 34/40] scoop mine eyes --- Content.Shared/Containers/ContainerCompSystem.cs | 13 ++++++------- .../Silicons/StationAi/SharedStationAiSystem.cs | 4 ++-- .../Prototypes/Entities/Mobs/Player/silicon.yml | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Content.Shared/Containers/ContainerCompSystem.cs b/Content.Shared/Containers/ContainerCompSystem.cs index 1e1983a331b9f5..75908efea3aca9 100644 --- a/Content.Shared/Containers/ContainerCompSystem.cs +++ b/Content.Shared/Containers/ContainerCompSystem.cs @@ -1,5 +1,6 @@ using Robust.Shared.Containers; using Robust.Shared.Prototypes; +using Robust.Shared.Timing; namespace Content.Shared.Containers; @@ -8,6 +9,7 @@ namespace Content.Shared.Containers; /// public sealed class ContainerCompSystem : EntitySystem { + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPrototypeManager _proto = default!; public override void Initialize() @@ -19,21 +21,18 @@ public override void Initialize() private void OnConRemove(Entity ent, ref EntRemovedFromContainerMessage args) { - if (args.Container.ID != ent.Comp.Container) + if (args.Container.ID != ent.Comp.Container || _timing.ApplyingState) return; - if (_proto.TryIndex(ent.Comp.Container, out var entProto)) + if (_proto.TryIndex(ent.Comp.Proto, out var entProto)) { - foreach (var entry in entProto.Components.Values) - { - RemComp(args.Entity, entry.Component); - } + EntityManager.RemoveComponents(args.Entity, entProto.Components); } } private void OnConInsert(Entity ent, ref EntInsertedIntoContainerMessage args) { - if (args.Container.ID != ent.Comp.Container) + if (args.Container.ID != ent.Comp.Container || _timing.ApplyingState) return; if (_proto.TryIndex(ent.Comp.Proto, out var entProto)) diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index a4ab3b0db0d651..e77881f5201797 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -151,7 +151,7 @@ private void OnHolderInteract(Entity ent, ref AfterInt // Try to insert our thing into them if (_slots.CanEject(ent.Owner, args.User, ent.Comp.Slot)) { - if (!_slots.TryInsert(args.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User)) + if (!_slots.TryInsert(args.Target.Value, targetHolder.Slot, ent.Comp.Slot.Item!.Value, args.User, excludeUserAudio: true)) { return; } @@ -163,7 +163,7 @@ private void OnHolderInteract(Entity ent, ref AfterInt // Otherwise try to take from them if (_slots.CanEject(args.Target.Value, args.User, targetHolder.Slot)) { - if (!_slots.TryInsert(ent.Owner, ent.Comp.Slot, targetHolder.Slot.Item!.Value, args.User)) + if (!_slots.TryInsert(ent.Owner, ent.Comp.Slot, targetHolder.Slot.Item!.Value, args.User, excludeUserAudio: true)) { return; } diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 300b5105a57cb1..857144a7b970fa 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -4,6 +4,21 @@ description: Components added / removed from an entity that gets inserted into an AI core. noSpawn: true components: + - type: IntrinsicRadioReceiver + - type: IntrinsicRadioTransmitter + channels: + - Binary + - Common + - Command + - Engineering + - Medical + - Science + - Security + - Service + - Supply + - type: ActiveRadio + receiveAllChannels: true + globalReceive: true - type: IgnoreUIRange - type: StationAiHeld - type: StationAiOverlay From 05e0a96309b8867106d486406ce6ebb4bdac409d Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 26 Aug 2024 02:03:36 +1000 Subject: [PATCH 35/40] fixes --- .../Entities/Objects/Fun/Instruments/base_instruments.yml | 2 +- Resources/Prototypes/Entities/Objects/Misc/paper.yml | 4 ++-- .../Prototypes/Entities/Objects/Tools/access_configurator.yml | 2 +- .../Entities/Structures/Machines/Medical/cryo_pod.yml | 2 +- .../Entities/Structures/Wallmounts/surveillance_camera.yml | 1 - Resources/Prototypes/Loadouts/role_loadouts.yml | 1 - 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml index 614af2a4886ace..5a3b967f98904d 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/base_instruments.yml @@ -32,7 +32,7 @@ blockSpectators: true # otherwise they can play client-side music inHandsOnly: false singleUser: true - requireHands: true + requiresComplex: true verbText: verb-instrument-openui key: enum.InstrumentUiKey.Key - type: InteractionOutline diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index 1cf333bbafad11..4646c930865442 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -18,7 +18,7 @@ - type: PaperLabelType - type: ActivatableUI key: enum.PaperUiKey.Key - requireHands: false + requiresComplex: false - type: UserInterface interfaces: enum.PaperUiKey.Key: @@ -647,7 +647,7 @@ - Paper - type: ActivatableUI key: enum.PaperUiKey.Key - requireHands: false + requiresComplex: false - type: UserInterface interfaces: enum.PaperUiKey.Key: diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index 3c5bc93b42c27f..66f4689099e0ca 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -66,7 +66,7 @@ type: AccessOverriderBoundUserInterface - type: ActivatableUI key: enum.AccessOverriderUiKey.Key - requireHands: true + requiresComplex: true requireActiveHand: false singleUser: true - type: ItemSlots diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml index 0485b5a517836b..539c8a244ae34f 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml @@ -96,7 +96,7 @@ type: WiresBoundUserInterface - type: ActivatableUI key: enum.HealthAnalyzerUiKey.Key - requireHands: false + requiresComplex: false - type: ActivatableUIRequiresPower - type: PointLight color: "#3a807f" diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml index fd1d3ffcfa4c3b..3530fe196c09be 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/surveillance_camera.yml @@ -21,7 +21,6 @@ mask: - GhostImpassable - type: LightOnCollide - fixtureId: light - type: PointLight enabled: false radius: 5 diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index 9c0131dd8fb17c..b50e49fede7808 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -28,7 +28,6 @@ # Silicons - type: roleLoadout id: JobStationAi - canCustomiseName: true nameDataset: names_ai # Civilian From 8b79225276e113b56e352742f39fb5e44b2ed302 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 26 Aug 2024 02:05:42 +1000 Subject: [PATCH 36/40] reh --- Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index e77881f5201797..e4827ac1b784cb 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Verbs; using Robust.Shared.Containers; using Robust.Shared.Map.Components; +using Robust.Shared.Network; using Robust.Shared.Serialization; using Robust.Shared.Timing; @@ -19,6 +20,7 @@ namespace Content.Shared.Silicons.StationAi; public abstract partial class SharedStationAiSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; @@ -199,6 +201,10 @@ private void OnHolderMapInit(Entity ent, ref MapInitEv private void OnAiShutdown(Entity ent, ref ComponentShutdown args) { + // TODO: Tryqueuedel + if (_net.IsClient) + return; + QueueDel(ent.Comp.RemoteEntity); ent.Comp.RemoteEntity = null; } From 4c635d4db3295dc70d7e299dce7e0dac1781823f Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Mon, 26 Aug 2024 19:32:08 +1000 Subject: [PATCH 37/40] Optimise --- .../Silicons/StationAi/StationAiOverlay.cs | 13 ++- Content.Server/Mind/MindSystem.cs | 4 +- Content.Shared/Mind/SharedMindSystem.cs | 4 + .../StationAi/SharedStationAiSystem.Held.cs | 25 ++++- .../StationAi/SharedStationAiSystem.cs | 36 +++++- .../StationAi/StationAiVisionSystem.cs | 103 ++++-------------- .../Locale/en-US/silicons/station-ai.ftl | 1 + .../Structures/Wallmounts/intercom.yml | 1 + .../Entities/Structures/Wallmounts/timer.yml | 1 + 9 files changed, 99 insertions(+), 89 deletions(-) diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs index dd1614eab59ae2..8bf436b200ac9b 100644 --- a/Content.Client/Silicons/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -5,6 +5,7 @@ using Robust.Shared.Enums; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; +using Robust.Shared.Timing; namespace Content.Client.Silicons.StationAi; @@ -22,6 +23,9 @@ public sealed class StationAiOverlay : Overlay private IRenderTexture? _staticTexture; private IRenderTexture? _stencilTexture; + private float _updateRate = 1f / 30f; + private float _accumulator; + public StationAiOverlay() { IoCManager.InjectDependencies(this); @@ -49,14 +53,19 @@ protected override void Draw(in OverlayDrawArgs args) _entManager.TryGetComponent(gridUid, out MapGridComponent? grid); var invMatrix = args.Viewport.GetWorldToLocalMatrix(); + _accumulator -= (float) IoCManager.Resolve().FrameTime.TotalSeconds; if (grid != null) { var lookups = _entManager.System(); var xforms = _entManager.System(); - _visibleTiles.Clear(); - _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + if (_accumulator <= 0f) + { + _accumulator = MathF.Max(0f, _accumulator + _updateRate); + _visibleTiles.Clear(); + _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + } var gridMatrix = xforms.GetWorldMatrix(gridUid); var matty = Matrix3x2.Multiply(gridMatrix, invMatrix); diff --git a/Content.Server/Mind/MindSystem.cs b/Content.Server/Mind/MindSystem.cs index 4271d76b445916..2e7c31ec7a65d9 100644 --- a/Content.Server/Mind/MindSystem.cs +++ b/Content.Server/Mind/MindSystem.cs @@ -341,13 +341,13 @@ public override void SetUserId(EntityUid mindId, NetUserId? userId, MindComponen } } - public void ControlMob(EntityUid user, EntityUid target) + public override void ControlMob(EntityUid user, EntityUid target) { if (TryComp(user, out ActorComponent? actor)) ControlMob(actor.PlayerSession.UserId, target); } - public void ControlMob(NetUserId user, EntityUid target) + public override void ControlMob(NetUserId user, EntityUid target) { var (mindId, mind) = GetOrCreateMind(user); diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 24b47b641205d2..c8e1c1a4b3a332 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -315,6 +315,10 @@ public virtual void TransferTo(EntityUid mindId, EntityUid? entity, bool ghostCh { } + public virtual void ControlMob(EntityUid user, EntityUid target) {} + + public virtual void ControlMob(NetUserId user, EntityUid target) {} + /// /// Tries to create and add an objective from its prototype id. /// diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index 0dac2be39ac804..a6c57f5940012e 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -32,6 +32,24 @@ private void OnCoreJump(Entity ent, ref JumpToCoreEvent _xforms.DropNextTo(core.Comp.RemoteEntity.Value, core.Owner) ; } + /// + /// Tries to get the entity held in the AI core. + /// + private bool TryGetHeld(Entity entity, out EntityUid held) + { + held = EntityUid.Invalid; + + if (!Resolve(entity.Owner, ref entity.Comp)) + return false; + + if (!_containers.TryGetContainer(entity.Owner, StationAiCoreComponent.Container, out var container) || + container.ContainedEntities.Count == 0) + return false; + + held = container.ContainedEntities[0]; + return true; + } + private bool TryGetCore(EntityUid ent, out Entity core) { if (!_containers.TryGetContainingContainer(ent, out var container) || @@ -87,8 +105,13 @@ private void OnHeldInteraction(Entity ent, ref Interacti private void OnTargetVerbs(Entity ent, ref GetVerbsEvent args) { - if (!args.CanComplexInteract || !ent.Comp.Enabled || !HasComp(args.User)) + if (!args.CanComplexInteract || + !ent.Comp.Enabled || + !HasComp(args.User) || + !HasComp(args.Target)) + { return; + } var user = args.User; var target = args.Target; diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index e4827ac1b784cb..4fe02b1b74707e 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -1,9 +1,12 @@ using Content.Shared.ActionBlocker; using Content.Shared.Actions; +using Content.Shared.Administration.Managers; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Database; using Content.Shared.Doors.Systems; using Content.Shared.Interaction; using Content.Shared.Item.ItemToggle; +using Content.Shared.Mind; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Content.Shared.Power; @@ -12,6 +15,7 @@ using Robust.Shared.Containers; using Robust.Shared.Map.Components; using Robust.Shared.Network; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; @@ -19,17 +23,19 @@ namespace Content.Shared.Silicons.StationAi; public abstract partial class SharedStationAiSystem : EntitySystem { + [Dependency] private readonly ISharedAdminManager _admin = default!; [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ItemSlotsSystem _slots = default!; + [Dependency] private readonly ItemToggleSystem _toggles = default!; [Dependency] private readonly ActionBlockerSystem _blocker = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedContainerSystem _containers = default!; [Dependency] private readonly SharedDoorSystem _doors = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; - [Dependency] private readonly ItemToggleSystem _toggles = default!; [Dependency] protected readonly SharedMapSystem Maps = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedTransformSystem _xforms = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; @@ -42,6 +48,9 @@ public abstract partial class SharedStationAiSystem : EntitySystem // StationAiOverlay handles the static overlay. It also handles interaction blocking on client and server // for anything under it. + [ValidatePrototypeId] + private static readonly EntProtoId DefaultAi = "StationAiBrain"; + public override void Initialize() { base.Initialize(); @@ -68,6 +77,29 @@ public override void Initialize() SubscribeLocalEvent(OnAiMapInit); SubscribeLocalEvent(OnAiShutdown); SubscribeLocalEvent(OnCorePower); + SubscribeLocalEvent>(OnCoreVerbs); + } + + private void OnCoreVerbs(Entity ent, ref GetVerbsEvent args) + { + if (!_admin.IsAdmin(args.User) || + TryGetHeld((ent.Owner, ent.Comp), out _)) + { + return; + } + + var user = args.User; + + args.Verbs.Add(new Verb() + { + Text = Loc.GetString("station-ai-takeover"), + Act = () => + { + var brain = SpawnInContainerOrDrop(DefaultAi, ent.Owner, StationAiCoreComponent.Container); + _mind.ControlMob(user, brain); + }, + Impact = LogImpact.High, + }); } private void OnAiAccessible(Entity ent, ref AccessibleOverrideEvent args) diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index 4056fd41347eb0..b70078751e09d6 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -37,11 +37,6 @@ public sealed class StationAiVisionSystem : EntitySystem /// private bool FastPath; - /// - /// Have we found the target tile if we're only checking for a single one. - /// - private bool TargetFound; - public override void Initialize() { base.Initialize(); @@ -111,14 +106,12 @@ public bool IsAccessible(Entity grid, Vector2i tile, float exp _job.BoundaryTiles.Add(new HashSet()); } - _job.TargetTile = tile; - TargetFound = false; _singleTiles.Clear(); _job.Grid = grid; _job.VisibleTiles = _singleTiles; _parallel.ProcessNow(_job, _job.Data.Count); - return TargetFound; + return _job.VisibleTiles.Contains(tile); } private bool IsOccluded(Entity grid, Vector2i tile) @@ -149,12 +142,13 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _viewportTiles.Clear(); _opaque.Clear(); _seeds.Clear(); - var expandedBounds = worldBounds.Enlarged(expansionSize); // TODO: Would be nice to be able to run this while running the other stuff. _seedJob.Grid = grid; - var localAABB = _xforms.GetInvWorldMatrix(grid).TransformBox(expandedBounds); - _seedJob.ExpandedBounds = localAABB; + var invMatrix = _xforms.GetInvWorldMatrix(grid); + var localAabb = invMatrix.TransformBox(worldBounds); + var enlargedLocalAabb = invMatrix.TransformBox(worldBounds.Enlarged(expansionSize)); + _seedJob.ExpandedBounds = enlargedLocalAabb; _parallel.ProcessNow(_seedJob); _job.Data.Clear(); FastPath = false; @@ -171,7 +165,7 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash return; // Get viewport tiles - var tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAABB, ignoreEmpty: false); + var tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAabb, ignoreEmpty: false); while (tileEnumerator.MoveNext(out var tileRef)) { @@ -183,9 +177,8 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _viewportTiles.Add(tileRef.GridIndices); } - tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, localAABB, ignoreEmpty: false); + tileEnumerator = _maps.GetLocalTilesEnumerator(grid, grid, enlargedLocalAabb, ignoreEmpty: false); - // Get all other relevant tiles. while (tileEnumerator.MoveNext(out var tileRef)) { if (_viewportTiles.Contains(tileRef.GridIndices)) @@ -207,8 +200,6 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _job.BoundaryTiles.Add(new HashSet()); } - _job.TargetTile = null; - TargetFound = false; _job.Grid = grid; _job.VisibleTiles = visibleTiles; _parallel.ProcessNow(_job, _job.Data.Count); @@ -251,6 +242,7 @@ private bool CheckNeighborsVis( return false; } + /// /// Checks whether this tile fits the definition of a "corner" /// private bool IsCorner( @@ -303,9 +295,6 @@ private record struct ViewJob() : IParallelRobustJob public Entity Grid; public List> Data = new(); - // If we're doing range-checks might be able to early out - public Vector2i? TargetTile; - public HashSet VisibleTiles; public readonly List> Vis1 = new(); @@ -316,18 +305,6 @@ private record struct ViewJob() : IParallelRobustJob public void Execute(int index) { - // If we're looking for a single tile then early-out if someone else has found it. - if (TargetTile != null) - { - lock (System) - { - if (System.TargetFound) - { - return; - } - } - } - var seed = Data[index]; var seedXform = EntManager.GetComponent(seed); @@ -339,30 +316,11 @@ public void Execute(int index) Grid.Comp, new Circle(System._xforms.GetWorldPosition(seedXform), seed.Comp.Range), ignoreEmpty: false); - // Try to find the target tile. - if (TargetTile != null) + lock (VisibleTiles) { foreach (var tile in squircles) { - if (tile.GridIndices == TargetTile) - { - lock (System) - { - System.TargetFound = true; - } - - return; - } - } - } - else - { - lock (VisibleTiles) - { - foreach (var tile in squircles) - { - VisibleTiles.Add(tile.GridIndices); - } + VisibleTiles.Add(tile.GridIndices); } } @@ -481,40 +439,21 @@ public void Execute(int index) vis1[tile] = -1; } - if (TargetTile != null) - { - if (vis1.TryGetValue(TargetTile.Value, out var tileVis)) - { - DebugTools.Assert(seedTiles.Contains(TargetTile.Value)); - - if (tileVis != 0) - { - lock (System) - { - System.TargetFound = true; - return; - } - } - } - } - else + // vis2 is what we care about for LOS. + foreach (var tile in seedTiles) { - // vis2 is what we care about for LOS. - foreach (var tile in seedTiles) - { - // If not in viewport don't care. - if (!System._viewportTiles.Contains(tile)) - continue; + // If not in viewport don't care. + if (!System._viewportTiles.Contains(tile)) + continue; - var tileVis = vis1.GetValueOrDefault(tile, 0); + var tileVis = vis1.GetValueOrDefault(tile, 0); - if (tileVis != 0) + if (tileVis != 0) + { + // No idea if it's better to do this inside or out. + lock (VisibleTiles) { - // No idea if it's better to do this inside or out. - lock (VisibleTiles) - { - VisibleTiles.Add(tile); - } + VisibleTiles.Add(tile); } } } diff --git a/Resources/Locale/en-US/silicons/station-ai.ftl b/Resources/Locale/en-US/silicons/station-ai.ftl index 598d4218395607..d51a99ebb0437c 100644 --- a/Resources/Locale/en-US/silicons/station-ai.ftl +++ b/Resources/Locale/en-US/silicons/station-ai.ftl @@ -2,6 +2,7 @@ ai-wire-snipped = Wire has been cut at {$coords}. wire-name-ai-vision-light = AIV wire-name-ai-act-light = AIA +station-ai-takeover = AI takeover # Radial actions ai-open = Open actions diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml index ca1b1b6c40f51e..60cea31fff296d 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -4,6 +4,7 @@ description: An intercom. For when the station just needs to know something. abstract: true components: + - type: StationAiWhitelist - type: WallMount - type: ApcPowerReceiver - type: Electrified diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml index ff935055ff9582..595915202d86f6 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml @@ -7,6 +7,7 @@ snap: - Wallmount components: + - type: StationAiWhitelist - type: Transform anchored: true - type: WallMount From 369216e1d065e9c05efdc66ad8a3c79fa7f09e77 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 27 Aug 2024 00:31:32 +1000 Subject: [PATCH 38/40] Final round of optimisations --- .../Silicons/StationAi/StationAiOverlay.cs | 9 ++++--- .../StationAi/SharedStationAiSystem.cs | 15 ++++++++--- .../StationAi/StationAiVisionSystem.cs | 27 +++++++++++-------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/Content.Client/Silicons/StationAi/StationAiOverlay.cs b/Content.Client/Silicons/StationAi/StationAiOverlay.cs index 8bf436b200ac9b..15a8a3a63fe90a 100644 --- a/Content.Client/Silicons/StationAi/StationAiOverlay.cs +++ b/Content.Client/Silicons/StationAi/StationAiOverlay.cs @@ -4,6 +4,7 @@ using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Map.Components; +using Robust.Shared.Physics; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -13,6 +14,7 @@ public sealed class StationAiOverlay : Overlay { [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPrototypeManager _proto = default!; @@ -51,11 +53,12 @@ protected override void Draw(in OverlayDrawArgs args) _entManager.TryGetComponent(playerEnt, out TransformComponent? playerXform); var gridUid = playerXform?.GridUid ?? EntityUid.Invalid; _entManager.TryGetComponent(gridUid, out MapGridComponent? grid); + _entManager.TryGetComponent(gridUid, out BroadphaseComponent? broadphase); var invMatrix = args.Viewport.GetWorldToLocalMatrix(); - _accumulator -= (float) IoCManager.Resolve().FrameTime.TotalSeconds; + _accumulator -= (float) _timing.FrameTime.TotalSeconds; - if (grid != null) + if (grid != null && broadphase != null) { var lookups = _entManager.System(); var xforms = _entManager.System(); @@ -64,7 +67,7 @@ protected override void Draw(in OverlayDrawArgs args) { _accumulator = MathF.Max(0f, _accumulator + _updateRate); _visibleTiles.Clear(); - _entManager.System().GetView((gridUid, grid), worldBounds, _visibleTiles); + _entManager.System().GetView((gridUid, broadphase, grid), worldBounds, _visibleTiles); } var gridMatrix = xforms.GetWorldMatrix(gridUid); diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 4fe02b1b74707e..ec5d906b38c84b 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Containers; using Robust.Shared.Map.Components; using Robust.Shared.Network; +using Robust.Shared.Physics; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; @@ -48,6 +49,9 @@ public abstract partial class SharedStationAiSystem : EntitySystem // StationAiOverlay handles the static overlay. It also handles interaction blocking on client and server // for anything under it. + private EntityQuery _broadphaseQuery; + private EntityQuery _gridQuery; + [ValidatePrototypeId] private static readonly EntProtoId DefaultAi = "StationAiBrain"; @@ -55,6 +59,9 @@ public override void Initialize() { base.Initialize(); + _broadphaseQuery = GetEntityQuery(); + _gridQuery = GetEntityQuery(); + InitializeAirlock(); InitializeHeld(); InitializeLight(); @@ -138,7 +145,7 @@ private void OnAiBuiCheck(Entity ent, ref BoundUser return; } - if (!TryComp(targetXform.GridUid, out MapGridComponent? grid)) + if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid)) { return; } @@ -147,7 +154,7 @@ private void OnAiBuiCheck(Entity ent, ref BoundUser lock (_vision) { - if (_vision.IsAccessible((targetXform.GridUid.Value, grid), targetTile, fastPath: true)) + if (_vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile, fastPath: true)) { args.Result = BoundUserInterfaceRangeResult.Pass; } @@ -167,14 +174,14 @@ private void OnAiInRange(Entity ent, ref InRangeOverr // Validate it's in camera range yes this is expensive. // Yes it needs optimising - if (!TryComp(targetXform.GridUid, out MapGridComponent? grid)) + if (!_broadphaseQuery.TryComp(targetXform.GridUid, out var broadphase) || !_gridQuery.TryComp(targetXform.GridUid, out var grid)) { return; } var targetTile = Maps.LocalToTile(targetXform.GridUid.Value, grid, targetXform.Coordinates); - args.InRange = _vision.IsAccessible((targetXform.GridUid.Value, grid), targetTile); + args.InRange = _vision.IsAccessible((targetXform.GridUid.Value, broadphase, grid), targetTile); } private void OnHolderInteract(Entity ent, ref AfterInteractEvent args) diff --git a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs index b70078751e09d6..bdc62a6bb3788e 100644 --- a/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs +++ b/Content.Shared/Silicons/StationAi/StationAiVisionSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.StationAi; using Robust.Shared.Map.Components; +using Robust.Shared.Physics; using Robust.Shared.Threading; using Robust.Shared.Utility; @@ -25,6 +26,8 @@ public sealed class StationAiVisionSystem : EntitySystem private readonly HashSet> _seeds = new(); private readonly HashSet _viewportTiles = new(); + private EntityQuery _occluderQuery; + // Dummy set private readonly HashSet _singleTiles = new(); @@ -41,6 +44,8 @@ public override void Initialize() { base.Initialize(); + _occluderQuery = GetEntityQuery(); + _seedJob = new() { System = this, @@ -57,16 +62,16 @@ public override void Initialize() /// /// Returns whether a tile is accessible based on vision. /// - public bool IsAccessible(Entity grid, Vector2i tile, float expansionSize = 8.5f, bool fastPath = false) + public bool IsAccessible(Entity grid, Vector2i tile, float expansionSize = 8.5f, bool fastPath = false) { _viewportTiles.Clear(); _opaque.Clear(); _seeds.Clear(); _viewportTiles.Add(tile); - var localBounds = _lookup.GetLocalBounds(tile, grid.Comp.TileSize); + var localBounds = _lookup.GetLocalBounds(tile, grid.Comp2.TileSize); var expandedBounds = localBounds.Enlarged(expansionSize); - _seedJob.Grid = grid; + _seedJob.Grid = (grid.Owner, grid.Comp2); _seedJob.ExpandedBounds = expandedBounds; _parallel.ProcessNow(_seedJob); _job.Data.Clear(); @@ -107,18 +112,18 @@ public bool IsAccessible(Entity grid, Vector2i tile, float exp } _singleTiles.Clear(); - _job.Grid = grid; + _job.Grid = (grid.Owner, grid.Comp2); _job.VisibleTiles = _singleTiles; _parallel.ProcessNow(_job, _job.Data.Count); return _job.VisibleTiles.Contains(tile); } - private bool IsOccluded(Entity grid, Vector2i tile) + private bool IsOccluded(Entity grid, Vector2i tile) { - var tileBounds = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(-0.05f); + var tileBounds = _lookup.GetLocalBounds(tile, grid.Comp2.TileSize).Enlarged(-0.05f); _occluders.Clear(); - _lookup.GetLocalEntitiesIntersecting(grid.Owner, tileBounds, _occluders, LookupFlags.Static); + _lookup.GetLocalEntitiesIntersecting((grid.Owner, grid.Comp1), tileBounds, _occluders, query: _occluderQuery, flags: LookupFlags.Static | LookupFlags.Approximate); var anyOccluders = false; foreach (var occluder in _occluders) @@ -137,14 +142,14 @@ private bool IsOccluded(Entity grid, Vector2i tile) /// Gets a byond-equivalent for tiles in the specified worldAABB. /// /// How much to expand the bounds before to find vision intersecting it. Makes this the largest vision size + 1 tile. - public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 8.5f) + public void GetView(Entity grid, Box2Rotated worldBounds, HashSet visibleTiles, float expansionSize = 8.5f) { _viewportTiles.Clear(); _opaque.Clear(); _seeds.Clear(); // TODO: Would be nice to be able to run this while running the other stuff. - _seedJob.Grid = grid; + _seedJob.Grid = (grid.Owner, grid.Comp2); var invMatrix = _xforms.GetInvWorldMatrix(grid); var localAabb = invMatrix.TransformBox(worldBounds); var enlargedLocalAabb = invMatrix.TransformBox(worldBounds.Enlarged(expansionSize)); @@ -200,7 +205,7 @@ public void GetView(Entity grid, Box2Rotated worldBounds, Hash _job.BoundaryTiles.Add(new HashSet()); } - _job.Grid = grid; + _job.Grid = (grid.Owner, grid.Comp2); _job.VisibleTiles = visibleTiles; _parallel.ProcessNow(_job, _job.Data.Count); } @@ -280,7 +285,7 @@ private record struct SeedJob() : IRobustJob public void Execute() { - System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds); + System._lookup.GetLocalEntitiesIntersecting(Grid.Owner, ExpandedBounds, System._seeds, flags: LookupFlags.All | LookupFlags.Approximate); } } From 05839d19778f1670cb5d7efa8a42fca30c3c84d0 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 28 Aug 2024 10:00:07 +1000 Subject: [PATCH 39/40] Fixes --- Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs | 1 + Resources/Prototypes/Maps/debug.yml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index ec5d906b38c84b..348b0b04657d17 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -100,6 +100,7 @@ private void OnCoreVerbs(Entity ent, ref GetVerbsEvent { var brain = SpawnInContainerOrDrop(DefaultAi, ent.Owner, StationAiCoreComponent.Container); diff --git a/Resources/Prototypes/Maps/debug.yml b/Resources/Prototypes/Maps/debug.yml index ee9ab75e078964..8d4cc550a27c97 100644 --- a/Resources/Prototypes/Maps/debug.yml +++ b/Resources/Prototypes/Maps/debug.yml @@ -27,7 +27,6 @@ - type: StationJobs availableJobs: Captain: [ -1, -1 ] - StationAi: [1, 1] - type: gameMap id: TestTeg From c394c766c0aa12973028add9d324b5a52f4034a1 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Wed, 28 Aug 2024 10:46:53 +1000 Subject: [PATCH 40/40] fixes --- Resources/Prototypes/Entities/Mobs/Player/silicon.yml | 1 + .../Entities/Structures/Machines/Computers/computers.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 857144a7b970fa..686f575e8bb14e 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -104,6 +104,7 @@ abstract: true description: Handles AI interactions across holocards + AI cores components: + - type: ItemSlots - type: StationAiHolder slot: name: station-ai-mind-slot diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index 18dae76dec1bca..8d1480e779c5f1 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -1160,3 +1160,4 @@ - type: ContainerContainer containers: circuit_holder: !type:ContainerSlot + board: !type:Container