diff --git a/Content.Client/Commands/DebugAiCommand.cs b/Content.Client/Commands/DebugAiCommand.cs index da453d3059..e35dd21387 100644 --- a/Content.Client/Commands/DebugAiCommand.cs +++ b/Content.Client/Commands/DebugAiCommand.cs @@ -1,4 +1,4 @@ -using Content.Client.AI; +using Content.Client.NPC; using JetBrains.Annotations; using Robust.Shared.Console; using Robust.Shared.GameObjects; diff --git a/Content.Client/Commands/DebugPathfindingCommand.cs b/Content.Client/Commands/DebugPathfindingCommand.cs index 6484edd716..69738559df 100644 --- a/Content.Client/Commands/DebugPathfindingCommand.cs +++ b/Content.Client/Commands/DebugPathfindingCommand.cs @@ -1,4 +1,4 @@ -using Content.Client.AI; +using Content.Client.NPC; using JetBrains.Annotations; using Robust.Shared.Console; using Robust.Shared.GameObjects; diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 78ba052e49..989e0f2c05 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -80,6 +80,8 @@ namespace Content.Client.Entry prototypes.RegisterIgnore("objective"); prototypes.RegisterIgnore("holiday"); prototypes.RegisterIgnore("aiFaction"); + prototypes.RegisterIgnore("htnCompound"); + prototypes.RegisterIgnore("htnPrimitive"); prototypes.RegisterIgnore("gameMap"); prototypes.RegisterIgnore("behaviorSet"); prototypes.RegisterIgnore("lobbyBackground"); diff --git a/Content.Client/MobState/DamageStateVisualizerSystem.cs b/Content.Client/MobState/DamageStateVisualizerSystem.cs index dadcdaf3d1..d4cdd4ffdd 100644 --- a/Content.Client/MobState/DamageStateVisualizerSystem.cs +++ b/Content.Client/MobState/DamageStateVisualizerSystem.cs @@ -48,15 +48,18 @@ public sealed class DamageStateVisualizerSystem : VisualizerSystem (int) DrawDepth.Items) + if (data == DamageState.Dead) { - component.OriginalDrawDepth = sprite.DrawDepth; - sprite.DrawDepth = (int) DrawDepth.Items; + if (sprite.DrawDepth > (int) DrawDepth.FloorObjects) + { + component.OriginalDrawDepth = sprite.DrawDepth; + sprite.DrawDepth = (int) DrawDepth.FloorObjects; + } } else if (component.OriginalDrawDepth != null) { sprite.DrawDepth = component.OriginalDrawDepth.Value; - component. OriginalDrawDepth = null; + component.OriginalDrawDepth = null; } } } diff --git a/Content.Client/AI/ClientAiDebugSystem.cs b/Content.Client/NPC/ClientAiDebugSystem.cs similarity index 89% rename from Content.Client/AI/ClientAiDebugSystem.cs rename to Content.Client/NPC/ClientAiDebugSystem.cs index e9b1c3b2cf..5f807d1398 100644 --- a/Content.Client/AI/ClientAiDebugSystem.cs +++ b/Content.Client/NPC/ClientAiDebugSystem.cs @@ -1,29 +1,23 @@ -using System; -using System.Collections.Generic; using Content.Client.Stylesheets; using Content.Shared.AI; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; using static Robust.Client.UserInterface.Controls.BoxContainer; -namespace Content.Client.AI +namespace Content.Client.NPC { -#if DEBUG public sealed class ClientAiDebugSystem : EntitySystem { [Dependency] private readonly IEyeManager _eyeManager = default!; - private AiDebugMode _tooltips = AiDebugMode.None; + public AiDebugMode Tooltips { get; private set; } = AiDebugMode.None; private readonly Dictionary _aiBoxes = new(); public override void Update(float frameTime) { base.Update(frameTime); - if (_tooltips == 0) + if (Tooltips == 0) { if (_aiBoxes.Count > 0) { @@ -76,7 +70,7 @@ namespace Content.Client.AI private void HandleUtilityAiDebugMessage(SharedAiDebug.UtilityAiDebugMessage message) { - if ((_tooltips & AiDebugMode.Thonk) != 0) + if ((Tooltips & AiDebugMode.Thonk) != 0) { // I guess if it's out of range we don't know about it? var entity = message.EntityUid; @@ -93,7 +87,7 @@ namespace Content.Client.AI private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message) { - if ((_tooltips & AiDebugMode.Paths) != 0) + if ((Tooltips & AiDebugMode.Paths) != 0) { var entity = message.EntityUid; TryCreatePanel(entity); @@ -107,7 +101,7 @@ namespace Content.Client.AI private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message) { - if ((_tooltips & AiDebugMode.Paths) != 0) + if ((Tooltips & AiDebugMode.Paths) != 0) { var entity = message.EntityUid; TryCreatePanel(entity); @@ -126,23 +120,23 @@ namespace Content.Client.AI tooltip.Dispose(); } _aiBoxes.Clear(); - _tooltips = AiDebugMode.None; + Tooltips = AiDebugMode.None; } - private void EnableTooltip(AiDebugMode tooltip) + public void EnableTooltip(AiDebugMode tooltip) { - _tooltips |= tooltip; + Tooltips |= tooltip; } - private void DisableTooltip(AiDebugMode tooltip) + public void DisableTooltip(AiDebugMode tooltip) { - _tooltips &= ~tooltip; + Tooltips &= ~tooltip; } public void ToggleTooltip(AiDebugMode tooltip) { - if ((_tooltips & tooltip) != 0) + if ((Tooltips & tooltip) != 0) { DisableTooltip(tooltip); } @@ -200,5 +194,4 @@ namespace Content.Client.AI Paths = 1 << 1, Thonk = 1 << 2, } -#endif } diff --git a/Content.Client/AI/ClientPathfindingDebugSystem.cs b/Content.Client/NPC/ClientPathfindingDebugSystem.cs similarity index 94% rename from Content.Client/AI/ClientPathfindingDebugSystem.cs rename to Content.Client/NPC/ClientPathfindingDebugSystem.cs index d65f4a87c0..7c1de6105e 100644 --- a/Content.Client/AI/ClientPathfindingDebugSystem.cs +++ b/Content.Client/NPC/ClientPathfindingDebugSystem.cs @@ -1,29 +1,21 @@ -using System; -using System.Collections.Generic; using System.Linq; using Content.Shared.AI; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; -namespace Content.Client.AI +namespace Content.Client.NPC { -#if DEBUG public sealed class ClientPathfindingDebugSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - - private PathfindingDebugMode _modes = PathfindingDebugMode.None; + public PathfindingDebugMode Modes { get; private set; } = PathfindingDebugMode.None; private float _routeDuration = 4.0f; // How long before we remove a route from the overlay private DebugPathfindingOverlay? _overlay; @@ -47,8 +39,8 @@ namespace Content.Client.AI private void HandleAStarRouteMessage(SharedAiDebug.AStarRouteMessage message) { - if ((_modes & PathfindingDebugMode.Nodes) != 0 || - (_modes & PathfindingDebugMode.Route) != 0) + if ((Modes & PathfindingDebugMode.Nodes) != 0 || + (Modes & PathfindingDebugMode.Route) != 0) { _overlay?.AStarRoutes.Add(message); Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () => @@ -61,8 +53,8 @@ namespace Content.Client.AI private void HandleJpsRouteMessage(SharedAiDebug.JpsRouteMessage message) { - if ((_modes & PathfindingDebugMode.Nodes) != 0 || - (_modes & PathfindingDebugMode.Route) != 0) + if ((Modes & PathfindingDebugMode.Nodes) != 0 || + (Modes & PathfindingDebugMode.Route) != 0) { _overlay?.JpsRoutes.Add(message); Timer.Spawn(TimeSpan.FromSeconds(_routeDuration), () => @@ -96,7 +88,7 @@ namespace Content.Client.AI } var overlayManager = IoCManager.Resolve(); - _overlay = new DebugPathfindingOverlay(EntityManager, _eyeManager, _playerManager, _prototypeManager) {Modes = _modes}; + _overlay = new DebugPathfindingOverlay(EntityManager, _eyeManager, _playerManager, _prototypeManager) {Modes = Modes}; overlayManager.AddOverlay(_overlay); return _overlay; @@ -117,22 +109,22 @@ namespace Content.Client.AI public void Disable() { - _modes = PathfindingDebugMode.None; + Modes = PathfindingDebugMode.None; DisableOverlay(); } - private void EnableMode(PathfindingDebugMode tooltip) + public void EnableMode(PathfindingDebugMode tooltip) { - _modes |= tooltip; - if (_modes != 0) + Modes |= tooltip; + if (Modes != 0) { EnableOverlay(); } if (_overlay != null) { - _overlay.Modes = _modes; + _overlay.Modes = Modes; } if (tooltip == PathfindingDebugMode.Graph) @@ -149,27 +141,27 @@ namespace Content.Client.AI // So need further investigation. } - private void DisableMode(PathfindingDebugMode mode) + public void DisableMode(PathfindingDebugMode mode) { - if (mode == PathfindingDebugMode.Regions && (_modes & PathfindingDebugMode.Regions) != 0) + if (mode == PathfindingDebugMode.Regions && (Modes & PathfindingDebugMode.Regions) != 0) { RaiseNetworkEvent(new SharedAiDebug.UnsubscribeReachableMessage()); } - _modes &= ~mode; - if (_modes == 0) + Modes &= ~mode; + if (Modes == 0) { DisableOverlay(); } else if (_overlay != null) { - _overlay.Modes = _modes; + _overlay.Modes = Modes; } } public void ToggleTooltip(PathfindingDebugMode mode) { - if ((_modes & mode) != 0) + if ((Modes & mode) != 0) { DisableMode(mode); } @@ -525,5 +517,4 @@ namespace Content.Client.AI CachedRegions = 1 << 3, Regions = 1 << 4, } -#endif } diff --git a/Content.Client/NPC/HTN/HTNComponent.cs b/Content.Client/NPC/HTN/HTNComponent.cs new file mode 100644 index 0000000000..4e5d233e19 --- /dev/null +++ b/Content.Client/NPC/HTN/HTNComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Client.NPC.HTN; + +[RegisterComponent] +public sealed class HTNComponent : NPCComponent +{ + public string DebugText = string.Empty; +} diff --git a/Content.Client/NPC/HTN/HTNOverlay.cs b/Content.Client/NPC/HTN/HTNOverlay.cs new file mode 100644 index 0000000000..30234cb31a --- /dev/null +++ b/Content.Client/NPC/HTN/HTNOverlay.cs @@ -0,0 +1,41 @@ +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Enums; + +namespace Content.Client.NPC.HTN; + +public sealed class HTNOverlay : Overlay +{ + private readonly IEntityManager _entManager = default!; + private readonly Font _font = default!; + + public override OverlaySpace Space => OverlaySpace.ScreenSpace; + + public HTNOverlay(IEntityManager entManager, IResourceCache resourceCache) + { + _entManager = entManager; + _font = new VectorFont(resourceCache.GetResource("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (args.ViewportControl == null) + return; + + var handle = args.ScreenHandle; + + foreach (var (comp, xform) in _entManager.EntityQuery(true)) + { + if (string.IsNullOrEmpty(comp.DebugText) || xform.MapID != args.MapId) + continue; + + var worldPos = xform.WorldPosition; + + if (!args.WorldAABB.Contains(worldPos)) + continue; + + var screenPos = args.ViewportControl.WorldToScreen(worldPos); + handle.DrawString(_font, screenPos + new Vector2(0, 10f), comp.DebugText); + } + } +} diff --git a/Content.Client/NPC/HTN/HTNSystem.cs b/Content.Client/NPC/HTN/HTNSystem.cs new file mode 100644 index 0000000000..f12dfaf33b --- /dev/null +++ b/Content.Client/NPC/HTN/HTNSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared.NPC; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; + +namespace Content.Client.NPC.HTN; + +public sealed class HTNSystem : EntitySystem +{ + /* + * Mainly handles clientside debugging for HTN NPCs. + */ + public bool EnableOverlay + { + get => _enableOverlay; + set + { + var overlayManager = IoCManager.Resolve(); + _enableOverlay = value; + + if (_enableOverlay) + { + overlayManager.AddOverlay(new HTNOverlay(EntityManager, IoCManager.Resolve())); + RaiseNetworkEvent(new RequestHTNMessage() + { + Enabled = true, + }); + } + else + { + overlayManager.RemoveOverlay(); + RaiseNetworkEvent(new RequestHTNMessage() + { + Enabled = false, + }); + } + } + } + + private bool _enableOverlay; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(OnHTNMessage); + } + + private void OnHTNMessage(HTNMessage ev) + { + if (!TryComp(ev.Uid, out var htn)) + return; + + htn.DebugText = ev.Text; + } +} diff --git a/Content.Client/NPC/NPCComponent.cs b/Content.Client/NPC/NPCComponent.cs new file mode 100644 index 0000000000..31bcb18bd2 --- /dev/null +++ b/Content.Client/NPC/NPCComponent.cs @@ -0,0 +1,8 @@ +using Content.Shared.NPC; + +namespace Content.Client.NPC; + +public abstract class NPCComponent : SharedNPCComponent +{ + +} diff --git a/Content.Client/NPC/NPCEui.cs b/Content.Client/NPC/NPCEui.cs new file mode 100644 index 0000000000..d5f91fb914 --- /dev/null +++ b/Content.Client/NPC/NPCEui.cs @@ -0,0 +1,22 @@ +using Content.Client.Eui; + +namespace Content.Client.NPC; + +public sealed class NPCEui : BaseEui +{ + private NPCWindow? _window = new(); + + public override void Opened() + { + base.Opened(); + _window = new NPCWindow(); + _window.OpenCentered(); + } + + public override void Closed() + { + base.Closed(); + _window?.Close(); + _window = null; + } +} diff --git a/Content.Client/NPC/NPCWindow.xaml b/Content.Client/NPC/NPCWindow.xaml new file mode 100644 index 0000000000..377826ac19 --- /dev/null +++ b/Content.Client/NPC/NPCWindow.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/Content.Client/NPC/NPCWindow.xaml.cs b/Content.Client/NPC/NPCWindow.xaml.cs new file mode 100644 index 0000000000..35c97bd07a --- /dev/null +++ b/Content.Client/NPC/NPCWindow.xaml.cs @@ -0,0 +1,33 @@ +using Content.Client.UserInterface; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Console; + +namespace Content.Client.NPC; + +[GenerateTypedNameReferences] +public sealed partial class NPCWindow : FancyWindow +{ + public NPCWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + var sysManager = IoCManager.Resolve(); + var debugSys = sysManager.GetEntitySystem(); + var path = sysManager.GetEntitySystem(); + + NPCPath.Pressed = (debugSys.Tooltips & AiDebugMode.Paths) != 0x0; + NPCThonk.Pressed = (debugSys.Tooltips & AiDebugMode.Thonk) != 0x0; + + NPCPath.OnToggled += args => debugSys.ToggleTooltip(AiDebugMode.Paths); + NPCThonk.OnToggled += args => debugSys.ToggleTooltip(AiDebugMode.Thonk); + + PathNodes.Pressed = (path.Modes & PathfindingDebugMode.Nodes) != 0x0; + PathRegions.Pressed = (path.Modes & PathfindingDebugMode.Regions) != 0x0; + PathRoutes.Pressed = (path.Modes & PathfindingDebugMode.Route) != 0x0; + + PathNodes.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Nodes); + PathRegions.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Regions); + PathRoutes.OnToggled += args => path.ToggleTooltip(PathfindingDebugMode.Route); + } +} diff --git a/Content.Client/NPC/ShowHTNCommand.cs b/Content.Client/NPC/ShowHTNCommand.cs new file mode 100644 index 0000000000..b6adc0759f --- /dev/null +++ b/Content.Client/NPC/ShowHTNCommand.cs @@ -0,0 +1,16 @@ +using Content.Client.NPC.HTN; +using Robust.Shared.Console; + +namespace Content.Client.NPC; + +public sealed class ShowHTNCommand : IConsoleCommand +{ + public string Command => "showhtn"; + public string Description => "Shows the current status for HTN NPCs"; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + var npcs = IoCManager.Resolve().GetEntitySystem(); + npcs.EnableOverlay ^= true; + } +} diff --git a/Content.Client/Sandbox/SandboxSystem.cs b/Content.Client/Sandbox/SandboxSystem.cs index 05870e3736..7933a11144 100644 --- a/Content.Client/Sandbox/SandboxSystem.cs +++ b/Content.Client/Sandbox/SandboxSystem.cs @@ -1,5 +1,6 @@ using Content.Client.Administration.Managers; using Content.Client.Decals.UI; +using Content.Client.Eui; using Content.Client.HUD; using Content.Client.Markers; using Content.Client.SubFloor; diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index a6e9c5b95b..d28e06bfa3 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -235,8 +235,19 @@ public sealed partial class GunSystem : SharedGunSystem protected override void CreateEffect(EntityUid uid, MuzzleFlashEvent message, EntityUid? user = null) { - if (!Timing.IsFirstTimePredicted || !TryComp(uid, out var xform)) return; - var ent = Spawn(message.Prototype, xform.Coordinates); + if (!Timing.IsFirstTimePredicted) + return; + + EntityCoordinates coordinates; + + if (message.MatchRotation) + coordinates = new EntityCoordinates(uid, Vector2.Zero); + else if (TryComp(uid, out var xform)) + coordinates = xform.Coordinates; + else + return; + + var ent = Spawn(message.Prototype, coordinates); var effectXform = Transform(ent); effectXform.LocalRotation -= MathF.PI / 2; diff --git a/Content.IntegrationTests/Tests/AI/BehaviorSetsTest.cs b/Content.IntegrationTests/Tests/AI/BehaviorSetsTest.cs deleted file mode 100644 index 1f21343c15..0000000000 --- a/Content.IntegrationTests/Tests/AI/BehaviorSetsTest.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Content.Server.AI.Utility; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.AiLogic; -using NUnit.Framework; -using Robust.Shared.Prototypes; -using Robust.Shared.Reflection; - -namespace Content.IntegrationTests.Tests.AI -{ - [TestFixture] - [TestOf(typeof(BehaviorSetPrototype))] - public sealed class BehaviorSetsTest - { - [Test] - public async Task TestBehaviorSets() - { - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true}); - var server = pairTracker.Pair.Server; - - var protoManager = server.ResolveDependency(); - var reflectionManager = server.ResolveDependency(); - - Dictionary> behaviorSets = new(); - - // Test that all BehaviorSet actions exist. - await server.WaitAssertion(() => - { - foreach (var proto in protoManager.EnumeratePrototypes()) - { - behaviorSets[proto.ID] = proto.Actions.ToList(); - - foreach (var action in proto.Actions) - { - if (!reflectionManager.TryLooseGetType(action, out var actionType) || - !typeof(IAiUtility).IsAssignableFrom(actionType)) - { - Assert.Fail($"Action {action} is not valid within BehaviorSet {proto.ID}"); - } - } - } - }); - - // Test that all BehaviorSets on NPCs exist. - await server.WaitAssertion(() => - { - foreach (var entity in protoManager.EnumeratePrototypes()) - { - if (!entity.TryGetComponent("UtilityAI", out var npcNode)) continue; - - foreach (var entry in npcNode.BehaviorSets) - { - Assert.That(behaviorSets.ContainsKey(entry), $"BehaviorSet {entry} in entity {entity.ID} not found"); - } - } - }); - await pairTracker.CleanReturnAsync(); - } - } -} diff --git a/Content.IntegrationTests/Tests/NPC/NPCTest.cs b/Content.IntegrationTests/Tests/NPC/NPCTest.cs new file mode 100644 index 0000000000..96e7f752a4 --- /dev/null +++ b/Content.IntegrationTests/Tests/NPC/NPCTest.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Content.Server.NPC.HTN; +using NUnit.Framework; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.IntegrationTests.Tests.NPC; + +[TestFixture] +public sealed class NPCTest +{ + [Test] + public async Task CompoundRecursion() + { + var pool = await PoolManager.GetServerClient(new PoolSettings() { NoClient = true }); + var server = pool.Pair.Server; + + await server.WaitIdleAsync(); + + var protoManager = server.ResolveDependency(); + + await server.WaitAssertion(() => + { + var counts = new Dictionary(); + + foreach (var compound in protoManager.EnumeratePrototypes()) + { + Count(compound, counts); + counts.Clear(); + } + }); + + await pool.CleanReturnAsync(); + } + + private static void Count(HTNCompoundTask compound, Dictionary counts) + { + foreach (var branch in compound.Branches) + { + foreach (var task in branch.Tasks) + { + if (task is HTNCompoundTask compoundTask) + { + var count = counts.GetOrNew(compound.ID); + count++; + + Assert.That(count, Is.LessThan(50)); + counts[compound.ID] = count; + Count(compoundTask, counts); + } + } + } + } +} diff --git a/Content.Server/AI/Commands/AddAiCommand.cs b/Content.Server/AI/Commands/AddAiCommand.cs deleted file mode 100644 index d56996a704..0000000000 --- a/Content.Server/AI/Commands/AddAiCommand.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Content.Server.Administration; -using Content.Server.AI.Components; -using Content.Server.AI.EntitySystems; -using Content.Server.AI.Utility; -using Content.Server.AI.Utility.AiLogic; -using Content.Shared.Administration; -using Content.Shared.Movement.Components; -using Robust.Shared.Console; - -namespace Content.Server.AI.Commands -{ - [AdminCommand(AdminFlags.Fun)] - public sealed class AddAiCommand : IConsoleCommand - { - [Dependency] private readonly IEntityManager _entities = default!; - - public string Command => "addai"; - public string Description => "Add an ai component with a given processor to an entity."; - public string Help => "Usage: addai ..." - + "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this." - + "\n behaviorSet: Name of a behaviorset to add to the component on initialize."; - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - if(args.Length < 1) - { - shell.WriteLine("Wrong number of args."); - return; - } - - var entId = new EntityUid(int.Parse(args[0])); - - if (!_entities.EntityExists(entId)) - { - shell.WriteLine($"Unable to find entity with uid {entId}"); - return; - } - - if (_entities.HasComponent(entId)) - { - shell.WriteLine("Entity already has an AI component."); - return; - } - - var comp = _entities.AddComponent(entId); - var npcSystem = IoCManager.Resolve().EntitySysManager.GetEntitySystem(); - - for (var i = 1; i < args.Length; i++) - { - var bSet = args[i]; - npcSystem.AddBehaviorSet(comp, bSet, false); - } - - npcSystem.RebuildActions(comp); - shell.WriteLine("AI component added."); - } - } -} diff --git a/Content.Server/AI/Components/AiFactionTagComponent.cs b/Content.Server/AI/Components/AiFactionTagComponent.cs deleted file mode 100644 index 3b8ca629d2..0000000000 --- a/Content.Server/AI/Components/AiFactionTagComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Server.AI.EntitySystems; - -namespace Content.Server.AI.Components -{ - [RegisterComponent] - public sealed class AiFactionTagComponent : Component - { - [DataField("factions")] - public Faction Factions { get; private set; } = Faction.None; - } -} diff --git a/Content.Server/AI/Components/NPCComponent.cs b/Content.Server/AI/Components/NPCComponent.cs deleted file mode 100644 index 97e229f422..0000000000 --- a/Content.Server/AI/Components/NPCComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Server.AI.EntitySystems; - -namespace Content.Server.AI.Components -{ - [Access(typeof(NPCSystem))] - public abstract class NPCComponent : Component - { - // TODO: Soon. I didn't realise how much effort it was to deprecate the old one. - /// - /// Contains all of the world data for a particular NPC in terms of how it sees the world. - /// - //[ViewVariables, DataField("blackboardA")] - //public Dictionary BlackboardA = new(); - - public float VisionRadius => 7f; - } -} diff --git a/Content.Server/AI/Considerations/Bots/CanInjectCon.cs b/Content.Server/AI/Considerations/Bots/CanInjectCon.cs deleted file mode 100644 index 02169eac3a..0000000000 --- a/Content.Server/AI/Considerations/Bots/CanInjectCon.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.Tracking; -using Content.Shared.Damage; -using Content.Shared.MobState.Components; -using Content.Server.Silicons.Bots; - -namespace Content.Server.AI.Utility.Considerations.Bot -{ - public sealed class CanInjectCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var entMan = IoCManager.Resolve(); - var target = context.GetState().GetValue(); - - if (target == null || !entMan.TryGetComponent(target, out DamageableComponent? damageableComponent)) - return 0; - - if (entMan.TryGetComponent(target, out RecentlyInjectedComponent? recently)) - return 0f; - - if (!entMan.TryGetComponent(target, out MobStateComponent? mobState) || mobState.IsDead()) - return 0f; - - if (damageableComponent.TotalDamage == 0) - return 0f; - - if (damageableComponent.TotalDamage <= MedibotComponent.StandardMedDamageThreshold) - return 1f; - - if (damageableComponent.TotalDamage >= MedibotComponent.EmergencyMedDamageThreshold) - return 1f; - - return 0f; - } - } -} diff --git a/Content.Server/AI/EntitySystems/GoToPuddleSystem.cs b/Content.Server/AI/EntitySystems/GoToPuddleSystem.cs deleted file mode 100644 index c9816aba27..0000000000 --- a/Content.Server/AI/EntitySystems/GoToPuddleSystem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Server.Fluids.Components; - -namespace Content.Server.AI.EntitySystems -{ - public sealed class GoToPuddleSystem : EntitySystem - { - [Dependency] private readonly EntityLookupSystem _lookup = default!; - - public EntityUid GetNearbyPuddle(EntityUid cleanbot, float range = 10) - { - foreach (var entity in _lookup.GetEntitiesInRange(cleanbot, range)) - { - if (HasComp(entity)) - return entity; - } - - return default; - } - } -} diff --git a/Content.Server/AI/EntitySystems/InjectNearbySystem.cs b/Content.Server/AI/EntitySystems/InjectNearbySystem.cs deleted file mode 100644 index f702f5db48..0000000000 --- a/Content.Server/AI/EntitySystems/InjectNearbySystem.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Content.Server.Chemistry.Components.SolutionManager; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.AI.Tracking; -using Content.Server.Popups; -using Content.Server.Chat.Systems; -using Content.Server.Silicons.Bots; -using Content.Shared.MobState.Components; -using Content.Shared.Damage; -using Content.Shared.Interaction; -using Robust.Shared.Player; -using Robust.Shared.Audio; - -namespace Content.Server.AI.EntitySystems -{ - public sealed class InjectNearbySystem : EntitySystem - { - [Dependency] private readonly EntityLookupSystem _lookup = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; - - public EntityUid GetNearbyInjectable(EntityUid medibot, float range = 4) - { - foreach (var entity in _lookup.GetEntitiesInRange(medibot, range)) - { - if (HasComp(entity) && HasComp(entity)) - return entity; - } - - return default; - } - - public bool Inject(EntityUid medibot, EntityUid target) - { - if (!TryComp(medibot, out var botComp)) - return false; - - if (!TryComp(target, out var damage)) - return false; - - if (!_solutionSystem.TryGetInjectableSolution(target, out var injectable)) - return false; - - if (!_interactionSystem.InRangeUnobstructed(medibot, target)) - return true; // return true lets the bot reattempt the action on the same target - - if (damage.TotalDamage == 0) - return false; - - if (damage.TotalDamage <= MedibotComponent.StandardMedDamageThreshold) - { - _solutionSystem.TryAddReagent(target, injectable, botComp.StandardMed, botComp.StandardMedInjectAmount, out var accepted); - EnsureComp(target); - _popupSystem.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, Filter.Entities(target)); - SoundSystem.Play("/Audio/Items/hypospray.ogg", Filter.Pvs(target), target); - _chat.TrySendInGameICMessage(medibot, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, false); - return true; - } - - if (damage.TotalDamage >= MedibotComponent.EmergencyMedDamageThreshold) - { - _solutionSystem.TryAddReagent(target, injectable, botComp.EmergencyMed, botComp.EmergencyMedInjectAmount, out var accepted); - EnsureComp(target); - _popupSystem.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, Filter.Entities(target)); - SoundSystem.Play("/Audio/Items/hypospray.ogg", Filter.Pvs(target), target); - _chat.TrySendInGameICMessage(medibot, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, false); - return true; - } - - return false; - } - } -} diff --git a/Content.Server/AI/EntitySystems/NPCSystem.Blackboard.cs b/Content.Server/AI/EntitySystems/NPCSystem.Blackboard.cs deleted file mode 100644 index b818aeb1a5..0000000000 --- a/Content.Server/AI/EntitySystems/NPCSystem.Blackboard.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Server.AI.Components; - -namespace Content.Server.AI.EntitySystems; - -public sealed partial class NPCSystem -{ - /* - /// - /// Tries to get the blackboard data for a particular key. Returns default if not found - /// - public T? GetValueOrDefault(NPCComponent component, string key) - { - if (component.BlackboardA.TryGetValue(key, out var value)) - { - return (T) value; - } - - return default; - } - - /// - /// Tries to get the blackboard data for a particular key. - /// - public bool TryGetValue(NPCComponent component, string key, [NotNullWhen(true)] out T? value) - { - if (component.BlackboardA.TryGetValue(key, out var data)) - { - value = (T) data; - return true; - } - - value = default; - return false; - } - - /* - * Constants to make development easier - */ - - public const string VisionRadius = "VisionRadius"; -} diff --git a/Content.Server/AI/EntitySystems/NPCSystem.Utility.cs b/Content.Server/AI/EntitySystems/NPCSystem.Utility.cs deleted file mode 100644 index 10f96b3660..0000000000 --- a/Content.Server/AI/EntitySystems/NPCSystem.Utility.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System.Runtime.ExceptionServices; -using System.Threading; -using Content.Server.AI.Components; -using Content.Server.AI.LoadBalancer; -using Content.Server.AI.Operators; -using Content.Server.AI.Utility; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.AiLogic; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Utility; -using Content.Server.CPUJob.JobQueues; -using Content.Server.CPUJob.JobQueues.Queues; -using Robust.Shared.Prototypes; -using Robust.Shared.Reflection; - -namespace Content.Server.AI.EntitySystems; - -public sealed partial class NPCSystem -{ - /* - * Handles Utility AI, implemented via IAUS - */ - - private readonly NpcActionComparer _comparer = new(); - - private Dictionary> _behaviorSets = new(); - - private readonly AiActionJobQueue _aiRequestQueue = new(); - - private void InitializeUtility() - { - SubscribeLocalEvent(OnUtilityStartup); - - foreach (var bSet in _prototypeManager.EnumeratePrototypes()) - { - var actions = new List(); - - foreach (var act in bSet.Actions) - { - if (!_reflectionManager.TryLooseGetType(act, out var parsedType) || - !typeof(IAiUtility).IsAssignableFrom(parsedType)) - { - _sawmill.Error($"Unable to parse AI action for {act}"); - } - else - { - actions.Add(parsedType); - } - } - - _behaviorSets[bSet.ID] = actions; - } - } - - private void OnUtilityStartup(EntityUid uid, UtilityNPCComponent component, ComponentStartup args) - { - if (component.BehaviorSets.Count > 0) - { - RebuildActions(component); - } - - component._planCooldownRemaining = component.PlanCooldown; - component._blackboard = new Blackboard(component.Owner); - } - - public AiActionRequestJob RequestAction(UtilityNPCComponent component, AiActionRequest request, CancellationTokenSource cancellationToken) - { - var job = new AiActionRequestJob(0.002, request, cancellationToken.Token); - // AI should already know if it shouldn't request again - _aiRequestQueue.EnqueueJob(job); - return job; - } - - private void UpdateUtility(float frameTime) - { - foreach (var (_, comp) in EntityQuery()) - { - if (_count >= _maxUpdates) break; - - Update(comp, frameTime); - _count++; - } - - _aiRequestQueue.Process(); - } - - private void ReceivedAction(UtilityNPCComponent component) - { - if (component._actionRequest == null) - { - return; - } - - switch (component._actionRequest.Exception) - { - case null: - break; - default: - _sawmill.Fatal(component._actionRequest.Exception.ToString()); - ExceptionDispatchInfo.Capture(component._actionRequest.Exception).Throw(); - // The code never actually reaches here, because the above throws. - // This is to tell the compiler that the flow never leaves here. - throw component._actionRequest.Exception; - } - var action = component._actionRequest.Result; - component._actionRequest = null; - // Actions with lower scores should be implicitly dumped by GetAction - // If we're not allowed to replace the action with an action of the same type then dump. - if (action == null || !action.CanOverride && component.CurrentAction?.GetType() == action.GetType()) - { - return; - } - - var currentOp = component.CurrentAction?.ActionOperators.Peek(); - if (currentOp != null && currentOp.HasStartup) - { - currentOp.Shutdown(Outcome.Failed); - } - - component.CurrentAction = action; - action.SetupOperators(component._blackboard); - } - - private void Update(UtilityNPCComponent component, float frameTime) - { - // If we asked for a new action we don't want to dump the existing one. - if (component._actionRequest != null) - { - if (component._actionRequest.Status != JobStatus.Finished) - { - return; - } - - ReceivedAction(component); - // Do something next tick - return; - } - - component._planCooldownRemaining -= frameTime; - - // Might find a better action while we're doing one already - if (component._planCooldownRemaining <= 0.0f) - { - component._planCooldownRemaining = component.PlanCooldown; - component._actionCancellation = new CancellationTokenSource(); - component._actionRequest = RequestAction(component, new AiActionRequest(component.Owner, component._blackboard, component.AvailableActions), component._actionCancellation); - - return; - } - - // When we spawn in we won't get an action for a bit - if (component.CurrentAction == null) - { - return; - } - - var outcome = component.CurrentAction.Execute(frameTime); - - switch (outcome) - { - case Outcome.Success: - if (component.CurrentAction.ActionOperators.Count == 0) - { - component.CurrentAction.Shutdown(); - component.CurrentAction = null; - // Nothing to compare new action to - component._blackboard.GetState().SetValue(0.0f); - } - break; - case Outcome.Continuing: - break; - case Outcome.Failed: - component.CurrentAction.Shutdown(); - component.CurrentAction = null; - component._blackboard.GetState().SetValue(0.0f); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - /// - /// Adds the BehaviorSet to the NPC. - /// - /// - /// - /// Set to false if you want to manually rebuild it after bulk updates. - public void AddBehaviorSet(UtilityNPCComponent npc, string behaviorSet, bool rebuild = true) - { - if (!_behaviorSets.ContainsKey(behaviorSet)) - { - _sawmill.Error($"Tried to add BehaviorSet {behaviorSet} to {npc} but no such BehaviorSet found!"); - return; - } - - if (!npc.BehaviorSets.Add(behaviorSet)) - { - _sawmill.Error($"Tried to add BehaviorSet {behaviorSet} to {npc} which already has the BehaviorSet!"); - return; - } - - if (rebuild) - RebuildActions(npc); - - if (npc.BehaviorSets.Count == 1 && !IsAwake(npc)) - WakeNPC(npc); - } - - /// - /// Removes the BehaviorSet from the NPC. - /// - /// - /// - /// Set to false if yo uwant to manually rebuild it after bulk updates. - public void RemoveBehaviorSet(UtilityNPCComponent npc, string behaviorSet, bool rebuild = true) - { - if (!_behaviorSets.TryGetValue(behaviorSet, out var actions)) - { - Logger.Error($"Tried to remove BehaviorSet {behaviorSet} from {npc} but no such BehaviorSet found!"); - return; - } - - if (!npc.BehaviorSets.Remove(behaviorSet)) - { - Logger.Error($"Tried to remove BehaviorSet {behaviorSet} from {npc} but it doesn't have that BehaviorSet!"); - return; - } - - if (rebuild) - RebuildActions(npc); - - if (npc.BehaviorSets.Count == 0) - SleepNPC(npc); - } - - /// - /// Clear our actions and re-instantiate them from our BehaviorSets. - /// Will ensure each action is unique. - /// - /// - public void RebuildActions(UtilityNPCComponent npc) - { - npc.AvailableActions.Clear(); - foreach (var bSet in npc.BehaviorSets) - { - foreach (var action in GetActions(bSet)) - { - if (npc.AvailableActions.Contains(action)) continue; - // Setup - action.Owner = npc.Owner; - - // Ad to actions. - npc.AvailableActions.Add(action); - } - } - - SortActions(npc); - } - - private IEnumerable GetActions(string behaviorSet) - { - foreach (var action in _behaviorSets[behaviorSet]) - { - yield return (IAiUtility) _typeFactory.CreateInstance(action); - } - } - - /// - /// Whenever the behavior sets are changed we'll re-sort the actions by bonus - /// - private void SortActions(UtilityNPCComponent npc) - { - npc.AvailableActions.Sort(_comparer); - } - - private sealed class NpcActionComparer : Comparer - { - public override int Compare(IAiUtility? x, IAiUtility? y) - { - if (x == null || y == null) return 0; - return y.Bonus.CompareTo(x.Bonus); - } - } -} diff --git a/Content.Server/AI/EntitySystems/ServerAiDebugSystem.cs b/Content.Server/AI/EntitySystems/ServerAiDebugSystem.cs deleted file mode 100644 index 9186e68ed4..0000000000 --- a/Content.Server/AI/EntitySystems/ServerAiDebugSystem.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Server.AI.LoadBalancer; -using Content.Shared.AI; -using JetBrains.Annotations; - -namespace Content.Server.AI.EntitySystems -{ -#if DEBUG - [UsedImplicitly] - public sealed class ServerAiDebugSystem : EntitySystem - { - public override void Initialize() - { - base.Initialize(); - AiActionRequestJob.FoundAction += NotifyActionJob; - } - - public override void Shutdown() - { - base.Shutdown(); - AiActionRequestJob.FoundAction -= NotifyActionJob; - } - - private void NotifyActionJob(SharedAiDebug.UtilityAiDebugMessage message) - { - RaiseNetworkEvent(message); - } - } -#endif -} diff --git a/Content.Server/AI/LoadBalancer/AiActionRequest.cs b/Content.Server/AI/LoadBalancer/AiActionRequest.cs deleted file mode 100644 index fb2f4d6dd4..0000000000 --- a/Content.Server/AI/LoadBalancer/AiActionRequest.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.WorldState; - -namespace Content.Server.AI.LoadBalancer -{ - public sealed class AiActionRequest - { - public EntityUid EntityUid { get; } - public Blackboard? Context { get; } - public IEnumerable? Actions { get; } - - public AiActionRequest(EntityUid uid, Blackboard context, IEnumerable actions) - { - EntityUid = uid; - Context = context; - Actions = actions; - } - } -} diff --git a/Content.Server/AI/LoadBalancer/AiActionRequestJob.cs b/Content.Server/AI/LoadBalancer/AiActionRequestJob.cs deleted file mode 100644 index 452aca7961..0000000000 --- a/Content.Server/AI/LoadBalancer/AiActionRequestJob.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Content.Server.AI.Components; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.ExpandableActions; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Utility; -using Content.Server.CPUJob.JobQueues; -using Content.Shared.AI; -using Robust.Shared.Utility; - -namespace Content.Server.AI.LoadBalancer -{ - public sealed class AiActionRequestJob : Job - { -#if DEBUG - public static event Action? FoundAction; -#endif - private readonly AiActionRequest _request; - - public AiActionRequestJob( - double maxTime, - AiActionRequest request, - CancellationToken cancellationToken = default) : base(maxTime, cancellationToken) - { - _request = request; - } - - protected override async Task Process() - { - if (_request.Context == null) - { - return null; - } - - var entity = _request.Context.GetState().GetValue(); - - if (!IoCManager.Resolve().HasComponent(entity)) - { - return null; - } - - if (_request.Actions == null || _request.Context == null) - { - return null; - } - - var consideredTaskCount = 0; - // Actions are pre-sorted - var actions = new Stack(_request.Actions); - - // So essentially we go through and once we have a valid score that score becomes the cutoff; - // once the bonus of new tasks is below the cutoff we can stop evaluating. - - // Use last action as the basis for the cutoff - var cutoff = _request.Context.GetState().GetValue(); - UtilityAction? foundAction = null; - - // To see what I was trying to do watch these 2 videos about Infinite Axis Utility System (IAUS): - // Architecture Tricks: Managing Behaviors in Time, Space, and Depth - // Building a Better Centaur - - // We'll want to cap the considered entities at some point, e.g. if 500 guns are in a stack cap it at 256 or whatever - while (actions.Count > 0) - { - if (consideredTaskCount > 0 && consideredTaskCount % 5 == 0) - { - await SuspendIfOutOfTime(); - - // If this happens then that means something changed when we resumed so ABORT - if (actions.Count == 0 || _request.Context == null) - { - return null; - } - } - - var action = actions.Pop(); - switch (action) - { - case ExpandableUtilityAction expandableUtilityAction: - if (!expandableUtilityAction.IsValid(_request.Context)) - { - break; - } - - foreach (var expanded in expandableUtilityAction.GetActions(_request.Context)) - { - actions.Push(expanded); - } - break; - case UtilityAction utilityAction: - consideredTaskCount++; - var bonus = utilityAction.Bonus; - - if (bonus < cutoff) - { - // We know none of the other actions can beat this as they're pre-sorted - actions.Clear(); - break; - } - - var score = utilityAction.GetScore(_request.Context, cutoff); - if (score > cutoff) - { - foundAction = utilityAction; - cutoff = score; - } - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - _request.Context.GetState().SetValue(cutoff); -#if DEBUG - if (foundAction != null) - { - var selfState = _request.Context.GetState().GetValue(); - - DebugTools.AssertNotNull(selfState); - - FoundAction?.Invoke(new SharedAiDebug.UtilityAiDebugMessage( - selfState!, - DebugTime, - cutoff, - foundAction.GetType().Name, - consideredTaskCount)); - } - -#endif - _request.Context.ResetPlanning(); - - return foundAction; - } - } -} diff --git a/Content.Server/AI/Operators/AiOperator.cs b/Content.Server/AI/Operators/AiOperator.cs deleted file mode 100644 index cf7a264bd2..0000000000 --- a/Content.Server/AI/Operators/AiOperator.cs +++ /dev/null @@ -1,52 +0,0 @@ - -namespace Content.Server.AI.Operators -{ - public abstract class AiOperator - { - public bool HasStartup { get; private set; } - - public bool HasShutdown { get; private set; } - - /// - /// Called once when the NPC starts this action - /// - /// true if it hasn't started up previously - public virtual bool Startup() - { - // If we've already startup then no point continuing - // This signals to the override that it's already startup - // Should probably throw but it made some code elsewhere marginally easier - if (HasStartup) - return false; - - HasStartup = true; - return true; - } - - /// - /// Called once when the NPC is done with this action if the outcome is successful or fails. - /// - public virtual bool Shutdown(Outcome outcome) - { - if (HasShutdown) - return false; - - HasShutdown = true; - return true; - } - - /// - /// Called every tick for the AI - /// - /// - /// - public abstract Outcome Execute(float frameTime); - } - - public enum Outcome - { - Success, - Continuing, - Failed, - } -} diff --git a/Content.Server/AI/Operators/Bots/InjectOperator.cs b/Content.Server/AI/Operators/Bots/InjectOperator.cs deleted file mode 100644 index d8061763b9..0000000000 --- a/Content.Server/AI/Operators/Bots/InjectOperator.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.AI.EntitySystems; - -namespace Content.Server.AI.Operators.Bots -{ - public sealed class InjectOperator : AiOperator - { - private EntityUid _medibot; - private EntityUid _target; - public InjectOperator(EntityUid medibot, EntityUid target) - { - _medibot = medibot; - _target = target; - } - - public override Outcome Execute(float frameTime) - { - var injectSystem = IoCManager.Resolve().GetEntitySystem(); - if (injectSystem.Inject(_medibot, _target)) - return Outcome.Success; - - return Outcome.Failed; - } - } -} diff --git a/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs b/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs deleted file mode 100644 index c2e258b216..0000000000 --- a/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Content.Server.CombatMode; -using Content.Server.Hands.Components; -using Content.Server.Interaction; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Operators.Combat.Melee -{ - public sealed class SwingMeleeWeaponOperator : AiOperator - { - [Dependency] private readonly IEntityManager _entMan = default!; - - private readonly float _burstTime; - private float _elapsedTime; - - private readonly EntityUid _owner; - private readonly EntityUid _target; - - public SwingMeleeWeaponOperator(EntityUid owner, EntityUid target, float burstTime = 1.0f) - { - IoCManager.InjectDependencies(this); - - _owner = owner; - _target = target; - _burstTime = burstTime; - } - - public override bool Startup() - { - if (!base.Startup()) - { - return true; - } - - if (!_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent)) - { - return false; - } - - if (!combatModeComponent.IsInCombatMode) - { - combatModeComponent.IsInCombatMode = true; - } - - return true; - } - - public override bool Shutdown(Outcome outcome) - { - if (!base.Shutdown(outcome)) - return false; - - if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent)) - { - combatModeComponent.IsInCombatMode = false; - } - - return true; - } - - public override Outcome Execute(float frameTime) - { - if (_burstTime <= _elapsedTime) - { - return Outcome.Success; - } - - if (!_entMan.TryGetComponent(_owner, out HandsComponent? hands) || hands.ActiveHandEntity == null) - { - return Outcome.Failed; - } - - var meleeWeapon = hands.ActiveHandEntity; - _entMan.TryGetComponent(meleeWeapon, out MeleeWeaponComponent? meleeWeaponComponent); - - if ((_entMan.GetComponent(_target).Coordinates.Position - _entMan.GetComponent(_owner).Coordinates.Position).Length > - meleeWeaponComponent?.Range) - { - return Outcome.Failed; - } - - var interactionSystem = IoCManager.Resolve().GetEntitySystem(); - - interactionSystem.AiUseInteraction(_owner, _entMan.GetComponent(_target).Coordinates, _target); - _elapsedTime += frameTime; - return Outcome.Continuing; - } - } -} diff --git a/Content.Server/AI/Operators/Combat/Melee/UnarmedCombatOperator.cs b/Content.Server/AI/Operators/Combat/Melee/UnarmedCombatOperator.cs deleted file mode 100644 index d7dccf07c3..0000000000 --- a/Content.Server/AI/Operators/Combat/Melee/UnarmedCombatOperator.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Content.Server.CombatMode; -using Content.Server.Interaction; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Operators.Combat.Melee -{ - public sealed class UnarmedCombatOperator : AiOperator - { - [Dependency] private readonly IEntityManager _entMan = default!; - - private readonly float _burstTime; - private float _elapsedTime; - - private readonly EntityUid _owner; - private readonly EntityUid _target; - private MeleeWeaponComponent? _unarmedCombat; - - public UnarmedCombatOperator(EntityUid owner, EntityUid target, float burstTime = 1.0f) - { - IoCManager.InjectDependencies(this); - - _owner = owner; - _target = target; - _burstTime = burstTime; - } - - public override bool Startup() - { - if (!base.Startup()) - { - return true; - } - - if (!_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent)) - { - return false; - } - - if (!combatModeComponent.IsInCombatMode) - { - combatModeComponent.IsInCombatMode = true; - } - - if (_entMan.TryGetComponent(_owner, out MeleeWeaponComponent? unarmedCombatComponent)) - { - _unarmedCombat = unarmedCombatComponent; - } - else - { - return false; - } - - return true; - } - - public override bool Shutdown(Outcome outcome) - { - if (!base.Shutdown(outcome)) - return false; - - if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent)) - { - combatModeComponent.IsInCombatMode = false; - } - - return true; - } - - public override Outcome Execute(float frameTime) - { - if (_unarmedCombat == null || - !_entMan.GetComponent(_target).Coordinates.TryDistance(_entMan, _entMan.GetComponent(_owner).Coordinates, out var distance) || distance > - _unarmedCombat.Range) - { - return Outcome.Failed; - } - - if (_burstTime <= _elapsedTime) - { - return Outcome.Success; - } - - if (_unarmedCombat?.Deleted ?? true) - { - return Outcome.Failed; - } - - var interactionSystem = IoCManager.Resolve().GetEntitySystem(); - interactionSystem.AiUseInteraction(_owner, _entMan.GetComponent(_target).Coordinates, _target); - _elapsedTime += frameTime; - return Outcome.Continuing; - } - } -} diff --git a/Content.Server/AI/Operators/Generic/WaitOperator.cs b/Content.Server/AI/Operators/Generic/WaitOperator.cs deleted file mode 100644 index 40e0026649..0000000000 --- a/Content.Server/AI/Operators/Generic/WaitOperator.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Content.Server.AI.Operators.Generic -{ - public sealed class WaitOperator : AiOperator - { - private readonly float _waitTime; - private float _accumulatedTime = 0.0f; - - public WaitOperator(float waitTime) - { - _waitTime = waitTime; - } - - public override Outcome Execute(float frameTime) - { - if (_accumulatedTime < _waitTime) - { - _accumulatedTime += frameTime; - return Outcome.Continuing; - } - - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs b/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs deleted file mode 100644 index 69454e6554..0000000000 --- a/Content.Server/AI/Operators/Inventory/CloseStorageOperator.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Content.Server.AI.Utility; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Storage.Components; -using Content.Server.Storage.EntitySystems; -using Content.Shared.Interaction; - -namespace Content.Server.AI.Operators.Inventory -{ - /// - /// Close the last EntityStorage we opened - /// This will also update the State for it (which a regular InteractWith won't do) - /// - public sealed class CloseLastStorageOperator : AiOperator - { - private readonly EntityUid _owner; - private EntityUid _target; - - public CloseLastStorageOperator(EntityUid owner) - { - _owner = owner; - } - - public override bool Startup() - { - if (!base.Startup()) - { - return true; - } - - var blackboard = UtilityAiHelpers.GetBlackboard(_owner); - - if (blackboard == null) - { - return false; - } - - _target = blackboard.GetState().GetValue(); - - return _target != default; - } - - public override bool Shutdown(Outcome outcome) - { - if (!base.Shutdown(outcome)) - return false; - - var blackboard = UtilityAiHelpers.GetBlackboard(_owner); - - blackboard?.GetState().SetValue(default); - return true; - } - - public override Outcome Execute(float frameTime) - { - if (_target == default || !EntitySystem.Get().InRangeUnobstructed(_owner, _target, popup: true)) - { - return Outcome.Failed; - } - - var entMan = IoCManager.Resolve(); - - if (!entMan.TryGetComponent(_target, out EntityStorageComponent? storageComponent) || - storageComponent.IsWeldedShut) - { - return Outcome.Failed; - } - - if (entMan.EntitySysManager.TryGetEntitySystem(out var entStorage) && storageComponent.Open) - { - entStorage.ToggleOpen(_owner, _target, storageComponent); - } - - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs b/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs deleted file mode 100644 index e86f20ee37..0000000000 --- a/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Shared.Hands.EntitySystems; - -namespace Content.Server.AI.Operators.Inventory -{ - public sealed class DropEntityOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityUid _entity; - public DropEntityOperator(EntityUid owner, EntityUid entity) - { - _owner = owner; - _entity = entity; - } - - /// - /// Requires EquipEntityOperator to put it in the active hand first - /// - /// - /// - public override Outcome Execute(float frameTime) - { - return IoCManager.Resolve().GetEntitySystem().TryDrop(_owner, _entity) ? Outcome.Success : Outcome.Failed; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs b/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs deleted file mode 100644 index 18a62b4453..0000000000 --- a/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Server.Hands.Components; -using Content.Shared.Hands.EntitySystems; - -namespace Content.Server.AI.Operators.Inventory -{ - public sealed class DropHandItemsOperator : AiOperator - { - private readonly EntityUid _owner; - - public DropHandItemsOperator(EntityUid owner) - { - _owner = owner; - } - - public override Outcome Execute(float frameTime) - { - if (!IoCManager.Resolve().TryGetComponent(_owner, out HandsComponent? handsComponent)) - { - return Outcome.Failed; - } - - var sys = IoCManager.Resolve().GetEntitySystem(); - - foreach (var hand in handsComponent.Hands.Values) - { - if (!hand.IsEmpty) - sys.TryDrop(_owner, hand, handsComp: handsComponent); - } - - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs b/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs deleted file mode 100644 index 643b31d14c..0000000000 --- a/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Shared.Hands.EntitySystems; - -namespace Content.Server.AI.Operators.Inventory -{ - public sealed class EquipEntityOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityUid _entity; - public EquipEntityOperator(EntityUid owner, EntityUid entity) - { - _owner = owner; - _entity = entity; - } - - public override Outcome Execute(float frameTime) - { - var sys = IoCManager.Resolve().GetEntitySystem(); - - // TODO: If in clothing then click on it - - if (sys.TrySelect(_owner, _entity)) - return Outcome.Success; - - // TODO: Get free hand count; if no hands free then fail right here - - // TODO: Go through inventory - return Outcome.Failed; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs b/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs deleted file mode 100644 index 0a1bc09fdc..0000000000 --- a/Content.Server/AI/Operators/Inventory/InteractWithEntityOperator.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Content.Server.CombatMode; -using Content.Server.Interaction; - -namespace Content.Server.AI.Operators.Inventory -{ - /// - /// A Generic interacter; if you need to check stuff then make your own - /// - public sealed class InteractWithEntityOperator : AiOperator - { - [Dependency] private readonly IEntityManager _entMan = default!; - - private readonly EntityUid _owner; - private readonly EntityUid _useTarget; - - public InteractWithEntityOperator(EntityUid owner, EntityUid useTarget) - { - IoCManager.InjectDependencies(this); - - _owner = owner; - _useTarget = useTarget; - - } - - public override Outcome Execute(float frameTime) - { - var targetTransform = _entMan.GetComponent(_useTarget); - - if (targetTransform.GridUid != _entMan.GetComponent(_owner).GridUid) - { - return Outcome.Failed; - } - - var interactionSystem = EntitySystem.Get(); - - if (!interactionSystem.InRangeUnobstructed(_owner, _useTarget, popup: true)) - { - return Outcome.Failed; - } - - if (_entMan.TryGetComponent(_owner, out CombatModeComponent? combatModeComponent)) - { - combatModeComponent.IsInCombatMode = false; - } - - // Click on da thing - interactionSystem.AiUseInteraction(_owner, targetTransform.Coordinates, _useTarget); - - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs b/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs deleted file mode 100644 index d58cf6a790..0000000000 --- a/Content.Server/AI/Operators/Inventory/OpenStorageOperator.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Content.Server.AI.Utility; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Storage.Components; -using Content.Server.Storage.EntitySystems; -using Content.Shared.Interaction; -using Robust.Shared.Containers; - -namespace Content.Server.AI.Operators.Inventory -{ - /// - /// If the target is in EntityStorage will open its parent container - /// - public sealed class OpenStorageOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityUid _target; - - public OpenStorageOperator(EntityUid owner, EntityUid target) - { - _owner = owner; - _target = target; - } - - public override Outcome Execute(float frameTime) - { - if (!_target.TryGetContainer(out var container)) - { - return Outcome.Success; - } - - if (!EntitySystem.Get().InRangeUnobstructed(_owner, container.Owner, popup: true)) - { - return Outcome.Failed; - } - - if (!IoCManager.Resolve().TryGetComponent(container.Owner, out EntityStorageComponent? storageComponent) || - storageComponent.IsWeldedShut) - { - return Outcome.Failed; - } - - if (!storageComponent.Open) - { - IoCManager.Resolve().GetEntitySystem().ToggleOpen(_owner, _target, storageComponent); - } - - var blackboard = UtilityAiHelpers.GetBlackboard(_owner); - blackboard?.GetState().SetValue(container.Owner); - - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs deleted file mode 100644 index ef76ed1ec9..0000000000 --- a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.Interaction; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Item; -using Robust.Shared.Containers; - -namespace Content.Server.AI.Operators.Inventory -{ - public sealed class PickupEntityOperator : AiOperator - { - // Input variables - private readonly EntityUid _owner; - private readonly EntityUid _target; - - public PickupEntityOperator(EntityUid owner, EntityUid target) - { - _owner = owner; - _target = target; - } - - public override Outcome Execute(float frameTime) - { - var entMan = IoCManager.Resolve(); - var sysMan = IoCManager.Resolve(); - var interactionSystem = sysMan.GetEntitySystem(); - var handsSys = sysMan.GetEntitySystem(); - - if (entMan.Deleted(_target) - || !entMan.HasComponent(_target) - || _target.IsInContainer() - || !interactionSystem.InRangeUnobstructed(_owner, _target, popup: true)) - { - return Outcome.Failed; - } - - // select empty hand - if (!handsSys.TrySelectEmptyHand(_owner)) - return Outcome.Failed; - - interactionSystem.InteractHand(_owner, _target); - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs b/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs deleted file mode 100644 index dfa2a69c45..0000000000 --- a/Content.Server/AI/Operators/Inventory/UseItemInInventoryOperator.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Server.Hands.Components; -using Content.Shared.Hands.EntitySystems; - -namespace Content.Server.AI.Operators.Inventory -{ - /// - /// Will find the item in storage, put it in an active hand, then use it - /// - public sealed class UseItemInInventoryOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityUid _target; - - public UseItemInInventoryOperator(EntityUid owner, EntityUid target) - { - _owner = owner; - _target = target; - } - - public override Outcome Execute(float frameTime) - { - var entMan = IoCManager.Resolve(); - var sysMan = IoCManager.Resolve(); - var sys = sysMan.GetEntitySystem(); - - // TODO: Also have this check storage a la backpack etc. - if (!entMan.TryGetComponent(_owner, out HandsComponent? handsComponent) - || !sys.TrySelect(_owner, _target, handsComponent) - || !sys.TryUseItemInHand(_owner, false, handsComponent)) - { - return Outcome.Failed; - } - - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs b/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs deleted file mode 100644 index ff43245fff..0000000000 --- a/Content.Server/AI/Operators/Movement/MoveToEntityOperator.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Content.Server.AI.Steering; -using Robust.Shared.Map; -using Robust.Shared.Utility; - -namespace Content.Server.AI.Operators.Movement -{ - public sealed class MoveToEntityOperator : AiOperator - { - // TODO: This and steering need to support InRangeUnobstructed now - private readonly EntityUid _owner; - private readonly EntityUid _target; - // For now we'll just get as close as we can because we're not doing LOS checks to be able to pick up at the max interaction range - public float ArrivalDistance { get; } - public float PathfindingProximity { get; } - - private readonly bool _requiresInRangeUnobstructed; - - public MoveToEntityOperator( - EntityUid owner, - EntityUid target, - float arrivalDistance = 1.0f, - float pathfindingProximity = 1.5f, - bool requiresInRangeUnobstructed = false) - { - _owner = owner; - _target = target; - ArrivalDistance = arrivalDistance; - PathfindingProximity = pathfindingProximity; - _requiresInRangeUnobstructed = requiresInRangeUnobstructed; - } - - public override bool Startup() - { - if (!base.Startup()) - { - return true; - } - - var steering = EntitySystem.Get(); - var comp = steering.Register(_owner, new EntityCoordinates(_target, Vector2.Zero)); - comp.Range = ArrivalDistance; - return true; - } - - public override bool Shutdown(Outcome outcome) - { - if (!base.Shutdown(outcome)) - return false; - - var steering = EntitySystem.Get(); - steering.Unregister(_owner); - return true; - } - - public override Outcome Execute(float frameTime) - { - if (!IoCManager.Resolve().TryGetComponent(_owner, out var steering)) - return Outcome.Failed; - - switch (steering.Status) - { - case SteeringStatus.NoPath: - return Outcome.Failed; - case SteeringStatus.InRange: - return Outcome.Success; - case SteeringStatus.Moving: - return Outcome.Continuing; - default: - throw new ArgumentOutOfRangeException(); - } - } - } -} diff --git a/Content.Server/AI/Operators/Movement/MoveToGridOperator.cs b/Content.Server/AI/Operators/Movement/MoveToGridOperator.cs deleted file mode 100644 index 03d276ee3c..0000000000 --- a/Content.Server/AI/Operators/Movement/MoveToGridOperator.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Content.Server.AI.Steering; -using Robust.Shared.Map; -using Robust.Shared.Utility; - -namespace Content.Server.AI.Operators.Movement -{ - public sealed class MoveToGridOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityCoordinates _target; - public float DesiredRange { get; set; } - - public MoveToGridOperator(EntityUid owner, EntityCoordinates target, float desiredRange = 1.5f) - { - _owner = owner; - _target = target; - DesiredRange = desiredRange; - } - - public override bool Startup() - { - if (!base.Startup()) - { - return true; - } - - var steering = EntitySystem.Get(); - var comp = steering.Register(_owner, _target); - comp.Range = DesiredRange; - return true; - } - - public override bool Shutdown(Outcome outcome) - { - if (!base.Shutdown(outcome)) - return false; - - var steering = EntitySystem.Get(); - steering.Unregister(_owner); - return true; - } - - public override Outcome Execute(float frameTime) - { - if (!IoCManager.Resolve().TryGetComponent(_owner, out var steering)) - return Outcome.Failed; - - switch (steering.Status) - { - case SteeringStatus.NoPath: - return Outcome.Failed; - case SteeringStatus.InRange: - return Outcome.Success; - case SteeringStatus.Moving: - return Outcome.Continuing; - default: - throw new ArgumentOutOfRangeException(); - } - } - } -} diff --git a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs deleted file mode 100644 index c987408b6d..0000000000 --- a/Content.Server/AI/Operators/Nutrition/UseDrinkInInventoryOperator.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Content.Server.Hands.Components; -using Content.Server.Nutrition.Components; -using Content.Server.Nutrition.EntitySystems; -using Content.Shared.Hands.EntitySystems; -using Robust.Shared.Random; - -namespace Content.Server.AI.Operators.Nutrition -{ - public sealed class UseDrinkInInventoryOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityUid _target; - private float _interactionCooldown; - - public UseDrinkInInventoryOperator(EntityUid owner, EntityUid target) - { - _owner = owner; - _target = target; - } - - public override Outcome Execute(float frameTime) - { - if (_interactionCooldown >= 0) - { - _interactionCooldown -= frameTime; - return Outcome.Continuing; - } - - var entities = IoCManager.Resolve(); - var sysMan = IoCManager.Resolve(); - var handsSys = sysMan.GetEntitySystem(); - - // TODO: Also have this check storage a la backpack etc. - if (entities.Deleted(_target) || - !entities.TryGetComponent(_owner, out HandsComponent? handsComponent)) - { - return Outcome.Failed; - } - - if (!handsSys.TrySelect(_owner, out var drinkComponent, handsComponent)) - return Outcome.Failed; - - if (!handsSys.TryUseItemInHand(_owner, false, handsComponent)) - return Outcome.Failed; - - _interactionCooldown = IoCManager.Resolve().NextFloat() + 0.5f; - - if (drinkComponent.Deleted || EntitySystem.Get().IsEmpty(drinkComponent.Owner, drinkComponent) - || entities.TryGetComponent(_owner, out ThirstComponent? thirstComponent) && - thirstComponent.CurrentThirst >= thirstComponent.ThirstThresholds[ThirstThreshold.Okay]) - { - return Outcome.Success; - } - - /// uuhhh do afters for drinks might mess this up? - return Outcome.Continuing; - } - } -} diff --git a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs b/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs deleted file mode 100644 index 21b30119fe..0000000000 --- a/Content.Server/AI/Operators/Nutrition/UseFoodInInventoryOperator.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Server.Hands.Components; -using Content.Server.Nutrition.Components; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Nutrition.Components; - -namespace Content.Server.AI.Operators.Nutrition -{ - public sealed class UseFoodInInventoryOperator : AiOperator - { - private readonly EntityUid _owner; - private readonly EntityUid _target; - private float _interactionCooldown; - - public UseFoodInInventoryOperator(EntityUid owner, EntityUid target) - { - _owner = owner; - _target = target; - } - - public override Outcome Execute(float frameTime) - { - if (_interactionCooldown >= 0) - { - _interactionCooldown -= frameTime; - return Outcome.Continuing; - } - - var entities = IoCManager.Resolve(); - var sysMan = IoCManager.Resolve(); - var handsSys = sysMan.GetEntitySystem(); - - // TODO: Also have this check storage a la backpack etc. - if (entities.Deleted(_target) || - !entities.TryGetComponent(_owner, out HandsComponent? handsComponent)) - { - return Outcome.Failed; - } - - if (!handsSys.TrySelect(_owner, out var foodComponent, handsComponent)) - return Outcome.Failed; - - if (!handsSys.TryUseItemInHand(_owner, false, handsComponent)) - return Outcome.Failed; - - if ((!entities.EntityExists(_target) ? EntityLifeStage.Deleted : entities.GetComponent(_target).EntityLifeStage) >= EntityLifeStage.Deleted || - foodComponent.UsesRemaining == 0 || - entities.TryGetComponent(_owner, out HungerComponent? hungerComponent) && - hungerComponent.CurrentHunger >= hungerComponent.HungerThresholds[HungerThreshold.Okay]) - { - return Outcome.Success; - } - - /// do afters for food might mess this up? - return Outcome.Continuing; - } - } -} diff --git a/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs b/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs deleted file mode 100644 index d9934099dc..0000000000 --- a/Content.Server/AI/Operators/Sequences/GoPickupEntitySequence.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Operators.Movement; - -namespace Content.Server.AI.Operators.Sequences -{ - public sealed class GoPickupEntitySequence : SequenceOperator - { - public GoPickupEntitySequence(EntityUid owner, EntityUid target) - { - Sequence = new Queue(new AiOperator[] - { - new MoveToEntityOperator(owner, target, requiresInRangeUnobstructed: true), - new OpenStorageOperator(owner, target), - new PickupEntityOperator(owner, target), - }); - } - } -} diff --git a/Content.Server/AI/Operators/Sequences/SequenceOperator.cs b/Content.Server/AI/Operators/Sequences/SequenceOperator.cs deleted file mode 100644 index 264b4e6f22..0000000000 --- a/Content.Server/AI/Operators/Sequences/SequenceOperator.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Content.Server.AI.Operators.Sequences -{ - /// - /// Sequential chain of operators - /// Saves having to duplicate stuff like MoveTo and PickUp everywhere - /// - public abstract class SequenceOperator : AiOperator - { - public Queue Sequence { get; protected set; } = new(); - - public override Outcome Execute(float frameTime) - { - if (Sequence.Count == 0) - { - return Outcome.Success; - } - - var op = Sequence.Peek(); - op.Startup(); - var outcome = op.Execute(frameTime); - - switch (outcome) - { - case Outcome.Success: - op.Shutdown(outcome); - // Not over until all operators are done - Sequence.Dequeue(); - return Outcome.Continuing; - case Outcome.Continuing: - return Outcome.Continuing; - case Outcome.Failed: - op.Shutdown(outcome); - Sequence.Clear(); - return Outcome.Failed; - - default: - throw new ArgumentOutOfRangeException(); - } - } - } -} diff --git a/Content.Server/AI/Operators/Speech/SpeechOperator.cs b/Content.Server/AI/Operators/Speech/SpeechOperator.cs deleted file mode 100644 index 19fa4698ae..0000000000 --- a/Content.Server/AI/Operators/Speech/SpeechOperator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.Chat.Systems; - -namespace Content.Server.AI.Operators.Speech -{ - public sealed class SpeakOperator : AiOperator - { - private EntityUid _speaker; - private string _speechString; - public SpeakOperator(EntityUid speaker, string speechString) - { - _speaker = speaker; - _speechString = speechString; - } - - public override Outcome Execute(float frameTime) - { - var chatSystem = IoCManager.Resolve().GetEntitySystem(); - chatSystem.TrySendInGameICMessage(_speaker, _speechString, InGameICChatType.Speak, false); - return Outcome.Success; - } - } -} diff --git a/Content.Server/AI/Pathfinding/GridPathfindingComponent.cs b/Content.Server/AI/Pathfinding/GridPathfindingComponent.cs deleted file mode 100644 index 4366378b93..0000000000 --- a/Content.Server/AI/Pathfinding/GridPathfindingComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Content.Server.AI.Pathfinding; - -[RegisterComponent] -[Access(typeof(PathfindingSystem))] -public sealed class GridPathfindingComponent : Component, IPathfindingGraph -{ - public Dictionary Graph = new(); -} diff --git a/Content.Server/AI/Pathfinding/IPathfindingGraph.cs b/Content.Server/AI/Pathfinding/IPathfindingGraph.cs deleted file mode 100644 index 645b980c8c..0000000000 --- a/Content.Server/AI/Pathfinding/IPathfindingGraph.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Content.Server.AI.Pathfinding; - -public interface IPathfindingGraph -{ - -} diff --git a/Content.Server/AI/Steering/IAiSteeringRequest.cs b/Content.Server/AI/Steering/IAiSteeringRequest.cs deleted file mode 100644 index 5fef13158c..0000000000 --- a/Content.Server/AI/Steering/IAiSteeringRequest.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Robust.Shared.Map; - -namespace Content.Server.AI.Steering -{ - public interface IAiSteeringRequest - { - SteeringStatus Status { get; set; } - MapCoordinates TargetMap { get; } - EntityCoordinates TargetGrid { get; } - /// - /// How close we have to get before we've arrived - /// - float ArrivalDistance { get; } - - /// - /// How close the pathfinder needs to get. Typically you want this set lower than ArrivalDistance - /// - float PathfindingProximity { get; } - - /// - /// If we need LOS on the entity first before interaction - /// - bool RequiresInRangeUnobstructed { get; } - - /// - /// To avoid spamming InRangeUnobstructed we'll apply a cd to it. - /// - public float TimeUntilInteractionCheck { get; set; } - } -} diff --git a/Content.Server/AI/Tracking/RecentlyInjectedSystem.cs b/Content.Server/AI/Tracking/RecentlyInjectedSystem.cs deleted file mode 100644 index 0a9ad16a4d..0000000000 --- a/Content.Server/AI/Tracking/RecentlyInjectedSystem.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Content.Server.AI.Tracking -{ - public sealed class RecentlyInjectedSystem : EntitySystem - { - - Queue RemQueue = new(); - public override void Update(float frameTime) - { - base.Update(frameTime); - foreach (var toRemove in RemQueue) - { - RemComp(toRemove); - } - RemQueue.Clear(); - foreach (var entity in EntityQuery()) - { - entity.Accumulator += frameTime; - if (entity.Accumulator < entity.RemoveTime.TotalSeconds) - continue; - entity.Accumulator = 0; - RemQueue.Enqueue(entity.Owner); - } - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Bots/GoToPuddleAndWait.cs b/Content.Server/AI/Utility/Actions/Bots/GoToPuddleAndWait.cs deleted file mode 100644 index d9dff3f0e5..0000000000 --- a/Content.Server/AI/Utility/Actions/Bots/GoToPuddleAndWait.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Generic; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.ActionBlocker; -using Content.Server.AI.WorldState.States.Movement; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Bots -{ - public sealed class GoToPuddleAndWait : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - MoveToEntityOperator moveOperator = new MoveToEntityOperator(Owner, Target, 0, 0); - float waitTime = 3f; - - ActionOperators = new Queue(new AiOperator[] - { - moveOperator, - new WaitOperator(waitTime), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - context.GetState().SetValue(Target); - // Can just set ourselves as entity given unarmed just inherits from meleeweapon - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Bots/InjectNearby.cs b/Content.Server/AI/Utility/Actions/Bots/InjectNearby.cs deleted file mode 100644 index 13ec3ab469..0000000000 --- a/Content.Server/AI/Utility/Actions/Bots/InjectNearby.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Generic; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.Operators.Bots; -using Content.Server.AI.Operators.Speech; -using Content.Server.AI.WorldState; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.ActionBlocker; -using Content.Server.AI.WorldState.States.Movement; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.Utility.Considerations.Bot; - - -namespace Content.Server.AI.Utility.Actions.Bots -{ - public sealed class InjectNearby : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - MoveToEntityOperator moveOperator = new MoveToEntityOperator(Owner, Target); - float waitTime = 3f; - - ActionOperators = new Queue(new AiOperator[] - { - moveOperator, - new SpeakOperator(Owner, Loc.GetString("medibot-start-inject")), - new WaitOperator(waitTime), - new InjectOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs b/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs deleted file mode 100644 index ee77f2957e..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/Gloves/EquipGloves.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.Gloves -{ - public sealed class EquipGloves : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target), - new UseItemInInventoryOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/Gloves/PickUpGloves.cs b/Content.Server/AI/Utility/Actions/Clothing/Gloves/PickUpGloves.cs deleted file mode 100644 index 0388130e18..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/Gloves/PickUpGloves.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.Gloves -{ - public sealed class PickUpGloves : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs b/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs deleted file mode 100644 index f51bffdba0..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/Head/EquipHead.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.Head -{ - public sealed class EquipHead : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target), - new UseItemInInventoryOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/Head/PickUpHead.cs b/Content.Server/AI/Utility/Actions/Clothing/Head/PickUpHead.cs deleted file mode 100644 index b8951f2a11..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/Head/PickUpHead.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.Head -{ - public sealed class PickUpHead : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs b/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs deleted file mode 100644 index ceaee08292..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/EquipOuterClothing.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing -{ - public sealed class EquipOuterClothing : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target), - new UseItemInInventoryOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/PickUpOuterClothing.cs b/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/PickUpOuterClothing.cs deleted file mode 100644 index a8ab0b601a..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/OuterClothing/PickUpOuterClothing.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.OuterClothing -{ - public sealed class PickUpOuterClothing : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs b/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs deleted file mode 100644 index 41c91413ca..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/Shoes/EquipShoes.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.Shoes -{ - public sealed class EquipShoes : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target), - new UseItemInInventoryOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Clothing/Shoes/PickUpShoes.cs b/Content.Server/AI/Utility/Actions/Clothing/Shoes/PickUpShoes.cs deleted file mode 100644 index ec9a969c5c..0000000000 --- a/Content.Server/AI/Utility/Actions/Clothing/Shoes/PickUpShoes.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Clothing.Shoes -{ - public sealed class PickUpShoes : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs b/Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs deleted file mode 100644 index 7ac5bd8999..0000000000 --- a/Content.Server/AI/Utility/Actions/Combat/Melee/EquipMelee.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Combat; - -namespace Content.Server.AI.Utility.Actions.Combat.Melee -{ - public sealed class EquipMelee : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target) - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.5f, 0.0f, 0.0f), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.25f, 0.0f, 0.0f), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Combat/Melee/MeleeWeaponAttackEntity.cs b/Content.Server/AI/Utility/Actions/Combat/Melee/MeleeWeaponAttackEntity.cs deleted file mode 100644 index 8d62c89d30..0000000000 --- a/Content.Server/AI/Utility/Actions/Combat/Melee/MeleeWeaponAttackEntity.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Combat.Melee; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Combat; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.AI.WorldState.States.Movement; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Actions.Combat.Melee -{ - public sealed class MeleeWeaponAttackEntity : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - MoveToEntityOperator moveOperator; - var equipped = context.GetState().GetValue(); - if (equipped != default && IoCManager.Resolve().TryGetComponent(equipped, out MeleeWeaponComponent? meleeWeaponComponent)) - { - moveOperator = new MoveToEntityOperator(Owner, Target, meleeWeaponComponent.Range - 0.01f); - } - else - { - // TODO: Abort - moveOperator = new MoveToEntityOperator(Owner, Target); - } - - ActionOperators = new Queue(new AiOperator[] - { - moveOperator, - new SwingMeleeWeaponOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - context.GetState().SetValue(Target); - var equipped = context.GetState().GetValue(); - context.GetState().SetValue(equipped); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .InverseBoolCurve(context), - considerationsManager.Get() - .QuadraticCurve(context, -0.8f, 1.0f, 1.0f, 0.0f), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.TargetHealth), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.5f, 0.0f, 0.0f), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.25f, 0.0f, 0.0f), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Combat/Melee/PickUpMeleeWeapon.cs b/Content.Server/AI/Utility/Actions/Combat/Melee/PickUpMeleeWeapon.cs deleted file mode 100644 index 4312c6568e..0000000000 --- a/Content.Server/AI/Utility/Actions/Combat/Melee/PickUpMeleeWeapon.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Combat; - -namespace Content.Server.AI.Utility.Actions.Combat.Melee -{ - public sealed class PickUpMeleeWeapon : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.25f, 0.0f, 0.0f), - considerationsManager.Get() - .QuadraticCurve(context, -1.0f, 0.5f, 1.0f, 0.0f), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Combat/Melee/UnarmedAttackEntity.cs b/Content.Server/AI/Utility/Actions/Combat/Melee/UnarmedAttackEntity.cs deleted file mode 100644 index d1d089f20c..0000000000 --- a/Content.Server/AI/Utility/Actions/Combat/Melee/UnarmedAttackEntity.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Combat.Melee; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Combat; -using Content.Server.AI.WorldState.States.Movement; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Actions.Combat.Melee -{ - public sealed class UnarmedAttackEntity : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - MoveToEntityOperator moveOperator; - if (IoCManager.Resolve().TryGetComponent(Owner, out MeleeWeaponComponent? unarmedCombatComponent)) - { - moveOperator = new MoveToEntityOperator(Owner, Target, unarmedCombatComponent.Range - 0.01f); - } - // I think it's possible for this to happen given planning is time-sliced? - // TODO: At this point we should abort - else - { - moveOperator = new MoveToEntityOperator(Owner, Target); - } - - ActionOperators = new Queue(new AiOperator[] - { - moveOperator, - new UnarmedCombatOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - context.GetState().SetValue(Target); - // Can just set ourselves as entity given unarmed just inherits from meleeweapon - context.GetState().SetValue(Owner); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .InverseBoolCurve(context), - considerationsManager.Get() - .QuadraticCurve(context, -0.8f, 1.0f, 1.0f, 0.0f), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.TargetHealth), - considerationsManager.Get() - .BoolCurve(context), - // TODO: Consider our Speed and Damage to compare this to using a weapon - // Also need to unequip our weapon if we have one (xenos can't hold one so no issue for now) - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/IAiUtility.cs b/Content.Server/AI/Utility/Actions/IAiUtility.cs deleted file mode 100644 index fe9c1a39b5..0000000000 --- a/Content.Server/AI/Utility/Actions/IAiUtility.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Content.Server.AI.Utility.Actions -{ - public interface IAiUtility - { - /// - /// NPC this action is attached to. - /// - EntityUid Owner { get; set; } - - /// - /// Highest possible score for this action. - /// - float Bonus { get; } - } -} diff --git a/Content.Server/AI/Utility/Actions/Idle/CloseLastEntityStorage.cs b/Content.Server/AI/Utility/Actions/Idle/CloseLastEntityStorage.cs deleted file mode 100644 index 24df7aec9b..0000000000 --- a/Content.Server/AI/Utility/Actions/Idle/CloseLastEntityStorage.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.Utility.Considerations.State; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; - -namespace Content.Server.AI.Utility.Actions.Idle -{ - /// - /// If we just picked up a bunch of stuff and have time then close it - /// - public sealed class CloseLastEntityStorage : UtilityAction - { - public override float Bonus => IdleBonus + 0.01f; - - public override void SetupOperators(Blackboard context) - { - var lastStorage = context.GetState().GetValue(); - - if (!lastStorage.IsValid()) - { - ActionOperators = new Queue(new AiOperator[] - { - new CloseLastStorageOperator(Owner), - }); - - return; - } - - ActionOperators = new Queue(new AiOperator[] - { - new MoveToEntityOperator(Owner, lastStorage), - new CloseLastStorageOperator(Owner), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - var lastStorage = context.GetState(); - context.GetState().SetValue(lastStorage.GetValue()); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get().Set(typeof(LastOpenedStorageState), context) - .InverseBoolCurve(context), - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .BoolCurve(context), - }; - } - - } -} diff --git a/Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs b/Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs deleted file mode 100644 index 6eac412f02..0000000000 --- a/Content.Server/AI/Utility/Actions/Idle/WanderAndWait.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Generic; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.Pathfinding; -using Content.Server.AI.Pathfinding.Accessible; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.ActionBlocker; -using Content.Server.AI.WorldState; -using Robust.Shared.Map; -using Robust.Shared.Random; - -namespace Content.Server.AI.Utility.Actions.Idle -{ - /// - /// Will move to a random spot close by - /// - public sealed class WanderAndWait : UtilityAction - { - public override bool CanOverride => false; - public override float Bonus => 1.0f; - - public override void SetupOperators(Blackboard context) - { - var robustRandom = IoCManager.Resolve(); - var randomGrid = FindRandomGrid(robustRandom); - float waitTime; - if (randomGrid != EntityCoordinates.Invalid) - { - waitTime = robustRandom.Next(3, 8); - } - else - { - waitTime = 0.0f; - } - - ActionOperators = new Queue(new AiOperator[] - { - new MoveToGridOperator(Owner, randomGrid), - new WaitOperator(waitTime), - }); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - - private EntityCoordinates FindRandomGrid(IRobustRandom robustRandom, IEntityManager? entMan = null) - { - IoCManager.Resolve(ref entMan); - - // Very inefficient (should weight each region by its node count) but better than the old system - var reachableSystem = entMan.EntitySysManager.GetEntitySystem(); - var reachableArgs = ReachableArgs.GetArgs(Owner); - var entityRegion = reachableSystem.GetRegion(Owner); - var reachableRegions = reachableSystem.GetReachableRegions(reachableArgs, entityRegion); - - // TODO: When SetupOperators can fail this should be null and fail the setup. - if (reachableRegions.Count == 0) - { - return default; - } - - var reachableNodes = new List(); - - foreach (var region in reachableRegions) - { - foreach (var node in region.Nodes) - { - reachableNodes.Add(node); - } - } - - var targetNode = robustRandom.Pick(reachableNodes); - - if (!entMan.TryGetComponent(entMan.GetComponent(Owner).GridUid, out IMapGridComponent? grid)) - return default; - - var targetGrid = grid.Grid.GridTileToLocal(targetNode.TileRef.GridIndices); - - return targetGrid; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs b/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs deleted file mode 100644 index edbdfcef36..0000000000 --- a/Content.Server/AI/Utility/Actions/Nutrition/Drink/PickUpDrink.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.Utility.Considerations.Nutrition.Drink; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Nutrition.Drink -{ - public sealed class PickUpDrink : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f), - considerationsManager.Get() - .BoolCurve(context), - }; - } - - } -} diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs b/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs deleted file mode 100644 index a68fa5e024..0000000000 --- a/Content.Server/AI/Utility/Actions/Nutrition/Drink/UseDrinkInInventory.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Operators.Nutrition; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.Utility.Considerations.Nutrition.Drink; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Nutrition.Drink -{ - public sealed class UseDrinkInInventory : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target), - new UseDrinkInInventoryOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Food/PickUpFood.cs b/Content.Server/AI/Utility/Actions/Nutrition/Food/PickUpFood.cs deleted file mode 100644 index 8303f502d2..0000000000 --- a/Content.Server/AI/Utility/Actions/Nutrition/Food/PickUpFood.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators.Sequences; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Containers; -using Content.Server.AI.Utility.Considerations.Movement; -using Content.Server.AI.Utility.Considerations.Nutrition.Food; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Nutrition.Food -{ - public sealed class PickUpFood : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new GoPickupEntitySequence(Owner, Target).Sequence; - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .PresetCurve(context, PresetCurve.Distance), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f), - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs b/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs deleted file mode 100644 index cf0475aa33..0000000000 --- a/Content.Server/AI/Utility/Actions/Nutrition/Food/UseFoodInInventory.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Inventory; -using Content.Server.AI.Operators.Nutrition; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Inventory; -using Content.Server.AI.Utility.Considerations.Nutrition.Food; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Actions.Nutrition.Food -{ - public sealed class UseFoodInInventory : UtilityAction - { - public EntityUid Target { get; set; } = default!; - - public override void SetupOperators(Blackboard context) - { - ActionOperators = new Queue(new AiOperator[] - { - new EquipEntityOperator(Owner, Target), - new UseFoodInInventoryOperator(Owner, Target), - }); - } - - protected override void UpdateBlackboard(Blackboard context) - { - base.UpdateBlackboard(context); - context.GetState().SetValue(Target); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .QuadraticCurve(context, 1.0f, 0.4f, 0.0f, 0.0f), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/Test/MoveRightAndLeftTen.cs b/Content.Server/AI/Utility/Actions/Test/MoveRightAndLeftTen.cs deleted file mode 100644 index f78d3b3e61..0000000000 --- a/Content.Server/AI/Utility/Actions/Test/MoveRightAndLeftTen.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.Operators.Movement; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.WorldState; - -namespace Content.Server.AI.Utility.Actions.Test -{ - /// - /// Used for pathfinding debugging - /// - public sealed class MoveRightAndLeftTen : UtilityAction - { - public override bool CanOverride => false; - - public override void SetupOperators(Blackboard context) - { - var entMan = IoCManager.Resolve(); - var currentPosition = entMan.GetComponent(Owner).Coordinates; - var nextPosition = entMan.GetComponent(Owner).Coordinates.Offset(new Vector2(10.0f, 0.0f)); - var originalPosOp = new MoveToGridOperator(Owner, currentPosition, 0.25f); - var newPosOp = new MoveToGridOperator(Owner, nextPosition, 0.25f); - - ActionOperators = new Queue(new AiOperator[] - { - newPosOp, - originalPosOp - }); - } - - protected override IReadOnlyCollection> GetConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - } -} diff --git a/Content.Server/AI/Utility/Actions/UtilityAction.cs b/Content.Server/AI/Utility/Actions/UtilityAction.cs deleted file mode 100644 index 203e94d1d9..0000000000 --- a/Content.Server/AI/Utility/Actions/UtilityAction.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Content.Server.AI.Operators; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Utility; -using Robust.Shared.Utility; - -namespace Content.Server.AI.Utility.Actions -{ - /// - /// The same DSE can be used across multiple actions. - /// - public abstract class UtilityAction : IAiUtility - { - /// - /// If we're trying to find a new action can we replace a currently running one with one of the same type. - /// e.g. If you're already wandering you don't want to replace it with a different wander. - /// - public virtual bool CanOverride => false; - - /// - /// This is used to sort actions; if there's a top-tier action available we won't bother checking the lower tiers. - /// Threshold doesn't necessarily mean we'll do an action at a higher threshold; - /// if it's really un-optimal (i.e. low score) then we'll also check lower tiers - /// - public virtual float Bonus { get; set; } = IdleBonus; - // For GW2 they had the bonuses close together but IMO it feels better when they're more like discrete tiers. - - // These are just baselines to make mass-updates easier; actions can do whatever - // e.g. if you want shooting a gun to be considered before picking up a gun you could + 1.0f it or w/e - public const float IdleBonus = 1.0f; - public const float NormalBonus = 5.0f; - public const float NeedsBonus = 10.0f; - public const float CombatPrepBonus = 20.0f; - public const float CombatBonus = 30.0f; - public const float DangerBonus = 50.0f; - - public EntityUid Owner { get; set; } - - /// - /// All the considerations are multiplied together to get the final score; a consideration of 0.0 means the action is not possible. - /// Ideally you put anything that's easy to assess and can cause an early-out first just so the rest aren't evaluated. - /// - /// Uses Func as you don't want to eval the later considerations unless necessary, but we also need the total count - /// so can't use IEnumerable - protected abstract IReadOnlyCollection> GetConsiderations(Blackboard context); - - /// - /// To keep the operators simple we can chain them together here, e.g. move to can be chained with other operators. - /// - public Queue ActionOperators { get; protected set; } - - /// - /// Sometimes we may need to set the target for an action or the likes. - /// This is mainly useful for expandable states so each one can have a separate target. - /// - /// - protected virtual void UpdateBlackboard(Blackboard context) {} - - // Needs to be able to be instantiated without args via typefactory. - public UtilityAction() - { - Owner = default!; - ActionOperators = default!; - } - - public virtual void Shutdown() {} - - /// - /// If this action is chosen then setup the operators to run. This also allows for operators to be reset. - /// - public abstract void SetupOperators(Blackboard context); - - // Call the task's operator with Execute and get the outcome - public Outcome Execute(float frameTime) - { - if (!ActionOperators.TryPeek(out var op)) - { - return Outcome.Success; - } - - op.Startup(); - var outcome = op.Execute(frameTime); - - switch (outcome) - { - case Outcome.Success: - op.Shutdown(outcome); - ActionOperators.Dequeue(); - break; - case Outcome.Continuing: - break; - case Outcome.Failed: - op.Shutdown(outcome); - ActionOperators.Clear(); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - return outcome; - } - - /// - /// AKA the Decision Score Evaluator (DSE) - /// This is where the magic happens - /// - /// - /// - /// - public float GetScore(Blackboard context, float min) - { - UpdateBlackboard(context); - var considerations = GetConsiderations(context); - DebugTools.Assert(considerations.Count > 0); - - // Overall structure is based on Building a better centaur - // Ideally we should early-out each action as cheaply as possible if it's not valid, thus - // the finalScore can only go down over time. - - var finalScore = 1.0f; - var minThreshold = min / Bonus; - context.GetState().SetValue(considerations.Count); - - foreach (var consideration in considerations) - { - var score = consideration.Invoke(); - finalScore *= score; - DebugTools.Assert(!float.IsNaN(score)); - - // The score can only ever go down from each consideration so if we're below minimum no point continuing. - if (0.0f >= finalScore || finalScore < minThreshold) { - return 0.0f; - } - } - - DebugTools.Assert(finalScore <= 1.0f); - - return finalScore * Bonus; - } - } -} diff --git a/Content.Server/AI/Utility/AiLogic/UtilityNPCComponent.cs b/Content.Server/AI/Utility/AiLogic/UtilityNPCComponent.cs deleted file mode 100644 index 880512a8d4..0000000000 --- a/Content.Server/AI/Utility/AiLogic/UtilityNPCComponent.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.LoadBalancer; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.WorldState; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; -using System.Threading; -using Content.Server.AI.EntitySystems; - -namespace Content.Server.AI.Utility.AiLogic -{ - [RegisterComponent, Access(typeof(NPCSystem))] - [ComponentReference(typeof(NPCComponent))] - public sealed class UtilityNPCComponent : NPCComponent - { - public Blackboard Blackboard => _blackboard; - public Blackboard _blackboard = default!; - - /// - /// The sum of all BehaviorSets gives us what actions the AI can take - /// - [DataField("behaviorSets", customTypeSerializer:typeof(PrototypeIdHashSetSerializer))] - public HashSet BehaviorSets { get; } = new(); - - public List AvailableActions { get; set; } = new(); - - /// - /// The currently running action; most importantly are the operators. - /// - public UtilityAction? CurrentAction { get; set; } - - /// - /// How frequently we can re-plan. If an AI's in combat you could decrease the cooldown, - /// or if there's no players nearby increase it. - /// - public float PlanCooldown { get; } = 0.5f; - - public float _planCooldownRemaining; - - /// - /// If we've requested a plan then wait patiently for the action - /// - public AiActionRequestJob? _actionRequest; - - public CancellationTokenSource? _actionCancellation; - } -} diff --git a/Content.Server/AI/Utility/BehaviorSetPrototype.cs b/Content.Server/AI/Utility/BehaviorSetPrototype.cs deleted file mode 100644 index c0e964b901..0000000000 --- a/Content.Server/AI/Utility/BehaviorSetPrototype.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Server.AI.Utility -{ - [Prototype("behaviorSet")] - public sealed class BehaviorSetPrototype : IPrototype - { - /// - /// Name of the BehaviorSet. - /// - [ViewVariables] - [IdDataFieldAttribute] - public string ID { get; } = default!; - - /// - /// Actions that this BehaviorSet grants to the entity. - /// - [DataField("actions")] - public IReadOnlyList Actions { get; private set; } = new List(); - } -} diff --git a/Content.Server/AI/Utility/Considerations/ActionBlocker/CanMoveCon.cs b/Content.Server/AI/Utility/Considerations/ActionBlocker/CanMoveCon.cs deleted file mode 100644 index 00485c0f8e..0000000000 --- a/Content.Server/AI/Utility/Considerations/ActionBlocker/CanMoveCon.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Shared.ActionBlocker; - -namespace Content.Server.AI.Utility.Considerations.ActionBlocker -{ - public sealed class CanMoveCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var self = context.GetState().GetValue(); - - if (!EntitySystem.Get().CanMove(self)) - { - return 0.0f; - } - - return 1.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Clothing/ClothingInInventoryCon.cs b/Content.Server/AI/Utility/Considerations/Clothing/ClothingInInventoryCon.cs deleted file mode 100644 index dbedc4dc9a..0000000000 --- a/Content.Server/AI/Utility/Considerations/Clothing/ClothingInInventoryCon.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Clothing; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.Considerations.Clothing -{ - public sealed class ClothingInInventoryCon : Consideration - { - public ClothingInInventoryCon Slot(SlotFlags slotFlags, Blackboard context) - { - // Ideally we'd just use a variable but then if we were iterating through multiple AI at once it'd be - // Stuffed so we need to store it on the AI's context. - context.GetState().SetValue(slotFlags); - return this; - } - - protected override float GetScore(Blackboard context) - { - var slots = context.GetState().GetValue(); - if (slots == null) return 0.0f; - - foreach (var entity in context.GetState().GetValue()) - { - if (!IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothingComponent) || - !EntitySystem.Get().TryGetSlot(entity, slots, out var slotDef)) - { - continue; - } - - if ((clothingComponent.Slots & slotDef.SlotFlags) != 0) - { - return 1.0f; - } - } - - return 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Clothing/ClothingInSlotCon.cs b/Content.Server/AI/Utility/Considerations/Clothing/ClothingInSlotCon.cs deleted file mode 100644 index 572f26598e..0000000000 --- a/Content.Server/AI/Utility/Considerations/Clothing/ClothingInSlotCon.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Clothing; - -namespace Content.Server.AI.Utility.Considerations.Clothing -{ - public sealed class ClothingInSlotCon : Consideration - { - - public ClothingInSlotCon Slot(string slot, Blackboard context) - { - context.GetState().SetValue(slot); - return this; - } - - protected override float GetScore(Blackboard context) - { - var slot = context.GetState().GetValue(); - var inventory = context.GetState().GetValue(); - return slot != null && inventory.ContainsKey(slot) ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/Melee/CanUnarmedCombatCon.cs b/Content.Server/AI/Utility/Considerations/Combat/Melee/CanUnarmedCombatCon.cs deleted file mode 100644 index ab8548d0dc..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/Melee/CanUnarmedCombatCon.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat.Melee -{ - public sealed class CanUnarmedCombatCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var entityManager = IoCManager.Resolve(); - var entity = context.GetState().GetValue(); - return entityManager.HasComponent(entity) ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/Melee/HasMeleeWeaponCon.cs b/Content.Server/AI/Utility/Considerations/Combat/Melee/HasMeleeWeaponCon.cs deleted file mode 100644 index 3c15ca558f..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/Melee/HasMeleeWeaponCon.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat.Melee -{ - public sealed class HasMeleeWeaponCon : Consideration - { - protected override float GetScore(Blackboard context) - { - foreach (var item in context.GetState().GetValue()) - { - if (IoCManager.Resolve().HasComponent(item)) - { - return 1.0f; - } - } - - return 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponDamageCon.cs b/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponDamageCon.cs deleted file mode 100644 index 7a6502b943..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponDamageCon.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Combat; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat.Melee -{ - public sealed class MeleeWeaponDamageCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().TryGetComponent(target, out MeleeWeaponComponent? meleeWeaponComponent)) - { - return 0.0f; - } - - // Just went with max health - return (meleeWeaponComponent.Damage.Total / 300.0f).Float(); - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponEquippedCon.cs b/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponEquippedCon.cs deleted file mode 100644 index 507a724251..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponEquippedCon.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat.Melee -{ - public sealed class MeleeWeaponEquippedCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var equipped = context.GetState().GetValue(); - - if (equipped == null) - { - return 0.0f; - } - - return IoCManager.Resolve().HasComponent(equipped) ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponSpeedCon.cs b/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponSpeedCon.cs deleted file mode 100644 index 68334191de..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/Melee/MeleeWeaponSpeedCon.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Combat; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat.Melee -{ - public sealed class MeleeWeaponSpeedCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().TryGetComponent(target, out MeleeWeaponComponent? meleeWeaponComponent)) - { - return 0.0f; - } - - return meleeWeaponComponent.ArcCooldownTime / 10.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs deleted file mode 100644 index 74065dfd8a..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Shared.Damage; - -namespace Content.Server.AI.Utility.Considerations.Combat -{ - public sealed class TargetHealthCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().TryGetComponent(target, out DamageableComponent? damageableComponent)) - { - return 0.0f; - } - - return (float) damageableComponent.TotalDamage / 300.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs deleted file mode 100644 index 1c0e698093..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Shared.MobState.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat -{ - public sealed class TargetIsCritCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().TryGetComponent(target, out MobStateComponent? mobState)) - { - return 0.0f; - } - - if (mobState.IsCritical()) - { - return 1.0f; - } - - return 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs deleted file mode 100644 index bc1568857b..0000000000 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Shared.MobState.Components; - -namespace Content.Server.AI.Utility.Considerations.Combat -{ - public sealed class TargetIsDeadCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().TryGetComponent(target, out MobStateComponent? mobState)) - { - return 0.0f; - } - - if (mobState.IsDead()) - { - return 1.0f; - } - - return 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Consideration.cs b/Content.Server/AI/Utility/Considerations/Consideration.cs deleted file mode 100644 index 07e4119449..0000000000 --- a/Content.Server/AI/Utility/Considerations/Consideration.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Utility; -using JetBrains.Annotations; - -namespace Content.Server.AI.Utility.Considerations -{ - public abstract class Consideration - { - protected abstract float GetScore(Blackboard context); - - private float GetAdjustedScore(Blackboard context) - { - var score = GetScore(context); - /* - * Now using the geometric mean - * for n scores you take the n-th root of the scores multiplied - * e.g. a, b, c scores you take Math.Pow(a * b * c, 1/3) - * To get the ACTUAL geometric mean at any one stage you'd need to divide by the running consideration count - * however, the downside to this is it will fluctuate up and down over time. - * For our purposes if we go below the minimum threshold we want to cut it off, thus we take a - * "running geometric mean" which can only ever go down (and by the final value will equal the actual geometric mean). - */ - - // Previously we used a makeupvalue method although the geometric mean is less punishing for more considerations - var considerationsCount = context.GetState().GetValue(); - var adjustedScore = MathF.Pow(score, 1 / (float) considerationsCount); - return MathHelper.Clamp(adjustedScore, 0.0f, 1.0f); - } - - [Pure] - private static float BoolCurve(float x) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator - return x > 0.0f ? 1.0f : 0.0f; - } - - public Func BoolCurve(Blackboard context) - { - float Result() - { - var adjustedScore = GetAdjustedScore(context); - return BoolCurve(adjustedScore); - } - - return Result; - } - - [Pure] - private static float InverseBoolCurve(float x) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator - return x == 0.0f ? 1.0f : 0.0f; - } - - public Func InverseBoolCurve(Blackboard context) - { - float Result() - { - var adjustedScore = GetAdjustedScore(context); - return InverseBoolCurve(adjustedScore); - } - - return Result; - } - - [Pure] - private static float LogisticCurve(float x, float slope, float exponent, float yOffset, float xOffset) - { - return MathHelper.Clamp( - exponent * (1 / (1 + (float) Math.Pow(Math.Log(1000) * slope, -1 * x + xOffset))) + yOffset, 0.0f, 1.0f); - } - - public Func LogisticCurve(Blackboard context, float slope, float exponent, float yOffset, float xOffset) - { - float Result() - { - var adjustedScore = GetAdjustedScore(context); - return LogisticCurve(adjustedScore, slope, exponent, yOffset, xOffset); - } - - return Result; - } - - [Pure] - private static float QuadraticCurve(float x, float slope, float exponent, float yOffset, float xOffset) - { - return MathHelper.Clamp(slope * (float) Math.Pow(x - xOffset, exponent) + yOffset, 0.0f, 1.0f); - } - - public Func QuadraticCurve(Blackboard context, float slope, float exponent, float yOffset, float xOffset) - { - float Result() - { - var adjustedScore = GetAdjustedScore(context); - return QuadraticCurve(adjustedScore, slope, exponent, yOffset, xOffset); - } - - return Result; - } - - /// - /// For any curves that are re-used across actions so you only need to update it once. - /// - /// - /// - /// - /// - public Func PresetCurve(Blackboard context, PresetCurve preset) - { - float Result() - { - var adjustedScore = GetAdjustedScore(context); - - switch (preset) - { - case Considerations.PresetCurve.Distance: - return QuadraticCurve(adjustedScore, -1.0f, 1.0f, 1.0f, 0.02f); - case Considerations.PresetCurve.Nutrition: - return QuadraticCurve(adjustedScore, 2.0f, 1.0f, -1.0f, -0.2f); - case Considerations.PresetCurve.TargetHealth: - return QuadraticCurve(adjustedScore, 1.0f, 0.4f, 0.0f, -0.02f); - default: - throw new ArgumentOutOfRangeException(nameof(preset), preset, null); - } - } - - return Result; - } - } - - /// - /// Preset response curves for considerations - /// - public enum PresetCurve - { - Distance, - Nutrition, - TargetHealth, - } -} diff --git a/Content.Server/AI/Utility/Considerations/ConsiderationsManager.cs b/Content.Server/AI/Utility/Considerations/ConsiderationsManager.cs deleted file mode 100644 index 6465bb24b4..0000000000 --- a/Content.Server/AI/Utility/Considerations/ConsiderationsManager.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Robust.Shared.Reflection; - -namespace Content.Server.AI.Utility.Considerations -{ - public sealed class ConsiderationsManager - { - private readonly Dictionary _considerations = new(); - - public void Initialize() - { - var reflectionManager = IoCManager.Resolve(); - var typeFactory = IoCManager.Resolve(); - - foreach (var conType in reflectionManager.GetAllChildren(typeof(Consideration))) - { - var con = (Consideration) typeFactory.CreateInstance(conType); - _considerations.Add(conType, con); - } - } - - public T Get() where T : Consideration - { - return (T) _considerations[typeof(T)]; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Containers/TargetAccessibleCon.cs b/Content.Server/AI/Utility/Considerations/Containers/TargetAccessibleCon.cs deleted file mode 100644 index c502210de6..0000000000 --- a/Content.Server/AI/Utility/Considerations/Containers/TargetAccessibleCon.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Content.Server.AI.Pathfinding.Accessible; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.Storage.Components; -using Content.Shared.Interaction; -using Robust.Shared.Containers; - -namespace Content.Server.AI.Utility.Considerations.Containers -{ - /// - /// Returns 1.0f if the item is freely accessible (e.g. in storage we can open, on ground, etc.) - /// This can be expensive so consider using this last for the considerations - /// - public sealed class TargetAccessibleCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var entMan = IoCManager.Resolve(); - var target = context.GetState().GetValue(); - if (!entMan.EntityExists(target)) - { - return 0.0f; - } - - if (target!.Value.TryGetContainer(out var container)) - { - if (entMan.TryGetComponent(container.Owner, out EntityStorageComponent? storageComponent)) - { - if (storageComponent.IsWeldedShut && !storageComponent.Open) - { - return 0.0f; - } - } - else - { - // If we're in a container (e.g. held or whatever) then we probably can't get it. Only exception - // Is a locker / crate - return 0.0f; - } - } - - var owner = context.GetState().GetValue(); - - return EntitySystem.Get().CanAccess(owner, target.Value, SharedInteractionSystem.InteractionRange) ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/DummyCon.cs b/Content.Server/AI/Utility/Considerations/DummyCon.cs deleted file mode 100644 index 503ddbefc9..0000000000 --- a/Content.Server/AI/Utility/Considerations/DummyCon.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Server.AI.WorldState; - -namespace Content.Server.AI.Utility.Considerations -{ - public sealed class DummyCon : Consideration - { - protected override float GetScore(Blackboard context) => 1.0f; - } -} diff --git a/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs b/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs deleted file mode 100644 index d0f2520b9c..0000000000 --- a/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Shared.Hands.Components; - -namespace Content.Server.AI.Utility.Considerations.Hands -{ - public sealed class FreeHandCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var owner = context.GetState().GetValue(); - - if (!owner.IsValid() || !IoCManager.Resolve().TryGetComponent(owner, out SharedHandsComponent? handsComponent)) - { - return 0.0f; - } - - return (float) handsComponent.CountFreeHands() / handsComponent.Count; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Inventory/CanPutTargetInInventoryCon.cs b/Content.Server/AI/Utility/Considerations/Inventory/CanPutTargetInInventoryCon.cs deleted file mode 100644 index 0b5c86484f..0000000000 --- a/Content.Server/AI/Utility/Considerations/Inventory/CanPutTargetInInventoryCon.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Hands; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Shared.Item; - -namespace Content.Server.AI.Utility.Considerations.Inventory -{ - public sealed class CanPutTargetInInventoryCon : Consideration - { - protected override float GetScore(Blackboard context) - { - // First check if target in inventory already - // If not then check if we have a free hand - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().HasComponent(target)) - { - return 0.0f; - } - - foreach (var item in context.GetState().GetValue()) - { - if (item == target) - { - return 1.0f; - } - } - - return context.GetState().GetValue() ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Inventory/TargetInOurInventoryCon.cs b/Content.Server/AI/Utility/Considerations/Inventory/TargetInOurInventoryCon.cs deleted file mode 100644 index fc1ccd18a5..0000000000 --- a/Content.Server/AI/Utility/Considerations/Inventory/TargetInOurInventoryCon.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Shared.Item; - -namespace Content.Server.AI.Utility.Considerations.Inventory -{ - public sealed class TargetInOurInventoryCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().HasComponent(target)) - { - return 0.0f; - } - - foreach (var item in context.GetState().GetValue()) - { - if (item == target) - { - return 1.0f; - } - } - - return 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs b/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs deleted file mode 100644 index 38dc8736f6..0000000000 --- a/Content.Server/AI/Utility/Considerations/Movement/TargetDistanceCon.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.Considerations.Movement -{ - public sealed class TargetDistanceCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var self = context.GetState().GetValue(); - var entities = IoCManager.Resolve(); - - if (context.GetState().GetValue() is not {Valid: true} target || entities.Deleted(target) || - entities.GetComponent(target).GridUid != entities.GetComponent(self).GridUid) - { - return 0.0f; - } - - // Anything further than 100 tiles gets clamped - return (entities.GetComponent(target).Coordinates.Position - entities.GetComponent(self).Coordinates.Position).Length / 100; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Nutrition/Drink/DrinkValueCon.cs b/Content.Server/AI/Utility/Considerations/Nutrition/Drink/DrinkValueCon.cs deleted file mode 100644 index e2d97f2d51..0000000000 --- a/Content.Server/AI/Utility/Considerations/Nutrition/Drink/DrinkValueCon.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Nutrition.Components; - -namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink -{ - public sealed class DrinkValueCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null - || IoCManager.Resolve().Deleted(target) - || !EntitySystem.Get().TryGetSolution(target.Value, DrinkComponent.DefaultSolutionName, out var drink)) - { - return 0.0f; - } - - var nutritionValue = 0; - - foreach (var reagent in drink.Contents) - { - // TODO - nutritionValue += (reagent.Quantity * 30).Int(); - } - - return nutritionValue / 1000.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Nutrition/Drink/ThirstCon.cs b/Content.Server/AI/Utility/Considerations/Nutrition/Drink/ThirstCon.cs deleted file mode 100644 index c6dc789ed8..0000000000 --- a/Content.Server/AI/Utility/Considerations/Nutrition/Drink/ThirstCon.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.Nutrition.Components; - -namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink -{ - public sealed class ThirstCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var owner = context.GetState().GetValue(); - - if (!IoCManager.Resolve().TryGetComponent(owner, out ThirstComponent? thirst)) - { - return 0.0f; - } - - return 1 - (thirst.CurrentThirst / thirst.ThirstThresholds[ThirstThreshold.OverHydrated]); - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Nutrition/Food/FoodValueCon.cs b/Content.Server/AI/Utility/Considerations/Nutrition/Food/FoodValueCon.cs deleted file mode 100644 index 4ae26050a6..0000000000 --- a/Content.Server/AI/Utility/Considerations/Nutrition/Food/FoodValueCon.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.Chemistry.EntitySystems; -using Content.Server.Nutrition.Components; - -namespace Content.Server.AI.Utility.Considerations.Nutrition.Food -{ - public sealed class FoodValueCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var target = context.GetState().GetValue(); - - if (target == null || !IoCManager.Resolve().TryGetComponent(target.Value, out var foodComp) - || !EntitySystem.Get().TryGetSolution(target.Value, foodComp.SolutionName, out var food)) - { - return 0.0f; - } - - var nutritionValue = 0; - - foreach (var reagent in food.Contents) - { - // TODO - nutritionValue += (reagent.Quantity * 30).Int(); - } - - return nutritionValue / 1000.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/Nutrition/Food/HungerCon.cs b/Content.Server/AI/Utility/Considerations/Nutrition/Food/HungerCon.cs deleted file mode 100644 index 75da9b4c0e..0000000000 --- a/Content.Server/AI/Utility/Considerations/Nutrition/Food/HungerCon.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.Nutrition.Components; -using Content.Shared.Nutrition.Components; - -namespace Content.Server.AI.Utility.Considerations.Nutrition.Food -{ - - public sealed class HungerCon : Consideration - { - protected override float GetScore(Blackboard context) - { - var owner = context.GetState().GetValue(); - - if (!IoCManager.Resolve().TryGetComponent(owner, out HungerComponent? hunger)) - { - return 0.0f; - } - - return 1 - (hunger.CurrentHunger / hunger.HungerThresholds[HungerThreshold.Overfed]); - } - } -} diff --git a/Content.Server/AI/Utility/Considerations/State/StoredStateEntityIsNullCon.cs b/Content.Server/AI/Utility/Considerations/State/StoredStateEntityIsNullCon.cs deleted file mode 100644 index fd7756931b..0000000000 --- a/Content.Server/AI/Utility/Considerations/State/StoredStateEntityIsNullCon.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States.Utility; - -namespace Content.Server.AI.Utility.Considerations.State -{ - /// - /// Simple NullCheck on a StoredState - /// - public sealed class StoredStateEntityIsNullCon : Consideration - { - public StoredStateEntityIsNullCon Set(Type type, Blackboard context) - { - // Ideally we'd just use a variable but then if we were iterating through multiple AI at once it'd be - // Stuffed so we need to store it on the AI's context. - context.GetState().SetValue(type); - return this; - } - - protected override float GetScore(Blackboard context) - { - var stateData = context.GetState().GetValue(); - - if (stateData == null) - { - return 0; - } - - context.GetStoredState(stateData, out StoredStateData state); - return !state.GetValue().IsValid() ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Curves/BoolCurve.cs b/Content.Server/AI/Utility/Curves/BoolCurve.cs deleted file mode 100644 index d7e86c3c8f..0000000000 --- a/Content.Server/AI/Utility/Curves/BoolCurve.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Content.Server.AI.Utility.Curves -{ - /// - /// For stuff that's a simple 0.0f or 1.0f - /// - public struct BoolCurve : IResponseCurve - { - public float GetResponse(float score) - { - return score > 0.0f ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Curves/IResponseCurve.cs b/Content.Server/AI/Utility/Curves/IResponseCurve.cs deleted file mode 100644 index a2a78c839f..0000000000 --- a/Content.Server/AI/Utility/Curves/IResponseCurve.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Server.AI.Utility.Curves -{ - /// - /// Using an interface also lets us define preset curves that can be re-used - /// - public interface IResponseCurve - { - float GetResponse(float score); - } -} diff --git a/Content.Server/AI/Utility/Curves/InverseBoolCurve.cs b/Content.Server/AI/Utility/Curves/InverseBoolCurve.cs deleted file mode 100644 index 4296ffee5b..0000000000 --- a/Content.Server/AI/Utility/Curves/InverseBoolCurve.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Server.AI.Utility.Curves -{ - public struct InverseBoolCurve : IResponseCurve - { - public float GetResponse(float score) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator - return score == 0.0f ? 1.0f : 0.0f; - } - } -} diff --git a/Content.Server/AI/Utility/Curves/LogisticCurve.cs b/Content.Server/AI/Utility/Curves/LogisticCurve.cs deleted file mode 100644 index 54ee7d5dd4..0000000000 --- a/Content.Server/AI/Utility/Curves/LogisticCurve.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Content.Server.AI.Utility.Curves -{ - public struct LogisticCurve : IResponseCurve - { - private readonly float _slope; - - private readonly float _exponent; - // Vertical shift - private readonly float _yOffset; - // Horizontal shift - private readonly float _xOffset; - - public LogisticCurve(float slope, float exponent, float yOffset, float xOffset) - { - _slope = slope; - _exponent = exponent; - _yOffset = yOffset; - _xOffset = xOffset; - } - - public float GetResponse(float score) - { - return _exponent * (1 / (1 + (float) Math.Pow(Math.Log(1000) * _slope, -1 * score + _xOffset))) + _yOffset; - } - } -} diff --git a/Content.Server/AI/Utility/Curves/QuadraticCurve.cs b/Content.Server/AI/Utility/Curves/QuadraticCurve.cs deleted file mode 100644 index 20df18e652..0000000000 --- a/Content.Server/AI/Utility/Curves/QuadraticCurve.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Content.Server.AI.Utility.Curves -{ - /// - /// Also Linear - /// - public struct QuadraticCurve : IResponseCurve - { - private readonly float _slope; - - private readonly float _exponent; - // Vertical shift - private readonly float _yOffset; - // Horizontal shift - private readonly float _xOffset; - - public QuadraticCurve(float slope, float exponent, float yOffset, float xOffset) - { - _slope = slope; - _exponent = exponent; - _yOffset = yOffset; - _xOffset = xOffset; - } - - public float GetResponse(float score) - { - return _slope * (float) Math.Pow(score - _xOffset, _exponent) + _yOffset; - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/Gloves/EquipAnyGlovesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/Gloves/EquipAnyGlovesExp.cs deleted file mode 100644 index 65eeb013dd..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/Gloves/EquipAnyGlovesExp.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.Gloves; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Gloves -{ - /// - /// Equip any head item currently in our inventory - /// - public sealed class EquipAnyGlovesExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new [] - { - considerationsManager.Get().Slot("gloves", context) - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.GLOVES) != 0) - { - yield return new EquipGloves {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/Gloves/PickUpAnyNearbyGlovesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/Gloves/PickUpAnyNearbyGlovesExp.cs deleted file mode 100644 index 1d7ec7637b..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/Gloves/PickUpAnyNearbyGlovesExp.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.Gloves; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Clothing; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Gloves -{ - public sealed class PickUpAnyNearbyGlovesExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - return new[] - { - considerationsManager.Get().Slot("gloves", context) - .InverseBoolCurve(context), - considerationsManager.Get().Slot(SlotFlags.GLOVES, context) - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.GLOVES) != 0) - { - yield return new PickUpGloves {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/Head/EquipAnyHeadExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/Head/EquipAnyHeadExp.cs deleted file mode 100644 index c15ca9e80e..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/Head/EquipAnyHeadExp.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.Head; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Head -{ - /// - /// Equip any head item currently in our inventory - /// - public sealed class EquipAnyHeadExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] { - considerationsManager.Get().Slot("head", context) - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.HEAD) != 0) - { - yield return new EquipHead {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/Head/PickUpAnyNearbyHeadExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/Head/PickUpAnyNearbyHeadExp.cs deleted file mode 100644 index d159a99741..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/Head/PickUpAnyNearbyHeadExp.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.Head; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Clothing; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Head -{ - public sealed class PickUpAnyNearbyHeadExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - return new[] - { - considerationsManager.Get().Slot("head", context) - .InverseBoolCurve(context), - considerationsManager.Get().Slot(SlotFlags.HEAD, context) - .InverseBoolCurve(context) - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.HEAD) != 0) - { - yield return new PickUpHead {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/OuterClothing/EquipAnyOuterClothingExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/OuterClothing/EquipAnyOuterClothingExp.cs deleted file mode 100644 index bc20c66401..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/OuterClothing/EquipAnyOuterClothingExp.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.OuterClothing; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.OuterClothing -{ - /// - /// Equip any head item currently in our inventory - /// - public sealed class EquipAnyOuterClothingExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get().Slot("outerClothing", context) - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.OUTERCLOTHING) != 0) - { - yield return new EquipOuterClothing {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/OuterClothing/PickUpAnyNearbyOuterClothingExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/OuterClothing/PickUpAnyNearbyOuterClothingExp.cs deleted file mode 100644 index 156757f2ab..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/OuterClothing/PickUpAnyNearbyOuterClothingExp.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.OuterClothing; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Clothing; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.OuterClothing -{ - public sealed class PickUpAnyNearbyOuterClothingExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get().Slot("outerClothing", context) - .InverseBoolCurve(context), - considerationsManager.Get().Slot(SlotFlags.OUTERCLOTHING, context) - .InverseBoolCurve(context) - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.OUTERCLOTHING) != 0) - { - yield return new PickUpOuterClothing {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/Shoes/EquipAnyShoesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/Shoes/EquipAnyShoesExp.cs deleted file mode 100644 index d0493fe736..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/Shoes/EquipAnyShoesExp.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.Shoes; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Shoes -{ - /// - /// Equip any head item currently in our inventory - /// - public sealed class EquipAnyShoesExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get().Slot("shoes", context) - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.FEET) != 0) - { - yield return new EquipShoes {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Clothing/Shoes/PickUpAnyNearbyShoesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Clothing/Shoes/PickUpAnyNearbyShoesExp.cs deleted file mode 100644 index 6512c16ebe..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Clothing/Shoes/PickUpAnyNearbyShoesExp.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Clothing.Shoes; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Clothing; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Clothing; -using Content.Server.Clothing.Components; -using Content.Shared.Inventory; - -namespace Content.Server.AI.Utility.ExpandableActions.Clothing.Shoes -{ - public sealed class PickUpAnyNearbyShoesExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NormalBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get().Slot("shoes", context) - .InverseBoolCurve(context), - considerationsManager.Get().Slot(SlotFlags.FEET, context) - .InverseBoolCurve(context) - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (IoCManager.Resolve().TryGetComponent(entity, out ClothingComponent? clothing) && - (clothing.Slots & SlotFlags.FEET) != 0) - { - yield return new PickUpShoes {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/EquipMeleeExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/EquipMeleeExp.cs deleted file mode 100644 index 7d54211661..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/EquipMeleeExp.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Combat.Melee; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Weapon.Melee.Components; - -namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee -{ - public sealed class EquipMeleeExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.CombatPrepBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (!IoCManager.Resolve().HasComponent(entity)) - { - continue; - } - - yield return new EquipMelee {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyExp.cs deleted file mode 100644 index 4f2db88e76..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyExp.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.EntitySystems; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Combat.Melee; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee -{ - public sealed class MeleeAttackNearbyExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.CombatBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - if (!IoCManager.Resolve().TryGetComponent(owner, out NPCComponent? controller)) - { - throw new InvalidOperationException(); - } - - foreach (var target in EntitySystem.Get() - .GetNearbyHostiles(owner, controller.VisionRadius)) - { - yield return new MeleeWeaponAttackEntity {Owner = owner, Target = target, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/PickUpMeleeWeaponExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/PickUpMeleeWeaponExp.cs deleted file mode 100644 index f0145cff42..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/PickUpMeleeWeaponExp.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Combat.Melee; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.Utility.Considerations.Hands; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Combat.Nearby; - -namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee -{ - public sealed class PickUpMeleeWeaponExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.CombatPrepBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - considerationsManager.Get() - .InverseBoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - yield return new PickUpMeleeWeapon() {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyHostilesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyHostilesExp.cs deleted file mode 100644 index cf2b6120a2..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyHostilesExp.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.EntitySystems; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Combat.Melee; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; - -namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee -{ - public sealed class UnarmedAttackNearbyHostilesExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.CombatBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - if (!IoCManager.Resolve().TryGetComponent(owner, out NPCComponent? controller)) - { - throw new InvalidOperationException(); - } - - foreach (var target in EntitySystem.Get() - .GetNearbyHostiles(owner, controller.VisionRadius)) - { - yield return new UnarmedAttackEntity() {Owner = owner, Target = target, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/ExpandableUtilityAction.cs b/Content.Server/AI/Utility/ExpandableActions/ExpandableUtilityAction.cs deleted file mode 100644 index 6dc97af18b..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/ExpandableUtilityAction.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.WorldState; - -namespace Content.Server.AI.Utility.ExpandableActions -{ - /// - /// Expands into multiple separate utility actions for consideration, e.g. 5 nearby weapons 5 different actions - /// Ideally you would use the cached states for this - /// - public abstract class ExpandableUtilityAction : IAiUtility - { - public EntityUid Owner { get; set; } = default!; - - public abstract float Bonus { get; } - - /// - /// No point expanding nodes if none of them can ever be valid. - /// Fails if any of the common considerations is 0.0f (i.e. invalid) - /// - /// - /// - public bool IsValid(Blackboard context) - { - foreach (var con in GetCommonConsiderations(context)) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (con.Invoke() == 0.0f) return false; - } - - return true; - } - - /// - /// Called by IsValid to try and early-out the expandable action. - /// No point going through all nearby clothes if we can't fit it in a slot. - /// - /// Similar to HTN's compound tasks where they can have overall conditions that have to be met before the actions are considered. - /// Ideally any binary early-outs that are common to all expanded actions would be checked once, e.g. a boolean free hand check - /// Use this if you want to optimise the expandable further. - /// - /// - protected virtual IEnumerable> GetCommonConsiderations(Blackboard context) - { - yield break; - } - - // e.g. you may have a "PickupFood" action for all nearby food sources if you have the "Hungry" BehaviorSet. - public abstract IEnumerable GetActions(Blackboard context); - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Nutrition/PickUpNearbyDrinkExp.cs b/Content.Server/AI/Utility/ExpandableActions/Nutrition/PickUpNearbyDrinkExp.cs deleted file mode 100644 index eeae2faaa2..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Nutrition/PickUpNearbyDrinkExp.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Nutrition.Drink; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Hands; -using Content.Server.AI.Utility.Considerations.Nutrition.Drink; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Nutrition; - -namespace Content.Server.AI.Utility.ExpandableActions.Nutrition -{ - public sealed class PickUpNearbyDrinkExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NeedsBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - return new[] - { - considerationsManager.Get().PresetCurve(context, PresetCurve.Nutrition), - considerationsManager.Get().BoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - yield return new PickUpDrink() {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Nutrition/PickUpNearbyFoodExp.cs b/Content.Server/AI/Utility/ExpandableActions/Nutrition/PickUpNearbyFoodExp.cs deleted file mode 100644 index b206a525ff..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Nutrition/PickUpNearbyFoodExp.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Nutrition.Food; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Hands; -using Content.Server.AI.Utility.Considerations.Nutrition.Food; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Nutrition; - -namespace Content.Server.AI.Utility.ExpandableActions.Nutrition -{ - public sealed class PickUpNearbyFoodExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NeedsBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - return new[] - { - considerationsManager.Get().PresetCurve(context, PresetCurve.Nutrition), - considerationsManager.Get().BoolCurve(context), - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - yield return new PickUpFood {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Nutrition/UseDrinkInInventoryExp.cs b/Content.Server/AI/Utility/ExpandableActions/Nutrition/UseDrinkInInventoryExp.cs deleted file mode 100644 index 0877ed0568..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Nutrition/UseDrinkInInventoryExp.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Nutrition.Drink; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Nutrition.Drink; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Nutrition.Components; - -namespace Content.Server.AI.Utility.ExpandableActions.Nutrition -{ - public sealed class UseDrinkInInventoryExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NeedsBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - return new[] - { - considerationsManager.Get().PresetCurve(context, PresetCurve.Nutrition) - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (!IoCManager.Resolve().HasComponent(entity)) - { - continue; - } - - yield return new UseDrinkInInventory {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpandableActions/Nutrition/UseFoodInInventoryExp.cs b/Content.Server/AI/Utility/ExpandableActions/Nutrition/UseFoodInInventoryExp.cs deleted file mode 100644 index 54270a9555..0000000000 --- a/Content.Server/AI/Utility/ExpandableActions/Nutrition/UseFoodInInventoryExp.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Nutrition.Food; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Nutrition.Food; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.WorldState.States.Inventory; -using Content.Server.Nutrition.Components; - -namespace Content.Server.AI.Utility.ExpandableActions.Nutrition -{ - public sealed class UseFoodInInventoryExp : ExpandableUtilityAction - { - public override float Bonus => UtilityAction.NeedsBonus; - - protected override IEnumerable> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - return new[] - { - considerationsManager.Get().PresetCurve(context, PresetCurve.Nutrition) - }; - } - - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - - foreach (var entity in context.GetState().GetValue()) - { - if (!IoCManager.Resolve().HasComponent(entity)) - { - continue; - } - - yield return new UseFoodInInventory() {Owner = owner, Target = entity, Bonus = Bonus}; - } - } - } -} diff --git a/Content.Server/AI/Utility/ExpendableActions/Bots/BufferNearbyPuddlesExp.cs b/Content.Server/AI/Utility/ExpendableActions/Bots/BufferNearbyPuddlesExp.cs deleted file mode 100644 index 0b7f56536e..0000000000 --- a/Content.Server/AI/Utility/ExpendableActions/Bots/BufferNearbyPuddlesExp.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.EntitySystems; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Bots; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.Utility.Considerations.Combat.Melee; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.Utility.Considerations.ActionBlocker; - - - -namespace Content.Server.AI.Utility.ExpandableActions.Bots -{ - public sealed class BufferNearbyPuddlesExp : ExpandableUtilityAction - { - public override float Bonus => 30; - - protected override IReadOnlyCollection> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - if (!IoCManager.Resolve().TryGetComponent(owner, out NPCComponent? controller)) - { - throw new InvalidOperationException(); - } - - yield return new GoToPuddleAndWait() {Owner = owner, Target = EntitySystem.Get().GetNearbyPuddle(Owner), Bonus = Bonus}; - } - } -} diff --git a/Content.Server/AI/Utility/ExpendableActions/Bots/InjectNearbyExp.cs b/Content.Server/AI/Utility/ExpendableActions/Bots/InjectNearbyExp.cs deleted file mode 100644 index 0e74889841..0000000000 --- a/Content.Server/AI/Utility/ExpendableActions/Bots/InjectNearbyExp.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.EntitySystems; -using Content.Server.AI.Utility.Actions; -using Content.Server.AI.Utility.Actions.Bots; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.WorldState; -using Content.Server.AI.WorldState.States; -using Content.Server.AI.Utility.Considerations.ActionBlocker; -using Content.Server.Silicons.Bots; - -namespace Content.Server.AI.Utility.ExpandableActions.Bots -{ - public sealed class InjectNearbyExp : ExpandableUtilityAction - { - public override float Bonus => 30; - IEntityManager _entMan = IoCManager.Resolve(); - - protected override IReadOnlyCollection> GetCommonConsiderations(Blackboard context) - { - var considerationsManager = IoCManager.Resolve(); - - return new[] - { - considerationsManager.Get() - .BoolCurve(context), - }; - } - public override IEnumerable GetActions(Blackboard context) - { - var owner = context.GetState().GetValue(); - if (!_entMan.TryGetComponent(owner, out NPCComponent? controller) - || !_entMan.TryGetComponent(owner, out MedibotComponent? bot)) - { - throw new InvalidOperationException(); - } - - yield return new InjectNearby() {Owner = owner, Target = EntitySystem.Get().GetNearbyInjectable(Owner), Bonus = Bonus}; - } - } -} diff --git a/Content.Server/AI/Utility/UtilityAiHelpers.cs b/Content.Server/AI/Utility/UtilityAiHelpers.cs deleted file mode 100644 index 7d46a71fd7..0000000000 --- a/Content.Server/AI/Utility/UtilityAiHelpers.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.Utility.AiLogic; -using Content.Server.AI.WorldState; - -namespace Content.Server.AI.Utility -{ - public static class UtilityAiHelpers - { - public static Blackboard? GetBlackboard(EntityUid entity) - { - if (!IoCManager.Resolve().TryGetComponent(entity, out NPCComponent? aiControllerComponent)) - { - return null; - } - - if (aiControllerComponent is UtilityNPCComponent utilityAi) - { - return utilityAi.Blackboard; - } - - return null; - } - } -} diff --git a/Content.Server/AI/Utils/Visibility.cs b/Content.Server/AI/Utils/Visibility.cs deleted file mode 100644 index 7f833efd3a..0000000000 --- a/Content.Server/AI/Utils/Visibility.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Robust.Shared.Map; - -namespace Content.Server.AI.Utils -{ - public static class Visibility - { - // Should this be in robust or something? Fark it - public static IEnumerable GetNearestEntities(EntityCoordinates grid, Type component, float range) - { - var entMan = IoCManager.Resolve(); - var inRange = GetEntitiesInRange(grid, component, range).ToList(); - var sortedInRange = inRange.OrderBy(o => (entMan.GetComponent(o).Coordinates.Position - grid.Position).Length); - - return sortedInRange; - } - - public static IEnumerable GetEntitiesInRange(EntityCoordinates grid, Type component, float range) - { - var entityManager = IoCManager.Resolve(); - foreach (var entity in entityManager.GetAllComponents(component).Select(c => c.Owner)) - { - var transform = entityManager.GetComponent(entity); - - if (transform.Coordinates.GetGridUid(entityManager) != grid.GetGridUid(entityManager)) - { - continue; - } - - if ((transform.Coordinates.Position - grid.Position).Length <= range) - { - yield return entity; - } - } - } - } -} diff --git a/Content.Server/AI/WorldState/Blackboard.cs b/Content.Server/AI/WorldState/Blackboard.cs deleted file mode 100644 index 956f6abe5a..0000000000 --- a/Content.Server/AI/WorldState/Blackboard.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Content.Server.AI.WorldState -{ - /// - /// The blackboard functions as an AI's repository of knowledge in a common format. - /// - public sealed class Blackboard - { - // Some stuff like "My Health" is easy to represent as components but abstract stuff like "How much food is nearby" - // is harder. This also allows data to be cached if it's being hit frequently. - - // This also stops you from re-writing the same boilerplate everywhere of stuff like "Do I have OuterClothing on?" - - private readonly Dictionary _states = new(); - private readonly List _planningStates = new(); - - public Blackboard(EntityUid owner) - { - Setup(owner); - } - - private void Setup(EntityUid owner) - { - var typeFactory = IoCManager.Resolve(); - var blackboardManager = IoCManager.Resolve(); - - foreach (var state in blackboardManager.AiStates) - { - var newState = (IAiState) typeFactory.CreateInstance(state); - newState.Setup(owner); - _states.Add(newState.GetType(), newState); - - switch (newState) - { - case IPlanningState planningState: - _planningStates.Add(planningState); - break; - } - } - } - - /// - /// All planning states will have their values reset - /// - public void ResetPlanning() - { - foreach (var state in _planningStates) - { - state.Reset(); - } - } - - public void GetStoredState(Type type, out StoredStateData state) - { - state = (StoredStateData) _states[type]; - } - - /// - /// Get the AI state class - /// - /// - /// - /// - public T GetState() where T : IAiState - { - return (T) _states[typeof(T)]; - } - } -} diff --git a/Content.Server/AI/WorldState/BlackboardManager.cs b/Content.Server/AI/WorldState/BlackboardManager.cs deleted file mode 100644 index d8cf123b04..0000000000 --- a/Content.Server/AI/WorldState/BlackboardManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Robust.Shared.Reflection; -using Robust.Shared.Utility; - -namespace Content.Server.AI.WorldState -{ - // This will also handle the global blackboard at some point - /// - /// Manager the AI blackboard states - /// - public sealed class BlackboardManager - { - // Cache the known types - public IReadOnlyCollection AiStates => _aiStates; - private readonly List _aiStates = new(); - - public void Initialize() - { - var reflectionManager = IoCManager.Resolve(); - - foreach (var state in reflectionManager.GetAllChildren(typeof(IAiState))) - { - _aiStates.Add(state); - } - - DebugTools.AssertNotNull(_aiStates); - } - } -} diff --git a/Content.Server/AI/WorldState/StateData.cs b/Content.Server/AI/WorldState/StateData.cs deleted file mode 100644 index 0e38d602de..0000000000 --- a/Content.Server/AI/WorldState/StateData.cs +++ /dev/null @@ -1,153 +0,0 @@ -using Robust.Shared.Timing; - -namespace Content.Server.AI.WorldState -{ - /// - /// Basic StateDate, no frills - /// - public interface IAiState - { - void Setup(EntityUid owner); - } - - public interface IPlanningState - { - void Reset(); - } - - public interface ICachedState - { - void CheckCache(); - } - - public interface IStoredState {} - - /// - /// The default class for state values. Also see CachedStateData and PlanningStateData - /// - /// - public abstract class StateData : IAiState - { - public abstract string Name { get; } - protected EntityUid Owner { get; private set; } = default!; - - public void Setup(EntityUid owner) - { - Owner = owner; - } - - public abstract T? GetValue(); - } - - /// - /// For when we want to set StateData but not reset it when re-planning actions - /// Useful for group blackboard sharing or to avoid repeating the same action (e.g. bark phrases). - /// - /// - public abstract class StoredStateData : IAiState, IStoredState - { - // Probably not the best class name but couldn't think of anything better - public abstract string Name { get; } - private EntityUid Owner { get; set; } - - private T? _value; - - public void Setup(EntityUid owner) - { - Owner = owner; - } - - public virtual void SetValue(T? value) - { - _value = value; - } - - public T? GetValue() - { - return _value; - } - } - - /// - /// This is state data that is transient and forgotten every time we re-plan - /// e.g. "Current Target" gets updated for every action we consider - /// - /// - public abstract class PlanningStateData : IAiState, IPlanningState - { - public abstract string Name { get; } - protected EntityUid Owner { get; private set; } - protected T? Value; - - public void Setup(EntityUid owner) - { - Owner = owner; - } - - public abstract void Reset(); - - public T? GetValue() - { - return Value; - } - - public virtual void SetValue(T? value) - { - Value = value; - } - } - - /// - /// This is state data that is cached for n seconds before being discarded. - /// Mostly useful to get nearby components and store the value. - /// - /// - public abstract class CachedStateData : IAiState, ICachedState - { - public abstract string Name { get; } - protected EntityUid Owner { get; private set; } = default!; - private bool _cached; - protected T Value = default!; - private TimeSpan _lastCache = TimeSpan.Zero; - /// - /// How long something stays in the cache before new values are retrieved - /// - protected double CacheTime { get; set; } = 2.0f; - - public void Setup(EntityUid owner) - { - Owner = owner; - } - - public void CheckCache() - { - var curTime = IoCManager.Resolve().CurTime; - - if (!_cached || (curTime - _lastCache).TotalSeconds >= CacheTime) - { - _cached = false; - return; - } - - _cached = true; - } - - /// - /// When the cache is stale we'll retrieve the actual value and store it again - /// - protected abstract T GetTrueValue(); - - public T GetValue() - { - CheckCache(); - if (!_cached) - { - Value = GetTrueValue(); - _cached = true; - _lastCache = IoCManager.Resolve().CurTime; - } - - return Value; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Clothing/ClothingSlotConState.cs b/Content.Server/AI/WorldState/States/Clothing/ClothingSlotConState.cs deleted file mode 100644 index 92cfc38d5e..0000000000 --- a/Content.Server/AI/WorldState/States/Clothing/ClothingSlotConState.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Server.AI.WorldState.States.Clothing -{ - public sealed class ClothingSlotConState : PlanningStateData - { - public override string Name => "ClothingSlotCon"; - public override void Reset() - { - Value = ""; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Clothing/ClothingSlotFlagConState.cs b/Content.Server/AI/WorldState/States/Clothing/ClothingSlotFlagConState.cs deleted file mode 100644 index 7951d57e0c..0000000000 --- a/Content.Server/AI/WorldState/States/Clothing/ClothingSlotFlagConState.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Content.Shared.Inventory; - -namespace Content.Server.AI.WorldState.States.Clothing -{ - public sealed class ClothingSlotFlagConState : PlanningStateData - { - public override string Name => "ClothingSlotFlagCon"; - public override void Reset() - { - Value = SlotFlags.NONE; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Clothing/EquippedClothingState.cs b/Content.Server/AI/WorldState/States/Clothing/EquippedClothingState.cs deleted file mode 100644 index b4075a4b32..0000000000 --- a/Content.Server/AI/WorldState/States/Clothing/EquippedClothingState.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Content.Shared.Inventory; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Clothing -{ - [UsedImplicitly] - public sealed class EquippedClothingState : StateData> - { - public override string Name => "EquippedClothing"; - - public override Dictionary GetValue() - { - var result = new Dictionary(); - - var invSystem = EntitySystem.Get(); - if (!invSystem.TryGetSlots(Owner, out var slotDefinitions)) - { - return result; - } - - foreach (var slot in slotDefinitions) - { - if (!invSystem.HasSlot(Owner, slot.Name)) continue; - - if (invSystem.TryGetSlotEntity(Owner, slot.Name, out var entityUid)) - { - result.Add(slot.Name, entityUid.Value); - } - } - - return result; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Clothing/NearbyClothingState.cs b/Content.Server/AI/WorldState/States/Clothing/NearbyClothingState.cs deleted file mode 100644 index 36ccd688fd..0000000000 --- a/Content.Server/AI/WorldState/States/Clothing/NearbyClothingState.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.Utils; -using Content.Server.Clothing.Components; -using Content.Server.Storage.Components; -using JetBrains.Annotations; -using Robust.Server.Containers; - -namespace Content.Server.AI.WorldState.States.Clothing -{ - [UsedImplicitly] - public sealed class NearbyClothingState : CachedStateData> - { - public override string Name => "NearbyClothing"; - - protected override List GetTrueValue() - { - var result = new List(); - - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(Owner, out NPCComponent? controller)) - { - return result; - } - var containerSystem = entMan.EntitySysManager.GetEntitySystem(); - foreach (var entity in Visibility.GetNearestEntities(entMan.GetComponent(Owner).Coordinates, typeof(ClothingComponent), controller.VisionRadius)) - { - if (containerSystem.TryGetContainingContainer(entity, out var container)) - { - if (!entMan.HasComponent(container.Owner)) - { - continue; - } - } - result.Add(entity); - } - - return result; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Combat/Nearby/NearbyMeleeWeapons.cs b/Content.Server/AI/WorldState/States/Combat/Nearby/NearbyMeleeWeapons.cs deleted file mode 100644 index f95e9461e2..0000000000 --- a/Content.Server/AI/WorldState/States/Combat/Nearby/NearbyMeleeWeapons.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.Utils; -using Content.Server.Weapon.Melee.Components; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Combat.Nearby -{ - [UsedImplicitly] - public sealed class NearbyMeleeWeapons : CachedStateData> - { - public override string Name => "NearbyMeleeWeapons"; - - protected override List GetTrueValue() - { - var result = new List(); - var entMan = IoCManager.Resolve(); - - if (!entMan.TryGetComponent(Owner, out NPCComponent? controller)) - { - return result; - } - - foreach (var entity in Visibility.GetNearestEntities(entMan.GetComponent(Owner).Coordinates, typeof(MeleeWeaponComponent), controller.VisionRadius)) - { - result.Add(entity); - } - - return result; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Combat/WeaponEntityState.cs b/Content.Server/AI/WorldState/States/Combat/WeaponEntityState.cs deleted file mode 100644 index 51c1641330..0000000000 --- a/Content.Server/AI/WorldState/States/Combat/WeaponEntityState.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Combat -{ - [UsedImplicitly] - public sealed class WeaponEntityState : PlanningStateData - { - // Similar to TargetEntity - public override string Name => "WeaponEntity"; - public override void Reset() - { - Value = null; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs b/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs deleted file mode 100644 index 076d42b1e1..0000000000 --- a/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Shared.Hands.EntitySystems; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Hands -{ - [UsedImplicitly] - public sealed class AnyFreeHandState : StateData - { - public override string Name => "AnyFreeHand"; - public override bool GetValue() - { - return IoCManager.Resolve().GetEntitySystem().TryGetEmptyHand(Owner, out _); - } - } -} diff --git a/Content.Server/AI/WorldState/States/Hands/FreeHands.cs b/Content.Server/AI/WorldState/States/Hands/FreeHands.cs deleted file mode 100644 index ab6d0f8727..0000000000 --- a/Content.Server/AI/WorldState/States/Hands/FreeHands.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Linq; -using Content.Server.Hands.Components; -using Content.Shared.Hands.Components; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Hands -{ - [UsedImplicitly] - public sealed class FreeHands : StateData> - { - public override string Name => "FreeHands"; - - public override List GetValue() - { - var result = new List(); - - if (!IoCManager.Resolve().TryGetComponent(Owner, out HandsComponent? handsComponent)) - { - return new List(); - } - - return handsComponent.GetFreeHandNames().ToList(); - } - } -} diff --git a/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs b/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs deleted file mode 100644 index 7337017017..0000000000 --- a/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Linq; -using Content.Shared.Hands.EntitySystems; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Hands -{ - [UsedImplicitly] - public sealed class HandItemsState : StateData> - { - public override string Name => "HandItems"; - public override List GetValue() - { - return IoCManager.Resolve().GetEntitySystem().EnumerateHeld(Owner).ToList(); - } - } -} diff --git a/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs b/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs deleted file mode 100644 index 3cd7780588..0000000000 --- a/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Content.Server.Hands.Components; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Inventory -{ - /// - /// AKA what's in active hand - /// - [UsedImplicitly] - public sealed class EquippedEntityState : StateData - { - public override string Name => "EquippedEntity"; - - public override EntityUid? GetValue() - { - return IoCManager.Resolve().GetComponentOrNull(Owner)?.ActiveHandEntity; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs deleted file mode 100644 index 01793155bc..0000000000 --- a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Hands.EntitySystems; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Inventory -{ - [UsedImplicitly] - public sealed class EnumerableInventoryState : StateData> - { - public override string Name => "EnumerableInventory"; - - public override IEnumerable GetValue() - { - return IoCManager.Resolve().GetEntitySystem().EnumerateHeld(Owner); - } - } -} diff --git a/Content.Server/AI/WorldState/States/Inventory/LastOpenedStorageState.cs b/Content.Server/AI/WorldState/States/Inventory/LastOpenedStorageState.cs deleted file mode 100644 index 3ebbd5ebec..0000000000 --- a/Content.Server/AI/WorldState/States/Inventory/LastOpenedStorageState.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Content.Server.Storage.Components; - -namespace Content.Server.AI.WorldState.States.Inventory -{ - /// - /// If we open a storage locker than it will be stored here - /// Useful if we want to close it after - /// - public sealed class LastOpenedStorageState : StoredStateData - { - // TODO: IF we chain lockers need to handle it. - // Fine for now I guess - public override string Name => "LastOpenedStorage"; - - public override void SetValue(EntityUid value) - { - base.SetValue(value); - if (value.Valid && !IoCManager.Resolve().HasComponent(value)) - { - Logger.Warning("Set LastOpenedStorageState for an entity that doesn't have a storage component"); - } - } - } -} diff --git a/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs b/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs deleted file mode 100644 index 3b1ea76830..0000000000 --- a/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.Utils; -using Content.Shared.Body.Components; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Mobs -{ - [UsedImplicitly] - public sealed class NearbyBodiesState : CachedStateData> - { - public override string Name => "NearbyBodies"; - - protected override List GetTrueValue() - { - var result = new List(); - var entMan = IoCManager.Resolve(); - - if (!entMan.TryGetComponent(Owner, out NPCComponent? controller)) - { - return result; - } - - foreach (var entity in Visibility.GetEntitiesInRange(entMan.GetComponent(Owner).Coordinates, typeof(SharedBodyComponent), controller.VisionRadius)) - { - if (entity == Owner) continue; - result.Add(entity); - } - - return result; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs b/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs deleted file mode 100644 index 438db57262..0000000000 --- a/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Content.Server.AI.Components; -using Content.Shared.Damage; -using JetBrains.Annotations; -using Robust.Shared.Player; - -namespace Content.Server.AI.WorldState.States.Mobs -{ - [UsedImplicitly] - public sealed class NearbyPlayersState : CachedStateData> - { - public override string Name => "NearbyPlayers"; - - protected override List GetTrueValue() - { - var result = new List(); - - var entMan = IoCManager.Resolve(); - if (!entMan.TryGetComponent(Owner, out NPCComponent? controller)) - { - return result; - } - - var nearbyPlayers = Filter.Empty() - .AddInRange(entMan.GetComponent(Owner).MapPosition, controller.VisionRadius) - .Recipients; - - foreach (var player in nearbyPlayers) - { - if (player.AttachedEntity is not {Valid: true} playerEntity) - { - continue; - } - - if (player.AttachedEntity != Owner && entMan.HasComponent(playerEntity)) - { - result.Add(playerEntity); - } - } - - return result; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Movement/MoveTargetState.cs b/Content.Server/AI/WorldState/States/Movement/MoveTargetState.cs deleted file mode 100644 index f6e5d80c24..0000000000 --- a/Content.Server/AI/WorldState/States/Movement/MoveTargetState.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Movement -{ - [UsedImplicitly] - public sealed class MoveTargetState : PlanningStateData - { - public override string Name => "MoveTarget"; - public override void Reset() - { - Value = null; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Nutrition/HungryState.cs b/Content.Server/AI/WorldState/States/Nutrition/HungryState.cs deleted file mode 100644 index 11e5d07948..0000000000 --- a/Content.Server/AI/WorldState/States/Nutrition/HungryState.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Content.Server.Nutrition.Components; -using Content.Shared.Nutrition.Components; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Nutrition -{ - [UsedImplicitly] - public sealed class HungryState : StateData - { - public override string Name => "Hungry"; - - public override bool GetValue() - { - if (!IoCManager.Resolve().TryGetComponent(Owner, out HungerComponent? hungerComponent)) - { - return false; - } - - switch (hungerComponent.CurrentHungerThreshold) - { - case HungerThreshold.Overfed: - return false; - case HungerThreshold.Okay: - return false; - case HungerThreshold.Peckish: - return true; - case HungerThreshold.Starving: - return true; - case HungerThreshold.Dead: - return true; - default: - throw new ArgumentOutOfRangeException( - nameof(hungerComponent.CurrentHungerThreshold), - hungerComponent.CurrentHungerThreshold, - null); - } - } - } -} diff --git a/Content.Server/AI/WorldState/States/Nutrition/NearbyDrinkState.cs b/Content.Server/AI/WorldState/States/Nutrition/NearbyDrinkState.cs deleted file mode 100644 index 446b875653..0000000000 --- a/Content.Server/AI/WorldState/States/Nutrition/NearbyDrinkState.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.Utils; -using Content.Server.Nutrition.Components; -using Content.Server.Storage.Components; -using JetBrains.Annotations; -using Robust.Shared.Containers; - -namespace Content.Server.AI.WorldState.States.Nutrition -{ - [UsedImplicitly] - public sealed class NearbyDrinkState: CachedStateData> - { - public override string Name => "NearbyDrink"; - - protected override List GetTrueValue() - { - var result = new List(); - var entMan = IoCManager.Resolve(); - - if (!entMan.TryGetComponent(Owner, out NPCComponent? controller)) - { - return result; - } - - foreach (var entity in Visibility - .GetNearestEntities(entMan.GetComponent(Owner).Coordinates, typeof(DrinkComponent), controller.VisionRadius)) - { - if (entity.TryGetContainer(out var container)) - { - if (!entMan.HasComponent(container.Owner)) - { - continue; - } - } - result.Add(entity); - } - - return result; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Nutrition/NearbyFoodState.cs b/Content.Server/AI/WorldState/States/Nutrition/NearbyFoodState.cs deleted file mode 100644 index c1ab2c4479..0000000000 --- a/Content.Server/AI/WorldState/States/Nutrition/NearbyFoodState.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.AI.Components; -using Content.Server.AI.Utils; -using Content.Server.Nutrition.Components; -using Content.Server.Storage.Components; -using JetBrains.Annotations; -using Robust.Shared.Containers; - -namespace Content.Server.AI.WorldState.States.Nutrition -{ - [UsedImplicitly] - public sealed class NearbyFoodState : CachedStateData> - { - public override string Name => "NearbyFood"; - - protected override List GetTrueValue() - { - var result = new List(); - var entMan = IoCManager.Resolve(); - - if (!entMan.TryGetComponent(Owner, out NPCComponent? controller)) - { - return result; - } - - foreach (var entity in Visibility - .GetNearestEntities(entMan.GetComponent(Owner).Coordinates, typeof(FoodComponent), controller.VisionRadius)) - { - if (entity.TryGetContainer(out var container)) - { - if (!entMan.HasComponent(container.Owner)) - { - continue; - } - } - result.Add(entity); - } - - return result; - - } - } -} diff --git a/Content.Server/AI/WorldState/States/Nutrition/ThirstyState.cs b/Content.Server/AI/WorldState/States/Nutrition/ThirstyState.cs deleted file mode 100644 index 4e92284377..0000000000 --- a/Content.Server/AI/WorldState/States/Nutrition/ThirstyState.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Server.Nutrition.Components; -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Nutrition -{ - [UsedImplicitly] - public sealed class ThirstyState : StateData - { - public override string Name => "Thirsty"; - - public override bool GetValue() - { - if (!IoCManager.Resolve().TryGetComponent(Owner, out ThirstComponent? thirstComponent)) - { - return false; - } - - switch (thirstComponent.CurrentThirstThreshold) - { - case ThirstThreshold.OverHydrated: - return false; - case ThirstThreshold.Okay: - return false; - case ThirstThreshold.Thirsty: - return true; - case ThirstThreshold.Parched: - return true; - case ThirstThreshold.Dead: - return true; - default: - throw new ArgumentOutOfRangeException( - nameof(thirstComponent.CurrentThirstThreshold), - thirstComponent.CurrentThirstThreshold, - null); - } - } - } -} diff --git a/Content.Server/AI/WorldState/States/SelfState.cs b/Content.Server/AI/WorldState/States/SelfState.cs deleted file mode 100644 index 7e25d63190..0000000000 --- a/Content.Server/AI/WorldState/States/SelfState.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States -{ - [UsedImplicitly] - public sealed class SelfState : StateData - { - public override string Name => "Self"; - - public override EntityUid GetValue() - { - return Owner; - } - } -} diff --git a/Content.Server/AI/WorldState/States/TargetEntityState.cs b/Content.Server/AI/WorldState/States/TargetEntityState.cs deleted file mode 100644 index 3acdc60cd9..0000000000 --- a/Content.Server/AI/WorldState/States/TargetEntityState.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States -{ - /// - /// Could be target item to equip, target to attack, etc. - /// - [UsedImplicitly] - public sealed class TargetEntityState : PlanningStateData - { - public override string Name => "TargetEntity"; - - public override void Reset() - { - Value = null; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Utility/ConsiderationState.cs b/Content.Server/AI/WorldState/States/Utility/ConsiderationState.cs deleted file mode 100644 index d869a8b8e1..0000000000 --- a/Content.Server/AI/WorldState/States/Utility/ConsiderationState.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Server.AI.WorldState.States.Utility -{ - /// - /// Used by the utility AI to calc the adjusted scores - /// - public sealed class ConsiderationState : StoredStateData - { - public override string Name => "Consideration"; - } -} diff --git a/Content.Server/AI/WorldState/States/Utility/LastUtilityScoreState.cs b/Content.Server/AI/WorldState/States/Utility/LastUtilityScoreState.cs deleted file mode 100644 index 62cfb72c1e..0000000000 --- a/Content.Server/AI/WorldState/States/Utility/LastUtilityScoreState.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; - -namespace Content.Server.AI.WorldState.States.Utility -{ - /// - /// Used for the utility AI; sets the threshold score we need to beat - /// - [UsedImplicitly] - public sealed class LastUtilityScoreState : StateData - { - public override string Name => "LastBonus"; - private float _value = 0.0f; - - public void SetValue(float value) - { - _value = value; - } - - public override float GetValue() - { - return _value; - } - } -} diff --git a/Content.Server/AI/WorldState/States/Utility/StoredStateIsNullState.cs b/Content.Server/AI/WorldState/States/Utility/StoredStateIsNullState.cs deleted file mode 100644 index 5b1038f762..0000000000 --- a/Content.Server/AI/WorldState/States/Utility/StoredStateIsNullState.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Content.Server.AI.WorldState.States.Utility -{ - public sealed class StoredStateIsNullState : PlanningStateData - { - public override string Name => "StoredStateIsNull"; - public override void Reset() - { - Value = null; - } - } -} \ No newline at end of file diff --git a/Content.Server/Buckle/Systems/BuckleSystem.cs b/Content.Server/Buckle/Systems/BuckleSystem.cs index 470583b665..d15bd0fdba 100644 --- a/Content.Server/Buckle/Systems/BuckleSystem.cs +++ b/Content.Server/Buckle/Systems/BuckleSystem.cs @@ -13,7 +13,7 @@ using Robust.Shared.GameStates; namespace Content.Server.Buckle.Systems { [UsedImplicitly] - internal sealed class BuckleSystem : SharedBuckleSystem + public sealed class BuckleSystem : SharedBuckleSystem { public override void Initialize() { diff --git a/Content.Server/CPUJob/JobQueues/Job.cs b/Content.Server/CPUJob/JobQueues/Job.cs index 9005c87507..a0f8082bec 100644 --- a/Content.Server/CPUJob/JobQueues/Job.cs +++ b/Content.Server/CPUJob/JobQueues/Job.cs @@ -174,7 +174,7 @@ namespace Content.Server.CPUJob.JobQueues // Maybe? _taskTcs.TrySetResult(Result); } - catch (TaskCanceledException) + catch (OperationCanceledException) { _taskTcs.TrySetCanceled(); } diff --git a/Content.Server/CPUJob/JobQueues/Queues/JobQueue.cs b/Content.Server/CPUJob/JobQueues/Queues/JobQueue.cs index 96205973e9..1cccedf067 100644 --- a/Content.Server/CPUJob/JobQueues/Queues/JobQueue.cs +++ b/Content.Server/CPUJob/JobQueues/Queues/JobQueue.cs @@ -7,9 +7,7 @@ namespace Content.Server.CPUJob.JobQueues.Queues { private readonly IStopwatch _stopwatch; - public JobQueue() : this(new Stopwatch()) - { - } + public JobQueue() : this(new Stopwatch()) {} public JobQueue(IStopwatch stopwatch) { @@ -19,7 +17,7 @@ namespace Content.Server.CPUJob.JobQueues.Queues /// /// How long the job's allowed to run for before suspending /// - public virtual double MaxTime => 0.002; + public virtual double MaxTime { get; } = 0.002; private readonly Queue _pendingQueue = new(); private readonly List _waitingJobs = new(); diff --git a/Content.Server/Dragon/Components/DragonComponent.cs b/Content.Server/Dragon/Components/DragonComponent.cs index 789dc81147..1a0a9c77ad 100644 --- a/Content.Server/Dragon/Components/DragonComponent.cs +++ b/Content.Server/Dragon/Components/DragonComponent.cs @@ -23,7 +23,7 @@ namespace Content.Server.Dragon /// /// The amount of ichor injected per devour /// - [DataField("devourHealRate")] + [ViewVariables(VVAccess.ReadWrite), DataField("devourHealRate")] public float DevourHealRate = 15f; [DataField("devourActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index 22d935989a..6b89adf01f 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -12,6 +12,7 @@ using System.Threading; using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; +using Content.Server.NPC; using Content.Shared.Damage; using Content.Shared.Dragon; using Content.Shared.Examine; @@ -20,6 +21,7 @@ using Content.Shared.Movement.Systems; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Random; +using Content.Server.NPC.Systems; namespace Content.Server.Dragon { @@ -36,6 +38,7 @@ namespace Content.Server.Dragon [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly NPCSystem _npc = default!; /// /// Minimum distance between 2 rifts allowed. @@ -150,8 +153,8 @@ namespace Content.Server.Dragon if (comp.SpawnAccumulator > comp.SpawnCooldown) { comp.SpawnAccumulator -= comp.SpawnCooldown; - Spawn(comp.SpawnPrototype, Transform(comp.Owner).MapPosition); - // TODO: When NPC refactor make it guard the rift. + var ent = Spawn(comp.SpawnPrototype, Transform(comp.Owner).MapPosition); + _npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(comp.Owner, Vector2.Zero)); } } } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 3a54c8a15c..e62dd41d07 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -2,9 +2,6 @@ using Content.Server.Administration; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Afk; -using Content.Server.AI.Utility; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.WorldState; using Content.Server.Chat.Managers; using Content.Server.Connection; using Content.Server.Database; @@ -126,8 +123,6 @@ namespace Content.Server.Entry else { IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); - IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 16a4ba003c..b9de9e8aa1 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -23,6 +23,10 @@ using Robust.Shared.Random; using Robust.Shared.Utility; using Content.Server.Traitor; using Content.Shared.MobState.Components; +using System.Data; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; +using Content.Server.Traitor.Uplink; using Robust.Shared.Audio; using Robust.Shared.Player; @@ -387,12 +391,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem /// private void SetupOperativeEntity(EntityUid mob, string name, string gear) { - EntityManager.GetComponent(mob).EntityName = name; + MetaData(mob).EntityName = name; EntityManager.EnsureComponent(mob); EntityManager.EnsureComponent(mob); if(_startingGearPrototypes.TryGetValue(gear, out var gearPrototype)) _stationSpawningSystem.EquipStartingGear(mob, gearPrototype, null); + + var faction = EnsureComp(mob); + faction.Factions |= Faction.Syndicate; + faction.Factions &= ~Faction.NanoTrasen; } private void SpawnOperatives(int spawnCount, List sessions, bool addSpawnPoints) diff --git a/Content.Server/Interaction/InteractionPopupSystem.cs b/Content.Server/Interaction/InteractionPopupSystem.cs index 563eabca6e..ff66be5cc1 100644 --- a/Content.Server/Interaction/InteractionPopupSystem.cs +++ b/Content.Server/Interaction/InteractionPopupSystem.cs @@ -41,6 +41,9 @@ public sealed class InteractionPopupSystem : EntitySystem && !state.IsAlive()) // AND if that state is not Alive (e.g. dead/incapacitated/critical) return; + // TODO: Should be an attempt event + // TODO: Need to handle pausing with an accumulator. + string msg = ""; // Stores the text to be shown in the popup message string? sfx = null; // Stores the filepath of the sound to be played diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index 7b7449925a..498fb589c9 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -3,9 +3,6 @@ using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.Administration.Notes; using Content.Server.Afk; -using Content.Server.AI.Utility; -using Content.Server.AI.Utility.Considerations; -using Content.Server.AI.WorldState; using Content.Server.Chat.Managers; using Content.Server.Connection; using Content.Server.Database; @@ -41,8 +38,6 @@ namespace Content.Server.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Content.Server/Mind/Commands/MakeSentientCommand.cs b/Content.Server/Mind/Commands/MakeSentientCommand.cs index 2da02dce5c..8da2df3645 100644 --- a/Content.Server/Mind/Commands/MakeSentientCommand.cs +++ b/Content.Server/Mind/Commands/MakeSentientCommand.cs @@ -1,6 +1,6 @@ using Content.Server.Administration; -using Content.Server.AI.Components; using Content.Server.Mind.Components; +using Content.Server.NPC.Components; using Content.Shared.Administration; using Content.Shared.Emoting; using Content.Shared.Examine; diff --git a/Content.Server/NPC/Commands/AddNPCCommand.cs b/Content.Server/NPC/Commands/AddNPCCommand.cs new file mode 100644 index 0000000000..207733efa1 --- /dev/null +++ b/Content.Server/NPC/Commands/AddNPCCommand.cs @@ -0,0 +1,46 @@ +using Content.Server.Administration; +using Content.Server.NPC.HTN; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Server.NPC.Commands +{ + [AdminCommand(AdminFlags.Fun)] + public sealed class AddNPCCommand : IConsoleCommand + { + [Dependency] private readonly IEntityManager _entities = default!; + + public string Command => "addnpc"; + public string Description => "Add a HTN NPC component with a given root task"; + public string Help => "Usage: addnpc " + + "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this." + + "\n rootTask: Name of a behaviorset to add to the component on initialize."; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 2) + { + shell.WriteError("Wrong number of args."); + return; + } + + var entId = new EntityUid(int.Parse(args[0])); + + if (!_entities.EntityExists(entId)) + { + shell.WriteError($"Unable to find entity with uid {entId}"); + return; + } + + if (_entities.HasComponent(entId)) + { + shell.WriteError("Entity already has an NPC component."); + return; + } + + var comp = _entities.AddComponent(entId); + comp.RootTask = args[1]; + shell.WriteLine("AI component added."); + } + } +} diff --git a/Content.Server/AI/Commands/FactionCommand.cs b/Content.Server/NPC/Commands/FactionCommand.cs similarity index 97% rename from Content.Server/AI/Commands/FactionCommand.cs rename to Content.Server/NPC/Commands/FactionCommand.cs index 3a12afa99e..a052c0a9f7 100644 --- a/Content.Server/AI/Commands/FactionCommand.cs +++ b/Content.Server/NPC/Commands/FactionCommand.cs @@ -1,10 +1,10 @@ using System.Text; using Content.Server.Administration; -using Content.Server.AI.EntitySystems; +using Content.Server.NPC.Systems; using Content.Shared.Administration; using Robust.Shared.Console; -namespace Content.Server.AI.Commands +namespace Content.Server.NPC.Commands { [AdminCommand(AdminFlags.Fun)] public sealed class FactionCommand : IConsoleCommand diff --git a/Content.Server/NPC/Commands/NPCCommand.cs b/Content.Server/NPC/Commands/NPCCommand.cs new file mode 100644 index 0000000000..57fe223b66 --- /dev/null +++ b/Content.Server/NPC/Commands/NPCCommand.cs @@ -0,0 +1,26 @@ +using Content.Server.Administration; +using Content.Server.EUI; +using Content.Server.NPC.UI; +using Content.Shared.Administration; +using Robust.Server.Player; +using Robust.Shared.Console; + +namespace Content.Server.NPC.Commands; + +[AdminCommand(AdminFlags.Debug)] +public sealed class NPCCommand : IConsoleCommand +{ + public string Command => "npc"; + public string Description => "Opens the debug window for NPCs"; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not IPlayerSession playerSession) + { + return; + } + + var euiManager = IoCManager.Resolve(); + euiManager.OpenEui(new NPCEui(), playerSession); + } +} diff --git a/Content.Server/NPC/Commands/NPCDomainCommand.cs b/Content.Server/NPC/Commands/NPCDomainCommand.cs new file mode 100644 index 0000000000..d387993c89 --- /dev/null +++ b/Content.Server/NPC/Commands/NPCDomainCommand.cs @@ -0,0 +1,52 @@ +using System.Linq; +using Content.Server.Administration; +using Content.Server.NPC.HTN; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Prototypes; + +namespace Content.Server.NPC.Commands; + +/// +/// Lists out the domain of a particular HTN compound task. +/// +[AdminCommand(AdminFlags.Debug)] +public sealed class NPCDomainCommand : IConsoleCommand +{ + public string Command => "npcdomain"; + public string Description => "Lists the domain of a particular HTN compound task"; + public string Help => $"{Command} "; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteError("shell-need-exactly-one-argument"); + return; + } + + var protoManager = IoCManager.Resolve(); + + if (!protoManager.TryIndex(args[0], out var compound)) + { + shell.WriteError($"Unable to find HTN compound task for '{args[0]}'"); + return; + } + + var htnSystem = IoCManager.Resolve().GetEntitySystem(); + + foreach (var line in htnSystem.GetDomain(compound).Split("\n")) + { + shell.WriteLine(line); + } + } + + public CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + var protoManager = IoCManager.Resolve(); + + if (args.Length > 1) + return CompletionResult.Empty; + + return CompletionResult.FromHintOptions(protoManager.EnumeratePrototypes().Select(o => o.ID), "compound task"); + } +} diff --git a/Content.Server/AI/Components/ActiveNPCComponent.cs b/Content.Server/NPC/Components/ActiveNPCComponent.cs similarity index 79% rename from Content.Server/AI/Components/ActiveNPCComponent.cs rename to Content.Server/NPC/Components/ActiveNPCComponent.cs index 410c07e5f3..e81ad25a46 100644 --- a/Content.Server/AI/Components/ActiveNPCComponent.cs +++ b/Content.Server/NPC/Components/ActiveNPCComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.AI.Components; +namespace Content.Server.NPC.Components; /// /// Added to NPCs that are actively being updated. diff --git a/Content.Server/AI/Components/AiFactionPrototype.cs b/Content.Server/NPC/Components/AiFactionPrototype.cs similarity index 87% rename from Content.Server/AI/Components/AiFactionPrototype.cs rename to Content.Server/NPC/Components/AiFactionPrototype.cs index 90f657b606..4abe23fb6b 100644 --- a/Content.Server/AI/Components/AiFactionPrototype.cs +++ b/Content.Server/NPC/Components/AiFactionPrototype.cs @@ -1,6 +1,6 @@ using Robust.Shared.Prototypes; -namespace Content.Server.AI.Components +namespace Content.Server.NPC.Components { [Prototype("aiFaction")] public sealed class AiFactionPrototype : IPrototype @@ -8,7 +8,7 @@ namespace Content.Server.AI.Components // These are immutable so any dynamic changes aren't saved back over. // AiFactionSystem will just read these and then store them. [ViewVariables] - [IdDataFieldAttribute] + [IdDataField] public string ID { get; } = default!; [DataField("hostile")] diff --git a/Content.Server/NPC/Components/AiFactionTagComponent.cs b/Content.Server/NPC/Components/AiFactionTagComponent.cs new file mode 100644 index 0000000000..00ac79eb32 --- /dev/null +++ b/Content.Server/NPC/Components/AiFactionTagComponent.cs @@ -0,0 +1,11 @@ +using Content.Server.NPC.Systems; + +namespace Content.Server.NPC.Components +{ + [RegisterComponent] + public sealed class AiFactionTagComponent : Component + { + [DataField("factions")] + public Faction Factions { get; set; } = Faction.None; + } +} diff --git a/Content.Server/NPC/Components/NPCAvoidanceComponent.cs b/Content.Server/NPC/Components/NPCAvoidanceComponent.cs new file mode 100644 index 0000000000..57d954d4c1 --- /dev/null +++ b/Content.Server/NPC/Components/NPCAvoidanceComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.NPC.Components; + +/// +/// Should this entity be considered for collision avoidance +/// +[RegisterComponent] +public sealed class NPCAvoidanceComponent : Component +{ + [ViewVariables(VVAccess.ReadWrite), DataField("enabled")] + public bool Enabled = true; +} diff --git a/Content.Server/NPC/Components/NPCComponent.cs b/Content.Server/NPC/Components/NPCComponent.cs new file mode 100644 index 0000000000..932b927bf0 --- /dev/null +++ b/Content.Server/NPC/Components/NPCComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.NPC; + +namespace Content.Server.NPC.Components +{ + public abstract class NPCComponent : SharedNPCComponent + { + /// + /// Contains all of the world data for a particular NPC in terms of how it sees the world. + /// + [ViewVariables, DataField("blackboard", customTypeSerializer: typeof(NPCBlackboardSerializer))] + public NPCBlackboard Blackboard = new(); + } +} diff --git a/Content.Server/NPC/Components/NPCMeleeCombatComponent.cs b/Content.Server/NPC/Components/NPCMeleeCombatComponent.cs new file mode 100644 index 0000000000..0d11283fc1 --- /dev/null +++ b/Content.Server/NPC/Components/NPCMeleeCombatComponent.cs @@ -0,0 +1,47 @@ +namespace Content.Server.NPC.Components; + +/// +/// Added to NPCs whenever they're in melee combat so they can be handled by the dedicated system. +/// +[RegisterComponent] +public sealed class NPCMeleeCombatComponent : Component +{ + /// + /// Weapon we're using to attack the target. Can also be ourselves. + /// + [ViewVariables] public EntityUid Weapon; + + [ViewVariables] + public EntityUid Target; + + [ViewVariables] + public CombatStatus Status = CombatStatus.Normal; +} + +public enum CombatStatus : byte +{ + /// + /// The target isn't in LOS anymore. + /// + NotInSight, + + /// + /// Due to some generic reason we are unable to attack the target. + /// + Unspecified, + + /// + /// Set if we can't reach the target for whatever reason. + /// + TargetUnreachable, + + /// + /// Set if the weapon we were assigned is no longer valid. + /// + NoWeapon, + + /// + /// No dramas. + /// + Normal, +} diff --git a/Content.Server/NPC/Components/NPCPathfindPointComponent.cs b/Content.Server/NPC/Components/NPCPathfindPointComponent.cs new file mode 100644 index 0000000000..b941847f9d --- /dev/null +++ b/Content.Server/NPC/Components/NPCPathfindPointComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Server.NPC.Components; + +[RegisterComponent] +public sealed class NPCPathfindPointComponent : Component +{ + /// + /// Next point for the NPC to head to. + /// + // [ViewVariables(VVAccess.ReadWrite), DataField("nextPoint")] + // public EntityUid? NextPoint; +} diff --git a/Content.Server/NPC/Components/NPCRVOComponent.cs b/Content.Server/NPC/Components/NPCRVOComponent.cs new file mode 100644 index 0000000000..3fe76fc24e --- /dev/null +++ b/Content.Server/NPC/Components/NPCRVOComponent.cs @@ -0,0 +1,38 @@ +using Content.Server.NPC.Systems; + +namespace Content.Server.NPC.Components; + +/// +/// Stores data for RVO collision avoidance +/// +[RegisterComponent] +public sealed class NPCRVOComponent : Component +{ + /// + /// Maximum number of dynamic neighbors to consider for collision avoidance. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("maxNeighbors")] + public int MaxNeighbors = 5; + + /// + /// Time horizon to consider for dynamic neighbor collision + /// + [ViewVariables(VVAccess.ReadWrite)] public float TimeHorizon = 3f; + + /// + /// Time horizon to consider for static neighbor collision. + /// + [ViewVariables(VVAccess.ReadWrite)] public float ObstacleTimeHorizon = 3f; + + /// + /// Range considered for neighbor agents + /// + [ViewVariables(VVAccess.ReadWrite), DataField("neighborRange")] + public float NeighborRange = 3f; + + [ViewVariables] + public readonly HashSet ObstacleNeighbors = new(); + + [ViewVariables] + public readonly HashSet AgentNeighbors = new(); +} diff --git a/Content.Server/NPC/Components/NPCRangedCombatComponent.cs b/Content.Server/NPC/Components/NPCRangedCombatComponent.cs new file mode 100644 index 0000000000..7d6d86dcb5 --- /dev/null +++ b/Content.Server/NPC/Components/NPCRangedCombatComponent.cs @@ -0,0 +1,57 @@ +using Content.Server.NPC.Systems; +using Robust.Shared.Audio; + +namespace Content.Server.NPC.Components; + +/// +/// Added to an NPC doing ranged combat. +/// +[RegisterComponent] +public sealed class NPCRangedCombatComponent : Component +{ + [ViewVariables] + public EntityUid Target; + + [ViewVariables] + public CombatStatus Status = CombatStatus.Normal; + + // Most of the below is to deal with turrets. + + /// + /// If null it will instantly turn. + /// + [ViewVariables(VVAccess.ReadWrite)] public Angle? RotationSpeed; + + /// + /// Maximum distance, between our rotation and the target's, to consider shooting it. + /// + [ViewVariables(VVAccess.ReadWrite)] + public Angle AccuracyThreshold = Angle.FromDegrees(30); + + /// + /// How long until the last line of sight check. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float LOSAccumulator = 0f; + + /// + /// Is the target still considered in LOS since the last check. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool TargetInLOS = false; + + /// + /// Delay after target is in LOS before we start shooting. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float ShootDelay = 0.2f; + + [ViewVariables(VVAccess.ReadWrite)] + public float ShootAccumulator; + + /// + /// Sound to play if the target enters line of sight. + /// + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier? SoundTargetInLOS; +} diff --git a/Content.Server/AI/Tracking/RecentlyInjectedComponent.cs b/Content.Server/NPC/Components/NPCRecentlyInjectedComponent.cs similarity index 50% rename from Content.Server/AI/Tracking/RecentlyInjectedComponent.cs rename to Content.Server/NPC/Components/NPCRecentlyInjectedComponent.cs index ffcc025baa..0e8887eda4 100644 --- a/Content.Server/AI/Tracking/RecentlyInjectedComponent.cs +++ b/Content.Server/NPC/Components/NPCRecentlyInjectedComponent.cs @@ -1,12 +1,14 @@ -namespace Content.Server.AI.Tracking +namespace Content.Server.NPC.Components { /// Added when a medibot injects someone /// So they don't get injected again for at least a minute. [RegisterComponent] - public sealed class RecentlyInjectedComponent : Component + public sealed class NPCRecentlyInjectedComponent : Component { + [ViewVariables(VVAccess.ReadWrite), DataField("accumulator")] public float Accumulator = 0f; + [ViewVariables(VVAccess.ReadWrite), DataField("removeTime")] public TimeSpan RemoveTime = TimeSpan.FromMinutes(1); } } diff --git a/Content.Server/AI/Steering/NPCSteeringComponent.cs b/Content.Server/NPC/Components/NPCSteeringComponent.cs similarity index 83% rename from Content.Server/AI/Steering/NPCSteeringComponent.cs rename to Content.Server/NPC/Components/NPCSteeringComponent.cs index 08c0db087d..ce1f1cd4ee 100644 --- a/Content.Server/AI/Steering/NPCSteeringComponent.cs +++ b/Content.Server/NPC/Components/NPCSteeringComponent.cs @@ -1,9 +1,8 @@ using System.Threading; -using Content.Server.AI.Pathfinding.Pathfinders; using Content.Server.CPUJob.JobQueues; using Robust.Shared.Map; -namespace Content.Server.AI.Steering; +namespace Content.Server.NPC.Components; /// /// Added to NPCs that are moving. @@ -20,10 +19,15 @@ public sealed class NPCSteeringComponent : Component [ViewVariables] public Queue CurrentPath = new(); /// - /// Target that we're trying to move to. + /// End target that we're trying to move to. /// [ViewVariables(VVAccess.ReadWrite)] public EntityCoordinates Coordinates; + /// + /// Target that we're trying to move to. If we have a path then this will be the first node on the path. + /// + [ViewVariables] public EntityCoordinates CurrentTarget; + /// /// How close are we trying to get to the coordinates before being considered in range. /// diff --git a/Content.Server/NPC/HTN/HTNBranch.cs b/Content.Server/NPC/HTN/HTNBranch.cs new file mode 100644 index 0000000000..760a80e931 --- /dev/null +++ b/Content.Server/NPC/HTN/HTNBranch.cs @@ -0,0 +1,22 @@ +using Content.Server.NPC.HTN.Preconditions; + +namespace Content.Server.NPC.HTN; + +/// +/// AKA Method. This is a branch available for a compound task. +/// +[DataDefinition] +public sealed class HTNBranch +{ + // Made this its own class if we ever need to change it. + [ViewVariables, DataField("preconditions")] + public List Preconditions = new(); + + [ViewVariables] public List Tasks = new(); + + /// + /// Due to how serv3 works we need to defer getting the actual tasks until after they have all been serialized. + /// + [ViewVariables, DataField("tasks", required: true, customTypeSerializer:typeof(HTNTaskListSerializer))] + public List TaskPrototypes = default!; +} diff --git a/Content.Server/NPC/HTN/HTNComponent.cs b/Content.Server/NPC/HTN/HTNComponent.cs new file mode 100644 index 0000000000..b8fcca2a86 --- /dev/null +++ b/Content.Server/NPC/HTN/HTNComponent.cs @@ -0,0 +1,45 @@ +using System.Threading; +using Content.Server.NPC.Components; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.NPC.HTN; + +[RegisterComponent, ComponentReference(typeof(NPCComponent))] +public sealed class HTNComponent : NPCComponent +{ + /// + /// The base task to use for planning + /// + [ViewVariables(VVAccess.ReadWrite), + DataField("rootTask", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string RootTask = default!; + + /// + /// The NPC's current plan. + /// + [ViewVariables] + public HTNPlan? Plan; + + /// + /// How long to wait after having planned to try planning again. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("planCooldown")] + public float PlanCooldown = 0.45f; + + /// + /// How much longer until we can try re-planning. This will happen even during update in case something changed. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float PlanAccumulator = 0f; + + [ViewVariables] + public HTNPlanJob? PlanningJob = null; + + [ViewVariables] + public CancellationTokenSource? PlanningToken = null; + + /// + /// Is this NPC currently planning? + /// + [ViewVariables] public bool Planning => PlanningJob != null; +} diff --git a/Content.Server/NPC/HTN/HTNCompoundTask.cs b/Content.Server/NPC/HTN/HTNCompoundTask.cs new file mode 100644 index 0000000000..e3e31317b9 --- /dev/null +++ b/Content.Server/NPC/HTN/HTNCompoundTask.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Prototypes; + +namespace Content.Server.NPC.HTN; + +/// +/// Represents a network of multiple tasks. This gets expanded out to its relevant nodes. +/// +[Prototype("htnCompound")] +public sealed class HTNCompoundTask : HTNTask +{ + /// + /// The available branches for this compound task. + /// + [DataField("branches", required: true)] + public List Branches = default!; +} diff --git a/Content.Server/NPC/HTN/HTNPlan.cs b/Content.Server/NPC/HTN/HTNPlan.cs new file mode 100644 index 0000000000..af17ab4cee --- /dev/null +++ b/Content.Server/NPC/HTN/HTNPlan.cs @@ -0,0 +1,31 @@ +using Content.Server.NPC.HTN.PrimitiveTasks; + +namespace Content.Server.NPC.HTN; + +/// +/// The current plan for a HTN NPC. +/// +public sealed class HTNPlan +{ + /// + /// Effects that were applied for each primitive task in the plan. + /// + public readonly List?> Effects; + + public List BranchTraversalRecord; + + public List Tasks; + + public int Index = 0; + + public HTNPrimitiveTask CurrentTask => Tasks[Index]; + + public HTNOperator CurrentOperator => CurrentTask.Operator; + + public HTNPlan(List tasks, List branchTraversalRecord, List?> effects) + { + Tasks = tasks; + BranchTraversalRecord = branchTraversalRecord; + Effects = effects; + } +} diff --git a/Content.Server/NPC/HTN/HTNPlanJob.cs b/Content.Server/NPC/HTN/HTNPlanJob.cs new file mode 100644 index 0000000000..4f971b154b --- /dev/null +++ b/Content.Server/NPC/HTN/HTNPlanJob.cs @@ -0,0 +1,248 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Content.Server.CPUJob.JobQueues; +using Content.Server.NPC.HTN.PrimitiveTasks; + +namespace Content.Server.NPC.HTN; + +/// +/// A time-sliced job that will retrieve an HTN plan eventually. +/// +public sealed class HTNPlanJob : Job +{ + private readonly HTNCompoundTask _rootTask; + private NPCBlackboard _blackboard; + + /// + /// Branch traversal of an existing plan (if applicable). + /// + private List? _branchTraversal; + + public HTNPlanJob( + double maxTime, + HTNCompoundTask rootTask, + NPCBlackboard blackboard, + List? branchTraversal, + CancellationToken cancellationToken = default) : base(maxTime, cancellationToken) + { + _rootTask = rootTask; + _blackboard = blackboard; + _branchTraversal = branchTraversal; + } + + protected override async Task Process() + { + /* + * Really the best reference for what a HTN looks like is http://www.gameaipro.com/GameAIPro/GameAIPro_Chapter12_Exploring_HTN_Planners_through_Example.pdf + * It's kinda like a behaviour tree but also can consider multiple actions in sequence. + * + * Methods have been renamed to branches + */ + + var decompHistory = new Stack(); + + // branch traversal record. Whenever we find a new compound task this updates. + var btrIndex = 0; + var btr = new List(); + + // For some tasks we may do something expensive or want to re-use the planning result. + // e.g. pathfind to a target before deciding to attack it. + // Given all of the primitive tasks are singletons we need to store the data somewhere + // hence we'll store it here. + var appliedStates = new List?>(); + + var tasksToProcess = new Queue(); + var finalPlan = new List(); + tasksToProcess.Enqueue(_rootTask); + + // How many primitive tasks we've added since last record. + var primitiveCount = 0; + + while (tasksToProcess.TryDequeue(out var currentTask)) + { + switch (currentTask) + { + case HTNCompoundTask compound: + await SuspendIfOutOfTime(); + + if (TryFindSatisfiedMethod(compound, tasksToProcess, _blackboard, ref btrIndex)) + { + // Need to copy worldstate to roll it back + // Don't need to copy taskstoprocess as we can just clear it and set it to the compound task we roll back to. + // Don't need to copy finalplan as we can just count how many primitives we've added since last record + + decompHistory.Push(new DecompositionState() + { + Blackboard = _blackboard.ShallowClone(), + CompoundTask = compound, + BranchTraversal = btrIndex, + PrimitiveCount = primitiveCount, + }); + + btr.Add(btrIndex); + + // TODO: Early out if existing plan is better and save lots of time. + // my brain is not working rn AAA + + primitiveCount = 0; + // Reset method traversal + btrIndex = 0; + } + else + { + RestoreTolastDecomposedTask(decompHistory, tasksToProcess, appliedStates, finalPlan, ref primitiveCount, ref _blackboard, ref btrIndex, ref btr); + } + break; + case HTNPrimitiveTask primitive: + if (await WaitAsyncTask(PrimitiveConditionMet(primitive, _blackboard, appliedStates))) + { + primitiveCount++; + finalPlan.Add(primitive); + } + else + { + RestoreTolastDecomposedTask(decompHistory, tasksToProcess, appliedStates, finalPlan, ref primitiveCount, ref _blackboard, ref btrIndex, ref btr); + } + + break; + } + } + + if (finalPlan.Count == 0) + { + return null; + } + + var branchTraversalRecord = decompHistory.Reverse().Select(o => o.BranchTraversal).ToList(); + + return new HTNPlan(finalPlan, branchTraversalRecord, appliedStates); + } + + private async Task PrimitiveConditionMet(HTNPrimitiveTask primitive, NPCBlackboard blackboard, List?> appliedStates) + { + blackboard.ReadOnly = true; + + foreach (var con in primitive.Preconditions) + { + if (con.IsMet(blackboard)) + continue; + + return false; + } + + var (valid, effects) = await primitive.Operator.Plan(blackboard); + + if (!valid) + return false; + + blackboard.ReadOnly = false; + + if (effects != null) + { + foreach (var (key, value) in effects) + { + blackboard.SetValue(key, value); + } + } + + appliedStates.Add(effects); + + return true; + } + + /// + /// Goes through each compound task branch and tries to find an appropriate one. + /// + private bool TryFindSatisfiedMethod(HTNCompoundTask compound, Queue tasksToProcess, NPCBlackboard blackboard, ref int mtrIndex) + { + for (var i = mtrIndex; i < compound.Branches.Count; i++) + { + var branch = compound.Branches[i]; + var isValid = true; + + foreach (var con in branch.Preconditions) + { + if (con.IsMet(blackboard)) + continue; + + isValid = false; + break; + } + + if (!isValid) + continue; + + foreach (var task in branch.Tasks) + { + tasksToProcess.Enqueue(task); + } + + return true; + } + + return false; + } + + /// + /// Restores the planner state. + /// + private void RestoreTolastDecomposedTask( + Stack decompHistory, + Queue tasksToProcess, + List?> appliedStates, + List finalPlan, + ref int primitiveCount, + ref NPCBlackboard blackboard, + ref int mtrIndex, + ref List btr) + { + tasksToProcess.Clear(); + + // No plan found so this will just break normally. + if (!decompHistory.TryPop(out var lastDecomp)) + return; + + // Increment MTR so next time we try the next method on the compound task. + mtrIndex = lastDecomp.BranchTraversal + 1; + + var count = finalPlan.Count; + + // Final plan only has primitive tasks added to it so we can just remove the count we've tracked since the last decomp. + finalPlan.RemoveRange(count - primitiveCount, primitiveCount); + appliedStates.RemoveRange(count - primitiveCount, primitiveCount); + btr.RemoveRange(count - primitiveCount, primitiveCount); + + primitiveCount = lastDecomp.PrimitiveCount; + blackboard = lastDecomp.Blackboard; + tasksToProcess.Enqueue(lastDecomp.CompoundTask); + } + + /// + /// Stores the state of an HTN Plan while planning it. This is so we can rollback if a particular branch is unsuitable. + /// + private sealed class DecompositionState + { + /// + /// Blackboard as at decomposition. + /// + public NPCBlackboard Blackboard = default!; + + /// + /// How many primitive tasks we've added since last decompositionstate. + /// + public int PrimitiveCount; + + /// + /// The compound task that owns this decomposition. + /// + public HTNCompoundTask CompoundTask = default!; + + // This may not be necessary for planning but may be useful for debugging so I didn't remove it. + /// + /// Which branch (AKA method) we took of the compound task. Whenever we rollback the decomposition state + /// this gets incremented by 1 so we check the next method. + /// + public int BranchTraversal; + } +} diff --git a/Content.Server/NPC/HTN/HTNSystem.cs b/Content.Server/NPC/HTN/HTNSystem.cs new file mode 100644 index 0000000000..41c52f0d0d --- /dev/null +++ b/Content.Server/NPC/HTN/HTNSystem.cs @@ -0,0 +1,372 @@ +using System.Linq; +using System.Text; +using System.Threading; +using Content.Server.Administration.Managers; +using Content.Server.CPUJob.JobQueues; +using Content.Server.CPUJob.JobQueues.Queues; +using Content.Server.NPC.Components; +using Content.Server.NPC.HTN.PrimitiveTasks; +using Content.Server.NPC.Systems; +using Content.Shared.Administration; +using Content.Shared.NPC; +using JetBrains.Annotations; +using Robust.Server.Player; +using Robust.Shared.Players; +using Robust.Shared.Prototypes; + +namespace Content.Server.NPC.HTN; + +public sealed class HTNSystem : EntitySystem +{ + [Dependency] private readonly IAdminManager _admin = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly NPCSystem _npc = default!; + + private ISawmill _sawmill = default!; + private readonly JobQueue _planQueue = new(); + + private readonly HashSet _subscribers = new(); + + // Hierarchical Task Network + public override void Initialize() + { + base.Initialize(); + _sawmill = Logger.GetSawmill("npc.htn"); + SubscribeLocalEvent(OnHTNShutdown); + SubscribeNetworkEvent(OnHTNMessage); + + _prototypeManager.PrototypesReloaded += OnPrototypeLoad; + OnLoad(); + } + + private void OnHTNMessage(RequestHTNMessage msg, EntitySessionEventArgs args) + { + if (!_admin.HasAdminFlag((IPlayerSession) args.SenderSession, AdminFlags.Debug)) + { + _subscribers.Remove(args.SenderSession); + return; + } + + if (_subscribers.Add(args.SenderSession)) + return; + + _subscribers.Remove(args.SenderSession); + } + + public override void Shutdown() + { + base.Shutdown(); + _prototypeManager.PrototypesReloaded -= OnPrototypeLoad; + } + + private void OnLoad() + { + // Add dependencies for all operators. + // We put code on operators as I couldn't think of a clean way to put it on systems. + foreach (var compound in _prototypeManager.EnumeratePrototypes()) + { + UpdateCompound(compound); + } + + foreach (var primitive in _prototypeManager.EnumeratePrototypes()) + { + UpdatePrimitive(primitive); + } + } + + private void OnPrototypeLoad(PrototypesReloadedEventArgs obj) + { + OnLoad(); + } + + private void UpdatePrimitive(HTNPrimitiveTask primitive) + { + foreach (var precon in primitive.Preconditions) + { + precon.Initialize(EntityManager.EntitySysManager); + } + + primitive.Operator.Initialize(EntityManager.EntitySysManager); + } + + private void UpdateCompound(HTNCompoundTask compound) + { + foreach (var branch in compound.Branches) + { + branch.Tasks.Clear(); + branch.Tasks.EnsureCapacity(branch.TaskPrototypes.Count); + + // Didn't do this in a typeserializer because we can't recursively grab our own prototype during it, woohoo! + foreach (var proto in branch.TaskPrototypes) + { + if (_prototypeManager.TryIndex(proto, out var compTask)) + { + branch.Tasks.Add(compTask); + } + else if (_prototypeManager.TryIndex(proto, out var primTask)) + { + branch.Tasks.Add(primTask); + } + else + { + _sawmill.Error($"Unable to find HTNTask for {proto} on {compound.ID}"); + } + } + + foreach (var precon in branch.Preconditions) + { + precon.Initialize(EntityManager.EntitySysManager); + } + } + } + + private void OnHTNShutdown(EntityUid uid, HTNComponent component, ComponentShutdown args) + { + component.PlanningToken?.Cancel(); + component.PlanningJob = null; + } + + /// + /// Forces the NPC to replan. + /// + [PublicAPI] + public void Replan(HTNComponent component) + { + component.PlanAccumulator = 0f; + } + + public void UpdateNPC(ref int count, int maxUpdates, float frameTime) + { + _planQueue.Process(); + + foreach (var (_, comp) in EntityQuery()) + { + // If we're over our max count or it's not MapInit then ignore the NPC. + if (count >= maxUpdates) + break; + + if (comp.PlanningJob != null) + { + if (comp.PlanningJob.Exception != null) + { + _sawmill.Fatal($"Received exception on planning job for {comp.Owner}!"); + _npc.SleepNPC(comp.Owner); + RemComp(comp.Owner); + throw comp.PlanningJob.Exception; + } + + // If a new planning job has finished then handle it. + if (comp.PlanningJob.Status != JobStatus.Finished) + continue; + + var newPlanBetter = false; + + // If old traversal is better than new traversal then ignore the new plan + if (comp.Plan != null && comp.PlanningJob.Result != null) + { + var oldMtr = comp.Plan.BranchTraversalRecord; + var mtr = comp.PlanningJob.Result.BranchTraversalRecord; + + for (var i = 0; i < oldMtr.Count; i++) + { + if (i < mtr.Count && oldMtr[i] > mtr[i]) + { + newPlanBetter = true; + break; + } + } + } + + if (comp.Plan == null || newPlanBetter) + { + comp.Plan?.CurrentTask.Operator.Shutdown(comp.Blackboard, HTNOperatorStatus.BetterPlan); + comp.Plan = comp.PlanningJob.Result; + + // Startup the first task and anything else we need to do. + if (comp.Plan != null) + { + StartupTask(comp.Plan.Tasks[comp.Plan.Index], comp.Blackboard, comp.Plan.Effects[comp.Plan.Index]); + } + + // Send debug info + foreach (var session in _subscribers) + { + var text = new StringBuilder(); + + if (comp.Plan != null) + { + text.AppendLine($"BTR: {string.Join(", ", comp.Plan.BranchTraversalRecord)}"); + text.AppendLine($"tasks:"); + + foreach (var task in comp.Plan.Tasks) + { + text.AppendLine($"- {task.ID}"); + } + } + + RaiseNetworkEvent(new HTNMessage() + { + Uid = comp.Owner, + Text = text.ToString(), + }, session.ConnectedClient); + } + } + + comp.PlanningJob = null; + comp.PlanningToken = null; + } + + Update(comp, frameTime); + count++; + } + } + + private void Update(HTNComponent component, float frameTime) + { + // If we're not planning then countdown to next one. + if (component.PlanningJob == null) + component.PlanAccumulator -= frameTime; + + // We'll still try re-planning occasionally even when we're updating in case new data comes in. + if (component.PlanAccumulator <= 0f) + { + RequestPlan(component); + } + + // Getting a new plan so do nothing. + if (component.Plan == null) + return; + + // Run the existing plan still + var status = HTNOperatorStatus.Finished; + + // Continuously run operators until we can't anymore. + while (status != HTNOperatorStatus.Continuing && component.Plan != null) + { + // Run the existing operator + var currentOperator = component.Plan.CurrentOperator; + var blackboard = component.Blackboard; + status = currentOperator.Update(blackboard, frameTime); + + switch (status) + { + case HTNOperatorStatus.Continuing: + break; + case HTNOperatorStatus.Failed: + currentOperator.Shutdown(blackboard, status); + component.Plan = null; + break; + // Operator completed so go to the next one. + case HTNOperatorStatus.Finished: + currentOperator.Shutdown(blackboard, status); + component.Plan.Index++; + + // Plan finished! + if (component.Plan.Tasks.Count <= component.Plan.Index) + { + component.Plan = null; + break; + } + + StartupTask(component.Plan.Tasks[component.Plan.Index], component.Blackboard, component.Plan.Effects[component.Plan.Index]); + break; + default: + throw new InvalidOperationException(); + } + } + } + + /// + /// Starts a new primitive task. Will apply effects from planning if applicable. + /// + private void StartupTask(HTNPrimitiveTask primitive, NPCBlackboard blackboard, Dictionary? effects) + { + // We may have planner only tasks where we want to reuse their data during update + // e.g. if we pathfind to an enemy to know if we can attack it, we don't want to do another pathfind immediately + if (effects != null && primitive.ApplyEffectsOnStartup) + { + foreach (var (key, value) in effects) + { + blackboard.SetValue(key, value); + } + } + + primitive.Operator.Startup(blackboard); + } + + /// + /// Request a new plan for this component, even if running an existing plan. + /// + /// + private void RequestPlan(HTNComponent component) + { + if (component.PlanningJob != null) + return; + + component.PlanAccumulator += component.PlanCooldown; + var cancelToken = new CancellationTokenSource(); + var branchTraversal = component.Plan?.BranchTraversalRecord; + + var job = new HTNPlanJob( + 0.02, + _prototypeManager.Index(component.RootTask), + component.Blackboard.ShallowClone(), branchTraversal, cancelToken.Token); + + _planQueue.EnqueueJob(job); + component.PlanningJob = job; + component.PlanningToken = cancelToken; + } + + public string GetDomain(HTNCompoundTask compound) + { + // TODO: Recursively add each one + var indent = 0; + var builder = new StringBuilder(); + AppendDomain(builder, compound, ref indent); + + return builder.ToString(); + } + + private void AppendDomain(StringBuilder builder, HTNTask task, ref int indent) + { + var buffer = string.Concat(Enumerable.Repeat(" ", indent)); + + if (task is HTNPrimitiveTask primitive) + { + builder.AppendLine(buffer + $"Primitive: {task.ID}"); + builder.AppendLine(buffer + $" operator: {primitive.Operator.GetType().Name}"); + } + else if (task is HTNCompoundTask compound) + { + builder.AppendLine(buffer + $"Compound: {task.ID}"); + + foreach (var branch in compound.Branches) + { + builder.AppendLine(buffer + " branch:"); + indent++; + + foreach (var branchTask in branch.Tasks) + { + AppendDomain(builder, branchTask, ref indent); + } + + indent--; + } + } + } +} + +/// +/// The outcome of the current operator during update. +/// +public enum HTNOperatorStatus : byte +{ + Continuing, + Failed, + Finished, + + /// + /// Was a better plan than this found? + /// + BetterPlan, +} diff --git a/Content.Server/NPC/HTN/HTNTask.cs b/Content.Server/NPC/HTN/HTNTask.cs new file mode 100644 index 0000000000..e438edbf50 --- /dev/null +++ b/Content.Server/NPC/HTN/HTNTask.cs @@ -0,0 +1,8 @@ +using Robust.Shared.Prototypes; + +namespace Content.Server.NPC.HTN; + +public abstract class HTNTask : IPrototype +{ + [IdDataField] public string ID { get; } = default!; +} diff --git a/Content.Server/NPC/HTN/HTNTaskListSerializer.cs b/Content.Server/NPC/HTN/HTNTaskListSerializer.cs new file mode 100644 index 0000000000..93c801075e --- /dev/null +++ b/Content.Server/NPC/HTN/HTNTaskListSerializer.cs @@ -0,0 +1,85 @@ +using Content.Server.NPC.HTN.PrimitiveTasks; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; + +namespace Content.Server.NPC.HTN; + +public sealed class HTNTaskListSerializer : ITypeSerializer, SequenceDataNode> +{ + public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node, + IDependencyCollection dependencies, ISerializationContext? context = null) + { + var list = new List(); + var protoManager = dependencies.Resolve(); + + foreach (var data in node.Sequence) + { + if (data is not MappingDataNode mapping) + { + list.Add(new ErrorNode(data, $"Found invalid mapping node on {data}")); + continue; + } + + var id = ((ValueDataNode) mapping["id"]).Value; + + var isCompound = protoManager.HasIndex(id); + var isPrimitive = protoManager.HasIndex(id); + + list.Add(isCompound ^ isPrimitive + ? new ValidatedValueNode(node) + : new ErrorNode(node, $"Found duplicated HTN compound and primitive tasks for {id}")); + } + + return new ValidatedSequenceNode(list); + } + + public List Read(ISerializationManager serializationManager, SequenceDataNode node, IDependencyCollection dependencies, + bool skipHook, ISerializationContext? context = null, List? value = default) + { + value ??= new List(); + foreach (var data in node.Sequence) + { + var mapping = (MappingDataNode) data; + var id = ((ValueDataNode) mapping["id"]).Value; + // Can't check prototypes here because we're still loading them so yay! + value.Add(id); + } + + return value; + } + + public DataNode Write(ISerializationManager serializationManager, List value, bool alwaysWrite = false, + ISerializationContext? context = null) + { + var sequence = new SequenceDataNode(); + + foreach (var task in value) + { + var mapping = new MappingDataNode + { + ["id"] = new ValueDataNode(task) + }; + + sequence.Add(mapping); + } + + return sequence; + } + + public List Copy(ISerializationManager serializationManager, List source, List target, bool skipHook, + ISerializationContext? context = null) + { + target.Clear(); + target.EnsureCapacity(source.Capacity); + + // Tasks are just prototypes soooo? + target.AddRange(source); + return target; + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs new file mode 100644 index 0000000000..e53ee579fa --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/BuckledPrecondition.cs @@ -0,0 +1,27 @@ +using Content.Server.Buckle.Systems; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Checks if the owner is buckled or not +/// +public sealed class BuckledPrecondition : HTNPrecondition +{ + private BuckleSystem _buckle = default!; + + [ViewVariables(VVAccess.ReadWrite)] [DataField("isBuckled")] public bool IsBuckled = true; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _buckle = sysManager.GetEntitySystem(); + } + + public override bool IsMet(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + return IsBuckled && _buckle.IsBuckled(owner) || + !IsBuckled && !_buckle.IsBuckled(owner); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs b/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs new file mode 100644 index 0000000000..d461803ab6 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/CoordinatesInRangePrecondition.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Is the specified coordinate in range of us. +/// +public sealed class CoordinatesInRangePrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [ViewVariables, DataField("targetKey", required: true)] public string TargetKey = default!; + + [ViewVariables, DataField("rangeKey", required: true)] + public string RangeKey = default!; + + public override bool IsMet(NPCBlackboard blackboard) + { + if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates)) + return false; + + if (!blackboard.TryGetValue(TargetKey, out var target)) + return false; + + return coordinates.InRange(_entManager, target, blackboard.GetValueOrDefault(RangeKey)); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs b/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs new file mode 100644 index 0000000000..c328b6ef5e --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/CoordinatesNotInRangePrecondition.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Is the specified coordinate not in range of us. +/// +public sealed class CoordinatesNotInRangePrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [ViewVariables, DataField("targetKey", required: true)] public string TargetKey = default!; + + [ViewVariables, DataField("rangeKey", required: true)] + public string RangeKey = default!; + + public override bool IsMet(NPCBlackboard blackboard) + { + if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates)) + return false; + + if (!blackboard.TryGetValue(TargetKey, out var target)) + return false; + + return !coordinates.InRange(_entManager, target, blackboard.GetValueOrDefault(RangeKey)); + } +} + diff --git a/Content.Server/NPC/HTN/Preconditions/HTNPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/HTNPrecondition.cs new file mode 100644 index 0000000000..959acb90e0 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/HTNPrecondition.cs @@ -0,0 +1,22 @@ +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Condition that needs to be true for a particular primitive task or compound task branch. +/// +[ImplicitDataDefinitionForInheritors] +public abstract class HTNPrecondition +{ + /// + /// Handles one-time initialization of this precondition. + /// + /// + public virtual void Initialize(IEntitySystemManager sysManager) + { + IoCManager.InjectDependencies(this); + } + + /// + /// Has this precondition been met for planning purposes? + /// + public abstract bool IsMet(NPCBlackboard blackboard); +} diff --git a/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs new file mode 100644 index 0000000000..766f85241f --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/KeyExistsPrecondition.cs @@ -0,0 +1,11 @@ +namespace Content.Server.NPC.HTN.Preconditions; + +public sealed class KeyExistsPrecondition : HTNPrecondition +{ + [DataField("key", required: true)] public string Key = string.Empty; + + public override bool IsMet(NPCBlackboard blackboard) + { + return blackboard.ContainsKey(Key); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs new file mode 100644 index 0000000000..d20dd55720 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs @@ -0,0 +1,27 @@ +using Content.Shared.Pulling; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Checks if the owner is being pulled or not. +/// +public sealed class PulledPrecondition : HTNPrecondition +{ + private SharedPullingSystem _pulling = default!; + + [ViewVariables(VVAccess.ReadWrite)] [DataField("isPulled")] public bool IsPulled = true; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _pulling = sysManager.GetEntitySystem(); + } + + public override bool IsMet(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + return IsPulled && _pulling.IsPulled(owner) || + !IsPulled && !_pulling.IsPulled(owner); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/TargetInLOSPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/TargetInLOSPrecondition.cs new file mode 100644 index 0000000000..4a7c846131 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/TargetInLOSPrecondition.cs @@ -0,0 +1,32 @@ +using Content.Server.Interaction; + +namespace Content.Server.NPC.HTN.Preconditions; + +public sealed class TargetInLOSPrecondition : HTNPrecondition +{ + private InteractionSystem _interaction = default!; + + [ViewVariables, DataField("targetKey")] + public string TargetKey = "CombatTarget"; + + [ViewVariables, DataField("rangeKey")] + public string RangeKey = "RangeKey"; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _interaction = sysManager.GetEntitySystem(); + } + + public override bool IsMet(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!blackboard.TryGetValue(TargetKey, out var target)) + return false; + + var range = blackboard.GetValueOrDefault(RangeKey); + + return _interaction.InRangeUnobstructed(owner, target, range); + } +} diff --git a/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs b/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs new file mode 100644 index 0000000000..f309841972 --- /dev/null +++ b/Content.Server/NPC/HTN/Preconditions/TargetInRangePrecondition.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.Preconditions; + +/// +/// Is the specified key within the specified range of us. +/// +public sealed class TargetInRangePrecondition : HTNPrecondition +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + [ViewVariables, DataField("targetKey", required: true)] public string TargetKey = default!; + + [ViewVariables, DataField("rangeKey", required: true)] + public string RangeKey = default!; + + public override bool IsMet(NPCBlackboard blackboard) + { + if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates)) + return false; + + if (!blackboard.TryGetValue(TargetKey, out var target) || + !_entManager.TryGetComponent(target, out var targetXform)) + return false; + + return coordinates.InRange(_entManager, targetXform.Coordinates, blackboard.GetValueOrDefault(RangeKey)); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/HTNOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/HTNOperator.cs new file mode 100644 index 0000000000..2cf8ca918e --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/HTNOperator.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; + +namespace Content.Server.NPC.HTN.PrimitiveTasks; + +/// +/// Concrete code that gets run for an NPC task. +/// +[ImplicitDataDefinitionForInheritors] +public abstract class HTNOperator +{ + /// + /// Called once whenever prototypes reload. Typically used to inject dependencies. + /// + public virtual void Initialize(IEntitySystemManager sysManager) + { + IoCManager.InjectDependencies(this); + } + + /// + /// Called during planning. + /// + /// The blackboard for the NPC. + /// Whether the plan is still valid and the effects to apply to the blackboard. + /// These get re-applied during execution and are up to the operator to use or discard. + public virtual async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + return (true, null); + } + + /// + /// Called during the NPC's regular updates. If the logic requires coordination between NPCs (e.g. steering or combat) + /// this may be better off using a component and letting an external system handling it. + /// + public virtual HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + return HTNOperatorStatus.Finished; + } + + /// + /// Called the first time an operator runs. + /// + public virtual void Startup(NPCBlackboard blackboard) {} + + /// + /// Called whenever the operator stops running. + /// + public virtual void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) {} +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/HTNPrimitiveTask.cs b/Content.Server/NPC/HTN/PrimitiveTasks/HTNPrimitiveTask.cs new file mode 100644 index 0000000000..f9d671041b --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/HTNPrimitiveTask.cs @@ -0,0 +1,22 @@ +using Content.Server.NPC.HTN.Preconditions; +using Robust.Shared.Prototypes; + +namespace Content.Server.NPC.HTN.PrimitiveTasks; + +[Prototype("htnPrimitive")] +public sealed class HTNPrimitiveTask : HTNTask +{ + /// + /// Should we re-apply our blackboard state as a result of our operator during startup? + /// This means you can re-use old data, e.g. re-using a pathfinder result, and avoid potentially expensive operations. + /// + [DataField("applyEffectsOnStartup")] public bool ApplyEffectsOnStartup = true; + + /// + /// What needs to be true for this task to be able to run. + /// The operator may also implement its own checks internally as well if every primitive task using it requires it. + /// + [DataField("preconditions")] public List Preconditions = new(); + + [DataField("operator", required:true)] public HTNOperator Operator = default!; +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Melee/MeleeOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Melee/MeleeOperator.cs new file mode 100644 index 0000000000..09e0a3ecf8 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Melee/MeleeOperator.cs @@ -0,0 +1,99 @@ +using System.Threading.Tasks; +using Content.Server.MobState; +using Content.Server.NPC.Components; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Melee; + +/// +/// Attacks the specified key in melee combat. +/// +public sealed class MeleeOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + /// + /// Key that contains the target entity. + /// + [ViewVariables, DataField("targetKey", required: true)] + public string TargetKey = default!; + + /// + /// Minimum damage state that the target has to be in for us to consider attacking. + /// + [ViewVariables, DataField("targetState")] + public DamageState TargetState = DamageState.Alive; + + // Like movement we add a component and pass it off to the dedicated system. + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + var melee = _entManager.EnsureComponent(blackboard.GetValue(NPCBlackboard.Owner)); + melee.Target = blackboard.GetValue(TargetKey); + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + // Don't attack if they're already as wounded as we want them. + if (!blackboard.TryGetValue(TargetKey, out var target)) + { + return (false, null); + } + + if (_entManager.TryGetComponent(target, out var mobState) && + mobState.CurrentState != null && + mobState.CurrentState > TargetState) + { + return (false, null); + } + + return (true, null); + } + + public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.Shutdown(blackboard, status); + _entManager.RemoveComponent(blackboard.GetValue(NPCBlackboard.Owner)); + blackboard.Remove(TargetKey); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + base.Update(blackboard, frameTime); + // TODO: + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var status = HTNOperatorStatus.Continuing; + + if (_entManager.TryGetComponent(owner, out var combat)) + { + // Success + if (_entManager.TryGetComponent(combat.Target, out var mobState) && + mobState.CurrentState != null && + mobState.CurrentState > TargetState) + { + status = HTNOperatorStatus.Finished; + } + else + { + switch (combat.Status) + { + case CombatStatus.Normal: + status = HTNOperatorStatus.Continuing; + break; + default: + status = HTNOperatorStatus.Failed; + break; + } + } + } + + if (status != HTNOperatorStatus.Continuing) + { + _entManager.RemoveComponent(owner); + } + + return status; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Melee/PickMeleeTargetOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Melee/PickMeleeTargetOperator.cs new file mode 100644 index 0000000000..992f34f72c --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Melee/PickMeleeTargetOperator.cs @@ -0,0 +1,33 @@ +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Melee; + +/// +/// Selects a target for melee. +/// +public sealed class PickMeleeTargetOperator : NPCCombatOperator +{ + protected override float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, bool canMove, EntityQuery xformQuery) + { + var ourCoordinates = blackboard.GetValueOrDefault(NPCBlackboard.OwnerCoordinates); + + if (!xformQuery.TryGetComponent(uid, out var targetXform)) + return -1f; + + var targetCoordinates = targetXform.Coordinates; + + if (!ourCoordinates.TryDistance(EntManager, targetCoordinates, out var distance)) + return -1f; + + var rating = 0f; + + if (existingTarget == uid) + { + rating += 3f; + } + + rating += 1f / distance * 4f; + + return rating; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/MoveToOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/MoveToOperator.cs new file mode 100644 index 0000000000..73b1d595d4 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/MoveToOperator.cs @@ -0,0 +1,190 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.NPC.Components; +using Content.Server.NPC.Pathfinding; +using Content.Server.NPC.Pathfinding.Pathfinders; +using Content.Server.NPC.Systems; +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +/// +/// Moves an NPC to the specified target key. Hands the actual steering off to NPCSystem.Steering +/// +public sealed class MoveToOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + private NPCSteeringSystem _steering = default!; + private PathfindingSystem _pathfind = default!; + + /// + /// Should we assume the MovementTarget is reachable during planning or should we pathfind to it? + /// + [ViewVariables, DataField("pathfindInPlanning")] + public bool PathfindInPlanning = true; + + /// + /// When we're finished moving to the target should we remove its key? + /// + [ViewVariables, DataField("removeKeyOnFinish")] + public bool RemoveKeyOnFinish = true; + + /// + /// Target Coordinates to move to. This gets removed after execution. + /// + [ViewVariables, DataField("targetKey")] + public string TargetKey = "MovementTarget"; + + /// + /// Where the pathfinding result will be stored (if applicable). This gets removed after execution. + /// + [ViewVariables, DataField("pathfindKey")] + public string PathfindKey = "MovementPathfind"; + + /// + /// How close we need to get before considering movement finished. + /// + [ViewVariables, DataField("rangeKey")] + public string RangeKey = "MovementRange"; + + private const string MovementCancelToken = "MovementCancelToken"; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _pathfind = sysManager.GetEntitySystem(); + _steering = sysManager.GetEntitySystem(); + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + if (!blackboard.TryGetValue(TargetKey, out var targetCoordinates)) + { + return (false, null); + } + + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!_entManager.TryGetComponent(owner, out var xform) || + !_entManager.TryGetComponent(owner, out var body)) + return (false, null); + + if (!_mapManager.TryGetGrid(xform.GridUid, out var ownerGrid) || + !_mapManager.TryGetGrid(targetCoordinates.GetGridUid(_entManager), out var targetGrid) || + ownerGrid != targetGrid) + { + return (false, null); + } + + var range = blackboard.GetValueOrDefault(RangeKey); + + if (xform.Coordinates.TryDistance(_entManager, targetCoordinates, out var distance) && distance <= range) + { + // In range + return (true, new Dictionary() + { + {NPCBlackboard.OwnerCoordinates, blackboard.GetValueOrDefault(NPCBlackboard.OwnerCoordinates)} + }); + } + + if (!PathfindInPlanning) + { + return (true, new Dictionary() + { + {NPCBlackboard.OwnerCoordinates, targetCoordinates} + }); + } + + var cancelToken = new CancellationTokenSource(); + var access = blackboard.GetValueOrDefault>(NPCBlackboard.Access) ?? new List(); + + var job = _pathfind.RequestPath( + new PathfindingArgs( + blackboard.GetValue(NPCBlackboard.Owner), + access, + body.CollisionMask, + ownerGrid.GetTileRef(xform.Coordinates), + ownerGrid.GetTileRef(targetCoordinates), + range), cancelToken.Token); + + job.Run(); + + await job.AsTask.WaitAsync(cancelToken.Token); + + if (job.Result == null) + return (false, null); + + return (true, new Dictionary() + { + {NPCBlackboard.OwnerCoordinates, targetCoordinates}, + {PathfindKey, job.Result} + }); + } + + // Given steering is complicated we'll hand it off to a dedicated system rather than this singleton operator. + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + + // Need to remove the planning value for execution. + blackboard.Remove(NPCBlackboard.OwnerCoordinates); + + // Re-use the path we may have if applicable. + var comp = _steering.Register(blackboard.GetValue(NPCBlackboard.Owner), blackboard.GetValue(TargetKey)); + + if (blackboard.TryGetValue(RangeKey, out var range)) + { + comp.Range = range; + } + + if (blackboard.TryGetValue>(PathfindKey, out var path)) + { + if (blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates)) + { + _steering.PrunePath(coordinates, path); + } + + comp.CurrentPath = path; + } + } + + public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.Shutdown(blackboard, status); + + // Cleanup the blackboard and remove steering. + if (blackboard.TryGetValue(MovementCancelToken, out var cancelToken)) + { + cancelToken.Cancel(); + blackboard.Remove(MovementCancelToken); + } + + // OwnerCoordinates is only used in planning so dump it. + blackboard.Remove>(PathfindKey); + + if (RemoveKeyOnFinish) + { + blackboard.Remove(TargetKey); + } + + _steering.Unregister(blackboard.GetValue(NPCBlackboard.Owner)); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!_entManager.TryGetComponent(owner, out var steering)) + return HTNOperatorStatus.Failed; + + return steering.Status switch + { + SteeringStatus.InRange => HTNOperatorStatus.Finished, + SteeringStatus.NoPath => HTNOperatorStatus.Failed, + SteeringStatus.Moving => HTNOperatorStatus.Continuing, + _ => throw new ArgumentOutOfRangeException() + }; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/NPCCombatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/NPCCombatOperator.cs new file mode 100644 index 0000000000..0c2ee3f5ed --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/NPCCombatOperator.cs @@ -0,0 +1,82 @@ +using System.Threading.Tasks; +using Content.Server.Interaction; +using Content.Server.NPC.Systems; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public abstract class NPCCombatOperator : HTNOperator +{ + [Dependency] protected readonly IEntityManager EntManager = default!; + private AiFactionTagSystem _tags = default!; + protected InteractionSystem Interaction = default!; + + [ViewVariables, DataField("key")] public string Key = "CombatTarget"; + + /// + /// The EntityCoordinates of the specified target. + /// + [ViewVariables, DataField("keyCoordinates")] + public string KeyCoordinates = "CombatTargetCoordinates"; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _tags = sysManager.GetEntitySystem(); + Interaction = sysManager.GetEntitySystem(); + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + var targets = GetTargets(blackboard); + + if (targets.Count == 0) + { + return (false, null); + } + + // TODO: Need some level of rng in ratings (outside of continuing to attack the same target) + var selectedTarget = targets[0].Entity; + + var effects = new Dictionary() + { + {Key, selectedTarget}, + {KeyCoordinates, new EntityCoordinates(selectedTarget, Vector2.Zero)} + }; + + return (true, effects); + } + + private List<(EntityUid Entity, float Rating)> GetTargets(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var radius = blackboard.GetValueOrDefault(NPCBlackboard.VisionRadius, EntManager); + var targets = new List<(EntityUid Entity, float Rating)>(); + + blackboard.TryGetValue(Key, out var existingTarget); + var xformQuery = EntManager.GetEntityQuery(); + var mobQuery = EntManager.GetEntityQuery(); + var canMove = blackboard.GetValueOrDefault(NPCBlackboard.CanMove, EntManager); + + // TODO: Need a perception system instead + foreach (var target in _tags + .GetNearbyHostiles(owner, radius)) + { + if (mobQuery.TryGetComponent(target, out var mobState) && + mobState.CurrentState > DamageState.Alive) + { + continue; + } + + targets.Add((target, GetRating(blackboard, target, existingTarget, canMove, xformQuery))); + } + + targets.Sort((x, y) => y.Rating.CompareTo(x.Rating)); + return targets; + } + + protected abstract float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, bool canMove, + EntityQuery xformQuery); +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs new file mode 100644 index 0000000000..b76a6ea4f4 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleComponentOperator.cs @@ -0,0 +1,95 @@ +using System.Threading; +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +/// +/// Picks a nearby component that is accessible. +/// +public sealed class PickAccessibleComponentOperator : HTNOperator +{ + [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + private PathfindingSystem _path = default!; + private EntityLookupSystem _lookup = default!; + + [DataField("rangeKey", required: true)] + public string RangeKey = string.Empty; + + [ViewVariables, DataField("targetKey", required: true)] + public string TargetKey = string.Empty; + + [ViewVariables, DataField("component", required: true)] + public string Component = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _path = sysManager.GetEntitySystem(); + _lookup = sysManager.GetEntitySystem(); + } + + /// + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + // Check if the component exists + if (!_factory.TryGetRegistration(Component, out var registration)) + { + return (false, null); + } + + var range = blackboard.GetValueOrDefault(RangeKey); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!blackboard.TryGetValue(NPCBlackboard.OwnerCoordinates, out var coordinates)) + { + return (false, null); + } + + var compType = registration.Type; + var query = _entManager.GetEntityQuery(compType); + var targets = new List(); + + // TODO: Need to get ones that are accessible. + // TODO: Look at unreal HTN to see repeatable ones maybe? + foreach (var entity in _lookup.GetEntitiesInRange(coordinates, range)) + { + if (entity == owner || !query.TryGetComponent(entity, out var comp)) + continue; + + targets.Add(comp); + } + + if (targets.Count == 0) + { + return (false, null); + } + + while (targets.Count > 0) + { + // TODO: Get nearest at some stage + var target = _random.PickAndTake(targets); + + // TODO: God the path api sucks PLUS I need some fast way to get this. + var job = _path.RequestPath(owner, target.Owner, CancellationToken.None); + + await job.AsTask; + + if (job.Result == null || !_entManager.TryGetComponent(target.Owner, out var targetXform)) + { + continue; + } + + return (true, new Dictionary() + { + { TargetKey, targetXform.Coordinates }, + }); + } + + return (false, null); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleOperator.cs new file mode 100644 index 0000000000..2b099402fa --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickAccessibleOperator.cs @@ -0,0 +1,63 @@ +using System.Threading.Tasks; +using Content.Server.NPC.Pathfinding; +using Content.Server.NPC.Pathfinding.Accessible; +using Robust.Shared.Random; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +/// +/// Chooses a nearby coordinate and puts it into the resulting key. +/// +public sealed class PickAccessibleOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + private AiReachableSystem _reachable = default!; + + [DataField("rangeKey", required: true)] + public string RangeKey = string.Empty; + + [ViewVariables, DataField("targetKey", required: true)] + public string TargetKey = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _reachable = IoCManager.Resolve().GetEntitySystem(); + } + + /// + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + // Very inefficient (should weight each region by its node count) but better than the old system + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!_entManager.TryGetComponent(_entManager.GetComponent(owner).GridUid, out IMapGridComponent? grid)) + return (false, null); + + var reachableArgs = ReachableArgs.GetArgs(owner, blackboard.GetValueOrDefault(RangeKey)); + var entityRegion = _reachable.GetRegion(owner); + var reachableRegions = _reachable.GetReachableRegions(reachableArgs, entityRegion); + + if (reachableRegions.Count == 0) + return (false, null); + + var reachableNodes = new List(); + + foreach (var region in reachableRegions) + { + foreach (var node in region.Nodes) + { + reachableNodes.Add(node); + } + } + + var targetNode = _random.Pick(reachableNodes); + + var target = grid.Grid.GridTileToLocal(targetNode.TileRef.GridIndices); + return (true, new Dictionary() + { + { TargetKey, target }, + }); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickRandomRotationOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickRandomRotationOperator.cs new file mode 100644 index 0000000000..4253e0aa66 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/PickRandomRotationOperator.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Robust.Shared.Random; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed class PickRandomRotationOperator : HTNOperator +{ + [Dependency] private readonly IRobustRandom _random = default!; + + [ViewVariables, DataField("targetKey")] + public string TargetKey = "RotateTarget"; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + var rotation = _random.NextAngle(); + return (true, new Dictionary() + { + {TargetKey, rotation} + }); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/RandomOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/RandomOperator.cs new file mode 100644 index 0000000000..f9c6e67b2d --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/RandomOperator.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Robust.Shared.Random; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed class RandomOperator : HTNOperator +{ + [Dependency] private readonly IRobustRandom _random = default!; + + /// + /// Target blackboard key to set the value to. Doesn't need to exist beforehand. + /// + [DataField("targetKey", required: true)] public string TargetKey = string.Empty; + + /// + /// Minimum idle time. + /// + [DataField("minKey", required: true)] public string MinKey = string.Empty; + + /// + /// Maximum idle time. + /// + [DataField("maxKey", required: true)] public string MaxKey = string.Empty; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + return (true, new Dictionary() + { + { + TargetKey, + _random.NextFloat(blackboard.GetValueOrDefault(MinKey), + blackboard.GetValueOrDefault(MaxKey)) + } + }); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Ranged/PickRangedTargetOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Ranged/PickRangedTargetOperator.cs new file mode 100644 index 0000000000..72ad617bb7 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Ranged/PickRangedTargetOperator.cs @@ -0,0 +1,44 @@ +using Robust.Shared.Map; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Ranged; + +/// +/// Selects a target for ranged combat. +/// +public sealed class PickRangedTargetOperator : NPCCombatOperator +{ + protected override float GetRating(NPCBlackboard blackboard, EntityUid uid, EntityUid existingTarget, bool canMove, EntityQuery xformQuery) + { + var ourCoordinates = blackboard.GetValueOrDefault(NPCBlackboard.OwnerCoordinates); + + if (!xformQuery.TryGetComponent(uid, out var targetXform)) + return -1f; + + var targetCoordinates = targetXform.Coordinates; + + if (!ourCoordinates.TryDistance(EntManager, targetCoordinates, out var distance)) + return -1f; + + // TODO: Uhh make this better with penetration or something. + var inLOS = Interaction.InRangeUnobstructed(blackboard.GetValue(NPCBlackboard.Owner), + uid, distance + 0.1f); + + if (!canMove && !inLOS) + return -1f; + + // Yeah look I just came up with values that seemed okay but they will need a lot of tweaking. + // Having a debug overlay just to project these would be very useful when finetuning in future. + var rating = 0f; + + if (inLOS) + rating += 4f; + + if (existingTarget == uid) + { + rating += 2f; + } + + rating += 1f / distance * 4f; + return rating; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Ranged/RangedOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Ranged/RangedOperator.cs new file mode 100644 index 0000000000..3f4e43703c --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Ranged/RangedOperator.cs @@ -0,0 +1,109 @@ +using System.Threading.Tasks; +using Content.Server.NPC.Components; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; +using Robust.Shared.Audio; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Ranged; + +public sealed class RangedOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + /// + /// Key that contains the target entity. + /// + [ViewVariables, DataField("targetKey", required: true)] + public string TargetKey = default!; + + /// + /// Minimum damage state that the target has to be in for us to consider attacking. + /// + [ViewVariables, DataField("targetState")] + public DamageState TargetState = DamageState.Alive; + + // Like movement we add a component and pass it off to the dedicated system. + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + // Don't attack if they're already as wounded as we want them. + if (!blackboard.TryGetValue(TargetKey, out var target)) + { + return (false, null); + } + + if (_entManager.TryGetComponent(target, out var mobState) && + mobState.CurrentState != null && + mobState.CurrentState > TargetState) + { + return (false, null); + } + + return (true, null); + } + + public override void Startup(NPCBlackboard blackboard) + { + base.Startup(blackboard); + var ranged = _entManager.EnsureComponent(blackboard.GetValue(NPCBlackboard.Owner)); + ranged.Target = blackboard.GetValue(TargetKey); + + if (blackboard.TryGetValue(NPCBlackboard.RotateSpeed, out var rotSpeed)) + { + ranged.RotationSpeed = new Angle(rotSpeed); + } + + if (blackboard.TryGetValue("SoundTargetInLOS", out var losSound)) + { + ranged.SoundTargetInLOS = losSound; + } + } + + public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.Shutdown(blackboard, status); + _entManager.RemoveComponent(blackboard.GetValue(NPCBlackboard.Owner)); + blackboard.Remove(TargetKey); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + base.Update(blackboard, frameTime); + var owner = blackboard.GetValue(NPCBlackboard.Owner); + var status = HTNOperatorStatus.Continuing; + + if (_entManager.TryGetComponent(owner, out var combat)) + { + // Success + if (_entManager.TryGetComponent(combat.Target, out var mobState) && + mobState.CurrentState != null && + mobState.CurrentState > TargetState) + { + status = HTNOperatorStatus.Finished; + } + else + { + switch (combat.Status) + { + case CombatStatus.TargetUnreachable: + case CombatStatus.NotInSight: + status = HTNOperatorStatus.Failed; + break; + case CombatStatus.Normal: + status = HTNOperatorStatus.Continuing; + break; + default: + status = HTNOperatorStatus.Failed; + break; + } + } + } + + if (status != HTNOperatorStatus.Continuing) + { + _entManager.RemoveComponent(owner); + } + + return status; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/RotateToTargetOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/RotateToTargetOperator.cs new file mode 100644 index 0000000000..e9e848daa0 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/RotateToTargetOperator.cs @@ -0,0 +1,53 @@ +using Content.Shared.Interaction; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed class RotateToTargetOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + private RotateToFaceSystem _rotate = default!; + + [ViewVariables, DataField("targetKey")] + public string TargetKey = "RotateTarget"; + + [ViewVariables, DataField("rotateSpeedKey")] + public string RotationSpeedKey = NPCBlackboard.RotateSpeed; + + // Didn't use a key because it's likely the same between all NPCs + [ViewVariables, DataField("tolerance")] + public Angle Tolerance = Angle.FromDegrees(1); + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _rotate = sysManager.GetEntitySystem(); + } + + public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.Shutdown(blackboard, status); + blackboard.Remove(TargetKey); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + if (!blackboard.TryGetValue(TargetKey, out var rotateTarget, _entityManager)) + { + return HTNOperatorStatus.Failed; + } + + if (!blackboard.TryGetValue(RotationSpeedKey, out var rotateSpeed, _entityManager)) + { + return HTNOperatorStatus.Failed; + } + + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (_rotate.TryRotateTo(owner, rotateTarget, frameTime, Tolerance, rotateSpeed)) + { + return HTNOperatorStatus.Finished; + } + + return HTNOperatorStatus.Continuing; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SetFloatOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SetFloatOperator.cs new file mode 100644 index 0000000000..591a7f369d --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SetFloatOperator.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +/// +/// Just sets a blackboard key to a float +/// +public sealed class SetFloatOperator : HTNOperator +{ + [DataField("targetKey", required: true)] public string TargetKey = string.Empty; + + [ViewVariables(VVAccess.ReadWrite), DataField("amount")] + public float Amount; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + return (true, new Dictionary() + { + {TargetKey, Amount}, + }); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs new file mode 100644 index 0000000000..7149f2eb7d --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs @@ -0,0 +1,25 @@ +using Content.Server.Chat.Systems; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +public sealed class SpeakOperator : HTNOperator +{ + private ChatSystem _chat = default!; + + [ViewVariables, DataField("speech", required: true)] + public string Speech = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _chat = IoCManager.Resolve().GetEntitySystem(); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + var speaker = blackboard.GetValue(NPCBlackboard.Owner); + + _chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, false); + return base.Update(blackboard, frameTime); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs new file mode 100644 index 0000000000..326fd20131 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs @@ -0,0 +1,87 @@ +using Content.Server.Chat.Systems; +using Content.Server.Chemistry.EntitySystems; +using Content.Server.NPC.Components; +using Content.Server.Silicons.Bots; +using Content.Shared.Damage; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Robust.Shared.Audio; +using Robust.Shared.Player; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific; + +public sealed class MedibotInjectOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private ChatSystem _chat = default!; + private SharedInteractionSystem _interactionSystem = default!; + private SharedPopupSystem _popupSystem = default!; + private SolutionContainerSystem _solutionSystem = default!; + + /// + /// Target entity to inject. + /// + [ViewVariables, DataField("targetKey", required: true)] + public string TargetKey = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _chat = sysManager.GetEntitySystem(); + _interactionSystem = sysManager.GetEntitySystem(); + _popupSystem = sysManager.GetEntitySystem(); + _solutionSystem = sysManager.GetEntitySystem(); + } + + public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.Shutdown(blackboard, status); + blackboard.Remove(TargetKey); + } + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + // TODO: Wat + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!blackboard.TryGetValue(TargetKey, out var target)) + return HTNOperatorStatus.Failed; + + if (!_entManager.TryGetComponent(owner, out var botComp)) + return HTNOperatorStatus.Failed; + + if (!_entManager.TryGetComponent(target, out var damage)) + return HTNOperatorStatus.Failed; + + if (!_solutionSystem.TryGetInjectableSolution(target, out var injectable)) + return HTNOperatorStatus.Failed; + + if (!_interactionSystem.InRangeUnobstructed(owner, target)) + return HTNOperatorStatus.Failed; + + if (damage.TotalDamage == 0) + return HTNOperatorStatus.Failed; + + if (damage.TotalDamage <= MedibotComponent.StandardMedDamageThreshold) + { + _solutionSystem.TryAddReagent(target, injectable, botComp.StandardMed, botComp.StandardMedInjectAmount, out var accepted); + _entManager.EnsureComponent(target); + _popupSystem.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, Filter.Entities(target)); + SoundSystem.Play("/Audio/Items/hypospray.ogg", Filter.Pvs(target), target); + _chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, false); + return HTNOperatorStatus.Finished; + } + + if (damage.TotalDamage >= MedibotComponent.EmergencyMedDamageThreshold) + { + _solutionSystem.TryAddReagent(target, injectable, botComp.EmergencyMed, botComp.EmergencyMedInjectAmount, out var accepted); + _entManager.EnsureComponent(target); + _popupSystem.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, Filter.Entities(target)); + SoundSystem.Play("/Audio/Items/hypospray.ogg", Filter.Pvs(target), target); + _chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, false); + return HTNOperatorStatus.Finished; + } + + return HTNOperatorStatus.Failed; + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs new file mode 100644 index 0000000000..51a1494cac --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/PickNearbyInjectableOperator.cs @@ -0,0 +1,64 @@ +using System.Threading.Tasks; +using Content.Server.Chemistry.Components.SolutionManager; +using Content.Server.NPC.Components; +using Content.Shared.Damage; +using Content.Shared.MobState.Components; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Specific; + +public sealed class PickNearbyInjectableOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + private EntityLookupSystem _lookup = default!; + + [ViewVariables, DataField("rangeKey")] public string RangeKey = NPCBlackboard.MedibotInjectRange; + + /// + /// Target entity to inject + /// + [ViewVariables, DataField("targetKey", required: true)] + public string TargetKey = string.Empty; + + /// + /// Target entitycoordinates to move to. + /// + [ViewVariables, DataField("targetMoveKey", required: true)] + public string TargetMoveKey = string.Empty; + + public override void Initialize(IEntitySystemManager sysManager) + { + base.Initialize(sysManager); + _lookup = sysManager.GetEntitySystem(); + } + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + if (!blackboard.TryGetValue(RangeKey, out var range)) + return (false, null); + + var damageQuery = _entManager.GetEntityQuery(); + var injectQuery = _entManager.GetEntityQuery(); + var recentlyInjected = _entManager.GetEntityQuery(); + var mobState = _entManager.GetEntityQuery(); + + foreach (var entity in _lookup.GetEntitiesInRange(owner, range)) + { + if (mobState.HasComponent(entity) && + injectQuery.HasComponent(entity) && + damageQuery.TryGetComponent(entity, out var damage) && + damage.TotalDamage > 0 && + !recentlyInjected.HasComponent(entity)) + { + return (true, new Dictionary() + { + {TargetKey, entity}, + {TargetMoveKey, _entManager.GetComponent(entity).Coordinates} + }); + } + } + + return (false, null); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Test/PickPathfindPointOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Test/PickPathfindPointOperator.cs new file mode 100644 index 0000000000..bf05835983 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Test/PickPathfindPointOperator.cs @@ -0,0 +1,44 @@ +using System.Linq; +using System.Threading.Tasks; +using Content.Server.NPC.Components; +using Robust.Shared.Random; + +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators.Test; + +public sealed class PickPathfindPointOperator : HTNOperator +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override async Task<(bool Valid, Dictionary? Effects)> Plan(NPCBlackboard blackboard) + { + var owner = blackboard.GetValue(NPCBlackboard.Owner); + + // Find all pathfind points on the same grid and choose to move to it. + var xform = _entManager.GetComponent(owner); + var gridUid = xform.GridUid; + + if (gridUid == null) + return (false, null); + + var points = new List(); + + foreach (var (point, pointXform) in _entManager.EntityQuery(true)) + { + if (gridUid != pointXform.GridUid) + continue; + + points.Add(pointXform); + } + + if (points.Count == 0) + return (false, null); + + var selected = _random.Pick(points); + + return (true, new Dictionary() + { + { NPCBlackboard.MovementTarget, selected.Coordinates } + }); + } +} diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/WaitOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/WaitOperator.cs new file mode 100644 index 0000000000..fc6a9db456 --- /dev/null +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/WaitOperator.cs @@ -0,0 +1,36 @@ +namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; + +/// +/// Waits the specified amount of time. Removes the key when finished. +/// +public sealed class WaitOperator : HTNOperator +{ + /// + /// Blackboard key for the time we'll wait for. + /// + [ViewVariables, DataField("key", required: true)] public string Key = string.Empty; + + public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) + { + if (!blackboard.TryGetValue(Key, out var timer)) + { + return HTNOperatorStatus.Finished; + } + + timer -= frameTime; + blackboard.SetValue(Key, timer); + + return timer <= 0f ? HTNOperatorStatus.Finished : HTNOperatorStatus.Continuing; + } + + public override void Shutdown(NPCBlackboard blackboard, HTNOperatorStatus status) + { + base.Shutdown(blackboard, status); + + // The replacement plan may want this value so only dump it if we're successful. + if (status != HTNOperatorStatus.BetterPlan) + { + blackboard.Remove(Key); + } + } +} diff --git a/Content.Server/NPC/NPCBlackboard.cs b/Content.Server/NPC/NPCBlackboard.cs new file mode 100644 index 0000000000..52b70058ca --- /dev/null +++ b/Content.Server/NPC/NPCBlackboard.cs @@ -0,0 +1,211 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Access.Systems; +using Content.Shared.ActionBlocker; +using Robust.Shared.Utility; + +namespace Content.Server.NPC; + +[DataDefinition] +public sealed class NPCBlackboard : IEnumerable> +{ + /// + /// Global defaults for NPCs + /// + private static readonly Dictionary BlackboardDefaults = new() + { + {"BufferRange", 10f}, + {"FollowCloseRange", 3f}, + {"FollowRange", 7f}, + {"IdleRange", 7f}, + {"MaximumIdleTime", 7f}, + {MedibotInjectRange, 4f}, + {"MeleeRange", 1f}, + {"MinimumIdleTime", 2f}, + {"MovementRange", 1.5f}, + {"RangedRange", 7f}, + {"RotateSpeed", MathF.PI}, + {"VisionRadius", 7f}, + }; + + /// + /// The specific blackboard for this NPC. + /// + private readonly Dictionary _blackboard = new(); + + /// + /// Should we allow setting values on the blackboard. This is true when we are planning. + /// + /// The effects get stored separately so they can potentially be re-applied during execution. + /// + /// + public bool ReadOnly = false; + + public NPCBlackboard ShallowClone() + { + var dict = new NPCBlackboard(); + foreach (var item in _blackboard) + { + dict.SetValue(item.Key, item.Value); + } + return dict; + } + + public bool ContainsKey(string key) + { + return _blackboard.ContainsKey(key); + } + + /// + /// Get the blackboard data for a particular key. + /// + public T GetValue(string key) + { + return (T) _blackboard[key]; + } + + /// + /// Tries to get the blackboard data for a particular key. Returns default if not found + /// + public T? GetValueOrDefault(string key, IEntityManager? entManager = null) + { + if (_blackboard.TryGetValue(key, out var value)) + { + return (T) value; + } + + if (TryGetEntityDefault(key, out value, entManager)) + { + return (T) value; + } + + if (BlackboardDefaults.TryGetValue(key, out value)) + { + return (T) value; + } + + return default; + } + + /// + /// Tries to get the blackboard data for a particular key. + /// + public bool TryGetValue(string key, [NotNullWhen(true)] out T? value, IEntityManager? entManager = null) + { + if (_blackboard.TryGetValue(key, out var data)) + { + value = (T) data; + return true; + } + + if (TryGetEntityDefault(key, out data, entManager)) + { + value = (T) data; + return true; + } + + if (BlackboardDefaults.TryGetValue(key, out data)) + { + value = (T) data; + return true; + } + + value = default; + return false; + } + + public void SetValue(string key, object value) + { + if (ReadOnly) + { + AssertReadonly(); + return; + } + + _blackboard[key] = value; + } + + private void AssertReadonly() + { + DebugTools.Assert(false, $"Tried to write to an NPC blackboard that is readonly!"); + } + + private bool TryGetEntityDefault(string key, [NotNullWhen(true)] out object? value, IEntityManager? entManager = null) + { + // TODO: Pass this in + IoCManager.Resolve(ref entManager); + value = default; + EntityUid owner; + + switch (key) + { + case Access: + if (!TryGetValue(Owner, out owner)) + { + return false; + } + + var access = entManager.EntitySysManager.GetEntitySystem(); + value = access.FindAccessTags(owner); + return true; + case CanMove: + if (!TryGetValue(Owner, out owner)) + { + return false; + } + + var blocker = entManager.EntitySysManager.GetEntitySystem(); + value = blocker.CanMove(owner); + return true; + case OwnerCoordinates: + if (!TryGetValue(Owner, out owner)) + { + return false; + } + + if (entManager.TryGetComponent(owner, out var xform)) + { + value = xform.Coordinates; + return true; + } + + return false; + default: + return false; + } + } + + public bool Remove(string key) + { + DebugTools.Assert(!_blackboard.ContainsKey(key) || _blackboard[key] is T); + return _blackboard.Remove(key); + } + + // I Ummd and Ahhd about using strings vs enums and decided on tags because + // if a fork wants to do their own thing they don't need to touch the enum. + + /* + * Constants to make development easier + */ + + public const string Access = "Access"; + public const string CanMove = "CanMove"; + public const string FollowTarget = "FollowTarget"; + public const string MedibotInjectRange = "MedibotInjectRange"; + public const string Owner = "Owner"; + public const string OwnerCoordinates = "OwnerCoordinates"; + public const string MovementTarget = "MovementTarget"; + public const string RotateSpeed = "RotateSpeed"; + public const string VisionRadius = "VisionRadius"; + public const float MeleeRange = 1f; + + public IEnumerator> GetEnumerator() + { + return _blackboard.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} diff --git a/Content.Server/NPC/NPCBlackboardSerializer.cs b/Content.Server/NPC/NPCBlackboardSerializer.cs new file mode 100644 index 0000000000..a2fb891385 --- /dev/null +++ b/Content.Server/NPC/NPCBlackboardSerializer.cs @@ -0,0 +1,80 @@ +using Robust.Shared.Reflection; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; +using Robust.Shared.Utility; + +namespace Content.Server.NPC; + +public sealed class NPCBlackboardSerializer : ITypeReader +{ + public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node, + IDependencyCollection dependencies, ISerializationContext? context = null) + { + var validated = new List(); + + if (node.Count > 0) + { + var reflection = dependencies.Resolve(); + + foreach (var data in node) + { + var key = data.Key.ToYamlNode().AsString(); + + if (data.Value.Tag == null) + { + validated.Add(new ErrorNode(data.Key, $"Unable to validate {key}'s type")); + continue; + } + + var typeString = data.Value.Tag[6..]; + + if (!reflection.TryLooseGetType(typeString, out var type)) + { + validated.Add(new ErrorNode(data.Key, $"Unable to find type for {typeString}")); + continue; + } + + var validatedNode = serializationManager.ValidateNode(type, data.Value, context); + validated.Add(validatedNode); + } + } + + return new ValidatedSequenceNode(validated); + } + + public NPCBlackboard Read(ISerializationManager serializationManager, MappingDataNode node, IDependencyCollection dependencies, + bool skipHook, ISerializationContext? context = null, NPCBlackboard? value = default) + { + value ??= new NPCBlackboard(); + + if (node.Count > 0) + { + var reflection = dependencies.Resolve(); + + foreach (var data in node) + { + var key = data.Key.ToYamlNode().AsString(); + + if (data.Value.Tag == null) + throw new NullReferenceException($"Found null tag for {key}"); + + var typeString = data.Value.Tag[6..]; + + if (!reflection.TryLooseGetType(typeString, out var type)) + throw new NullReferenceException($"Found null type for {key}"); + + var bbData = serializationManager.Read(type, data.Value, context, skipHook); + + if (bbData == null) + throw new NullReferenceException($"Found null data for {key}, expected {type}"); + + value.SetValue(key, bbData); + } + } + + return value; + } +} diff --git a/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs b/Content.Server/NPC/Pathfinding/Accessible/AiReachableSystem.cs similarity index 99% rename from Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs rename to Content.Server/NPC/Pathfinding/Accessible/AiReachableSystem.cs index a9bd15991d..d840f8b3a6 100644 --- a/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs +++ b/Content.Server/NPC/Pathfinding/Accessible/AiReachableSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.AI.Pathfinding.Pathfinders; +using Content.Server.NPC.Pathfinding.Pathfinders; using Content.Shared.Access.Systems; using Content.Shared.AI; using Content.Shared.GameTicking; @@ -9,7 +9,7 @@ using Robust.Shared.Physics; using Robust.Shared.Timing; using Robust.Shared.Utility; -namespace Content.Server.AI.Pathfinding.Accessible +namespace Content.Server.NPC.Pathfinding.Accessible { /// /// Determines whether an AI has access to a specific pathfinding node. @@ -221,7 +221,7 @@ namespace Content.Server.AI.Pathfinding.Accessible } // We'll go from target's position to us because most of the time it's probably in a locked room rather than vice versa - var reachableArgs = ReachableArgs.GetArgs(entity); + var reachableArgs = ReachableArgs.GetArgs(entity, 7f); var reachableRegions = GetReachableRegions(reachableArgs, targetRegion); return entityRegion != null && reachableRegions.Contains(entityRegion); diff --git a/Content.Server/AI/Pathfinding/Accessible/BFSPathfinder.cs b/Content.Server/NPC/Pathfinding/Accessible/BFSPathfinder.cs similarity index 96% rename from Content.Server/AI/Pathfinding/Accessible/BFSPathfinder.cs rename to Content.Server/NPC/Pathfinding/Accessible/BFSPathfinder.cs index b7a8345aad..f6b88d200d 100644 --- a/Content.Server/AI/Pathfinding/Accessible/BFSPathfinder.cs +++ b/Content.Server/NPC/Pathfinding/Accessible/BFSPathfinder.cs @@ -1,7 +1,7 @@ -using Content.Server.AI.Pathfinding.Pathfinders; +using Content.Server.NPC.Pathfinding.Pathfinders; using Robust.Shared.Map; -namespace Content.Server.AI.Pathfinding.Accessible +namespace Content.Server.NPC.Pathfinding.Accessible { /// /// The simplest pathfinder diff --git a/Content.Server/AI/Pathfinding/Accessible/PathfindingRegion.cs b/Content.Server/NPC/Pathfinding/Accessible/PathfindingRegion.cs similarity index 98% rename from Content.Server/AI/Pathfinding/Accessible/PathfindingRegion.cs rename to Content.Server/NPC/Pathfinding/Accessible/PathfindingRegion.cs index 3de1490864..8e5725c1ff 100644 --- a/Content.Server/AI/Pathfinding/Accessible/PathfindingRegion.cs +++ b/Content.Server/NPC/Pathfinding/Accessible/PathfindingRegion.cs @@ -1,4 +1,4 @@ -namespace Content.Server.AI.Pathfinding.Accessible +namespace Content.Server.NPC.Pathfinding.Accessible { /// /// A group of homogenous PathfindingNodes inside a single chunk diff --git a/Content.Server/AI/Pathfinding/Accessible/ReachableArgs.cs b/Content.Server/NPC/Pathfinding/Accessible/ReachableArgs.cs similarity index 75% rename from Content.Server/AI/Pathfinding/Accessible/ReachableArgs.cs rename to Content.Server/NPC/Pathfinding/Accessible/ReachableArgs.cs index c1f4544c62..b86e84e778 100644 --- a/Content.Server/AI/Pathfinding/Accessible/ReachableArgs.cs +++ b/Content.Server/NPC/Pathfinding/Accessible/ReachableArgs.cs @@ -1,9 +1,7 @@ -using Content.Server.AI.Components; -using Content.Server.AI.EntitySystems; using Content.Shared.Access.Systems; using Robust.Shared.Physics; -namespace Content.Server.AI.Pathfinding.Accessible +namespace Content.Server.NPC.Pathfinding.Accessible { public sealed class ReachableArgs { @@ -23,7 +21,7 @@ namespace Content.Server.AI.Pathfinding.Accessible /// /// /// - public static ReachableArgs GetArgs(EntityUid entity) + public static ReachableArgs GetArgs(EntityUid entity, float radius) { var collisionMask = 0; var entMan = IoCManager.Resolve(); @@ -34,9 +32,8 @@ namespace Content.Server.AI.Pathfinding.Accessible var accessSystem = EntitySystem.Get(); var access = accessSystem.FindAccessTags(entity); - var visionRadius = entMan.GetComponent(entity).VisionRadius; - return new ReachableArgs(visionRadius, access, collisionMask); + return new ReachableArgs(radius, access, collisionMask); } } } diff --git a/Content.Server/NPC/Pathfinding/GridPathfindingComponent.cs b/Content.Server/NPC/Pathfinding/GridPathfindingComponent.cs new file mode 100644 index 0000000000..4b5e23d8d3 --- /dev/null +++ b/Content.Server/NPC/Pathfinding/GridPathfindingComponent.cs @@ -0,0 +1,8 @@ +namespace Content.Server.NPC.Pathfinding; + +[RegisterComponent] +[Access(typeof(PathfindingSystem))] +public sealed class GridPathfindingComponent : Component +{ + public readonly Dictionary Graph = new(); +} diff --git a/Content.Server/AI/Pathfinding/Pathfinders/AStarPathfindingJob.cs b/Content.Server/NPC/Pathfinding/Pathfinders/AStarPathfindingJob.cs similarity index 91% rename from Content.Server/AI/Pathfinding/Pathfinders/AStarPathfindingJob.cs rename to Content.Server/NPC/Pathfinding/Pathfinders/AStarPathfindingJob.cs index 67f72414ea..2e3d292461 100644 --- a/Content.Server/AI/Pathfinding/Pathfinders/AStarPathfindingJob.cs +++ b/Content.Server/NPC/Pathfinding/Pathfinders/AStarPathfindingJob.cs @@ -1,11 +1,13 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; using Content.Server.CPUJob.JobQueues; using Content.Shared.AI; using Robust.Shared.Map; +using Robust.Shared.Physics; using Robust.Shared.Utility; -namespace Content.Server.AI.Pathfinding.Pathfinders +namespace Content.Server.NPC.Pathfinding.Pathfinders { public sealed class AStarPathfindingJob : Job> { @@ -13,8 +15,8 @@ namespace Content.Server.AI.Pathfinding.Pathfinders public static event Action? DebugRoute; #endif - private readonly PathfindingNode? _startNode; - private PathfindingNode? _endNode; + private readonly PathfindingNode _startNode; + private PathfindingNode _endNode; private readonly PathfindingArgs _pathfindingArgs; private readonly IEntityManager _entityManager; @@ -34,8 +36,8 @@ namespace Content.Server.AI.Pathfinding.Pathfinders protected override async Task?> Process() { - if (_startNode == null || - _endNode == null || + if (_startNode.TileRef.Equals(TileRef.Zero) || + _endNode.TileRef.Equals(TileRef.Zero) || Status == JobStatus.Finished) { return null; @@ -134,6 +136,9 @@ namespace Content.Server.AI.Pathfinding.Pathfinders return null; } + var simplifiedRoute = PathfindingSystem.Simplify(route, 0f); + var actualRoute = new Queue(simplifiedRoute); + #if DEBUG // Need to get data into an easier format to send to the relevant clients if (DebugRoute != null && route.Count > 0) @@ -152,7 +157,7 @@ namespace Content.Server.AI.Pathfinding.Pathfinders var debugRoute = new SharedAiDebug.AStarRouteDebug( _pathfindingArgs.Uid, - route, + actualRoute, debugCameFrom, debugGScores, DebugTime); @@ -161,7 +166,7 @@ namespace Content.Server.AI.Pathfinding.Pathfinders } #endif - return route; + return actualRoute; } } } diff --git a/Content.Server/AI/Pathfinding/Pathfinders/JpsPathfindingJob.cs b/Content.Server/NPC/Pathfinding/Pathfinders/JpsPathfindingJob.cs similarity index 99% rename from Content.Server/AI/Pathfinding/Pathfinders/JpsPathfindingJob.cs rename to Content.Server/NPC/Pathfinding/Pathfinders/JpsPathfindingJob.cs index fcf610e3b0..caea46474a 100644 --- a/Content.Server/AI/Pathfinding/Pathfinders/JpsPathfindingJob.cs +++ b/Content.Server/NPC/Pathfinding/Pathfinders/JpsPathfindingJob.cs @@ -5,7 +5,7 @@ using Content.Shared.AI; using Robust.Shared.Map; using Robust.Shared.Utility; -namespace Content.Server.AI.Pathfinding.Pathfinders +namespace Content.Server.NPC.Pathfinding.Pathfinders { public sealed class JpsPathfindingJob : Job> { diff --git a/Content.Server/AI/Pathfinding/Pathfinders/PathfindingArgs.cs b/Content.Server/NPC/Pathfinding/Pathfinders/PathfindingArgs.cs similarity index 96% rename from Content.Server/AI/Pathfinding/Pathfinders/PathfindingArgs.cs rename to Content.Server/NPC/Pathfinding/Pathfinders/PathfindingArgs.cs index 3395bce85c..b125740b77 100644 --- a/Content.Server/AI/Pathfinding/Pathfinders/PathfindingArgs.cs +++ b/Content.Server/NPC/Pathfinding/Pathfinders/PathfindingArgs.cs @@ -1,6 +1,6 @@ using Robust.Shared.Map; -namespace Content.Server.AI.Pathfinding.Pathfinders +namespace Content.Server.NPC.Pathfinding.Pathfinders { public struct PathfindingArgs { diff --git a/Content.Server/AI/Pathfinding/Pathfinders/PathfindingComparer.cs b/Content.Server/NPC/Pathfinding/Pathfinders/PathfindingComparer.cs similarity index 82% rename from Content.Server/AI/Pathfinding/Pathfinders/PathfindingComparer.cs rename to Content.Server/NPC/Pathfinding/Pathfinders/PathfindingComparer.cs index 9a29f597cb..fb2edf59eb 100644 --- a/Content.Server/AI/Pathfinding/Pathfinders/PathfindingComparer.cs +++ b/Content.Server/NPC/Pathfinding/Pathfinders/PathfindingComparer.cs @@ -1,4 +1,4 @@ -namespace Content.Server.AI.Pathfinding.Pathfinders +namespace Content.Server.NPC.Pathfinding.Pathfinders { public sealed class PathfindingComparer : IComparer> { diff --git a/Content.Server/AI/Pathfinding/PathfindingChunk.cs b/Content.Server/NPC/Pathfinding/PathfindingChunk.cs similarity index 99% rename from Content.Server/AI/Pathfinding/PathfindingChunk.cs rename to Content.Server/NPC/Pathfinding/PathfindingChunk.cs index 1254540ce8..ffb53348c6 100644 --- a/Content.Server/AI/Pathfinding/PathfindingChunk.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingChunk.cs @@ -2,7 +2,7 @@ using System.Linq; using Robust.Shared.Map; using Robust.Shared.Timing; -namespace Content.Server.AI.Pathfinding +namespace Content.Server.NPC.Pathfinding { public sealed class PathfindingChunkUpdateMessage : EntityEventArgs { diff --git a/Content.Server/AI/Pathfinding/PathfindingHelpers.cs b/Content.Server/NPC/Pathfinding/PathfindingHelpers.cs similarity index 97% rename from Content.Server/AI/Pathfinding/PathfindingHelpers.cs rename to Content.Server/NPC/Pathfinding/PathfindingHelpers.cs index b3d011493e..f6c22908e5 100644 --- a/Content.Server/AI/Pathfinding/PathfindingHelpers.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingHelpers.cs @@ -1,9 +1,9 @@ -using Content.Server.AI.Pathfinding.Accessible; -using Content.Server.AI.Pathfinding.Pathfinders; +using Content.Server.NPC.Pathfinding.Accessible; +using Content.Server.NPC.Pathfinding.Pathfinders; using Content.Shared.Access.Systems; using Robust.Shared.Map; -namespace Content.Server.AI.Pathfinding +namespace Content.Server.NPC.Pathfinding { public static class PathfindingHelpers { @@ -125,7 +125,7 @@ namespace Content.Server.AI.Pathfinding return true; } - public static Queue ReconstructPath(Dictionary cameFrom, PathfindingNode current) + public static List ReconstructPath(Dictionary cameFrom, PathfindingNode current) { var running = new Stack(); running.Push(current.TileRef); @@ -137,7 +137,7 @@ namespace Content.Server.AI.Pathfinding running.Push(current.TileRef); } - var result = new Queue(running); + var result = new List(running); return result; } diff --git a/Content.Server/AI/Pathfinding/PathfindingNode.cs b/Content.Server/NPC/Pathfinding/PathfindingNode.cs similarity index 99% rename from Content.Server/AI/Pathfinding/PathfindingNode.cs rename to Content.Server/NPC/Pathfinding/PathfindingNode.cs index 9348998f3b..9503a3575c 100644 --- a/Content.Server/AI/Pathfinding/PathfindingNode.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingNode.cs @@ -6,7 +6,7 @@ using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Utility; -namespace Content.Server.AI.Pathfinding +namespace Content.Server.NPC.Pathfinding { public sealed class PathfindingNode { diff --git a/Content.Server/AI/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs similarity index 99% rename from Content.Server/AI/Pathfinding/PathfindingSystem.Grid.cs rename to Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs index b56250f36f..4ff7eef4bf 100644 --- a/Content.Server/AI/Pathfinding/PathfindingSystem.Grid.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs @@ -5,7 +5,7 @@ using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Utility; -namespace Content.Server.AI.Pathfinding; +namespace Content.Server.NPC.Pathfinding; public sealed partial class PathfindingSystem { diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Simplifier.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simplifier.cs new file mode 100644 index 0000000000..9ce4e89fdb --- /dev/null +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simplifier.cs @@ -0,0 +1,53 @@ +using Robust.Shared.Map; + +namespace Content.Server.NPC.Pathfinding; + +public sealed partial class PathfindingSystem +{ + // TODO: Re-use the existing simplifier. Because the pathfinding API sucks I just copy-pasted for now. + public static List Simplify(List vertices, float tolerance = 0) + { + if (vertices.Count <= 3) + return vertices; + + var simplified = new List(); + + for (var i = 0; i < vertices.Count; i++) + { + // No wraparound for negative sooooo + var prev = vertices[i == 0 ? vertices.Count - 1 : i - 1]; + var current = vertices[i]; + var next = vertices[(i + 1) % vertices.Count]; + + // If they collinear, continue + if (IsCollinear(in prev, in current, in next, tolerance)) + continue; + + simplified.Add(current); + } + + // Farseer didn't seem to handle straight lines and nuked all points + if (simplified.Count == 0) + { + simplified.Add(vertices[0]); + simplified.Add(vertices[^1]); + } + + return simplified; + } + + private static bool IsCollinear(in TileRef prev, in TileRef current, in TileRef next, float tolerance) + { + return FloatInRange(Area(in prev, in current, in next), -tolerance, tolerance); + } + + private static float Area(in TileRef a, in TileRef b, in TileRef c) + { + return a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y); + } + + private static bool FloatInRange(float value, float min, float max) + { + return (value >= min && value <= max); + } +} diff --git a/Content.Server/AI/Pathfinding/PathfindingSystem.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs similarity index 50% rename from Content.Server/AI/Pathfinding/PathfindingSystem.cs rename to Content.Server/NPC/Pathfinding/PathfindingSystem.cs index 37bd2438ff..91d379f7de 100644 --- a/Content.Server/AI/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs @@ -1,16 +1,12 @@ using System.Threading; -using Content.Server.Access; -using Content.Server.AI.Pathfinding.Pathfinders; using Content.Server.CPUJob.JobQueues; using Content.Server.CPUJob.JobQueues.Queues; +using Content.Server.NPC.Pathfinding.Pathfinders; using Content.Shared.Access.Systems; -using Content.Shared.GameTicking; using Content.Shared.Physics; using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Utility; -namespace Content.Server.AI.Pathfinding +namespace Content.Server.NPC.Pathfinding { /// /// This system handles pathfinding graph updates as well as dispatches to the pathfinder @@ -18,6 +14,8 @@ namespace Content.Server.AI.Pathfinding /// public sealed partial class PathfindingSystem : EntitySystem { + [Dependency] private readonly AccessReaderSystem _access = default!; + private readonly PathfindingJobQueue _pathfindingQueue = new(); public const int TrackedCollisionLayers = (int) @@ -29,14 +27,42 @@ namespace Content.Server.AI.Pathfinding /// /// Ask for the pathfinder to gimme somethin /// - /// - /// - /// public Job> RequestPath(PathfindingArgs pathfindingArgs, CancellationToken cancellationToken) { var startNode = GetNode(pathfindingArgs.Start); var endNode = GetNode(pathfindingArgs.End); - var job = new AStarPathfindingJob(0.003, startNode, endNode, pathfindingArgs, cancellationToken, EntityManager); + var job = new AStarPathfindingJob(0.001, startNode, endNode, pathfindingArgs, cancellationToken, EntityManager); + _pathfindingQueue.EnqueueJob(job); + + return job; + } + + public Job> RequestPath(EntityUid source, EntityUid target, CancellationToken cancellationToken) + { + var collisionMask = 0; + + if (TryComp(source, out var body)) + { + collisionMask = body.CollisionMask; + } + + var start = TileRef.Zero; + var end = TileRef.Zero; + + if (TryComp(source, out var xform) && + _mapManager.TryGetGrid(xform.GridUid, out var grid) && + TryComp(target, out var targetXform) && + _mapManager.TryGetGrid(targetXform.GridUid, out var targetGrid)) + { + start = grid.GetTileRef(xform.Coordinates); + end = grid.GetTileRef(targetXform.Coordinates); + } + + var args = new PathfindingArgs(source, _access.FindAccessTags(source), collisionMask, start, end); + + var startNode = GetNode(start); + var endNode = GetNode(end); + var job = new AStarPathfindingJob(0.001, startNode, endNode, args, cancellationToken, EntityManager); _pathfindingQueue.EnqueueJob(job); return job; diff --git a/Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs b/Content.Server/NPC/Pathfinding/ServerPathfindingDebugSystem.cs similarity index 97% rename from Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs rename to Content.Server/NPC/Pathfinding/ServerPathfindingDebugSystem.cs index e8020e41ef..6523513607 100644 --- a/Content.Server/AI/Pathfinding/ServerPathfindingDebugSystem.cs +++ b/Content.Server/NPC/Pathfinding/ServerPathfindingDebugSystem.cs @@ -1,9 +1,9 @@ -using Content.Server.AI.Pathfinding.Pathfinders; +using Content.Server.NPC.Pathfinding.Pathfinders; using Content.Shared.AI; using JetBrains.Annotations; using Robust.Shared.Map; -namespace Content.Server.AI.Pathfinding +namespace Content.Server.NPC.Pathfinding { #if DEBUG [UsedImplicitly] diff --git a/Content.Server/AI/EntitySystems/AiFactionTagSystem.cs b/Content.Server/NPC/Systems/AiFactionTagSystem.cs similarity index 83% rename from Content.Server/AI/EntitySystems/AiFactionTagSystem.cs rename to Content.Server/NPC/Systems/AiFactionTagSystem.cs index 6634e22ca5..3631a2a94a 100644 --- a/Content.Server/AI/EntitySystems/AiFactionTagSystem.cs +++ b/Content.Server/NPC/Systems/AiFactionTagSystem.cs @@ -1,7 +1,7 @@ -using Content.Server.AI.Components; +using Content.Server.NPC.Components; using Robust.Shared.Prototypes; -namespace Content.Server.AI.EntitySystems +namespace Content.Server.NPC.Systems { /// /// Outlines faction relationships with each other for AI. @@ -63,13 +63,22 @@ namespace Content.Server.AI.EntitySystems yield break; } + // TODO: Yes I know this system is shithouse + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(entity); + foreach (var component in EntityManager.EntityQuery(true)) { if ((component.Factions & hostile) == 0) continue; - if (EntityManager.GetComponent(component.Owner).MapID != EntityManager.GetComponent(entity).MapID) + + if (!xformQuery.TryGetComponent(component.Owner, out var targetXform)) continue; - if (!EntityManager.GetComponent(component.Owner).MapPosition.InRange(EntityManager.GetComponent(entity).MapPosition, range)) + + if (targetXform.MapID != xform.MapID) + continue; + + if (!targetXform.Coordinates.InRange(EntityManager, xform.Coordinates, range)) continue; yield return component.Owner; @@ -104,10 +113,11 @@ namespace Content.Server.AI.EntitySystems public enum Faction { None = 0, - NanoTrasen = 1 << 0, - SimpleHostile = 1 << 1, - SimpleNeutral = 1 << 2, - Syndicate = 1 << 3, - Xeno = 1 << 4, + Dragon = 1 << 0, + NanoTrasen = 1 << 1, + SimpleHostile = 1 << 2, + SimpleNeutral = 1 << 3, + Syndicate = 1 << 4, + Xeno = 1 << 5, } } diff --git a/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs b/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs new file mode 100644 index 0000000000..d712e31847 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs @@ -0,0 +1,89 @@ +using Content.Server.CombatMode; +using Content.Server.NPC.Components; +using Content.Server.Weapon.Melee.Components; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; + +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCCombatSystem +{ + private void InitializeMelee() + { + SubscribeLocalEvent(OnMeleeStartup); + SubscribeLocalEvent(OnMeleeShutdown); + } + + private void OnMeleeShutdown(EntityUid uid, NPCMeleeCombatComponent component, ComponentShutdown args) + { + if (TryComp(uid, out var combatMode)) + { + combatMode.IsInCombatMode = false; + } + } + + private void OnMeleeStartup(EntityUid uid, NPCMeleeCombatComponent component, ComponentStartup args) + { + if (TryComp(uid, out var combatMode)) + { + combatMode.IsInCombatMode = true; + } + + // TODO: Cleanup later, just looking for parity for now. + component.Weapon = uid; + } + + private void UpdateMelee(float frameTime) + { + var combatQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + + foreach (var (comp, _) in EntityQuery()) + { + if (!combatQuery.TryGetComponent(comp.Owner, out var combat) || !combat.IsInCombatMode) + { + RemComp(comp.Owner); + continue; + } + + Attack(comp, xformQuery); + } + } + + private void Attack(NPCMeleeCombatComponent component, EntityQuery xformQuery) + { + component.Status = CombatStatus.Normal; + + // TODO: Also need to co-ordinate with steering to keep in range. + // For now I've just moved the utlity version over. + // Also need some blackboard data for stuff like juke frequency, assigning target slots (to surround targets), etc. + // miss % + if (!TryComp(component.Weapon, out var weapon)) + { + component.Status = CombatStatus.NoWeapon; + return; + } + + if (weapon.CooldownEnd > _timing.CurTime) + { + return; + } + + if (!xformQuery.TryGetComponent(component.Owner, out var xform) || + !xformQuery.TryGetComponent(component.Target, out var targetXform)) + { + component.Status = CombatStatus.TargetUnreachable; + return; + } + + if (!xform.Coordinates.TryDistance(EntityManager, targetXform.Coordinates, out var distance) || + distance > weapon.Range) + { + // TODO: Steering in combat. + component.Status = CombatStatus.TargetUnreachable; + return; + } + + _interaction.DoAttack(component.Owner, targetXform.Coordinates, false, component.Target); + } +} diff --git a/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs b/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs new file mode 100644 index 0000000000..8ed27051f5 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs @@ -0,0 +1,159 @@ +using Content.Server.NPC.Components; +using Content.Shared.CombatMode; +using Content.Shared.Interaction; +using Robust.Shared.Map; + +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCCombatSystem +{ + [Dependency] private readonly RotateToFaceSystem _rotate = default!; + + // TODO: Don't predict for hitscan + private const float ShootSpeed = 20f; + + /// + /// Cooldown on raycasting to check LOS. + /// + public const float UnoccludedCooldown = 0.2f; + + private void InitializeRanged() + { + SubscribeLocalEvent(OnRangedStartup); + SubscribeLocalEvent(OnRangedShutdown); + } + + private void OnRangedStartup(EntityUid uid, NPCRangedCombatComponent component, ComponentStartup args) + { + if (TryComp(uid, out var combat)) + { + combat.IsInCombatMode = true; + } + else + { + component.Status = CombatStatus.Unspecified; + } + } + + private void OnRangedShutdown(EntityUid uid, NPCRangedCombatComponent component, ComponentShutdown args) + { + if (TryComp(uid, out var combat)) + { + combat.IsInCombatMode = false; + } + } + + private void UpdateRanged(float frameTime) + { + var bodyQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var combatQuery = GetEntityQuery(); + + foreach (var (comp, xform) in EntityQuery()) + { + if (comp.Status == CombatStatus.Unspecified) + continue; + + if (!xformQuery.TryGetComponent(comp.Target, out var targetXform) || + !bodyQuery.TryGetComponent(comp.Target, out var targetBody)) + { + comp.Status = CombatStatus.TargetUnreachable; + comp.ShootAccumulator = 0f; + continue; + } + + if (targetXform.MapID != xform.MapID) + { + comp.Status = CombatStatus.TargetUnreachable; + comp.ShootAccumulator = 0f; + continue; + } + + if (combatQuery.TryGetComponent(comp.Owner, out var combatMode)) + { + combatMode.IsInCombatMode = true; + } + + var gun = _gun.GetGun(comp.Owner); + + if (gun == null) + { + comp.Status = CombatStatus.NoWeapon; + comp.ShootAccumulator = 0f; + continue; + } + + comp.LOSAccumulator -= frameTime; + + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, xformQuery); + var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery); + + // We'll work out the projected spot of the target and shoot there instead of where they are. + var distance = (targetPos - worldPos).Length; + var oldInLos = comp.TargetInLOS; + + // TODO: Should be doing these raycasts in parallel + // Ideally we'd have 2 steps, 1. to go over the normal details for shooting and then 2. to handle beep / rotate / shoot + if (comp.LOSAccumulator < 0f) + { + comp.LOSAccumulator += UnoccludedCooldown; + comp.TargetInLOS = _interaction.InRangeUnobstructed(comp.Owner, comp.Target, distance + 0.1f); + } + + if (!comp.TargetInLOS) + { + comp.ShootAccumulator = 0f; + comp.Status = CombatStatus.TargetUnreachable; + continue; + } + + if (!oldInLos && comp.SoundTargetInLOS != null) + { + _audio.PlayPvs(comp.SoundTargetInLOS, comp.Owner); + } + + comp.ShootAccumulator += frameTime; + + if (comp.ShootAccumulator < comp.ShootDelay) + { + continue; + } + + var mapVelocity = targetBody.LinearVelocity; + var targetSpot = targetPos + mapVelocity * distance / ShootSpeed; + + // If we have a max rotation speed then do that. + var goalRotation = (targetSpot - worldPos).ToWorldAngle(); + var rotationSpeed = comp.RotationSpeed; + + if (!_rotate.TryRotateTo(comp.Owner, goalRotation, frameTime, comp.AccuracyThreshold, rotationSpeed?.Theta ?? double.MaxValue, xform)) + { + continue; + } + + // TODO: LOS + // TODO: Ammo checks + // TODO: Burst fire + // TODO: Cycling + // Max rotation speed + + // TODO: Check if we can face + + if (!_gun.CanShoot(gun)) + continue; + + EntityCoordinates targetCordinates; + + if (_mapManager.TryFindGridAt(xform.MapID, targetPos, out var mapGrid)) + { + targetCordinates = new EntityCoordinates(mapGrid.GridEntityId, mapGrid.WorldToLocal(targetSpot)); + } + else + { + targetCordinates = new EntityCoordinates(xform.MapUid!.Value, targetSpot); + } + + _gun.AttemptShoot(comp.Owner, gun, targetCordinates); + } + } +} diff --git a/Content.Server/NPC/Systems/NPCCombatSystem.cs b/Content.Server/NPC/Systems/NPCCombatSystem.cs new file mode 100644 index 0000000000..4fc8197f46 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCCombatSystem.cs @@ -0,0 +1,35 @@ +using Content.Server.Interaction; +using Content.Server.Weapon.Ranged.Systems; +using Content.Shared.CombatMode; +using Content.Shared.Interaction; +using Robust.Shared.Map; +using Robust.Shared.Timing; + +namespace Content.Server.NPC.Systems; + +/// +/// Handles combat for NPCs. +/// +public sealed partial class NPCCombatSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly GunSystem _gun = default!; + [Dependency] private readonly InteractionSystem _interaction = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + InitializeMelee(); + InitializeRanged(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + UpdateMelee(frameTime); + UpdateRanged(frameTime); + } +} diff --git a/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs b/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs new file mode 100644 index 0000000000..5b0955a206 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCPerceptionSystem.RecentlyInjected.cs @@ -0,0 +1,23 @@ +using Content.Server.NPC.Components; + +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCPerceptionSystem +{ + /// + /// Tracks targets recently injected by medibots. + /// + /// + private void UpdateRecentlyInjected(float frameTime) + { + foreach (var entity in EntityQuery()) + { + entity.Accumulator += frameTime; + if (entity.Accumulator < entity.RemoveTime.TotalSeconds) + continue; + entity.Accumulator = 0; + + RemComp(entity.Owner); + } + } +} diff --git a/Content.Server/NPC/Systems/NPCPerceptionSystem.cs b/Content.Server/NPC/Systems/NPCPerceptionSystem.cs new file mode 100644 index 0000000000..9a14fea345 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCPerceptionSystem.cs @@ -0,0 +1,13 @@ +namespace Content.Server.NPC.Systems; + +/// +/// Handles sight + sounds for NPCs. +/// +public sealed partial class NPCPerceptionSystem : EntitySystem +{ + public override void Update(float frameTime) + { + base.Update(frameTime); + UpdateRecentlyInjected(frameTime); + } +} diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs new file mode 100644 index 0000000000..44b7f418c1 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Avoidance.cs @@ -0,0 +1,44 @@ +using System.Linq; +using Content.Server.NPC.Components; +using Content.Shared.CCVar; +using Content.Shared.Movement.Components; +using Robust.Shared.Collections; +using Robust.Shared.Configuration; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Collision.Shapes; + +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCSteeringSystem +{ + // TODO + + // Derived from RVO2 library which uses ORCA (optimal reciprocal collision avoidance). + // Could also potentially use something force based or RVO or detour crowd. + + public bool CollisionAvoidanceEnabled { get; set; } = true; + + public bool ObstacleAvoidanceEnabled { get; set; } = true; + + private const float Radius = 0.35f; + private const float RVO_EPSILON = 0.00001f; + + private void InitializeAvoidance() + { + var configManager = IoCManager.Resolve(); + configManager.OnValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance); + } + + private void ShutdownAvoidance() + { + var configManager = IoCManager.Resolve(); + configManager.UnsubValueChanged(CCVars.NPCCollisionAvoidance, SetCollisionAvoidance); + } + + // I deleted all of my relevant code for now as I only had dynamic body avoidance working and not static + // but it will be added back real soon. + private void SetCollisionAvoidance(bool obj) + { + CollisionAvoidanceEnabled = obj; + } +} diff --git a/Content.Server/AI/Steering/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs similarity index 85% rename from Content.Server/AI/Steering/NPCSteeringSystem.cs rename to Content.Server/NPC/Systems/NPCSteeringSystem.cs index 13ef9dd70b..fc50262218 100644 --- a/Content.Server/AI/Steering/NPCSteeringSystem.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs @@ -1,37 +1,37 @@ using System.Linq; using System.Threading; -using Content.Server.AI.Components; -using Content.Server.AI.Pathfinding; -using Content.Server.AI.Pathfinding.Pathfinders; using Content.Server.CPUJob.JobQueues; +using Content.Server.NPC.Components; +using Content.Server.NPC.Pathfinding; +using Content.Server.NPC.Pathfinding.Pathfinders; using Content.Shared.Access.Systems; using Content.Shared.CCVar; using Content.Shared.Movement.Components; using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Physics; using Robust.Shared.Timing; -using Robust.Shared.Utility; -namespace Content.Server.AI.Steering +namespace Content.Server.NPC.Systems { - public sealed class NPCSteeringSystem : EntitySystem + public sealed partial class NPCSteeringSystem : EntitySystem { // http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview [Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!; + [Dependency] private readonly PathfindingSystem _pathfindingSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; // This will likely get moved onto an abstract pathfinding node that specifies the max distance allowed from the coordinate. - private const float TileTolerance = 0.1f; + private const float TileTolerance = 0.4f; private bool _enabled; public override void Initialize() { base.Initialize(); + InitializeAvoidance(); _configManager.OnValueChanged(CCVars.NPCEnabled, SetNPCEnabled, true); SubscribeLocalEvent(OnSteeringShutdown); @@ -58,15 +58,16 @@ namespace Content.Server.AI.Steering public override void Shutdown() { base.Shutdown(); + ShutdownAvoidance(); _configManager.UnsubValueChanged(CCVars.NPCEnabled, SetNPCEnabled); } /// /// Adds the AI to the steering system to move towards a specific target /// - public NPCSteeringComponent Register(EntityUid entity, EntityCoordinates coordinates) + public NPCSteeringComponent Register(EntityUid uid, EntityCoordinates coordinates) { - if (TryComp(entity, out var comp)) + if (TryComp(uid, out var comp)) { comp.PathfindToken?.Cancel(); comp.PathfindToken = null; @@ -74,9 +75,10 @@ namespace Content.Server.AI.Steering } else { - comp = AddComp(entity); + comp = AddComp(uid); } + EnsureComp(uid); comp.Coordinates = coordinates; return comp; } @@ -97,6 +99,7 @@ namespace Content.Server.AI.Steering component.PathfindToken?.Cancel(); component.PathfindToken = null; component.Pathfind = null; + RemComp(uid); RemComp(uid); } @@ -111,7 +114,11 @@ namespace Content.Server.AI.Steering var bodyQuery = GetEntityQuery(); var modifierQuery = GetEntityQuery(); - foreach (var (steering, _, mover, xform) in EntityQuery()) + var npcs = EntityQuery() + .ToArray(); + + // TODO: Do this in parallel. This will require pathfinder refactor to not use jobqueue. + foreach (var (steering, _, mover, xform) in npcs) { Steer(steering, mover, xform, modifierQuery, bodyQuery, frameTime); } @@ -206,6 +213,7 @@ namespace Content.Server.AI.Steering { SetDirection(mover, Vector2.Zero); steering.Status = SteeringStatus.NoPath; + steering.CurrentTarget = targetCoordinates; return; } @@ -229,6 +237,7 @@ namespace Content.Server.AI.Steering { SetDirection(mover, Vector2.Zero); steering.Status = SteeringStatus.NoPath; + steering.CurrentTarget = targetCoordinates; return; } @@ -240,6 +249,7 @@ namespace Content.Server.AI.Steering // This probably shouldn't happen as we check above but eh. SetDirection(mover, Vector2.Zero); steering.Status = SteeringStatus.InRange; + steering.CurrentTarget = targetCoordinates; return; } } @@ -270,7 +280,7 @@ namespace Content.Server.AI.Steering } modifierQuery.TryGetComponent(steering.Owner, out var modifier); - var moveSpeed = GetSprintSpeed(modifier); + var moveSpeed = GetSprintSpeed(steering.Owner, modifier); var input = direction.Normalized; @@ -282,6 +292,7 @@ namespace Content.Server.AI.Steering { SetDirection(mover, Vector2.Zero); steering.Status = SteeringStatus.NoPath; + steering.CurrentTarget = targetCoordinates; return; } @@ -300,18 +311,19 @@ namespace Content.Server.AI.Steering } SetDirection(mover, input); - - // todo: Need a console command to make an NPC steer to a specific spot. - - // TODO: Actual steering behaviours and collision avoidance. - // TODO: Need to handle path invalidation if nodes change. + steering.CurrentTarget = targetCoordinates; } /// /// We may be pathfinding and moving at the same time in which case early nodes may be out of date. /// - private void PrunePath(EntityCoordinates coordinates, Queue nodes) + /// Our coordinates we are pruning from + /// Path we're pruning + public void PrunePath(EntityCoordinates coordinates, Queue nodes) { + if (nodes.Count == 0) + return; + // Right now the pathfinder gives EVERY TILE back but ideally it won't someday, it'll just give straightline ones. // For now, we just prune up until the closest node + 1 extra. var closest = ((Vector2) nodes.Peek().GridIndices + 0.5f - coordinates.Position).Length; @@ -342,8 +354,8 @@ namespace Content.Server.AI.Steering { // Depending on what's going on we may return the target or a pathfind node. - // If it's the last node then just head to the target. - if (steering.CurrentPath.Count > 1 && steering.CurrentPath.TryPeek(out var nextTarget)) + // Even if we're at the last node may not be able to head to target in case we get stuck on a corner or the likes. + if (steering.CurrentPath.Count >= 1 && steering.CurrentPath.TryPeek(out var nextTarget)) { return new EntityCoordinates(nextTarget.GridUid, (Vector2) nextTarget.GridIndices + 0.5f); } @@ -385,14 +397,16 @@ namespace Content.Server.AI.Steering ), steering.PathfindToken.Token); } - private float GetSprintSpeed(MovementSpeedModifierComponent? modifier) - { - return modifier?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed; - } + // TODO: Move these to movercontroller - private float GetWalkSpeed(MovementSpeedModifierComponent? modifier) + private float GetSprintSpeed(EntityUid uid, MovementSpeedModifierComponent? modifier = null) { - return modifier?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed; + if (!Resolve(uid, ref modifier, false)) + { + return MovementSpeedModifierComponent.DefaultBaseSprintSpeed; + } + + return modifier.CurrentSprintSpeed; } } } diff --git a/Content.Server/NPC/Systems/NPCSystem.Blackboard.cs b/Content.Server/NPC/Systems/NPCSystem.Blackboard.cs new file mode 100644 index 0000000000..f2ce0ef80f --- /dev/null +++ b/Content.Server/NPC/Systems/NPCSystem.Blackboard.cs @@ -0,0 +1,17 @@ +using Content.Server.NPC.Components; + +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCSystem +{ + public void SetBlackboard(EntityUid uid, string key, object value, NPCComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + { + return; + } + + var blackboard = component.Blackboard; + blackboard.SetValue(key, value); + } +} diff --git a/Content.Server/NPC/Systems/NPCSystem.Debug.cs b/Content.Server/NPC/Systems/NPCSystem.Debug.cs new file mode 100644 index 0000000000..6f1a25dc92 --- /dev/null +++ b/Content.Server/NPC/Systems/NPCSystem.Debug.cs @@ -0,0 +1,9 @@ +namespace Content.Server.NPC.Systems; + +public sealed partial class NPCSystem +{ + private void InitializeDebug() + { + + } +} diff --git a/Content.Server/AI/EntitySystems/NPCSystem.cs b/Content.Server/NPC/Systems/NPCSystem.cs similarity index 74% rename from Content.Server/AI/EntitySystems/NPCSystem.cs rename to Content.Server/NPC/Systems/NPCSystem.cs index 878a929d1d..fd6e545cb6 100644 --- a/Content.Server/AI/EntitySystems/NPCSystem.cs +++ b/Content.Server/NPC/Systems/NPCSystem.cs @@ -1,4 +1,5 @@ -using Content.Server.AI.Components; +using Content.Server.NPC.Components; +using Content.Server.NPC.HTN; using Content.Shared.CCVar; using Content.Shared.MobState; using JetBrains.Annotations; @@ -6,7 +7,7 @@ using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Reflection; -namespace Content.Server.AI.EntitySystems +namespace Content.Server.NPC.Systems { /// /// Handles NPCs running every tick. @@ -15,9 +16,7 @@ namespace Content.Server.AI.EntitySystems public sealed partial class NPCSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly IDynamicTypeFactory _typeFactory = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IReflectionManager _reflectionManager = default!; + [Dependency] private readonly HTNSystem _htn = default!; private ISawmill _sawmill = default!; @@ -41,9 +40,8 @@ namespace Content.Server.AI.EntitySystems _sawmill = Logger.GetSawmill("npc"); _sawmill.Level = LogLevel.Info; - InitializeUtility(); SubscribeLocalEvent(OnMobStateChange); - SubscribeLocalEvent(OnNPCInit); + SubscribeLocalEvent(OnNPCMapInit); SubscribeLocalEvent(OnNPCShutdown); _configurationManager.OnValueChanged(CCVars.NPCEnabled, SetEnabled, true); _configurationManager.OnValueChanged(CCVars.NPCMaxUpdates, SetMaxUpdates, true); @@ -59,14 +57,15 @@ namespace Content.Server.AI.EntitySystems _configurationManager.UnsubValueChanged(CCVars.NPCMaxUpdates, SetMaxUpdates); } - private void OnNPCInit(EntityUid uid, NPCComponent component, ComponentInit args) + private void OnNPCMapInit(EntityUid uid, NPCComponent component, MapInitEvent args) { - WakeNPC(component); + component.Blackboard.SetValue(NPCBlackboard.Owner, uid); + WakeNPC(uid, component); } private void OnNPCShutdown(EntityUid uid, NPCComponent component, ComponentShutdown args) { - SleepNPC(component); + SleepNPC(uid, component); } /// @@ -80,17 +79,24 @@ namespace Content.Server.AI.EntitySystems /// /// Allows the NPC to actively be updated. /// - public void WakeNPC(NPCComponent component) + public void WakeNPC(EntityUid uid, NPCComponent? component = null) { + if (!Resolve(uid, ref component, false)) + { + return; + } + _sawmill.Debug($"Waking {ToPrettyString(component.Owner)}"); EnsureComp(component.Owner); } - /// - /// Stops the NPC from actively being updated. - /// - public void SleepNPC(NPCComponent component) + public void SleepNPC(EntityUid uid, NPCComponent? component = null) { + if (!Resolve(uid, ref component, false)) + { + return; + } + _sawmill.Debug($"Sleeping {ToPrettyString(component.Owner)}"); RemComp(component.Owner); } @@ -99,10 +105,13 @@ namespace Content.Server.AI.EntitySystems public override void Update(float frameTime) { base.Update(frameTime); - if (!Enabled) return; + + if (!Enabled) + return; _count = 0; - UpdateUtility(frameTime); + // Add your system here. + _htn.UpdateNPC(ref _count, _maxUpdates, frameTime); } private void OnMobStateChange(EntityUid uid, NPCComponent component, MobStateChangedEvent args) @@ -110,11 +119,11 @@ namespace Content.Server.AI.EntitySystems switch (args.CurrentMobState) { case DamageState.Alive: - WakeNPC(component); + WakeNPC(uid, component); break; case DamageState.Critical: case DamageState.Dead: - SleepNPC(component); + SleepNPC(uid, component); break; } } diff --git a/Content.Server/NPC/UI/NPCEui.cs b/Content.Server/NPC/UI/NPCEui.cs new file mode 100644 index 0000000000..1da76fe2fc --- /dev/null +++ b/Content.Server/NPC/UI/NPCEui.cs @@ -0,0 +1,8 @@ +using Content.Server.EUI; + +namespace Content.Server.NPC.UI; + +public sealed class NPCEui : BaseEui +{ + +} diff --git a/Content.Shared/Buckle/SharedBuckleSystem.cs b/Content.Shared/Buckle/SharedBuckleSystem.cs index 8d8714d99b..31da4dc9bb 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.cs @@ -63,6 +63,11 @@ namespace Content.Shared.Buckle } } + public bool IsBuckled(EntityUid uid, SharedBuckleComponent? component = null) + { + return Resolve(uid, ref component, false) && component.Buckled; + } + private void OnBuckleChangeDirectionAttempt(EntityUid uid, SharedBuckleComponent component, ChangeDirectionAttemptEvent args) { if (component.Buckled) diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 402a5a4ca8..1a4381b144 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -482,6 +482,8 @@ namespace Content.Shared.CCVar public static readonly CVarDef NPCEnabled = CVarDef.Create("npc.enabled", true); + public static readonly CVarDef NPCCollisionAvoidance = CVarDef.Create("npc.collision_avoidance", true); + /* * Net */ diff --git a/Content.Shared/Interaction/RotateToFaceSystem.cs b/Content.Shared/Interaction/RotateToFaceSystem.cs index 0ebdcf6418..e82d274f4a 100644 --- a/Content.Shared/Interaction/RotateToFaceSystem.cs +++ b/Content.Shared/Interaction/RotateToFaceSystem.cs @@ -2,7 +2,6 @@ using Content.Shared.ActionBlocker; using Content.Shared.Buckle.Components; using Content.Shared.Rotatable; using JetBrains.Annotations; -using Content.Shared.MobState.Components; using Content.Shared.MobState.EntitySystems; namespace Content.Shared.Interaction @@ -18,43 +17,97 @@ namespace Content.Shared.Interaction { [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedMobStateSystem _mobState = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; - public bool TryFaceCoordinates(EntityUid user, Vector2 coordinates) + /// + /// Tries to rotate the entity towards the target rotation. Returns false if it needs to keep rotating. + /// + public bool TryRotateTo(EntityUid uid, + Angle goalRotation, + float frameTime, + Angle tolerance, + double rotationSpeed = float.MaxValue, + TransformComponent? xform = null) { - var diff = coordinates - EntityManager.GetComponent(user).MapPosition.Position; + if (!Resolve(uid, ref xform)) + return true; + + // If we have a max rotation speed then do that. + // We'll rotate even if we can't shoot, looks better. + if (rotationSpeed < float.MaxValue) + { + var worldRot = _transform.GetWorldRotation(xform); + + var rotationDiff = Angle.ShortestDistance(worldRot, goalRotation).Theta; + var maxRotate = rotationSpeed * frameTime; + + if (Math.Abs(rotationDiff) > maxRotate) + { + var goalTheta = worldRot + Math.Sign(rotationDiff) * maxRotate; + _transform.SetWorldRotation(xform, goalTheta); + rotationDiff = (goalRotation - goalTheta); + + if (Math.Abs(rotationDiff) > tolerance) + { + return false; + } + + return true; + } + + _transform.SetWorldRotation(xform, goalRotation); + } + else + { + _transform.SetWorldRotation(xform, goalRotation); + } + + return true; + } + + public bool TryFaceCoordinates(EntityUid user, Vector2 coordinates, TransformComponent? xform = null) + { + if (!Resolve(user, ref xform)) + return false; + + var diff = coordinates - xform.MapPosition.Position; if (diff.LengthSquared <= 0.01f) return true; + var diffAngle = Angle.FromWorldVec(diff); return TryFaceAngle(user, diffAngle); } - public bool TryFaceAngle(EntityUid user, Angle diffAngle) + public bool TryFaceAngle(EntityUid user, Angle diffAngle, TransformComponent? xform = null) { + // TODO: MobState should be handling CanChangeDirection's event if (_actionBlockerSystem.CanChangeDirection(user) && !_mobState.IsIncapacitated(user)) { - Transform(user).WorldRotation = diffAngle; + if (!Resolve(user, ref xform)) + return false; + + xform.WorldRotation = diffAngle; return true; } - else + + if (EntityManager.TryGetComponent(user, out SharedBuckleComponent? buckle) && buckle.Buckled) { - if (EntityManager.TryGetComponent(user, out SharedBuckleComponent? buckle) && buckle.Buckled) + var suid = buckle.LastEntityBuckledTo; + if (suid != null) { - var suid = buckle.LastEntityBuckledTo; - if (suid != null) + // We're buckled to another object. Is that object rotatable? + if (TryComp(suid.Value!, out var rotatable) && rotatable.RotateWhileAnchored) { - // We're buckled to another object. Is that object rotatable? - if (EntityManager.TryGetComponent(suid.Value, out var rotatable) && rotatable.RotateWhileAnchored) - { - // Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel". - // (Since the user being buckled to it holds it down with their weight.) - // This is logically equivalent to RotateWhileAnchored. - // Barstools and office chairs have independent wheels, while regular chairs don't. - Transform(rotatable.Owner).WorldRotation = diffAngle; - return true; - } + // Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel". + // (Since the user being buckled to it holds it down with their weight.) + // This is logically equivalent to RotateWhileAnchored. + // Barstools and office chairs have independent wheels, while regular chairs don't. + Transform(rotatable.Owner).WorldRotation = diffAngle; + return true; } } } + return false; } } diff --git a/Content.Shared/NPC/HTNMessage.cs b/Content.Shared/NPC/HTNMessage.cs new file mode 100644 index 0000000000..415f2cf817 --- /dev/null +++ b/Content.Shared/NPC/HTNMessage.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.NPC; + +/// +/// Has debug information for HTN NPCs. +/// +[Serializable, NetSerializable] +public sealed class HTNMessage : EntityEventArgs +{ + public EntityUid Uid; + public string Text = string.Empty; +} diff --git a/Content.Shared/NPC/RequestHTNMessage.cs b/Content.Shared/NPC/RequestHTNMessage.cs new file mode 100644 index 0000000000..c57ee6c5ba --- /dev/null +++ b/Content.Shared/NPC/RequestHTNMessage.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.NPC; + +[Serializable, NetSerializable] +public sealed class RequestHTNMessage : EntityEventArgs +{ + public bool Enabled; +} diff --git a/Content.Shared/NPC/SharedNPCComponent.cs b/Content.Shared/NPC/SharedNPCComponent.cs new file mode 100644 index 0000000000..93dd64b4f8 --- /dev/null +++ b/Content.Shared/NPC/SharedNPCComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.NPC; + +[NetworkedComponent] +public abstract class SharedNPCComponent : Component +{ + +} diff --git a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs index 55b35688c3..b33e55bc3f 100644 --- a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs @@ -119,6 +119,11 @@ namespace Content.Shared.Pulling _alertsSystem.ClearAlert(component.Owner, AlertType.Pulled); } + public bool IsPulled(EntityUid uid, SharedPullableComponent? component = null) + { + return Resolve(uid, ref component, false) && component.BeingPulled; + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs b/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs index 84479c2e12..ca9c3f8706 100644 --- a/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/MuzzleFlashEvent.cs @@ -11,9 +11,15 @@ public sealed class MuzzleFlashEvent : EntityEventArgs public EntityUid Uid; public string Prototype; - public MuzzleFlashEvent(EntityUid uid, string prototype) + /// + /// Should the effect match the rotation of the entity. + /// + public bool MatchRotation; + + public MuzzleFlashEvent(EntityUid uid, string prototype, bool matchRotation = false) { Uid = uid; Prototype = prototype; + MatchRotation = matchRotation; } } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index b19184ae79..59ba754d42 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -154,6 +154,14 @@ public abstract partial class SharedGunSystem : EntitySystem component.AvailableModes = state.AvailableSelectiveFire; } + public bool CanShoot(GunComponent component) + { + if (component.NextFire > Timing.CurTime) + return false; + + return true; + } + public GunComponent? GetGun(EntityUid entity) { if (!_combatMode.IsInCombatMode(entity)) @@ -180,6 +188,16 @@ public abstract partial class SharedGunSystem : EntitySystem Dirty(gun); } + /// + /// Attempts to shoot at the target coordinates. Resets the shot counter after every shot. + /// + public void AttemptShoot(EntityUid user, GunComponent gun, EntityCoordinates toCoordinates) + { + gun.ShootCoordinates = toCoordinates; + AttemptShoot(user, gun); + gun.ShotCounter = 0; + } + private void AttemptShoot(EntityUid user, GunComponent gun) { if (gun.FireRate <= 0f) return; @@ -349,8 +367,7 @@ public abstract partial class SharedGunSystem : EntitySystem if (sprite == null) return; - var ev = new MuzzleFlashEvent(gun, sprite); - + var ev = new MuzzleFlashEvent(gun, sprite, user == gun); CreateEffect(gun, ev, user); } diff --git a/Resources/Audio/Effects/double_beep.ogg b/Resources/Audio/Effects/double_beep.ogg new file mode 100644 index 0000000000..152d4e631e Binary files /dev/null and b/Resources/Audio/Effects/double_beep.ogg differ diff --git a/Resources/Audio/Effects/licenses.txt b/Resources/Audio/Effects/licenses.txt index 18145786fc..9630104bbc 100644 --- a/Resources/Audio/Effects/licenses.txt +++ b/Resources/Audio/Effects/licenses.txt @@ -20,6 +20,8 @@ poster_being_set.ogg taken from https://github.com/tgstation/tgstation/blob/2834 saw.ogg taken from https://freesound.org/people/domiscz/sounds/461728/ and clipped - CC0-1.0 *It's actually an angle grinder +double_beep.ogg taken from https://freesound.org/people/andersmmg/sounds/511492/ under CC BY 4.0 + smoke.ogg taken from https://github.com/tgstation/tgstation/blob/a5d362ce84e4f0c61026236d5ec84d3c81553664/sound/effects/smoke.ogg voteding.ogg taken from "Bike, Bell Ding, Single, 01-01.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org at https://freesound.org/people/InspectorJ/sounds/484344/ under CC BY 3.0. The volume has been reduced. diff --git a/Resources/Audio/Weapons/Guns/Gunshots/gun_sentry.ogg b/Resources/Audio/Weapons/Guns/Gunshots/gun_sentry.ogg new file mode 100644 index 0000000000..04ae33fd10 Binary files /dev/null and b/Resources/Audio/Weapons/Guns/Gunshots/gun_sentry.ogg differ diff --git a/Resources/Locale/en-US/sandbox/sandbox-manager.ftl b/Resources/Locale/en-US/sandbox/sandbox-manager.ftl index f6c9cea573..b7f4d03451 100644 --- a/Resources/Locale/en-US/sandbox/sandbox-manager.ftl +++ b/Resources/Locale/en-US/sandbox/sandbox-manager.ftl @@ -12,4 +12,5 @@ sandbox-window-toggle-subfloor-button = Toggle Subfloor sandbox-window-toggle-suicide-button = Suicide sandbox-window-show-spawns-button = Shows Spawns sandbox-window-show-bb-button = Show BB +sandbox-window-show-npc-button = Show NPC sandbox-window-link-machines-button = Link machines diff --git a/Resources/Maps/Test/npc_test_map.yml b/Resources/Maps/Test/npc_test_map.yml new file mode 100644 index 0000000000..250e08ab81 --- /dev/null +++ b/Resources/Maps/Test/npc_test_map.yml @@ -0,0 +1,9413 @@ +meta: + format: 2 + name: DemoStation + author: Space-Wizards + postmapinit: false +tilemap: + 0: Space + 1: FloorArcadeBlue + 2: FloorArcadeBlue2 + 3: FloorArcadeRed + 4: FloorAsteroidCoarseSand0 + 5: FloorAsteroidCoarseSandDug + 6: FloorAsteroidIronsand1 + 7: FloorAsteroidIronsand2 + 8: FloorAsteroidIronsand3 + 9: FloorAsteroidIronsand4 + 10: FloorAsteroidSand + 11: FloorAsteroidTile + 12: FloorBar + 13: FloorBlue + 14: FloorBlueCircuit + 15: FloorBoxing + 16: FloorCarpetClown + 17: FloorCarpetOffice + 18: FloorCave + 19: FloorCaveDrought + 20: FloorClown + 21: FloorDark + 22: FloorDirt + 23: FloorEighties + 24: FloorElevatorShaft + 25: FloorFreezer + 26: FloorGlass + 27: FloorGold + 28: FloorGrass + 29: FloorGrassDark + 30: FloorGrassJungle + 31: FloorGrassLight + 32: FloorGreenCircuit + 33: FloorGym + 34: FloorHydro + 35: FloorKitchen + 36: FloorLaundry + 37: FloorLino + 38: FloorMetalDiamond + 39: FloorMime + 40: FloorMono + 41: FloorRGlass + 42: FloorReinforced + 43: FloorRockVault + 44: FloorShowroom + 45: FloorShuttleBlue + 46: FloorShuttleOrange + 47: FloorShuttlePurple + 48: FloorShuttleRed + 49: FloorShuttleWhite + 50: FloorSilver + 51: FloorSnow + 52: FloorSteel + 53: FloorSteelDirty + 54: FloorTechMaint + 55: FloorWhite + 56: FloorWood + 57: Lattice + 58: Plating +grids: +- settings: + chunksize: 16 + tilesize: 1 + chunks: + - ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: 0,-1 + tiles: KgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: 0,0 + tiles: KgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + - ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + - ind: -1,-2 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: 0,-2 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: 1,0 + tiles: NAAAADQAAAA0AAAANAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAA0AAAANAAAADQAAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + - ind: 1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAA0AAAANAAAADQAAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAANAAAADQAAAA0AAAANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAAAADQAAAA0AAAANAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAA0AAAANAAAADQAAAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAANAAAADQAAAA0AAAANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAAAADQAAAA0AAAANAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== +- settings: + chunksize: 16 + tilesize: 1 + chunks: + - ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: -2,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: -2,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: -1,0 + tiles: KgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAA== + - ind: -1,1 + tiles: KgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + - ind: -2,1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== +entities: +- uid: 0 + type: WallReinforced + components: + - pos: 1.5,1.5 + parent: 10 + type: Transform +- uid: 1 + type: WallReinforced + components: + - pos: -7.5,3.5 + parent: 10 + type: Transform +- uid: 2 + type: WallReinforced + components: + - pos: -0.5,1.5 + parent: 10 + type: Transform +- uid: 3 + type: WallReinforced + components: + - pos: -7.5,1.5 + parent: 10 + type: Transform +- uid: 4 + type: WallReinforced + components: + - pos: 2.5,1.5 + parent: 10 + type: Transform +- uid: 5 + type: WallReinforced + components: + - pos: 2.5,2.5 + parent: 10 + type: Transform +- uid: 6 + type: WallReinforced + components: + - pos: -1.5,9.5 + parent: 10 + type: Transform +- uid: 7 + type: WallReinforced + components: + - pos: -3.5,9.5 + parent: 10 + type: Transform +- uid: 8 + type: WallReinforced + components: + - pos: -5.5,9.5 + parent: 10 + type: Transform +- uid: 9 + components: + - index: 2 + type: Map +- uid: 10 + components: + - pos: -2.75,1.2898817 + parent: 9 + type: Transform + - index: 0 + type: MapGrid + - angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + type: Physics + - fixtures: [] + type: Fixtures + - gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + type: Gravity + - chunkCollection: {} + type: DecalGrid + - tiles: + -1,-6: 0 + -1,-5: 0 + -1,-4: 0 + -1,-3: 0 + -1,-2: 0 + -1,-1: 0 + 0,-6: 0 + 0,-5: 0 + 0,-4: 0 + -11,-16: 1 + -11,-15: 1 + -11,-14: 1 + -11,-13: 1 + -11,-12: 1 + -11,-11: 1 + -11,-10: 1 + -11,-9: 1 + -11,-8: 1 + -11,-7: 1 + -11,-6: 1 + -11,-5: 1 + -11,-4: 1 + -11,-3: 1 + -11,-2: 1 + -11,-1: 1 + -10,-16: 1 + -10,-15: 1 + -10,-14: 1 + -10,-13: 1 + -10,-12: 1 + -10,-11: 1 + -10,-10: 1 + -10,-9: 1 + -10,-8: 1 + -10,-7: 1 + -10,-6: 1 + -10,-5: 1 + -10,-4: 1 + -10,-3: 1 + -10,-2: 1 + -10,-1: 1 + -9,-16: 1 + -9,-15: 1 + -9,-14: 1 + -9,-13: 1 + -9,-12: 1 + -9,-11: 1 + -9,-10: 1 + -9,-9: 1 + -9,-8: 1 + -9,-7: 1 + -9,-6: 1 + -9,-5: 1 + -9,-4: 1 + -9,-3: 1 + -9,-2: 1 + -9,-1: 1 + -8,-16: 1 + -8,-15: 1 + -8,-14: 1 + -8,-13: 1 + -8,-12: 1 + -8,-11: 1 + -8,-10: 1 + -8,-9: 1 + -8,-8: 1 + -8,-7: 1 + -8,-6: 1 + -8,-5: 1 + -8,-4: 1 + -8,-3: 1 + -8,-2: 1 + -8,-1: 1 + -7,-16: 1 + -7,-15: 1 + -7,-14: 1 + -7,-13: 1 + -7,-12: 1 + -7,-11: 1 + -7,-10: 1 + -7,-9: 1 + -7,-8: 1 + -7,-7: 1 + -7,-6: 1 + -7,-5: 1 + -7,-4: 1 + -7,-3: 1 + -7,-2: 1 + -7,-1: 1 + -6,-16: 1 + -6,-15: 1 + -6,-14: 1 + -6,-13: 1 + -6,-12: 1 + -6,-11: 1 + -6,-10: 1 + -6,-9: 1 + -6,-8: 1 + -6,-7: 1 + -6,-6: 1 + -6,-5: 1 + -6,-4: 1 + -6,-3: 1 + -6,-2: 1 + -6,-1: 1 + -5,-16: 1 + -5,-15: 1 + -5,-14: 1 + -5,-13: 1 + -5,-12: 1 + -5,-11: 1 + -5,-10: 1 + -5,-9: 1 + -5,-8: 1 + -5,-7: 1 + -5,-6: 1 + -5,-5: 1 + -5,-4: 1 + -5,-3: 1 + -5,-2: 1 + -5,-1: 1 + -4,-16: 1 + -4,-15: 1 + -4,-14: 1 + -4,-13: 1 + -4,-12: 1 + -4,-11: 1 + -4,-10: 1 + -4,-9: 1 + -4,-8: 1 + -4,-7: 1 + -4,-6: 1 + -4,-5: 1 + -4,-4: 1 + -4,-3: 1 + -4,-2: 1 + -4,-1: 1 + -3,-16: 1 + -3,-15: 1 + -3,-14: 1 + -3,-13: 1 + -3,-12: 1 + -3,-11: 1 + -3,-10: 1 + -3,-9: 1 + -3,-8: 1 + -3,-7: 1 + -3,-6: 1 + -3,-5: 1 + -3,-4: 1 + -3,-3: 1 + -3,-2: 1 + -3,-1: 1 + -2,-16: 1 + -2,-15: 1 + -2,-14: 1 + -2,-13: 1 + -2,-12: 1 + -2,-11: 1 + -2,-10: 1 + -2,-9: 1 + -2,-8: 1 + -2,-7: 1 + -2,-6: 1 + -2,-5: 1 + -2,-4: 1 + -2,-3: 1 + -2,-2: 1 + -2,-1: 1 + -1,-16: 1 + -1,-15: 1 + -1,-14: 1 + -1,-13: 1 + -1,-12: 1 + -1,-11: 1 + -1,-10: 1 + -1,-9: 1 + -1,-8: 1 + -1,-7: 1 + 0,-16: 1 + 0,-15: 1 + 0,-14: 1 + 0,-13: 1 + 0,-12: 1 + 0,-11: 1 + 0,-10: 1 + 0,-9: 1 + 0,-8: 1 + 0,-7: 1 + 0,-3: 1 + 0,-2: 1 + 0,-1: 1 + 1,-16: 1 + 1,-15: 1 + 1,-14: 1 + 1,-13: 1 + 1,-12: 1 + 1,-11: 1 + 1,-10: 1 + 1,-9: 1 + 1,-8: 1 + 1,-7: 1 + 1,-6: 1 + 1,-5: 1 + 1,-4: 1 + 1,-3: 1 + 1,-2: 1 + 1,-1: 1 + 2,-16: 1 + 2,-15: 1 + 2,-14: 1 + 2,-13: 1 + 2,-12: 1 + 2,-11: 1 + 2,-10: 1 + 2,-9: 1 + 2,-8: 1 + 2,-7: 1 + 2,-6: 1 + 2,-5: 1 + 2,-4: 1 + 2,-3: 1 + 2,-2: 1 + 2,-1: 1 + 3,-16: 1 + 3,-15: 1 + 3,-14: 1 + 3,-13: 1 + 3,-12: 1 + 3,-11: 1 + 3,-10: 1 + 3,-9: 1 + 3,-8: 1 + 3,-7: 1 + 3,-6: 1 + 3,-5: 1 + 3,-4: 1 + 3,-3: 1 + 3,-2: 1 + 3,-1: 1 + 4,-16: 1 + 4,-15: 1 + 4,-14: 1 + 4,-13: 1 + 4,-12: 1 + 4,-11: 1 + 4,-10: 1 + 4,-9: 1 + 4,-8: 1 + 4,-7: 1 + 4,-6: 1 + 4,-5: 1 + 4,-4: 1 + 4,-3: 1 + 4,-2: 1 + 4,-1: 1 + 5,-16: 1 + 5,-15: 1 + 5,-14: 1 + 5,-13: 1 + 5,-12: 1 + 5,-11: 1 + 5,-10: 1 + 5,-9: 1 + 5,-8: 1 + 5,-7: 1 + 5,-6: 1 + 5,-5: 1 + 5,-4: 1 + 5,-3: 1 + 5,-2: 1 + 5,-1: 1 + 6,-16: 1 + 6,-15: 1 + 6,-14: 1 + 6,-13: 1 + 6,-12: 1 + 6,-11: 1 + 6,-10: 1 + 6,-9: 1 + 6,-8: 1 + 6,-7: 1 + 6,-6: 1 + 6,-5: 1 + 6,-4: 1 + 6,-3: 1 + 6,-2: 1 + 6,-1: 1 + 7,-16: 1 + 7,-15: 1 + 7,-14: 1 + 7,-13: 1 + 7,-12: 1 + 7,-11: 1 + 7,-10: 1 + 7,-9: 1 + 7,-8: 1 + 7,-7: 1 + 7,-6: 1 + 7,-5: 1 + 7,-4: 1 + 7,-3: 1 + 7,-2: 1 + 7,-1: 1 + 8,-16: 1 + 8,-15: 1 + 8,-14: 1 + 8,-13: 1 + 8,-12: 1 + 8,-11: 1 + 8,-10: 1 + 8,-9: 1 + 8,-8: 1 + 8,-7: 1 + 8,-6: 1 + 8,-5: 1 + 8,-4: 1 + 8,-3: 1 + 8,-2: 1 + 8,-1: 1 + 9,-16: 1 + 9,-15: 1 + 9,-14: 1 + 9,-13: 1 + 9,-12: 1 + 9,-11: 1 + 9,-10: 1 + 9,-9: 1 + 9,-8: 1 + 9,-7: 1 + 9,-6: 1 + 9,-5: 1 + 9,-4: 1 + 9,-3: 1 + 9,-2: 1 + 9,-1: 1 + 10,-16: 1 + 10,-15: 1 + 10,-14: 1 + 10,-13: 1 + 10,-12: 1 + 10,-11: 1 + 10,-10: 1 + 10,-9: 1 + 10,-8: 1 + 10,-7: 1 + 10,-6: 1 + 10,-5: 1 + 10,-4: 1 + 10,-3: 1 + 10,-2: 1 + 10,-1: 1 + 11,-16: 1 + 11,-15: 1 + 11,-14: 1 + 11,-13: 1 + 11,-12: 1 + 11,-11: 1 + 11,-10: 1 + 11,-9: 1 + 11,-8: 1 + 11,-7: 1 + 11,-6: 1 + 11,-5: 1 + 11,-4: 1 + 11,-3: 1 + 11,-2: 1 + 11,-1: 1 + 12,-16: 1 + 12,-15: 1 + 12,-14: 1 + 12,-13: 1 + 12,-12: 1 + 12,-11: 1 + 12,-10: 1 + 12,-9: 1 + 12,-8: 1 + 12,-7: 1 + 12,-6: 1 + 12,-5: 1 + 12,-4: 1 + 12,-3: 1 + 12,-2: 1 + 12,-1: 1 + 13,-16: 1 + 13,-15: 1 + 13,-14: 1 + 13,-13: 1 + 13,-12: 1 + 13,-11: 1 + 13,-10: 1 + 13,-9: 1 + 13,-8: 1 + 13,-7: 1 + 13,-6: 1 + 13,-5: 1 + 13,-4: 1 + 13,-3: 1 + 13,-2: 1 + 13,-1: 1 + 14,-16: 1 + 14,-15: 1 + 14,-14: 1 + 14,-13: 1 + 14,-12: 1 + 14,-11: 1 + 14,-10: 1 + 14,-9: 1 + 14,-8: 1 + 14,-7: 1 + 14,-6: 1 + 14,-5: 1 + 14,-4: 1 + 14,-3: 1 + 14,-2: 1 + 14,-1: 1 + 15,-16: 1 + 15,-15: 1 + 15,-14: 1 + 15,-13: 1 + 15,-12: 1 + 15,-11: 1 + 15,-10: 1 + 15,-9: 1 + 15,-8: 1 + 15,-7: 1 + 15,-6: 1 + 15,-5: 1 + 15,-4: 1 + 15,-3: 1 + 15,-2: 1 + 15,-1: 1 + 0,0: 1 + 0,1: 1 + 0,2: 1 + 0,3: 1 + 0,4: 1 + 0,5: 1 + 0,6: 1 + 0,7: 1 + 0,8: 1 + 0,9: 1 + 1,0: 1 + 1,1: 1 + 1,2: 1 + 1,3: 1 + 1,4: 1 + 1,5: 1 + 1,6: 1 + 1,7: 1 + 1,8: 1 + 1,9: 1 + 2,0: 1 + 2,1: 1 + 2,2: 1 + 2,3: 1 + 2,4: 1 + 2,5: 1 + 2,6: 1 + 2,7: 1 + 2,8: 1 + 2,9: 1 + 3,0: 1 + 3,1: 1 + 3,2: 1 + 3,3: 1 + 3,4: 1 + 3,5: 1 + 3,6: 1 + 3,7: 1 + 3,8: 1 + 3,9: 1 + 4,0: 1 + 4,1: 1 + 4,2: 1 + 4,3: 1 + 4,4: 1 + 4,5: 1 + 4,6: 1 + 4,7: 1 + 4,8: 1 + 4,9: 1 + 5,0: 1 + 5,1: 1 + 5,2: 1 + 5,3: 1 + 5,4: 1 + 5,5: 1 + 5,6: 1 + 5,7: 1 + 5,8: 1 + 5,9: 1 + 6,0: 1 + 6,1: 1 + 6,2: 1 + 6,3: 1 + 6,4: 1 + 6,5: 1 + 6,6: 1 + 6,7: 1 + 6,8: 1 + 6,9: 1 + 7,0: 1 + 7,1: 1 + 7,2: 1 + 7,3: 1 + 7,4: 1 + 7,5: 1 + 7,6: 1 + 7,7: 1 + 7,8: 1 + 7,9: 1 + 8,0: 1 + 8,1: 1 + 8,2: 1 + 8,3: 1 + 8,4: 1 + 8,5: 1 + 8,6: 1 + 8,7: 1 + 8,8: 1 + 8,9: 1 + 9,0: 1 + 9,1: 1 + 9,2: 1 + 9,3: 1 + 9,4: 1 + 9,5: 1 + 9,6: 1 + 9,7: 1 + 9,8: 1 + 9,9: 1 + 10,0: 1 + 10,1: 1 + 10,2: 1 + 10,3: 1 + 10,4: 1 + 10,5: 1 + 10,6: 1 + 10,7: 1 + 10,8: 1 + 10,9: 1 + 11,0: 1 + 11,1: 1 + 11,2: 1 + 11,3: 1 + 11,4: 1 + 11,5: 1 + 11,6: 1 + 11,7: 1 + 11,8: 1 + 11,9: 1 + 12,0: 1 + 12,1: 1 + 12,2: 1 + 12,3: 1 + 12,4: 1 + 12,5: 1 + 12,6: 1 + 12,7: 1 + 12,8: 1 + 12,9: 1 + 13,0: 1 + 13,1: 1 + 13,2: 1 + 13,3: 1 + 13,4: 1 + 13,5: 1 + 13,6: 1 + 13,7: 1 + 13,8: 1 + 13,9: 1 + 14,0: 1 + 14,1: 1 + 14,2: 1 + 14,3: 1 + 14,4: 1 + 14,5: 1 + 14,6: 1 + 14,7: 1 + 14,8: 1 + 14,9: 1 + 15,0: 1 + 15,1: 1 + 15,2: 1 + 15,3: 1 + 15,4: 1 + 15,5: 1 + 15,6: 1 + 15,7: 1 + 15,8: 1 + 15,9: 1 + -11,0: 1 + -11,1: 1 + -11,2: 1 + -11,3: 1 + -11,4: 1 + -11,5: 1 + -11,6: 1 + -11,7: 1 + -11,8: 1 + -11,9: 1 + -10,0: 1 + -10,1: 1 + -10,2: 1 + -10,3: 1 + -10,4: 1 + -10,5: 1 + -10,6: 1 + -10,7: 1 + -10,8: 1 + -10,9: 1 + -9,0: 1 + -9,1: 1 + -9,2: 1 + -9,3: 1 + -9,4: 1 + -9,5: 1 + -9,6: 1 + -9,7: 1 + -9,8: 1 + -9,9: 1 + -8,0: 1 + -8,1: 1 + -8,2: 1 + -8,3: 1 + -8,4: 1 + -8,5: 1 + -8,6: 1 + -8,7: 1 + -8,8: 1 + -8,9: 1 + -7,0: 1 + -7,1: 1 + -7,2: 1 + -7,3: 1 + -7,4: 1 + -7,5: 1 + -7,6: 1 + -7,7: 1 + -7,8: 1 + -7,9: 1 + -6,0: 1 + -6,1: 1 + -6,2: 1 + -6,3: 1 + -6,4: 1 + -6,5: 1 + -6,6: 1 + -6,7: 1 + -6,8: 1 + -6,9: 1 + -5,0: 1 + -5,1: 1 + -5,2: 1 + -5,3: 1 + -5,4: 1 + -5,5: 1 + -5,6: 1 + -5,7: 1 + -5,8: 1 + -5,9: 1 + -4,0: 1 + -4,1: 1 + -4,2: 1 + -4,3: 1 + -4,4: 1 + -4,5: 1 + -4,6: 1 + -4,7: 1 + -4,8: 1 + -4,9: 1 + -3,0: 1 + -3,1: 1 + -3,2: 1 + -3,3: 1 + -3,4: 1 + -3,5: 1 + -3,6: 1 + -3,7: 1 + -3,8: 1 + -3,9: 1 + -2,0: 1 + -2,1: 1 + -2,2: 1 + -2,3: 1 + -2,4: 1 + -2,5: 1 + -2,6: 1 + -2,7: 1 + -2,8: 1 + -2,9: 1 + -1,0: 1 + -1,1: 1 + -1,2: 1 + -1,3: 1 + -1,4: 1 + -1,5: 1 + -1,6: 1 + -1,7: 1 + -1,8: 1 + -1,9: 1 + -11,-17: 1 + -10,-17: 1 + -9,-17: 1 + -8,-17: 1 + -7,-17: 1 + -6,-17: 1 + -5,-17: 1 + -4,-17: 1 + -3,-17: 1 + -2,-17: 1 + -1,-17: 1 + 0,-17: 1 + 1,-17: 1 + 2,-17: 1 + 3,-17: 1 + 4,-17: 1 + 5,-17: 1 + 6,-17: 1 + 7,-17: 1 + 8,-17: 1 + 9,-17: 1 + 10,-17: 1 + 11,-17: 1 + 12,-17: 1 + 13,-17: 1 + 14,-17: 1 + 15,-17: 1 + 16,0: 1 + 16,1: 1 + 17,0: 1 + 17,1: 1 + 18,0: 1 + 18,1: 1 + 19,0: 1 + 19,1: 1 + 20,0: 1 + 20,1: 1 + 16,-6: 1 + 16,-5: 1 + 16,-4: 1 + 16,-3: 1 + 16,-2: 1 + 16,-1: 1 + 17,-6: 1 + 17,-5: 1 + 17,-4: 1 + 17,-3: 1 + 17,-2: 1 + 17,-1: 1 + 18,-6: 1 + 18,-5: 1 + 18,-4: 1 + 18,-3: 1 + 18,-2: 1 + 18,-1: 1 + 19,-6: 1 + 19,-5: 1 + 19,-4: 1 + 19,-3: 1 + 19,-2: 1 + 19,-1: 1 + 20,-6: 1 + 20,-5: 1 + 20,-4: 1 + 20,-3: 1 + 20,-2: 1 + 20,-1: 1 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + temperature: 293.15 + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + type: GridAtmosphere +- uid: 11 + type: WallReinforced + components: + - pos: -0.5,9.5 + parent: 10 + type: Transform +- uid: 12 + type: WallReinforced + components: + - pos: -0.5,8.5 + parent: 10 + type: Transform +- uid: 13 + type: WallReinforced + components: + - pos: -0.5,7.5 + parent: 10 + type: Transform +- uid: 14 + type: WallReinforced + components: + - pos: -0.5,6.5 + parent: 10 + type: Transform +- uid: 15 + type: WallReinforced + components: + - pos: 6.5,6.5 + parent: 10 + type: Transform +- uid: 16 + type: WallReinforced + components: + - pos: 6.5,7.5 + parent: 10 + type: Transform +- uid: 17 + type: WallReinforced + components: + - pos: 6.5,8.5 + parent: 10 + type: Transform +- uid: 18 + type: WallReinforced + components: + - pos: 6.5,9.5 + parent: 10 + type: Transform +- uid: 19 + type: WallReinforced + components: + - pos: 5.5,6.5 + parent: 10 + type: Transform +- uid: 20 + type: Grille + components: + - pos: 3.5,6.5 + parent: 10 + type: Transform +- uid: 21 + type: Grille + components: + - pos: 4.5,6.5 + parent: 10 + type: Transform +- uid: 22 + type: WallReinforced + components: + - pos: 2.5,6.5 + parent: 10 + type: Transform +- uid: 23 + type: WallReinforced + components: + - pos: -1.5,3.5 + parent: 10 + type: Transform +- uid: 24 + type: WallReinforced + components: + - pos: 6.5,-0.5 + parent: 10 + type: Transform +- uid: 25 + type: WallReinforced + components: + - pos: 6.5,1.5 + parent: 10 + type: Transform +- uid: 26 + type: WallReinforced + components: + - pos: -10.5,9.5 + parent: 10 + type: Transform +- uid: 27 + type: WallReinforced + components: + - pos: 7.5,9.5 + parent: 10 + type: Transform +- uid: 28 + type: WallReinforced + components: + - pos: -1.5,1.5 + parent: 10 + type: Transform +- uid: 29 + type: WallReinforced + components: + - pos: -1.5,2.5 + parent: 10 + type: Transform +- uid: 30 + type: WallReinforced + components: + - pos: -2.5,-0.5 + parent: 10 + type: Transform +- uid: 31 + type: WallReinforced + components: + - pos: -0.5,-0.5 + parent: 10 + type: Transform +- uid: 32 + type: Grille + components: + - pos: 0.5,-2.5 + parent: 10 + type: Transform +- uid: 33 + type: Grille + components: + - pos: 2.5,-0.5 + parent: 10 + type: Transform +- uid: 34 + type: WallReinforced + components: + - pos: 3.5,-0.5 + parent: 10 + type: Transform +- uid: 35 + type: WallReinforced + components: + - pos: 4.5,-0.5 + parent: 10 + type: Transform +- uid: 36 + type: WallReinforced + components: + - pos: 5.5,-0.5 + parent: 10 + type: Transform +- uid: 37 + type: WallReinforced + components: + - pos: 6.5,0.5 + parent: 10 + type: Transform +- uid: 38 + type: WallReinforced + components: + - pos: 2.5,3.5 + parent: 10 + type: Transform +- uid: 39 + type: WallReinforced + components: + - pos: -7.5,4.5 + parent: 10 + type: Transform +- uid: 40 + type: WallReinforced + components: + - pos: 6.5,3.5 + parent: 10 + type: Transform +- uid: 41 + type: WallReinforced + components: + - pos: -8.5,7.5 + parent: 10 + type: Transform +- uid: 42 + type: WallReinforced + components: + - pos: -8.5,6.5 + parent: 10 + type: Transform +- uid: 43 + type: WallReinforced + components: + - pos: -7.5,6.5 + parent: 10 + type: Transform +- uid: 44 + type: WallReinforced + components: + - pos: -6.5,6.5 + parent: 10 + type: Transform +- uid: 45 + type: WallReinforced + components: + - pos: -5.5,6.5 + parent: 10 + type: Transform +- uid: 46 + type: WallReinforced + components: + - pos: -9.5,9.5 + parent: 10 + type: Transform +- uid: 47 + type: WallReinforced + components: + - pos: -8.5,9.5 + parent: 10 + type: Transform +- uid: 48 + type: WallReinforced + components: + - pos: -7.5,9.5 + parent: 10 + type: Transform +- uid: 49 + type: WallReinforced + components: + - pos: -6.5,9.5 + parent: 10 + type: Transform +- uid: 50 + type: WallReinforced + components: + - pos: -4.5,9.5 + parent: 10 + type: Transform +- uid: 51 + type: WallReinforced + components: + - pos: -2.5,9.5 + parent: 10 + type: Transform +- uid: 52 + type: WallReinforced + components: + - pos: 6.5,4.5 + parent: 10 + type: Transform +- uid: 53 + type: WallReinforced + components: + - pos: 5.5,4.5 + parent: 10 + type: Transform +- uid: 54 + type: WallReinforced + components: + - pos: 4.5,4.5 + parent: 10 + type: Transform +- uid: 55 + type: WallReinforced + components: + - pos: 3.5,4.5 + parent: 10 + type: Transform +- uid: 56 + type: WallReinforced + components: + - pos: 2.5,4.5 + parent: 10 + type: Transform +- uid: 57 + type: Grille + components: + - pos: -0.5,4.5 + parent: 10 + type: Transform +- uid: 58 + type: Grille + components: + - pos: 0.5,4.5 + parent: 10 + type: Transform +- uid: 59 + type: Grille + components: + - pos: 1.5,4.5 + parent: 10 + type: Transform +- uid: 60 + type: WallReinforced + components: + - pos: -1.5,4.5 + parent: 10 + type: Transform +- uid: 61 + type: WallReinforced + components: + - pos: -2.5,4.5 + parent: 10 + type: Transform +- uid: 62 + type: WallReinforced + components: + - pos: -3.5,4.5 + parent: 10 + type: Transform +- uid: 63 + type: WallReinforced + components: + - pos: 8.5,0.5 + parent: 10 + type: Transform +- uid: 64 + type: WallReinforced + components: + - pos: 8.5,1.5 + parent: 10 + type: Transform +- uid: 65 + type: WallReinforced + components: + - pos: 8.5,2.5 + parent: 10 + type: Transform +- uid: 66 + type: WallReinforced + components: + - pos: 8.5,3.5 + parent: 10 + type: Transform +- uid: 67 + type: WallReinforced + components: + - pos: 8.5,4.5 + parent: 10 + type: Transform +- uid: 68 + type: WallReinforced + components: + - pos: 8.5,5.5 + parent: 10 + type: Transform +- uid: 69 + type: WallReinforced + components: + - pos: 8.5,6.5 + parent: 10 + type: Transform +- uid: 70 + type: WallReinforced + components: + - pos: 8.5,7.5 + parent: 10 + type: Transform +- uid: 71 + type: WallReinforced + components: + - pos: 8.5,8.5 + parent: 10 + type: Transform +- uid: 72 + type: WallReinforced + components: + - pos: 8.5,9.5 + parent: 10 + type: Transform +- uid: 73 + type: WallReinforced + components: + - pos: -4.5,2.5 + parent: 10 + type: Transform +- uid: 74 + type: WallReinforced + components: + - pos: -4.5,3.5 + parent: 10 + type: Transform +- uid: 75 + type: WallReinforced + components: + - pos: -4.5,4.5 + parent: 10 + type: Transform +- uid: 76 + type: DebugGenerator + components: + - pos: 16.5,-1.5 + parent: 10 + type: Transform +- uid: 77 + type: WallReinforced + components: + - pos: -4.5,6.5 + parent: 10 + type: Transform +- uid: 78 + type: WallReinforced + components: + - pos: -4.5,6.5 + parent: 10 + type: Transform +- uid: 79 + type: WallReinforced + components: + - pos: -3.5,6.5 + parent: 10 + type: Transform +- uid: 80 + type: WallReinforced + components: + - pos: -2.5,6.5 + parent: 10 + type: Transform +- uid: 81 + type: WallReinforced + components: + - pos: -1.5,6.5 + parent: 10 + type: Transform +- uid: 82 + type: WallReinforced + components: + - pos: -3.5,-0.5 + parent: 10 + type: Transform +- uid: 83 + type: WallReinforced + components: + - pos: -1.5,-0.5 + parent: 10 + type: Transform +- uid: 84 + type: Grille + components: + - pos: 1.5,-2.5 + parent: 10 + type: Transform +- uid: 85 + type: WallReinforced + components: + - pos: -7.5,2.5 + parent: 10 + type: Transform +- uid: 86 + type: WallReinforced + components: + - pos: 4.5,1.5 + parent: 10 + type: Transform +- uid: 87 + type: WallReinforced + components: + - pos: 5.5,1.5 + parent: 10 + type: Transform +- uid: 88 + type: WallReinforced + components: + - pos: -10.5,8.5 + parent: 10 + type: Transform +- uid: 89 + type: WallReinforced + components: + - pos: -10.5,7.5 + parent: 10 + type: Transform +- uid: 90 + type: WallReinforced + components: + - pos: -10.5,6.5 + parent: 10 + type: Transform +- uid: 91 + type: WallReinforced + components: + - pos: -10.5,5.5 + parent: 10 + type: Transform +- uid: 92 + type: WallReinforced + components: + - pos: -10.5,4.5 + parent: 10 + type: Transform +- uid: 93 + type: WallReinforced + components: + - pos: -10.5,3.5 + parent: 10 + type: Transform +- uid: 94 + type: WallReinforced + components: + - pos: -10.5,2.5 + parent: 10 + type: Transform +- uid: 95 + type: WallReinforced + components: + - pos: -10.5,1.5 + parent: 10 + type: Transform +- uid: 96 + type: WallReinforced + components: + - pos: -10.5,0.5 + parent: 10 + type: Transform +- uid: 97 + type: WallReinforced + components: + - pos: -10.5,-0.5 + parent: 10 + type: Transform +- uid: 98 + type: WallReinforced + components: + - pos: -10.5,-1.5 + parent: 10 + type: Transform +- uid: 99 + type: WallReinforced + components: + - pos: -10.5,-2.5 + parent: 10 + type: Transform +- uid: 100 + type: WallReinforced + components: + - pos: -10.5,-3.5 + parent: 10 + type: Transform +- uid: 101 + type: WallReinforced + components: + - pos: -10.5,-4.5 + parent: 10 + type: Transform +- uid: 102 + type: WallReinforced + components: + - pos: -10.5,-5.5 + parent: 10 + type: Transform +- uid: 103 + type: WallReinforced + components: + - pos: -10.5,-6.5 + parent: 10 + type: Transform +- uid: 104 + type: WallReinforced + components: + - pos: -10.5,-7.5 + parent: 10 + type: Transform +- uid: 105 + type: WallReinforced + components: + - pos: -10.5,-8.5 + parent: 10 + type: Transform +- uid: 106 + type: WallReinforced + components: + - pos: -10.5,-9.5 + parent: 10 + type: Transform +- uid: 107 + type: WallReinforced + components: + - pos: -10.5,-10.5 + parent: 10 + type: Transform +- uid: 108 + type: WallReinforced + components: + - pos: -10.5,-11.5 + parent: 10 + type: Transform +- uid: 109 + type: WallReinforced + components: + - pos: -10.5,-12.5 + parent: 10 + type: Transform +- uid: 110 + type: WallReinforced + components: + - pos: -10.5,-13.5 + parent: 10 + type: Transform +- uid: 111 + type: WallReinforced + components: + - pos: -10.5,-14.5 + parent: 10 + type: Transform +- uid: 112 + type: WallReinforced + components: + - pos: -10.5,-15.5 + parent: 10 + type: Transform +- uid: 113 + type: WallReinforced + components: + - pos: -10.5,-16.5 + parent: 10 + type: Transform +- uid: 114 + type: WallReinforced + components: + - pos: -9.5,-16.5 + parent: 10 + type: Transform +- uid: 115 + type: WallReinforced + components: + - pos: -8.5,-16.5 + parent: 10 + type: Transform +- uid: 116 + type: WallReinforced + components: + - pos: -7.5,-16.5 + parent: 10 + type: Transform +- uid: 117 + type: WallReinforced + components: + - pos: -6.5,-16.5 + parent: 10 + type: Transform +- uid: 118 + type: WallReinforced + components: + - pos: -5.5,-16.5 + parent: 10 + type: Transform +- uid: 119 + type: WallReinforced + components: + - pos: -4.5,-16.5 + parent: 10 + type: Transform +- uid: 120 + type: WallReinforced + components: + - pos: -3.5,-16.5 + parent: 10 + type: Transform +- uid: 121 + type: WallReinforced + components: + - pos: -2.5,-16.5 + parent: 10 + type: Transform +- uid: 122 + type: WallReinforced + components: + - pos: -1.5,-16.5 + parent: 10 + type: Transform +- uid: 123 + type: WallReinforced + components: + - pos: -0.5,-16.5 + parent: 10 + type: Transform +- uid: 124 + type: WallReinforced + components: + - pos: 0.5,-16.5 + parent: 10 + type: Transform +- uid: 125 + type: WallReinforced + components: + - pos: 1.5,-16.5 + parent: 10 + type: Transform +- uid: 126 + type: WallReinforced + components: + - pos: 2.5,-16.5 + parent: 10 + type: Transform +- uid: 127 + type: WallReinforced + components: + - pos: 3.5,-16.5 + parent: 10 + type: Transform +- uid: 128 + type: WallReinforced + components: + - pos: 4.5,-16.5 + parent: 10 + type: Transform +- uid: 129 + type: WallReinforced + components: + - pos: 5.5,-16.5 + parent: 10 + type: Transform +- uid: 130 + type: WallReinforced + components: + - pos: 6.5,-16.5 + parent: 10 + type: Transform +- uid: 131 + type: WallReinforced + components: + - pos: 7.5,-16.5 + parent: 10 + type: Transform +- uid: 132 + type: WallReinforced + components: + - pos: 8.5,-16.5 + parent: 10 + type: Transform +- uid: 133 + type: WallReinforced + components: + - pos: 9.5,-16.5 + parent: 10 + type: Transform +- uid: 134 + type: WallReinforced + components: + - pos: 10.5,-16.5 + parent: 10 + type: Transform +- uid: 135 + type: WallReinforced + components: + - pos: 11.5,-16.5 + parent: 10 + type: Transform +- uid: 136 + type: WallReinforced + components: + - pos: 12.5,-16.5 + parent: 10 + type: Transform +- uid: 137 + type: WallReinforced + components: + - pos: 13.5,-16.5 + parent: 10 + type: Transform +- uid: 138 + type: WallReinforced + components: + - pos: 14.5,-16.5 + parent: 10 + type: Transform +- uid: 139 + type: WallReinforced + components: + - pos: 15.5,-16.5 + parent: 10 + type: Transform +- uid: 140 + type: WallReinforced + components: + - pos: 15.5,-15.5 + parent: 10 + type: Transform +- uid: 141 + type: WallReinforced + components: + - pos: 15.5,-14.5 + parent: 10 + type: Transform +- uid: 142 + type: WallReinforced + components: + - pos: 15.5,-13.5 + parent: 10 + type: Transform +- uid: 143 + type: WallReinforced + components: + - pos: 15.5,-12.5 + parent: 10 + type: Transform +- uid: 144 + type: WallReinforced + components: + - pos: 15.5,-11.5 + parent: 10 + type: Transform +- uid: 145 + type: WallReinforced + components: + - pos: 15.5,-10.5 + parent: 10 + type: Transform +- uid: 146 + type: WallReinforced + components: + - pos: 15.5,-9.5 + parent: 10 + type: Transform +- uid: 147 + type: WallReinforced + components: + - pos: 15.5,-8.5 + parent: 10 + type: Transform +- uid: 148 + type: WallReinforced + components: + - pos: 15.5,-7.5 + parent: 10 + type: Transform +- uid: 149 + type: WallReinforced + components: + - pos: 15.5,-6.5 + parent: 10 + type: Transform +- uid: 150 + type: WallReinforced + components: + - pos: 15.5,-5.5 + parent: 10 + type: Transform +- uid: 151 + type: WallReinforced + components: + - pos: 15.5,-4.5 + parent: 10 + type: Transform +- uid: 152 + type: WallReinforced + components: + - pos: 15.5,-3.5 + parent: 10 + type: Transform +- uid: 153 + type: WallReinforced + components: + - pos: 15.5,-2.5 + parent: 10 + type: Transform +- uid: 154 + type: WallReinforced + components: + - pos: 15.5,-1.5 + parent: 10 + type: Transform +- uid: 155 + type: WallReinforced + components: + - pos: 15.5,-0.5 + parent: 10 + type: Transform +- uid: 156 + type: WallReinforced + components: + - pos: 15.5,0.5 + parent: 10 + type: Transform +- uid: 157 + type: WallReinforced + components: + - pos: 15.5,1.5 + parent: 10 + type: Transform +- uid: 158 + type: WallReinforced + components: + - pos: 15.5,2.5 + parent: 10 + type: Transform +- uid: 159 + type: WallReinforced + components: + - pos: 15.5,3.5 + parent: 10 + type: Transform +- uid: 160 + type: WallReinforced + components: + - pos: 15.5,4.5 + parent: 10 + type: Transform +- uid: 161 + type: WallReinforced + components: + - pos: 15.5,5.5 + parent: 10 + type: Transform +- uid: 162 + type: WallReinforced + components: + - pos: 15.5,6.5 + parent: 10 + type: Transform +- uid: 163 + type: WallReinforced + components: + - pos: 15.5,7.5 + parent: 10 + type: Transform +- uid: 164 + type: WallReinforced + components: + - pos: 15.5,8.5 + parent: 10 + type: Transform +- uid: 165 + type: WallReinforced + components: + - pos: 15.5,9.5 + parent: 10 + type: Transform +- uid: 166 + type: WallReinforced + components: + - pos: 14.5,9.5 + parent: 10 + type: Transform +- uid: 167 + type: WallReinforced + components: + - pos: 13.5,9.5 + parent: 10 + type: Transform +- uid: 168 + type: WallReinforced + components: + - pos: 12.5,9.5 + parent: 10 + type: Transform +- uid: 169 + type: WallReinforced + components: + - pos: 11.5,9.5 + parent: 10 + type: Transform +- uid: 170 + type: WallReinforced + components: + - pos: 10.5,9.5 + parent: 10 + type: Transform +- uid: 171 + type: WallReinforced + components: + - pos: 9.5,9.5 + parent: 10 + type: Transform +- uid: 172 + type: WallReinforced + components: + - pos: 5.5,9.5 + parent: 10 + type: Transform +- uid: 173 + type: WallReinforced + components: + - pos: 4.5,9.5 + parent: 10 + type: Transform +- uid: 174 + type: WallReinforced + components: + - pos: 3.5,9.5 + parent: 10 + type: Transform +- uid: 175 + type: WallReinforced + components: + - pos: 2.5,9.5 + parent: 10 + type: Transform +- uid: 176 + type: WallReinforced + components: + - pos: 1.5,9.5 + parent: 10 + type: Transform +- uid: 177 + type: WallReinforced + components: + - pos: 0.5,9.5 + parent: 10 + type: Transform +- uid: 178 + type: Grille + components: + - pos: -9.5,-2.5 + parent: 10 + type: Transform +- uid: 179 + type: WallReinforced + components: + - pos: -8.5,-3.5 + parent: 10 + type: Transform +- uid: 180 + type: WallReinforced + components: + - pos: 6.5,-5.5 + parent: 10 + type: Transform +- uid: 181 + type: GravityGenerator + components: + - pos: 17.5,1.5 + parent: 10 + type: Transform + - enabled: False + type: AmbientSound + - powerLoad: 2500 + type: ApcPowerReceiver + - radius: 2.5 + type: PointLight +- uid: 182 + type: WallReinforced + components: + - pos: -3.5,-2.5 + parent: 10 + type: Transform +- uid: 183 + type: Window + components: + - pos: -0.5,4.5 + parent: 10 + type: Transform +- uid: 184 + type: WallReinforced + components: + - pos: -3.5,-4.5 + parent: 10 + type: Transform +- uid: 185 + type: WallReinforced + components: + - pos: -3.5,-5.5 + parent: 10 + type: Transform +- uid: 186 + type: WallReinforced + components: + - pos: -3.5,-6.5 + parent: 10 + type: Transform +- uid: 187 + type: Grille + components: + - pos: -4.5,-4.5 + parent: 10 + type: Transform +- uid: 188 + type: WallReinforced + components: + - pos: 7.5,-6.5 + parent: 10 + type: Transform +- uid: 189 + type: WallReinforced + components: + - pos: -6.5,-5.5 + parent: 10 + type: Transform +- uid: 190 + type: WallReinforced + components: + - pos: -2.5,-2.5 + parent: 10 + type: Transform +- uid: 191 + type: WallReinforced + components: + - pos: -1.5,-2.5 + parent: 10 + type: Transform +- uid: 192 + type: WallReinforced + components: + - pos: -0.5,-2.5 + parent: 10 + type: Transform +- uid: 193 + type: Grille + components: + - pos: 1.5,-0.5 + parent: 10 + type: Transform +- uid: 194 + type: Grille + components: + - pos: 0.5,-0.5 + parent: 10 + type: Transform +- uid: 195 + type: WallReinforced + components: + - pos: 2.5,-2.5 + parent: 10 + type: Transform +- uid: 196 + type: WallReinforced + components: + - pos: 3.5,-2.5 + parent: 10 + type: Transform +- uid: 197 + type: WallReinforced + components: + - pos: 4.5,-2.5 + parent: 10 + type: Transform +- uid: 198 + type: WallReinforced + components: + - pos: 5.5,-2.5 + parent: 10 + type: Transform +- uid: 199 + type: WallReinforced + components: + - pos: 6.5,-2.5 + parent: 10 + type: Transform +- uid: 200 + type: WallReinforced + components: + - pos: 2.5,-3.5 + parent: 10 + type: Transform +- uid: 201 + type: WallReinforced + components: + - pos: 2.5,-4.5 + parent: 10 + type: Transform +- uid: 202 + type: WallReinforced + components: + - pos: 2.5,-5.5 + parent: 10 + type: Transform +- uid: 203 + type: WallReinforced + components: + - pos: 2.5,-6.5 + parent: 10 + type: Transform +- uid: 204 + type: WallReinforced + components: + - pos: 2.5,-7.5 + parent: 10 + type: Transform +- uid: 205 + type: Grille + components: + - pos: 5.5,-7.5 + parent: 10 + type: Transform +- uid: 206 + type: Grille + components: + - pos: 4.5,-7.5 + parent: 10 + type: Transform +- uid: 207 + type: Grille + components: + - pos: 3.5,-7.5 + parent: 10 + type: Transform +- uid: 208 + type: WallReinforced + components: + - pos: 6.5,-7.5 + parent: 10 + type: Transform +- uid: 209 + type: WallReinforced + components: + - pos: 10.5,-1.5 + parent: 10 + type: Transform +- uid: 210 + type: WallReinforced + components: + - pos: 5.5,-5.5 + parent: 10 + type: Transform +- uid: 211 + type: WallReinforced + components: + - pos: 5.5,-4.5 + parent: 10 + type: Transform +- uid: 212 + type: WallReinforced + components: + - pos: 7.5,-7.5 + parent: 10 + type: Transform +- uid: 213 + type: WallReinforced + components: + - pos: -0.5,-4.5 + parent: 10 + type: Transform +- uid: 214 + type: WallReinforced + components: + - pos: -0.5,-5.5 + parent: 10 + type: Transform +- uid: 215 + type: Grille + components: + - pos: -0.5,-6.5 + parent: 10 + type: Transform +- uid: 216 + type: Grille + components: + - pos: -0.5,-7.5 + parent: 10 + type: Transform +- uid: 217 + type: WallReinforced + components: + - pos: 10.5,6.5 + parent: 10 + type: Transform +- uid: 218 + type: WallReinforced + components: + - pos: 11.5,6.5 + parent: 10 + type: Transform +- uid: 219 + type: WallReinforced + components: + - pos: 12.5,6.5 + parent: 10 + type: Transform +- uid: 220 + type: WallReinforced + components: + - pos: 13.5,6.5 + parent: 10 + type: Transform +- uid: 221 + type: WallReinforced + components: + - pos: 14.5,6.5 + parent: 10 + type: Transform +- uid: 222 + type: WallReinforced + components: + - pos: 12.5,2.5 + parent: 10 + type: Transform +- uid: 223 + type: WallReinforced + components: + - pos: 11.5,2.5 + parent: 10 + type: Transform +- uid: 224 + type: WallReinforced + components: + - pos: 10.5,2.5 + parent: 10 + type: Transform +- uid: 225 + type: WallReinforced + components: + - pos: 9.5,2.5 + parent: 10 + type: Transform +- uid: 226 + type: WallReinforced + components: + - pos: 8.5,-3.5 + parent: 10 + type: Transform +- uid: 227 + type: WallReinforced + components: + - pos: 9.5,-3.5 + parent: 10 + type: Transform +- uid: 228 + type: WallReinforced + components: + - pos: 10.5,-3.5 + parent: 10 + type: Transform +- uid: 229 + type: WallReinforced + components: + - pos: 11.5,-3.5 + parent: 10 + type: Transform +- uid: 230 + type: WallReinforced + components: + - pos: 12.5,-3.5 + parent: 10 + type: Transform +- uid: 231 + type: WallReinforced + components: + - pos: 13.5,-3.5 + parent: 10 + type: Transform +- uid: 232 + type: WallReinforced + components: + - pos: 3.5,-4.5 + parent: 10 + type: Transform +- uid: 233 + type: WallReinforced + components: + - pos: 11.5,0.5 + parent: 10 + type: Transform +- uid: 234 + type: WallReinforced + components: + - pos: 11.5,-0.5 + parent: 10 + type: Transform +- uid: 235 + type: WallReinforced + components: + - pos: 11.5,-1.5 + parent: 10 + type: Transform +- uid: 236 + type: WallReinforced + components: + - pos: 11.5,1.5 + parent: 10 + type: Transform +- uid: 237 + type: WallReinforced + components: + - pos: 9.5,-5.5 + parent: 10 + type: Transform +- uid: 238 + type: WallReinforced + components: + - pos: 9.5,-6.5 + parent: 10 + type: Transform +- uid: 239 + type: WallReinforced + components: + - pos: 9.5,-7.5 + parent: 10 + type: Transform +- uid: 240 + type: WallReinforced + components: + - pos: 1.5,-4.5 + parent: 10 + type: Transform +- uid: 241 + type: WallReinforced + components: + - pos: 9.5,-9.5 + parent: 10 + type: Transform +- uid: 242 + type: WallReinforced + components: + - pos: 9.5,-10.5 + parent: 10 + type: Transform +- uid: 243 + type: WallReinforced + components: + - pos: 11.5,-14.5 + parent: 10 + type: Transform +- uid: 244 + type: WallReinforced + components: + - pos: 4.5,-9.5 + parent: 10 + type: Transform +- uid: 245 + type: WallReinforced + components: + - pos: 13.5,-12.5 + parent: 10 + type: Transform +- uid: 246 + type: WallReinforced + components: + - pos: 6.5,-9.5 + parent: 10 + type: Transform +- uid: 247 + type: WallReinforced + components: + - pos: 7.5,-9.5 + parent: 10 + type: Transform +- uid: 248 + type: Grille + components: + - pos: 12.5,-14.5 + parent: 10 + type: Transform +- uid: 249 + type: WallReinforced + components: + - pos: 10.5,-9.5 + parent: 10 + type: Transform +- uid: 250 + type: WallReinforced + components: + - pos: 11.5,-9.5 + parent: 10 + type: Transform +- uid: 251 + type: WallReinforced + components: + - pos: 12.5,-9.5 + parent: 10 + type: Transform +- uid: 252 + type: WallReinforced + components: + - pos: 13.5,-9.5 + parent: 10 + type: Transform +- uid: 253 + type: WallReinforced + components: + - pos: 14.5,-9.5 + parent: 10 + type: Transform +- uid: 254 + type: WallReinforced + components: + - pos: -2.5,-9.5 + parent: 10 + type: Transform +- uid: 255 + type: WallReinforced + components: + - pos: -2.5,-10.5 + parent: 10 + type: Transform +- uid: 256 + type: WallReinforced + components: + - pos: -2.5,-11.5 + parent: 10 + type: Transform +- uid: 257 + type: Grille + components: + - pos: -2.5,-12.5 + parent: 10 + type: Transform +- uid: 258 + type: Grille + components: + - pos: -2.5,-13.5 + parent: 10 + type: Transform +- uid: 259 + type: Grille + components: + - pos: -2.5,-14.5 + parent: 10 + type: Transform +- uid: 260 + type: WallReinforced + components: + - pos: -6.5,-10.5 + parent: 10 + type: Transform +- uid: 261 + type: WallReinforced + components: + - pos: -2.5,-15.5 + parent: 10 + type: Transform +- uid: 262 + type: WallReinforced + components: + - pos: -4.5,-10.5 + parent: 10 + type: Transform +- uid: 263 + type: WallReinforced + components: + - pos: -6.5,-8.5 + parent: 10 + type: Transform +- uid: 264 + type: Window + components: + - pos: -9.5,-2.5 + parent: 10 + type: Transform +- uid: 265 + type: WallReinforced + components: + - pos: -7.5,-7.5 + parent: 10 + type: Transform +- uid: 266 + type: WallReinforced + components: + - pos: -7.5,-8.5 + parent: 10 + type: Transform +- uid: 267 + type: WallReinforced + components: + - pos: -7.5,-9.5 + parent: 10 + type: Transform +- uid: 268 + type: WallReinforced + components: + - pos: -7.5,-10.5 + parent: 10 + type: Transform +- uid: 269 + type: WallReinforced + components: + - pos: -7.5,-11.5 + parent: 10 + type: Transform +- uid: 270 + type: WallReinforced + components: + - pos: -7.5,-12.5 + parent: 10 + type: Transform +- uid: 271 + type: WallReinforced + components: + - pos: -8.5,-11.5 + parent: 10 + type: Transform +- uid: 272 + type: WallReinforced + components: + - pos: -9.5,-15.5 + parent: 10 + type: Transform +- uid: 273 + type: WallReinforced + components: + - pos: -7.5,-14.5 + parent: 10 + type: Transform +- uid: 274 + type: WallReinforced + components: + - pos: -6.5,-12.5 + parent: 10 + type: Transform +- uid: 275 + type: WallReinforced + components: + - pos: -5.5,-10.5 + parent: 10 + type: Transform +- uid: 276 + type: WallReinforced + components: + - pos: -4.5,-12.5 + parent: 10 + type: Transform +- uid: 277 + type: WallReinforced + components: + - pos: -5.5,-8.5 + parent: 10 + type: Transform +- uid: 278 + type: WallReinforced + components: + - pos: -4.5,-13.5 + parent: 10 + type: Transform +- uid: 279 + type: WallReinforced + components: + - pos: -4.5,-14.5 + parent: 10 + type: Transform +- uid: 280 + type: WallReinforced + components: + - pos: -4.5,-15.5 + parent: 10 + type: Transform +- uid: 281 + type: WallReinforced + components: + - pos: -4.5,-8.5 + parent: 10 + type: Transform +- uid: 282 + type: WallReinforced + components: + - pos: -0.5,-10.5 + parent: 10 + type: Transform +- uid: 283 + type: WallReinforced + components: + - pos: -2.5,-8.5 + parent: 10 + type: Transform +- uid: 284 + type: WallReinforced + components: + - pos: 0.5,-10.5 + parent: 10 + type: Transform +- uid: 285 + type: WallReinforced + components: + - pos: -0.5,-8.5 + parent: 10 + type: Transform +- uid: 286 + type: WallReinforced + components: + - pos: 1.5,-10.5 + parent: 10 + type: Transform +- uid: 287 + type: WallReinforced + components: + - pos: 2.5,-10.5 + parent: 10 + type: Transform +- uid: 288 + type: WallReinforced + components: + - pos: 3.5,-10.5 + parent: 10 + type: Transform +- uid: 289 + type: WallReinforced + components: + - pos: 3.5,-9.5 + parent: 10 + type: Transform +- uid: 290 + type: WallReinforced + components: + - pos: -0.5,-11.5 + parent: 10 + type: Transform +- uid: 291 + type: WallReinforced + components: + - pos: -0.5,-12.5 + parent: 10 + type: Transform +- uid: 292 + type: WallReinforced + components: + - pos: -0.5,-13.5 + parent: 10 + type: Transform +- uid: 293 + type: WallReinforced + components: + - pos: -0.5,-14.5 + parent: 10 + type: Transform +- uid: 294 + type: WallReinforced + components: + - pos: 0.5,-14.5 + parent: 10 + type: Transform +- uid: 295 + type: WallReinforced + components: + - pos: 1.5,-14.5 + parent: 10 + type: Transform +- uid: 296 + type: WallReinforced + components: + - pos: 2.5,-14.5 + parent: 10 + type: Transform +- uid: 297 + type: WallReinforced + components: + - pos: 3.5,-14.5 + parent: 10 + type: Transform +- uid: 298 + type: WallReinforced + components: + - pos: 4.5,-14.5 + parent: 10 + type: Transform +- uid: 299 + type: WallReinforced + components: + - pos: 5.5,-12.5 + parent: 10 + type: Transform +- uid: 300 + type: WallReinforced + components: + - pos: 4.5,-12.5 + parent: 10 + type: Transform +- uid: 301 + type: WallReinforced + components: + - pos: 3.5,-12.5 + parent: 10 + type: Transform +- uid: 302 + type: WallReinforced + components: + - pos: 2.5,-12.5 + parent: 10 + type: Transform +- uid: 303 + type: WallReinforced + components: + - pos: 1.5,-12.5 + parent: 10 + type: Transform +- uid: 304 + type: WallReinforced + components: + - pos: 9.5,-12.5 + parent: 10 + type: Transform +- uid: 305 + type: WallReinforced + components: + - pos: 9.5,-13.5 + parent: 10 + type: Transform +- uid: 306 + type: WallReinforced + components: + - pos: 9.5,-14.5 + parent: 10 + type: Transform +- uid: 307 + type: WallReinforced + components: + - pos: 13.5,-13.5 + parent: 10 + type: Transform +- uid: 308 + type: WallReinforced + components: + - pos: 6.5,-13.5 + parent: 10 + type: Transform +- uid: 309 + type: WallReinforced + components: + - pos: 7.5,-13.5 + parent: 10 + type: Transform +- uid: 310 + type: WallReinforced + components: + - pos: 13.5,-14.5 + parent: 10 + type: Transform +- uid: 311 + type: WallReinforced + components: + - pos: 6.5,-10.5 + parent: 10 + type: Transform +- uid: 312 + type: WallReinforced + components: + - pos: 6.5,-11.5 + parent: 10 + type: Transform +- uid: 313 + type: WallReinforced + components: + - pos: 6.5,-12.5 + parent: 10 + type: Transform +- uid: 314 + type: WallReinforced + components: + - pos: 13.5,-11.5 + parent: 10 + type: Transform +- uid: 315 + type: WallReinforced + components: + - pos: 13.5,-4.5 + parent: 10 + type: Transform +- uid: 316 + type: WallReinforced + components: + - pos: 11.5,-10.5 + parent: 10 + type: Transform +- uid: 317 + type: WallReinforced + components: + - pos: 11.5,-11.5 + parent: 10 + type: Transform +- uid: 318 + type: WallReinforced + components: + - pos: 11.5,-12.5 + parent: 10 + type: Transform +- uid: 319 + type: WallReinforced + components: + - pos: 11.5,-13.5 + parent: 10 + type: Transform +- uid: 320 + type: WallReinforced + components: + - pos: 13.5,-5.5 + parent: 10 + type: Transform +- uid: 321 + type: WallReinforced + components: + - pos: 13.5,-6.5 + parent: 10 + type: Transform +- uid: 322 + type: WallReinforced + components: + - pos: 13.5,-7.5 + parent: 10 + type: Transform +- uid: 323 + type: WallReinforced + components: + - pos: 12.5,-7.5 + parent: 10 + type: Transform +- uid: 324 + type: WallReinforced + components: + - pos: 11.5,-7.5 + parent: 10 + type: Transform +- uid: 325 + type: WallReinforced + components: + - pos: 10.5,-7.5 + parent: 10 + type: Transform +- uid: 326 + type: WallReinforced + components: + - pos: -1.5,-4.5 + parent: 10 + type: Transform +- uid: 327 + type: WallReinforced + components: + - pos: -2.5,-4.5 + parent: 10 + type: Transform +- uid: 328 + type: WallReinforced + components: + - pos: 7.5,-5.5 + parent: 10 + type: Transform +- uid: 329 + type: WallReinforced + components: + - pos: 9.5,-1.5 + parent: 10 + type: Transform +- uid: 330 + type: WallReinforced + components: + - pos: 8.5,-1.5 + parent: 10 + type: Transform +- uid: 331 + type: WallReinforced + components: + - pos: 10.5,5.5 + parent: 10 + type: Transform +- uid: 332 + type: WallReinforced + components: + - pos: 10.5,4.5 + parent: 10 + type: Transform +- uid: 333 + type: WallReinforced + components: + - pos: 11.5,4.5 + parent: 10 + type: Transform +- uid: 334 + type: WallReinforced + components: + - pos: 12.5,4.5 + parent: 10 + type: Transform +- uid: 335 + type: WallReinforced + components: + - pos: 13.5,4.5 + parent: 10 + type: Transform +- uid: 336 + type: WallReinforced + components: + - pos: 11.5,8.5 + parent: 10 + type: Transform +- uid: 337 + type: WallReinforced + components: + - pos: 13.5,7.5 + parent: 10 + type: Transform +- uid: 338 + type: WallReinforced + components: + - pos: -6.5,-1.5 + parent: 10 + type: Transform +- uid: 339 + type: Grille + components: + - pos: -9.5,0.5 + parent: 10 + type: Transform +- uid: 340 + type: Window + components: + - pos: -9.5,0.5 + parent: 10 + type: Transform +- uid: 341 + type: WallReinforced + components: + - pos: -8.5,0.5 + parent: 10 + type: Transform +- uid: 342 + type: WallReinforced + components: + - pos: -8.5,-4.5 + parent: 10 + type: Transform +- uid: 343 + type: WallReinforced + components: + - pos: -5.5,1.5 + parent: 10 + type: Transform +- uid: 344 + type: WallReinforced + components: + - pos: -5.5,0.5 + parent: 10 + type: Transform +- uid: 345 + type: WallReinforced + components: + - pos: -5.5,-0.5 + parent: 10 + type: Transform +- uid: 346 + type: WallReinforced + components: + - pos: -5.5,-1.5 + parent: 10 + type: Transform +- uid: 347 + type: WallReinforced + components: + - pos: -5.5,-2.5 + parent: 10 + type: Transform +- uid: 348 + type: WallReinforced + components: + - pos: -5.5,-3.5 + parent: 10 + type: Transform +- uid: 349 + type: WallReinforced + components: + - pos: -5.5,-4.5 + parent: 10 + type: Transform +- uid: 350 + type: WallReinforced + components: + - pos: -5.5,-5.5 + parent: 10 + type: Transform +- uid: 351 + type: WallReinforced + components: + - pos: -5.5,-6.5 + parent: 10 + type: Transform +- uid: 352 + type: WallReinforced + components: + - pos: -7.5,0.5 + parent: 10 + type: Transform +- uid: 353 + type: WallReinforced + components: + - pos: -8.5,-5.5 + parent: 10 + type: Transform +- uid: 354 + type: WallReinforced + components: + - pos: -8.5,-2.5 + parent: 10 + type: Transform +- uid: 355 + type: WallReinforced + components: + - pos: -8.5,-1.5 + parent: 10 + type: Transform +- uid: 356 + type: WallReinforced + components: + - pos: -8.5,-0.5 + parent: 10 + type: Transform +- uid: 357 + type: WallReinforced + components: + - pos: -7.5,-3.5 + parent: 10 + type: Transform +- uid: 358 + type: Window + components: + - pos: -9.5,-5.5 + parent: 10 + type: Transform +- uid: 359 + type: Grille + components: + - pos: -9.5,-5.5 + parent: 10 + type: Transform +- uid: 360 + type: WallReinforced + components: + - pos: -8.5,-8.5 + parent: 10 + type: Transform +- uid: 361 + type: WallReinforced + components: + - pos: -5.5,-15.5 + parent: 10 + type: Transform +- uid: 362 + type: WallReinforced + components: + - pos: -6.5,-13.5 + parent: 10 + type: Transform +- uid: 363 + type: WallReinforced + components: + - pos: -4.5,1.5 + parent: 10 + type: Transform +- uid: 364 + type: WallReinforced + components: + - pos: -4.5,8.5 + parent: 10 + type: Transform +- uid: 365 + type: WallReinforced + components: + - pos: -6.5,7.5 + parent: 10 + type: Transform +- uid: 366 + type: WallReinforced + components: + - pos: -2.5,8.5 + parent: 10 + type: Transform +- uid: 367 + type: APCHyperCapacity + components: + - pos: 16.5,-0.5 + parent: 10 + type: Transform +- uid: 368 + type: DebugSubstation + components: + - pos: 17.5,-1.5 + parent: 10 + type: Transform + - containers: + - machine_parts + - machine_board + type: Construction +- uid: 369 + type: CableHV + components: + - pos: 16.5,-1.5 + parent: 10 + type: Transform +- uid: 370 + type: CableHV + components: + - pos: 17.5,-1.5 + parent: 10 + type: Transform +- uid: 371 + type: CableMV + components: + - pos: 17.5,-1.5 + parent: 10 + type: Transform +- uid: 372 + type: CableMV + components: + - pos: 17.5,-0.5 + parent: 10 + type: Transform +- uid: 373 + type: CableMV + components: + - pos: 16.5,-0.5 + parent: 10 + type: Transform +- uid: 374 + type: CableApcExtension + components: + - pos: 16.5,-0.5 + parent: 10 + type: Transform +- uid: 375 + type: CableApcExtension + components: + - pos: 16.5,0.5 + parent: 10 + type: Transform +- uid: 376 + type: CableApcExtension + components: + - pos: 16.5,1.5 + parent: 10 + type: Transform +- uid: 377 + type: CableApcExtension + components: + - pos: 17.5,1.5 + parent: 10 + type: Transform +- uid: 378 + type: CableApcExtension + components: + - pos: 17.5,0.5 + parent: 10 + type: Transform +- uid: 379 + type: AirlockSecurity + components: + - pos: 7.5,4.5 + parent: 10 + type: Transform +- uid: 380 + type: AirlockSecurity + components: + - pos: -5.5,4.5 + parent: 10 + type: Transform +- uid: 381 + type: AirlockSecurity + components: + - pos: -6.5,4.5 + parent: 10 + type: Transform +- uid: 382 + type: AirlockSecurity + components: + - pos: -6.5,0.5 + parent: 10 + type: Transform +- uid: 383 + type: AirlockSecurity + components: + - pos: -7.5,-5.5 + parent: 10 + type: Transform +- uid: 384 + type: AirlockSecurity + components: + - pos: -3.5,-8.5 + parent: 10 + type: Transform +- uid: 385 + type: AirlockSecurity + components: + - pos: -3.5,-10.5 + parent: 10 + type: Transform +- uid: 386 + type: AirlockSecurity + components: + - pos: -5.5,-12.5 + parent: 10 + type: Transform +- uid: 387 + type: AirlockSecurity + components: + - pos: 5.5,-9.5 + parent: 10 + type: Transform +- uid: 388 + type: AirlockSecurity + components: + - pos: 4.5,-13.5 + parent: 10 + type: Transform +- uid: 389 + type: AirlockSecurity + components: + - pos: 8.5,-9.5 + parent: 10 + type: Transform +- uid: 390 + type: AirlockSecurity + components: + - pos: 14.5,-11.5 + parent: 10 + type: Transform +- uid: 391 + type: AirlockSecurity + components: + - pos: 8.5,-0.5 + parent: 10 + type: Transform +- uid: 392 + type: AirlockSecurity + components: + - pos: 8.5,-5.5 + parent: 10 + type: Transform +- uid: 393 + type: AirlockSecurity + components: + - pos: 9.5,-4.5 + parent: 10 + type: Transform +- uid: 394 + type: AirlockSecurity + components: + - pos: -3.5,-1.5 + parent: 10 + type: Transform +- uid: 395 + type: AirlockSecurity + components: + - pos: 6.5,-1.5 + parent: 10 + type: Transform +- uid: 396 + type: AirlockSecurity + components: + - pos: 3.5,1.5 + parent: 10 + type: Transform +- uid: 397 + type: AirlockSecurity + components: + - pos: 0.5,1.5 + parent: 10 + type: Transform +- uid: 398 + type: AirlockSecurity + components: + - pos: 0.5,6.5 + parent: 10 + type: Transform +- uid: 399 + type: AirlockSecurity + components: + - pos: 1.5,6.5 + parent: 10 + type: Transform +- uid: 400 + type: AirlockSecurity + components: + - pos: -8.5,8.5 + parent: 10 + type: Transform +- uid: 401 + type: AirlockSecurity + components: + - pos: 11.5,7.5 + parent: 10 + type: Transform +- uid: 402 + type: AirlockSecurity + components: + - pos: 12.5,3.5 + parent: 10 + type: Transform +- uid: 403 + type: AirlockSecurity + components: + - pos: 12.5,-1.5 + parent: 10 + type: Transform +- uid: 404 + type: AirlockSecurity + components: + - pos: 13.5,-1.5 + parent: 10 + type: Transform +- uid: 405 + type: AirlockSecurity + components: + - pos: 14.5,-1.5 + parent: 10 + type: Transform +- uid: 406 + type: AirlockSecurity + components: + - pos: 12.5,0.5 + parent: 10 + type: Transform +- uid: 407 + type: AirlockSecurity + components: + - pos: 13.5,0.5 + parent: 10 + type: Transform +- uid: 408 + type: AirlockSecurity + components: + - pos: 14.5,0.5 + parent: 10 + type: Transform +- uid: 409 + type: AirlockSecurity + components: + - pos: 14.5,-4.5 + parent: 10 + type: Transform +- uid: 410 + type: AirlockSecurity + components: + - pos: 0.5,-4.5 + parent: 10 + type: Transform +- uid: 411 + type: AirlockSecurity + components: + - pos: -5.5,-7.5 + parent: 10 + type: Transform +- uid: 412 + type: AirlockSecurity + components: + - pos: -9.5,-8.5 + parent: 10 + type: Transform +- uid: 413 + type: AirlockSecurity + components: + - pos: -0.5,-9.5 + parent: 10 + type: Transform +- uid: 414 + type: AirlockSecurity + components: + - pos: 8.5,-13.5 + parent: 10 + type: Transform +- uid: 415 + type: AirlockSecurity + components: + - pos: 10.5,-14.5 + parent: 10 + type: Transform +- uid: 416 + type: Window + components: + - pos: 0.5,-0.5 + parent: 10 + type: Transform +- uid: 417 + type: Window + components: + - pos: 1.5,-0.5 + parent: 10 + type: Transform +- uid: 418 + type: Window + components: + - pos: 2.5,-0.5 + parent: 10 + type: Transform +- uid: 419 + type: Window + components: + - pos: 1.5,-2.5 + parent: 10 + type: Transform +- uid: 420 + type: Window + components: + - pos: 0.5,-2.5 + parent: 10 + type: Transform +- uid: 421 + type: Window + components: + - pos: 1.5,-2.5 + parent: 10 + type: Transform +- uid: 422 + type: Grille + components: + - pos: -2.5,7.5 + parent: 10 + type: Transform +- uid: 423 + type: Window + components: + - pos: 3.5,6.5 + parent: 10 + type: Transform +- uid: 424 + type: Window + components: + - pos: 4.5,6.5 + parent: 10 + type: Transform +- uid: 425 + type: Window + components: + - pos: -0.5,-6.5 + parent: 10 + type: Transform +- uid: 426 + type: Window + components: + - pos: -0.5,-7.5 + parent: 10 + type: Transform +- uid: 427 + type: Window + components: + - pos: 3.5,-7.5 + parent: 10 + type: Transform +- uid: 428 + type: Window + components: + - pos: 4.5,-7.5 + parent: 10 + type: Transform +- uid: 429 + type: Window + components: + - pos: 5.5,-7.5 + parent: 10 + type: Transform +- uid: 430 + type: Window + components: + - pos: -4.5,-4.5 + parent: 10 + type: Transform +- uid: 431 + type: Window + components: + - pos: 0.5,4.5 + parent: 10 + type: Transform +- uid: 432 + type: Window + components: + - pos: 1.5,4.5 + parent: 10 + type: Transform +- uid: 433 + type: Window + components: + - pos: -2.5,-12.5 + parent: 10 + type: Transform +- uid: 434 + type: Window + components: + - pos: -2.5,-13.5 + parent: 10 + type: Transform +- uid: 435 + type: Window + components: + - pos: -2.5,-14.5 + parent: 10 + type: Transform +- uid: 436 + type: Window + components: + - pos: 12.5,-14.5 + parent: 10 + type: Transform +- uid: 437 + type: Window + components: + - pos: -2.5,7.5 + parent: 10 + type: Transform +- uid: 438 + type: Window + components: + - pos: 4.5,3.5 + parent: 10 + type: Transform +- uid: 439 + type: Window + components: + - pos: 4.5,2.5 + parent: 10 + type: Transform +- uid: 440 + type: Grille + components: + - pos: 4.5,2.5 + parent: 10 + type: Transform +- uid: 441 + type: Grille + components: + - pos: 4.5,3.5 + parent: 10 + type: Transform +- uid: 442 + type: Window + components: + - pos: 9.5,-0.5 + parent: 10 + type: Transform +- uid: 443 + type: Window + components: + - pos: 10.5,-0.5 + parent: 10 + type: Transform +- uid: 444 + type: Window + components: + - pos: 10.5,0.5 + parent: 10 + type: Transform +- uid: 445 + type: Window + components: + - pos: 9.5,0.5 + parent: 10 + type: Transform +- uid: 446 + type: Grille + components: + - pos: 9.5,0.5 + parent: 10 + type: Transform +- uid: 447 + type: Grille + components: + - pos: 9.5,-0.5 + parent: 10 + type: Transform +- uid: 448 + type: Grille + components: + - pos: 10.5,0.5 + parent: 10 + type: Transform +- uid: 449 + type: Grille + components: + - pos: 10.5,-0.5 + parent: 10 + type: Transform +- uid: 450 + type: CableApcExtension + components: + - pos: -10.5,9.5 + parent: 10 + type: Transform +- uid: 451 + type: CableApcExtension + components: + - pos: -10.5,8.5 + parent: 10 + type: Transform +- uid: 452 + type: CableApcExtension + components: + - pos: -10.5,7.5 + parent: 10 + type: Transform +- uid: 453 + type: CableApcExtension + components: + - pos: -10.5,6.5 + parent: 10 + type: Transform +- uid: 454 + type: CableApcExtension + components: + - pos: -8.5,8.5 + parent: 10 + type: Transform +- uid: 455 + type: CableApcExtension + components: + - pos: -8.5,7.5 + parent: 10 + type: Transform +- uid: 456 + type: CableApcExtension + components: + - pos: -8.5,6.5 + parent: 10 + type: Transform +- uid: 457 + type: CableApcExtension + components: + - pos: -8.5,5.5 + parent: 10 + type: Transform +- uid: 458 + type: CableApcExtension + components: + - pos: -8.5,4.5 + parent: 10 + type: Transform +- uid: 459 + type: CableApcExtension + components: + - pos: -8.5,3.5 + parent: 10 + type: Transform +- uid: 460 + type: CableApcExtension + components: + - pos: -8.5,2.5 + parent: 10 + type: Transform +- uid: 461 + type: CableApcExtension + components: + - pos: -8.5,1.5 + parent: 10 + type: Transform +- uid: 462 + type: CableApcExtension + components: + - pos: -8.5,0.5 + parent: 10 + type: Transform +- uid: 463 + type: CableApcExtension + components: + - pos: -10.5,5.5 + parent: 10 + type: Transform +- uid: 464 + type: CableApcExtension + components: + - pos: -10.5,4.5 + parent: 10 + type: Transform +- uid: 465 + type: CableApcExtension + components: + - pos: -10.5,3.5 + parent: 10 + type: Transform +- uid: 466 + type: CableApcExtension + components: + - pos: -10.5,2.5 + parent: 10 + type: Transform +- uid: 467 + type: CableApcExtension + components: + - pos: -10.5,1.5 + parent: 10 + type: Transform +- uid: 468 + type: CableApcExtension + components: + - pos: -10.5,0.5 + parent: 10 + type: Transform +- uid: 469 + type: CableApcExtension + components: + - pos: -10.5,-0.5 + parent: 10 + type: Transform +- uid: 470 + type: CableApcExtension + components: + - pos: -10.5,-1.5 + parent: 10 + type: Transform +- uid: 471 + type: CableApcExtension + components: + - pos: -10.5,-2.5 + parent: 10 + type: Transform +- uid: 472 + type: CableApcExtension + components: + - pos: -10.5,-3.5 + parent: 10 + type: Transform +- uid: 473 + type: CableApcExtension + components: + - pos: -10.5,-4.5 + parent: 10 + type: Transform +- uid: 474 + type: CableApcExtension + components: + - pos: -10.5,-5.5 + parent: 10 + type: Transform +- uid: 475 + type: CableApcExtension + components: + - pos: -10.5,-6.5 + parent: 10 + type: Transform +- uid: 476 + type: CableApcExtension + components: + - pos: -10.5,-7.5 + parent: 10 + type: Transform +- uid: 477 + type: CableApcExtension + components: + - pos: -10.5,-8.5 + parent: 10 + type: Transform +- uid: 478 + type: CableApcExtension + components: + - pos: -10.5,-9.5 + parent: 10 + type: Transform +- uid: 479 + type: CableApcExtension + components: + - pos: -10.5,-10.5 + parent: 10 + type: Transform +- uid: 480 + type: CableApcExtension + components: + - pos: -10.5,-11.5 + parent: 10 + type: Transform +- uid: 481 + type: CableApcExtension + components: + - pos: -10.5,-12.5 + parent: 10 + type: Transform +- uid: 482 + type: CableApcExtension + components: + - pos: -10.5,-13.5 + parent: 10 + type: Transform +- uid: 483 + type: CableApcExtension + components: + - pos: -10.5,-14.5 + parent: 10 + type: Transform +- uid: 484 + type: CableApcExtension + components: + - pos: -10.5,-15.5 + parent: 10 + type: Transform +- uid: 485 + type: CableApcExtension + components: + - pos: -10.5,-16.5 + parent: 10 + type: Transform +- uid: 486 + type: CableApcExtension + components: + - pos: -9.5,9.5 + parent: 10 + type: Transform +- uid: 487 + type: CableApcExtension + components: + - pos: -9.5,8.5 + parent: 10 + type: Transform +- uid: 488 + type: CableApcExtension + components: + - pos: -9.5,7.5 + parent: 10 + type: Transform +- uid: 489 + type: CableApcExtension + components: + - pos: -9.5,6.5 + parent: 10 + type: Transform +- uid: 490 + type: CableApcExtension + components: + - pos: -9.5,5.5 + parent: 10 + type: Transform +- uid: 491 + type: CableApcExtension + components: + - pos: -9.5,4.5 + parent: 10 + type: Transform +- uid: 492 + type: CableApcExtension + components: + - pos: -9.5,3.5 + parent: 10 + type: Transform +- uid: 493 + type: CableApcExtension + components: + - pos: -9.5,2.5 + parent: 10 + type: Transform +- uid: 494 + type: CableApcExtension + components: + - pos: -9.5,1.5 + parent: 10 + type: Transform +- uid: 495 + type: CableApcExtension + components: + - pos: -9.5,0.5 + parent: 10 + type: Transform +- uid: 496 + type: CableApcExtension + components: + - pos: -9.5,-0.5 + parent: 10 + type: Transform +- uid: 497 + type: CableApcExtension + components: + - pos: -9.5,-1.5 + parent: 10 + type: Transform +- uid: 498 + type: CableApcExtension + components: + - pos: -9.5,-2.5 + parent: 10 + type: Transform +- uid: 499 + type: CableApcExtension + components: + - pos: -9.5,-3.5 + parent: 10 + type: Transform +- uid: 500 + type: CableApcExtension + components: + - pos: -9.5,-4.5 + parent: 10 + type: Transform +- uid: 501 + type: CableApcExtension + components: + - pos: -9.5,-5.5 + parent: 10 + type: Transform +- uid: 502 + type: CableApcExtension + components: + - pos: -9.5,-6.5 + parent: 10 + type: Transform +- uid: 503 + type: CableApcExtension + components: + - pos: -9.5,-7.5 + parent: 10 + type: Transform +- uid: 504 + type: CableApcExtension + components: + - pos: -9.5,-8.5 + parent: 10 + type: Transform +- uid: 505 + type: CableApcExtension + components: + - pos: -9.5,-9.5 + parent: 10 + type: Transform +- uid: 506 + type: CableApcExtension + components: + - pos: -9.5,-10.5 + parent: 10 + type: Transform +- uid: 507 + type: CableApcExtension + components: + - pos: -9.5,-11.5 + parent: 10 + type: Transform +- uid: 508 + type: CableApcExtension + components: + - pos: -9.5,-12.5 + parent: 10 + type: Transform +- uid: 509 + type: CableApcExtension + components: + - pos: -9.5,-13.5 + parent: 10 + type: Transform +- uid: 510 + type: CableApcExtension + components: + - pos: -9.5,-14.5 + parent: 10 + type: Transform +- uid: 511 + type: CableApcExtension + components: + - pos: -9.5,-15.5 + parent: 10 + type: Transform +- uid: 512 + type: CableApcExtension + components: + - pos: -9.5,-16.5 + parent: 10 + type: Transform +- uid: 513 + type: CableApcExtension + components: + - pos: -8.5,9.5 + parent: 10 + type: Transform +- uid: 514 + type: CableApcExtension + components: + - pos: -8.5,-0.5 + parent: 10 + type: Transform +- uid: 515 + type: CableApcExtension + components: + - pos: -8.5,-1.5 + parent: 10 + type: Transform +- uid: 516 + type: CableApcExtension + components: + - pos: -8.5,-2.5 + parent: 10 + type: Transform +- uid: 517 + type: CableApcExtension + components: + - pos: -8.5,-3.5 + parent: 10 + type: Transform +- uid: 518 + type: CableApcExtension + components: + - pos: -6.5,-2.5 + parent: 10 + type: Transform +- uid: 519 + type: CableApcExtension + components: + - pos: -6.5,-3.5 + parent: 10 + type: Transform +- uid: 520 + type: CableApcExtension + components: + - pos: -6.5,-4.5 + parent: 10 + type: Transform +- uid: 521 + type: CableApcExtension + components: + - pos: -6.5,-5.5 + parent: 10 + type: Transform +- uid: 522 + type: CableApcExtension + components: + - pos: -6.5,-6.5 + parent: 10 + type: Transform +- uid: 523 + type: CableApcExtension + components: + - pos: -6.5,-7.5 + parent: 10 + type: Transform +- uid: 524 + type: CableApcExtension + components: + - pos: -6.5,-8.5 + parent: 10 + type: Transform +- uid: 525 + type: CableApcExtension + components: + - pos: -6.5,-9.5 + parent: 10 + type: Transform +- uid: 526 + type: CableApcExtension + components: + - pos: -7.5,-3.5 + parent: 10 + type: Transform +- uid: 527 + type: CableApcExtension + components: + - pos: -7.5,-4.5 + parent: 10 + type: Transform +- uid: 528 + type: CableApcExtension + components: + - pos: -7.5,-5.5 + parent: 10 + type: Transform +- uid: 529 + type: CableApcExtension + components: + - pos: -7.5,-6.5 + parent: 10 + type: Transform +- uid: 530 + type: CableApcExtension + components: + - pos: -7.5,-7.5 + parent: 10 + type: Transform +- uid: 531 + type: CableApcExtension + components: + - pos: -7.5,-8.5 + parent: 10 + type: Transform +- uid: 532 + type: CableApcExtension + components: + - pos: -7.5,-9.5 + parent: 10 + type: Transform +- uid: 533 + type: CableApcExtension + components: + - pos: -7.5,-10.5 + parent: 10 + type: Transform +- uid: 534 + type: CableApcExtension + components: + - pos: -7.5,-11.5 + parent: 10 + type: Transform +- uid: 535 + type: CableApcExtension + components: + - pos: -7.5,-12.5 + parent: 10 + type: Transform +- uid: 536 + type: CableApcExtension + components: + - pos: -7.5,-13.5 + parent: 10 + type: Transform +- uid: 537 + type: CableApcExtension + components: + - pos: -7.5,-14.5 + parent: 10 + type: Transform +- uid: 538 + type: CableApcExtension + components: + - pos: -7.5,-15.5 + parent: 10 + type: Transform +- uid: 539 + type: CableApcExtension + components: + - pos: -7.5,-16.5 + parent: 10 + type: Transform +- uid: 540 + type: CableApcExtension + components: + - pos: -6.5,9.5 + parent: 10 + type: Transform +- uid: 541 + type: CableApcExtension + components: + - pos: -6.5,8.5 + parent: 10 + type: Transform +- uid: 542 + type: CableApcExtension + components: + - pos: -6.5,7.5 + parent: 10 + type: Transform +- uid: 543 + type: CableApcExtension + components: + - pos: -6.5,6.5 + parent: 10 + type: Transform +- uid: 544 + type: CableApcExtension + components: + - pos: -6.5,5.5 + parent: 10 + type: Transform +- uid: 545 + type: CableApcExtension + components: + - pos: -6.5,4.5 + parent: 10 + type: Transform +- uid: 546 + type: CableApcExtension + components: + - pos: -6.5,3.5 + parent: 10 + type: Transform +- uid: 547 + type: CableApcExtension + components: + - pos: -6.5,2.5 + parent: 10 + type: Transform +- uid: 548 + type: CableApcExtension + components: + - pos: -6.5,1.5 + parent: 10 + type: Transform +- uid: 549 + type: CableApcExtension + components: + - pos: -6.5,0.5 + parent: 10 + type: Transform +- uid: 550 + type: CableApcExtension + components: + - pos: -6.5,-0.5 + parent: 10 + type: Transform +- uid: 551 + type: CableApcExtension + components: + - pos: -6.5,-1.5 + parent: 10 + type: Transform +- uid: 552 + type: CableApcExtension + components: + - pos: -8.5,-4.5 + parent: 10 + type: Transform +- uid: 553 + type: CableApcExtension + components: + - pos: -8.5,-5.5 + parent: 10 + type: Transform +- uid: 554 + type: CableApcExtension + components: + - pos: -8.5,-6.5 + parent: 10 + type: Transform +- uid: 555 + type: CableApcExtension + components: + - pos: -8.5,-7.5 + parent: 10 + type: Transform +- uid: 556 + type: CableApcExtension + components: + - pos: -8.5,-8.5 + parent: 10 + type: Transform +- uid: 557 + type: CableApcExtension + components: + - pos: -8.5,-9.5 + parent: 10 + type: Transform +- uid: 558 + type: CableApcExtension + components: + - pos: -8.5,-10.5 + parent: 10 + type: Transform +- uid: 559 + type: CableApcExtension + components: + - pos: -8.5,-11.5 + parent: 10 + type: Transform +- uid: 560 + type: CableApcExtension + components: + - pos: -8.5,-12.5 + parent: 10 + type: Transform +- uid: 561 + type: CableApcExtension + components: + - pos: -8.5,-13.5 + parent: 10 + type: Transform +- uid: 562 + type: CableApcExtension + components: + - pos: -8.5,-14.5 + parent: 10 + type: Transform +- uid: 563 + type: CableApcExtension + components: + - pos: -8.5,-15.5 + parent: 10 + type: Transform +- uid: 564 + type: CableApcExtension + components: + - pos: -8.5,-16.5 + parent: 10 + type: Transform +- uid: 565 + type: CableApcExtension + components: + - pos: -7.5,9.5 + parent: 10 + type: Transform +- uid: 566 + type: CableApcExtension + components: + - pos: -7.5,8.5 + parent: 10 + type: Transform +- uid: 567 + type: CableApcExtension + components: + - pos: -7.5,7.5 + parent: 10 + type: Transform +- uid: 568 + type: CableApcExtension + components: + - pos: -7.5,6.5 + parent: 10 + type: Transform +- uid: 569 + type: CableApcExtension + components: + - pos: -7.5,5.5 + parent: 10 + type: Transform +- uid: 570 + type: CableApcExtension + components: + - pos: -7.5,4.5 + parent: 10 + type: Transform +- uid: 571 + type: CableApcExtension + components: + - pos: -7.5,3.5 + parent: 10 + type: Transform +- uid: 572 + type: CableApcExtension + components: + - pos: -7.5,2.5 + parent: 10 + type: Transform +- uid: 573 + type: CableApcExtension + components: + - pos: -7.5,1.5 + parent: 10 + type: Transform +- uid: 574 + type: CableApcExtension + components: + - pos: -7.5,0.5 + parent: 10 + type: Transform +- uid: 575 + type: CableApcExtension + components: + - pos: -7.5,-0.5 + parent: 10 + type: Transform +- uid: 576 + type: CableApcExtension + components: + - pos: -7.5,-1.5 + parent: 10 + type: Transform +- uid: 577 + type: CableApcExtension + components: + - pos: -7.5,-2.5 + parent: 10 + type: Transform +- uid: 578 + type: CableApcExtension + components: + - pos: -6.5,-10.5 + parent: 10 + type: Transform +- uid: 579 + type: CableApcExtension + components: + - pos: -6.5,-11.5 + parent: 10 + type: Transform +- uid: 580 + type: CableApcExtension + components: + - pos: -6.5,-12.5 + parent: 10 + type: Transform +- uid: 581 + type: CableApcExtension + components: + - pos: -6.5,-13.5 + parent: 10 + type: Transform +- uid: 582 + type: CableApcExtension + components: + - pos: -4.5,-11.5 + parent: 10 + type: Transform +- uid: 583 + type: CableApcExtension + components: + - pos: -4.5,-12.5 + parent: 10 + type: Transform +- uid: 584 + type: CableApcExtension + components: + - pos: -4.5,-13.5 + parent: 10 + type: Transform +- uid: 585 + type: CableApcExtension + components: + - pos: -4.5,-14.5 + parent: 10 + type: Transform +- uid: 586 + type: CableApcExtension + components: + - pos: -4.5,-15.5 + parent: 10 + type: Transform +- uid: 587 + type: CableApcExtension + components: + - pos: -4.5,-16.5 + parent: 10 + type: Transform +- uid: 588 + type: CableApcExtension + components: + - pos: -3.5,9.5 + parent: 10 + type: Transform +- uid: 589 + type: CableApcExtension + components: + - pos: -3.5,8.5 + parent: 10 + type: Transform +- uid: 590 + type: CableApcExtension + components: + - pos: -3.5,7.5 + parent: 10 + type: Transform +- uid: 591 + type: CableApcExtension + components: + - pos: -5.5,-12.5 + parent: 10 + type: Transform +- uid: 592 + type: CableApcExtension + components: + - pos: -5.5,-13.5 + parent: 10 + type: Transform +- uid: 593 + type: CableApcExtension + components: + - pos: -5.5,-14.5 + parent: 10 + type: Transform +- uid: 594 + type: CableApcExtension + components: + - pos: -5.5,-15.5 + parent: 10 + type: Transform +- uid: 595 + type: CableApcExtension + components: + - pos: -5.5,-16.5 + parent: 10 + type: Transform +- uid: 596 + type: CableApcExtension + components: + - pos: -4.5,9.5 + parent: 10 + type: Transform +- uid: 597 + type: CableApcExtension + components: + - pos: -4.5,8.5 + parent: 10 + type: Transform +- uid: 598 + type: CableApcExtension + components: + - pos: -4.5,7.5 + parent: 10 + type: Transform +- uid: 599 + type: CableApcExtension + components: + - pos: -4.5,6.5 + parent: 10 + type: Transform +- uid: 600 + type: CableApcExtension + components: + - pos: -4.5,5.5 + parent: 10 + type: Transform +- uid: 601 + type: CableApcExtension + components: + - pos: -4.5,4.5 + parent: 10 + type: Transform +- uid: 602 + type: CableApcExtension + components: + - pos: -4.5,3.5 + parent: 10 + type: Transform +- uid: 603 + type: CableApcExtension + components: + - pos: -4.5,2.5 + parent: 10 + type: Transform +- uid: 604 + type: CableApcExtension + components: + - pos: -4.5,1.5 + parent: 10 + type: Transform +- uid: 605 + type: CableApcExtension + components: + - pos: -4.5,0.5 + parent: 10 + type: Transform +- uid: 606 + type: CableApcExtension + components: + - pos: -4.5,-0.5 + parent: 10 + type: Transform +- uid: 607 + type: CableApcExtension + components: + - pos: -4.5,-1.5 + parent: 10 + type: Transform +- uid: 608 + type: CableApcExtension + components: + - pos: -4.5,-2.5 + parent: 10 + type: Transform +- uid: 609 + type: CableApcExtension + components: + - pos: -4.5,-3.5 + parent: 10 + type: Transform +- uid: 610 + type: CableApcExtension + components: + - pos: -4.5,-4.5 + parent: 10 + type: Transform +- uid: 611 + type: CableApcExtension + components: + - pos: -4.5,-5.5 + parent: 10 + type: Transform +- uid: 612 + type: CableApcExtension + components: + - pos: -4.5,-6.5 + parent: 10 + type: Transform +- uid: 613 + type: CableApcExtension + components: + - pos: -4.5,-7.5 + parent: 10 + type: Transform +- uid: 614 + type: CableApcExtension + components: + - pos: -4.5,-8.5 + parent: 10 + type: Transform +- uid: 615 + type: CableApcExtension + components: + - pos: -4.5,-9.5 + parent: 10 + type: Transform +- uid: 616 + type: CableApcExtension + components: + - pos: -4.5,-10.5 + parent: 10 + type: Transform +- uid: 617 + type: CableApcExtension + components: + - pos: -6.5,-14.5 + parent: 10 + type: Transform +- uid: 618 + type: CableApcExtension + components: + - pos: -6.5,-15.5 + parent: 10 + type: Transform +- uid: 619 + type: CableApcExtension + components: + - pos: -6.5,-16.5 + parent: 10 + type: Transform +- uid: 620 + type: CableApcExtension + components: + - pos: -5.5,9.5 + parent: 10 + type: Transform +- uid: 621 + type: CableApcExtension + components: + - pos: -5.5,8.5 + parent: 10 + type: Transform +- uid: 622 + type: CableApcExtension + components: + - pos: -5.5,7.5 + parent: 10 + type: Transform +- uid: 623 + type: CableApcExtension + components: + - pos: -5.5,6.5 + parent: 10 + type: Transform +- uid: 624 + type: CableApcExtension + components: + - pos: -5.5,5.5 + parent: 10 + type: Transform +- uid: 625 + type: CableApcExtension + components: + - pos: -5.5,4.5 + parent: 10 + type: Transform +- uid: 626 + type: CableApcExtension + components: + - pos: -5.5,3.5 + parent: 10 + type: Transform +- uid: 627 + type: CableApcExtension + components: + - pos: -5.5,2.5 + parent: 10 + type: Transform +- uid: 628 + type: CableApcExtension + components: + - pos: -5.5,1.5 + parent: 10 + type: Transform +- uid: 629 + type: CableApcExtension + components: + - pos: -5.5,0.5 + parent: 10 + type: Transform +- uid: 630 + type: CableApcExtension + components: + - pos: -5.5,-0.5 + parent: 10 + type: Transform +- uid: 631 + type: CableApcExtension + components: + - pos: -5.5,-1.5 + parent: 10 + type: Transform +- uid: 632 + type: CableApcExtension + components: + - pos: -5.5,-2.5 + parent: 10 + type: Transform +- uid: 633 + type: CableApcExtension + components: + - pos: -5.5,-3.5 + parent: 10 + type: Transform +- uid: 634 + type: CableApcExtension + components: + - pos: -5.5,-4.5 + parent: 10 + type: Transform +- uid: 635 + type: CableApcExtension + components: + - pos: -5.5,-5.5 + parent: 10 + type: Transform +- uid: 636 + type: CableApcExtension + components: + - pos: -5.5,-6.5 + parent: 10 + type: Transform +- uid: 637 + type: CableApcExtension + components: + - pos: -5.5,-7.5 + parent: 10 + type: Transform +- uid: 638 + type: CableApcExtension + components: + - pos: -5.5,-8.5 + parent: 10 + type: Transform +- uid: 639 + type: CableApcExtension + components: + - pos: -5.5,-9.5 + parent: 10 + type: Transform +- uid: 640 + type: CableApcExtension + components: + - pos: -5.5,-10.5 + parent: 10 + type: Transform +- uid: 641 + type: CableApcExtension + components: + - pos: -5.5,-11.5 + parent: 10 + type: Transform +- uid: 642 + type: CableApcExtension + components: + - pos: -3.5,6.5 + parent: 10 + type: Transform +- uid: 643 + type: CableApcExtension + components: + - pos: -3.5,5.5 + parent: 10 + type: Transform +- uid: 644 + type: CableApcExtension + components: + - pos: -3.5,4.5 + parent: 10 + type: Transform +- uid: 645 + type: CableApcExtension + components: + - pos: -3.5,3.5 + parent: 10 + type: Transform +- uid: 646 + type: CableApcExtension + components: + - pos: -1.5,4.5 + parent: 10 + type: Transform +- uid: 647 + type: CableApcExtension + components: + - pos: -1.5,3.5 + parent: 10 + type: Transform +- uid: 648 + type: CableApcExtension + components: + - pos: -1.5,2.5 + parent: 10 + type: Transform +- uid: 649 + type: CableApcExtension + components: + - pos: -1.5,1.5 + parent: 10 + type: Transform +- uid: 650 + type: CableApcExtension + components: + - pos: -1.5,0.5 + parent: 10 + type: Transform +- uid: 651 + type: CableApcExtension + components: + - pos: -1.5,-0.5 + parent: 10 + type: Transform +- uid: 652 + type: CableApcExtension + components: + - pos: -1.5,-1.5 + parent: 10 + type: Transform +- uid: 653 + type: CableApcExtension + components: + - pos: -1.5,-2.5 + parent: 10 + type: Transform +- uid: 654 + type: CableApcExtension + components: + - pos: -2.5,3.5 + parent: 10 + type: Transform +- uid: 655 + type: CableApcExtension + components: + - pos: -2.5,2.5 + parent: 10 + type: Transform +- uid: 656 + type: CableApcExtension + components: + - pos: -2.5,1.5 + parent: 10 + type: Transform +- uid: 657 + type: CableApcExtension + components: + - pos: -2.5,0.5 + parent: 10 + type: Transform +- uid: 658 + type: CableApcExtension + components: + - pos: -2.5,-0.5 + parent: 10 + type: Transform +- uid: 659 + type: CableApcExtension + components: + - pos: -2.5,-1.5 + parent: 10 + type: Transform +- uid: 660 + type: CableApcExtension + components: + - pos: -2.5,-2.5 + parent: 10 + type: Transform +- uid: 661 + type: CableApcExtension + components: + - pos: -2.5,-3.5 + parent: 10 + type: Transform +- uid: 662 + type: CableApcExtension + components: + - pos: -2.5,-4.5 + parent: 10 + type: Transform +- uid: 663 + type: CableApcExtension + components: + - pos: -2.5,-5.5 + parent: 10 + type: Transform +- uid: 664 + type: CableApcExtension + components: + - pos: -2.5,-6.5 + parent: 10 + type: Transform +- uid: 665 + type: CableApcExtension + components: + - pos: -2.5,-7.5 + parent: 10 + type: Transform +- uid: 666 + type: CableApcExtension + components: + - pos: -2.5,-8.5 + parent: 10 + type: Transform +- uid: 667 + type: CableApcExtension + components: + - pos: -2.5,-9.5 + parent: 10 + type: Transform +- uid: 668 + type: CableApcExtension + components: + - pos: -2.5,-10.5 + parent: 10 + type: Transform +- uid: 669 + type: CableApcExtension + components: + - pos: -2.5,-11.5 + parent: 10 + type: Transform +- uid: 670 + type: CableApcExtension + components: + - pos: -2.5,-12.5 + parent: 10 + type: Transform +- uid: 671 + type: CableApcExtension + components: + - pos: -2.5,-13.5 + parent: 10 + type: Transform +- uid: 672 + type: CableApcExtension + components: + - pos: -2.5,-14.5 + parent: 10 + type: Transform +- uid: 673 + type: CableApcExtension + components: + - pos: -2.5,-15.5 + parent: 10 + type: Transform +- uid: 674 + type: CableApcExtension + components: + - pos: -2.5,-16.5 + parent: 10 + type: Transform +- uid: 675 + type: CableApcExtension + components: + - pos: -1.5,9.5 + parent: 10 + type: Transform +- uid: 676 + type: CableApcExtension + components: + - pos: -1.5,8.5 + parent: 10 + type: Transform +- uid: 677 + type: CableApcExtension + components: + - pos: -1.5,7.5 + parent: 10 + type: Transform +- uid: 678 + type: CableApcExtension + components: + - pos: -1.5,6.5 + parent: 10 + type: Transform +- uid: 679 + type: CableApcExtension + components: + - pos: -1.5,5.5 + parent: 10 + type: Transform +- uid: 680 + type: CableApcExtension + components: + - pos: -3.5,2.5 + parent: 10 + type: Transform +- uid: 681 + type: CableApcExtension + components: + - pos: -3.5,1.5 + parent: 10 + type: Transform +- uid: 682 + type: CableApcExtension + components: + - pos: -3.5,0.5 + parent: 10 + type: Transform +- uid: 683 + type: CableApcExtension + components: + - pos: -3.5,-0.5 + parent: 10 + type: Transform +- uid: 684 + type: CableApcExtension + components: + - pos: -3.5,-1.5 + parent: 10 + type: Transform +- uid: 685 + type: CableApcExtension + components: + - pos: -3.5,-2.5 + parent: 10 + type: Transform +- uid: 686 + type: CableApcExtension + components: + - pos: -3.5,-3.5 + parent: 10 + type: Transform +- uid: 687 + type: CableApcExtension + components: + - pos: -3.5,-4.5 + parent: 10 + type: Transform +- uid: 688 + type: CableApcExtension + components: + - pos: -3.5,-5.5 + parent: 10 + type: Transform +- uid: 689 + type: CableApcExtension + components: + - pos: -3.5,-6.5 + parent: 10 + type: Transform +- uid: 690 + type: CableApcExtension + components: + - pos: -3.5,-7.5 + parent: 10 + type: Transform +- uid: 691 + type: CableApcExtension + components: + - pos: -3.5,-8.5 + parent: 10 + type: Transform +- uid: 692 + type: CableApcExtension + components: + - pos: -3.5,-9.5 + parent: 10 + type: Transform +- uid: 693 + type: CableApcExtension + components: + - pos: -3.5,-10.5 + parent: 10 + type: Transform +- uid: 694 + type: CableApcExtension + components: + - pos: -3.5,-11.5 + parent: 10 + type: Transform +- uid: 695 + type: CableApcExtension + components: + - pos: -3.5,-12.5 + parent: 10 + type: Transform +- uid: 696 + type: CableApcExtension + components: + - pos: -3.5,-13.5 + parent: 10 + type: Transform +- uid: 697 + type: CableApcExtension + components: + - pos: -3.5,-14.5 + parent: 10 + type: Transform +- uid: 698 + type: CableApcExtension + components: + - pos: -3.5,-15.5 + parent: 10 + type: Transform +- uid: 699 + type: CableApcExtension + components: + - pos: -3.5,-16.5 + parent: 10 + type: Transform +- uid: 700 + type: CableApcExtension + components: + - pos: -2.5,9.5 + parent: 10 + type: Transform +- uid: 701 + type: CableApcExtension + components: + - pos: -2.5,8.5 + parent: 10 + type: Transform +- uid: 702 + type: CableApcExtension + components: + - pos: -2.5,7.5 + parent: 10 + type: Transform +- uid: 703 + type: CableApcExtension + components: + - pos: -2.5,6.5 + parent: 10 + type: Transform +- uid: 704 + type: CableApcExtension + components: + - pos: -2.5,5.5 + parent: 10 + type: Transform +- uid: 705 + type: CableApcExtension + components: + - pos: -2.5,4.5 + parent: 10 + type: Transform +- uid: 706 + type: CableApcExtension + components: + - pos: -1.5,-3.5 + parent: 10 + type: Transform +- uid: 707 + type: CableApcExtension + components: + - pos: -1.5,-4.5 + parent: 10 + type: Transform +- uid: 708 + type: CableApcExtension + components: + - pos: -1.5,-5.5 + parent: 10 + type: Transform +- uid: 709 + type: CableApcExtension + components: + - pos: -1.5,-6.5 + parent: 10 + type: Transform +- uid: 710 + type: CableApcExtension + components: + - pos: 0.5,-4.5 + parent: 10 + type: Transform +- uid: 711 + type: CableApcExtension + components: + - pos: 0.5,-5.5 + parent: 10 + type: Transform +- uid: 712 + type: CableApcExtension + components: + - pos: 0.5,-6.5 + parent: 10 + type: Transform +- uid: 713 + type: CableApcExtension + components: + - pos: 0.5,-7.5 + parent: 10 + type: Transform +- uid: 714 + type: CableApcExtension + components: + - pos: 0.5,-8.5 + parent: 10 + type: Transform +- uid: 715 + type: CableApcExtension + components: + - pos: 0.5,-9.5 + parent: 10 + type: Transform +- uid: 716 + type: CableApcExtension + components: + - pos: 0.5,-10.5 + parent: 10 + type: Transform +- uid: 717 + type: CableApcExtension + components: + - pos: 0.5,-11.5 + parent: 10 + type: Transform +- uid: 718 + type: CableApcExtension + components: + - pos: 0.5,-12.5 + parent: 10 + type: Transform +- uid: 719 + type: CableApcExtension + components: + - pos: -0.5,-5.5 + parent: 10 + type: Transform +- uid: 720 + type: CableApcExtension + components: + - pos: -0.5,-6.5 + parent: 10 + type: Transform +- uid: 721 + type: CableApcExtension + components: + - pos: -0.5,-7.5 + parent: 10 + type: Transform +- uid: 722 + type: CableApcExtension + components: + - pos: -0.5,-8.5 + parent: 10 + type: Transform +- uid: 723 + type: CableApcExtension + components: + - pos: -0.5,-9.5 + parent: 10 + type: Transform +- uid: 724 + type: CableApcExtension + components: + - pos: -0.5,-10.5 + parent: 10 + type: Transform +- uid: 725 + type: CableApcExtension + components: + - pos: -0.5,-11.5 + parent: 10 + type: Transform +- uid: 726 + type: CableApcExtension + components: + - pos: -0.5,-12.5 + parent: 10 + type: Transform +- uid: 727 + type: CableApcExtension + components: + - pos: -0.5,-13.5 + parent: 10 + type: Transform +- uid: 728 + type: CableApcExtension + components: + - pos: -0.5,-14.5 + parent: 10 + type: Transform +- uid: 729 + type: CableApcExtension + components: + - pos: -0.5,-15.5 + parent: 10 + type: Transform +- uid: 730 + type: CableApcExtension + components: + - pos: -0.5,-16.5 + parent: 10 + type: Transform +- uid: 731 + type: CableApcExtension + components: + - pos: 0.5,9.5 + parent: 10 + type: Transform +- uid: 732 + type: CableApcExtension + components: + - pos: 0.5,8.5 + parent: 10 + type: Transform +- uid: 733 + type: CableApcExtension + components: + - pos: 0.5,7.5 + parent: 10 + type: Transform +- uid: 734 + type: CableApcExtension + components: + - pos: 0.5,6.5 + parent: 10 + type: Transform +- uid: 735 + type: CableApcExtension + components: + - pos: 0.5,5.5 + parent: 10 + type: Transform +- uid: 736 + type: CableApcExtension + components: + - pos: 0.5,4.5 + parent: 10 + type: Transform +- uid: 737 + type: CableApcExtension + components: + - pos: 0.5,3.5 + parent: 10 + type: Transform +- uid: 738 + type: CableApcExtension + components: + - pos: 0.5,2.5 + parent: 10 + type: Transform +- uid: 739 + type: CableApcExtension + components: + - pos: 0.5,1.5 + parent: 10 + type: Transform +- uid: 740 + type: CableApcExtension + components: + - pos: 0.5,0.5 + parent: 10 + type: Transform +- uid: 741 + type: CableApcExtension + components: + - pos: 0.5,-0.5 + parent: 10 + type: Transform +- uid: 742 + type: CableApcExtension + components: + - pos: 0.5,-1.5 + parent: 10 + type: Transform +- uid: 743 + type: CableApcExtension + components: + - pos: 0.5,-2.5 + parent: 10 + type: Transform +- uid: 744 + type: CableApcExtension + components: + - pos: 0.5,-3.5 + parent: 10 + type: Transform +- uid: 745 + type: CableApcExtension + components: + - pos: -1.5,-7.5 + parent: 10 + type: Transform +- uid: 746 + type: CableApcExtension + components: + - pos: -1.5,-8.5 + parent: 10 + type: Transform +- uid: 747 + type: CableApcExtension + components: + - pos: -1.5,-9.5 + parent: 10 + type: Transform +- uid: 748 + type: CableApcExtension + components: + - pos: -1.5,-10.5 + parent: 10 + type: Transform +- uid: 749 + type: CableApcExtension + components: + - pos: -1.5,-11.5 + parent: 10 + type: Transform +- uid: 750 + type: CableApcExtension + components: + - pos: -1.5,-12.5 + parent: 10 + type: Transform +- uid: 751 + type: CableApcExtension + components: + - pos: -1.5,-13.5 + parent: 10 + type: Transform +- uid: 752 + type: CableApcExtension + components: + - pos: -1.5,-14.5 + parent: 10 + type: Transform +- uid: 753 + type: CableApcExtension + components: + - pos: -1.5,-15.5 + parent: 10 + type: Transform +- uid: 754 + type: CableApcExtension + components: + - pos: -1.5,-16.5 + parent: 10 + type: Transform +- uid: 755 + type: CableApcExtension + components: + - pos: -0.5,9.5 + parent: 10 + type: Transform +- uid: 756 + type: CableApcExtension + components: + - pos: -0.5,8.5 + parent: 10 + type: Transform +- uid: 757 + type: CableApcExtension + components: + - pos: -0.5,7.5 + parent: 10 + type: Transform +- uid: 758 + type: CableApcExtension + components: + - pos: -0.5,6.5 + parent: 10 + type: Transform +- uid: 759 + type: CableApcExtension + components: + - pos: -0.5,5.5 + parent: 10 + type: Transform +- uid: 760 + type: CableApcExtension + components: + - pos: -0.5,4.5 + parent: 10 + type: Transform +- uid: 761 + type: CableApcExtension + components: + - pos: -0.5,3.5 + parent: 10 + type: Transform +- uid: 762 + type: CableApcExtension + components: + - pos: -0.5,2.5 + parent: 10 + type: Transform +- uid: 763 + type: CableApcExtension + components: + - pos: -0.5,1.5 + parent: 10 + type: Transform +- uid: 764 + type: CableApcExtension + components: + - pos: -0.5,0.5 + parent: 10 + type: Transform +- uid: 765 + type: CableApcExtension + components: + - pos: -0.5,-0.5 + parent: 10 + type: Transform +- uid: 766 + type: CableApcExtension + components: + - pos: -0.5,-1.5 + parent: 10 + type: Transform +- uid: 767 + type: CableApcExtension + components: + - pos: -0.5,-2.5 + parent: 10 + type: Transform +- uid: 768 + type: CableApcExtension + components: + - pos: -0.5,-3.5 + parent: 10 + type: Transform +- uid: 769 + type: CableApcExtension + components: + - pos: -0.5,-4.5 + parent: 10 + type: Transform +- uid: 770 + type: CableApcExtension + components: + - pos: 0.5,-13.5 + parent: 10 + type: Transform +- uid: 771 + type: CableApcExtension + components: + - pos: 0.5,-14.5 + parent: 10 + type: Transform +- uid: 772 + type: CableApcExtension + components: + - pos: 0.5,-15.5 + parent: 10 + type: Transform +- uid: 773 + type: CableApcExtension + components: + - pos: 0.5,-16.5 + parent: 10 + type: Transform +- uid: 774 + type: CableApcExtension + components: + - pos: 2.5,-14.5 + parent: 10 + type: Transform +- uid: 775 + type: CableApcExtension + components: + - pos: 2.5,-15.5 + parent: 10 + type: Transform +- uid: 776 + type: CableApcExtension + components: + - pos: 2.5,-16.5 + parent: 10 + type: Transform +- uid: 777 + type: CableApcExtension + components: + - pos: 3.5,9.5 + parent: 10 + type: Transform +- uid: 778 + type: CableApcExtension + components: + - pos: 3.5,8.5 + parent: 10 + type: Transform +- uid: 779 + type: CableApcExtension + components: + - pos: 3.5,7.5 + parent: 10 + type: Transform +- uid: 780 + type: CableApcExtension + components: + - pos: 3.5,6.5 + parent: 10 + type: Transform +- uid: 781 + type: CableApcExtension + components: + - pos: 3.5,5.5 + parent: 10 + type: Transform +- uid: 782 + type: CableApcExtension + components: + - pos: 3.5,4.5 + parent: 10 + type: Transform +- uid: 783 + type: CableApcExtension + components: + - pos: 1.5,-15.5 + parent: 10 + type: Transform +- uid: 784 + type: CableApcExtension + components: + - pos: 1.5,-16.5 + parent: 10 + type: Transform +- uid: 785 + type: CableApcExtension + components: + - pos: 2.5,9.5 + parent: 10 + type: Transform +- uid: 786 + type: CableApcExtension + components: + - pos: 2.5,8.5 + parent: 10 + type: Transform +- uid: 787 + type: CableApcExtension + components: + - pos: 2.5,7.5 + parent: 10 + type: Transform +- uid: 788 + type: CableApcExtension + components: + - pos: 2.5,6.5 + parent: 10 + type: Transform +- uid: 789 + type: CableApcExtension + components: + - pos: 2.5,5.5 + parent: 10 + type: Transform +- uid: 790 + type: CableApcExtension + components: + - pos: 2.5,4.5 + parent: 10 + type: Transform +- uid: 791 + type: CableApcExtension + components: + - pos: 2.5,3.5 + parent: 10 + type: Transform +- uid: 792 + type: CableApcExtension + components: + - pos: 2.5,2.5 + parent: 10 + type: Transform +- uid: 793 + type: CableApcExtension + components: + - pos: 2.5,1.5 + parent: 10 + type: Transform +- uid: 794 + type: CableApcExtension + components: + - pos: 2.5,0.5 + parent: 10 + type: Transform +- uid: 795 + type: CableApcExtension + components: + - pos: 2.5,-0.5 + parent: 10 + type: Transform +- uid: 796 + type: CableApcExtension + components: + - pos: 2.5,-1.5 + parent: 10 + type: Transform +- uid: 797 + type: CableApcExtension + components: + - pos: 2.5,-2.5 + parent: 10 + type: Transform +- uid: 798 + type: CableApcExtension + components: + - pos: 2.5,-3.5 + parent: 10 + type: Transform +- uid: 799 + type: CableApcExtension + components: + - pos: 2.5,-4.5 + parent: 10 + type: Transform +- uid: 800 + type: CableApcExtension + components: + - pos: 2.5,-5.5 + parent: 10 + type: Transform +- uid: 801 + type: CableApcExtension + components: + - pos: 2.5,-6.5 + parent: 10 + type: Transform +- uid: 802 + type: CableApcExtension + components: + - pos: 2.5,-7.5 + parent: 10 + type: Transform +- uid: 803 + type: CableApcExtension + components: + - pos: 2.5,-8.5 + parent: 10 + type: Transform +- uid: 804 + type: CableApcExtension + components: + - pos: 2.5,-9.5 + parent: 10 + type: Transform +- uid: 805 + type: CableApcExtension + components: + - pos: 2.5,-10.5 + parent: 10 + type: Transform +- uid: 806 + type: CableApcExtension + components: + - pos: 2.5,-11.5 + parent: 10 + type: Transform +- uid: 807 + type: CableApcExtension + components: + - pos: 2.5,-12.5 + parent: 10 + type: Transform +- uid: 808 + type: CableApcExtension + components: + - pos: 2.5,-13.5 + parent: 10 + type: Transform +- uid: 809 + type: CableApcExtension + components: + - pos: 1.5,9.5 + parent: 10 + type: Transform +- uid: 810 + type: CableApcExtension + components: + - pos: 1.5,8.5 + parent: 10 + type: Transform +- uid: 811 + type: CableApcExtension + components: + - pos: 1.5,7.5 + parent: 10 + type: Transform +- uid: 812 + type: CableApcExtension + components: + - pos: 1.5,6.5 + parent: 10 + type: Transform +- uid: 813 + type: CableApcExtension + components: + - pos: 1.5,5.5 + parent: 10 + type: Transform +- uid: 814 + type: CableApcExtension + components: + - pos: 1.5,4.5 + parent: 10 + type: Transform +- uid: 815 + type: CableApcExtension + components: + - pos: 1.5,3.5 + parent: 10 + type: Transform +- uid: 816 + type: CableApcExtension + components: + - pos: 1.5,2.5 + parent: 10 + type: Transform +- uid: 817 + type: CableApcExtension + components: + - pos: 1.5,1.5 + parent: 10 + type: Transform +- uid: 818 + type: CableApcExtension + components: + - pos: 1.5,0.5 + parent: 10 + type: Transform +- uid: 819 + type: CableApcExtension + components: + - pos: 1.5,-0.5 + parent: 10 + type: Transform +- uid: 820 + type: CableApcExtension + components: + - pos: 1.5,-1.5 + parent: 10 + type: Transform +- uid: 821 + type: CableApcExtension + components: + - pos: 1.5,-2.5 + parent: 10 + type: Transform +- uid: 822 + type: CableApcExtension + components: + - pos: 1.5,-3.5 + parent: 10 + type: Transform +- uid: 823 + type: CableApcExtension + components: + - pos: 1.5,-4.5 + parent: 10 + type: Transform +- uid: 824 + type: CableApcExtension + components: + - pos: 1.5,-5.5 + parent: 10 + type: Transform +- uid: 825 + type: CableApcExtension + components: + - pos: 1.5,-6.5 + parent: 10 + type: Transform +- uid: 826 + type: CableApcExtension + components: + - pos: 1.5,-7.5 + parent: 10 + type: Transform +- uid: 827 + type: CableApcExtension + components: + - pos: 1.5,-8.5 + parent: 10 + type: Transform +- uid: 828 + type: CableApcExtension + components: + - pos: 1.5,-9.5 + parent: 10 + type: Transform +- uid: 829 + type: CableApcExtension + components: + - pos: 1.5,-10.5 + parent: 10 + type: Transform +- uid: 830 + type: CableApcExtension + components: + - pos: 1.5,-11.5 + parent: 10 + type: Transform +- uid: 831 + type: CableApcExtension + components: + - pos: 1.5,-12.5 + parent: 10 + type: Transform +- uid: 832 + type: CableApcExtension + components: + - pos: 1.5,-13.5 + parent: 10 + type: Transform +- uid: 833 + type: CableApcExtension + components: + - pos: 1.5,-14.5 + parent: 10 + type: Transform +- uid: 834 + type: CableApcExtension + components: + - pos: 3.5,3.5 + parent: 10 + type: Transform +- uid: 835 + type: CableApcExtension + components: + - pos: 3.5,2.5 + parent: 10 + type: Transform +- uid: 836 + type: CableApcExtension + components: + - pos: 3.5,1.5 + parent: 10 + type: Transform +- uid: 837 + type: CableApcExtension + components: + - pos: 3.5,0.5 + parent: 10 + type: Transform +- uid: 838 + type: CableApcExtension + components: + - pos: 5.5,0.5 + parent: 10 + type: Transform +- uid: 839 + type: CableApcExtension + components: + - pos: 5.5,-0.5 + parent: 10 + type: Transform +- uid: 840 + type: CableApcExtension + components: + - pos: 5.5,-1.5 + parent: 10 + type: Transform +- uid: 841 + type: CableApcExtension + components: + - pos: 5.5,-2.5 + parent: 10 + type: Transform +- uid: 842 + type: CableApcExtension + components: + - pos: 5.5,-3.5 + parent: 10 + type: Transform +- uid: 843 + type: CableApcExtension + components: + - pos: 5.5,-4.5 + parent: 10 + type: Transform +- uid: 844 + type: CableApcExtension + components: + - pos: 5.5,-5.5 + parent: 10 + type: Transform +- uid: 845 + type: CableApcExtension + components: + - pos: 3.5,-0.5 + parent: 10 + type: Transform +- uid: 846 + type: CableApcExtension + components: + - pos: 3.5,-1.5 + parent: 10 + type: Transform +- uid: 847 + type: CableApcExtension + components: + - pos: 3.5,-2.5 + parent: 10 + type: Transform +- uid: 848 + type: CableApcExtension + components: + - pos: 3.5,-3.5 + parent: 10 + type: Transform +- uid: 849 + type: CableApcExtension + components: + - pos: 3.5,-4.5 + parent: 10 + type: Transform +- uid: 850 + type: CableApcExtension + components: + - pos: 3.5,-5.5 + parent: 10 + type: Transform +- uid: 851 + type: CableApcExtension + components: + - pos: 3.5,-6.5 + parent: 10 + type: Transform +- uid: 852 + type: CableApcExtension + components: + - pos: 3.5,-7.5 + parent: 10 + type: Transform +- uid: 853 + type: CableApcExtension + components: + - pos: 3.5,-8.5 + parent: 10 + type: Transform +- uid: 854 + type: CableApcExtension + components: + - pos: 3.5,-9.5 + parent: 10 + type: Transform +- uid: 855 + type: CableApcExtension + components: + - pos: 3.5,-10.5 + parent: 10 + type: Transform +- uid: 856 + type: CableApcExtension + components: + - pos: 3.5,-11.5 + parent: 10 + type: Transform +- uid: 857 + type: CableApcExtension + components: + - pos: 3.5,-12.5 + parent: 10 + type: Transform +- uid: 858 + type: CableApcExtension + components: + - pos: 3.5,-13.5 + parent: 10 + type: Transform +- uid: 859 + type: CableApcExtension + components: + - pos: 3.5,-14.5 + parent: 10 + type: Transform +- uid: 860 + type: CableApcExtension + components: + - pos: 3.5,-15.5 + parent: 10 + type: Transform +- uid: 861 + type: CableApcExtension + components: + - pos: 3.5,-16.5 + parent: 10 + type: Transform +- uid: 862 + type: CableApcExtension + components: + - pos: 4.5,9.5 + parent: 10 + type: Transform +- uid: 863 + type: CableApcExtension + components: + - pos: 4.5,8.5 + parent: 10 + type: Transform +- uid: 864 + type: CableApcExtension + components: + - pos: 4.5,7.5 + parent: 10 + type: Transform +- uid: 865 + type: CableApcExtension + components: + - pos: 4.5,6.5 + parent: 10 + type: Transform +- uid: 866 + type: CableApcExtension + components: + - pos: 4.5,5.5 + parent: 10 + type: Transform +- uid: 867 + type: CableApcExtension + components: + - pos: 4.5,4.5 + parent: 10 + type: Transform +- uid: 868 + type: CableApcExtension + components: + - pos: 4.5,3.5 + parent: 10 + type: Transform +- uid: 869 + type: CableApcExtension + components: + - pos: 4.5,2.5 + parent: 10 + type: Transform +- uid: 870 + type: CableApcExtension + components: + - pos: 4.5,1.5 + parent: 10 + type: Transform +- uid: 871 + type: CableApcExtension + components: + - pos: 4.5,0.5 + parent: 10 + type: Transform +- uid: 872 + type: CableApcExtension + components: + - pos: 4.5,-0.5 + parent: 10 + type: Transform +- uid: 873 + type: CableApcExtension + components: + - pos: 4.5,-1.5 + parent: 10 + type: Transform +- uid: 874 + type: CableApcExtension + components: + - pos: 4.5,-2.5 + parent: 10 + type: Transform +- uid: 875 + type: CableApcExtension + components: + - pos: 4.5,-3.5 + parent: 10 + type: Transform +- uid: 876 + type: CableApcExtension + components: + - pos: 4.5,-4.5 + parent: 10 + type: Transform +- uid: 877 + type: CableApcExtension + components: + - pos: 4.5,-5.5 + parent: 10 + type: Transform +- uid: 878 + type: CableApcExtension + components: + - pos: 4.5,-6.5 + parent: 10 + type: Transform +- uid: 879 + type: CableApcExtension + components: + - pos: 4.5,-7.5 + parent: 10 + type: Transform +- uid: 880 + type: CableApcExtension + components: + - pos: 4.5,-8.5 + parent: 10 + type: Transform +- uid: 881 + type: CableApcExtension + components: + - pos: 4.5,-9.5 + parent: 10 + type: Transform +- uid: 882 + type: CableApcExtension + components: + - pos: 4.5,-10.5 + parent: 10 + type: Transform +- uid: 883 + type: CableApcExtension + components: + - pos: 4.5,-11.5 + parent: 10 + type: Transform +- uid: 884 + type: CableApcExtension + components: + - pos: 4.5,-12.5 + parent: 10 + type: Transform +- uid: 885 + type: CableApcExtension + components: + - pos: 4.5,-13.5 + parent: 10 + type: Transform +- uid: 886 + type: CableApcExtension + components: + - pos: 4.5,-14.5 + parent: 10 + type: Transform +- uid: 887 + type: CableApcExtension + components: + - pos: 4.5,-15.5 + parent: 10 + type: Transform +- uid: 888 + type: CableApcExtension + components: + - pos: 4.5,-16.5 + parent: 10 + type: Transform +- uid: 889 + type: CableApcExtension + components: + - pos: 5.5,9.5 + parent: 10 + type: Transform +- uid: 890 + type: CableApcExtension + components: + - pos: 5.5,8.5 + parent: 10 + type: Transform +- uid: 891 + type: CableApcExtension + components: + - pos: 5.5,7.5 + parent: 10 + type: Transform +- uid: 892 + type: CableApcExtension + components: + - pos: 5.5,6.5 + parent: 10 + type: Transform +- uid: 893 + type: CableApcExtension + components: + - pos: 5.5,5.5 + parent: 10 + type: Transform +- uid: 894 + type: CableApcExtension + components: + - pos: 5.5,4.5 + parent: 10 + type: Transform +- uid: 895 + type: CableApcExtension + components: + - pos: 5.5,3.5 + parent: 10 + type: Transform +- uid: 896 + type: CableApcExtension + components: + - pos: 5.5,2.5 + parent: 10 + type: Transform +- uid: 897 + type: CableApcExtension + components: + - pos: 5.5,1.5 + parent: 10 + type: Transform +- uid: 898 + type: CableApcExtension + components: + - pos: 5.5,-6.5 + parent: 10 + type: Transform +- uid: 899 + type: CableApcExtension + components: + - pos: 5.5,-7.5 + parent: 10 + type: Transform +- uid: 900 + type: CableApcExtension + components: + - pos: 5.5,-8.5 + parent: 10 + type: Transform +- uid: 901 + type: CableApcExtension + components: + - pos: 5.5,-9.5 + parent: 10 + type: Transform +- uid: 902 + type: CableApcExtension + components: + - pos: 7.5,-6.5 + parent: 10 + type: Transform +- uid: 903 + type: CableApcExtension + components: + - pos: 7.5,-7.5 + parent: 10 + type: Transform +- uid: 904 + type: CableApcExtension + components: + - pos: 7.5,-8.5 + parent: 10 + type: Transform +- uid: 905 + type: CableApcExtension + components: + - pos: 7.5,-9.5 + parent: 10 + type: Transform +- uid: 906 + type: CableApcExtension + components: + - pos: 7.5,-10.5 + parent: 10 + type: Transform +- uid: 907 + type: CableApcExtension + components: + - pos: 7.5,-11.5 + parent: 10 + type: Transform +- uid: 908 + type: CableApcExtension + components: + - pos: 7.5,-12.5 + parent: 10 + type: Transform +- uid: 909 + type: CableApcExtension + components: + - pos: 7.5,-13.5 + parent: 10 + type: Transform +- uid: 910 + type: CableApcExtension + components: + - pos: 7.5,-14.5 + parent: 10 + type: Transform +- uid: 911 + type: CableApcExtension + components: + - pos: 7.5,-15.5 + parent: 10 + type: Transform +- uid: 912 + type: CableApcExtension + components: + - pos: 6.5,-7.5 + parent: 10 + type: Transform +- uid: 913 + type: CableApcExtension + components: + - pos: 6.5,-8.5 + parent: 10 + type: Transform +- uid: 914 + type: CableApcExtension + components: + - pos: 6.5,-9.5 + parent: 10 + type: Transform +- uid: 915 + type: CableApcExtension + components: + - pos: 6.5,-10.5 + parent: 10 + type: Transform +- uid: 916 + type: CableApcExtension + components: + - pos: 6.5,-11.5 + parent: 10 + type: Transform +- uid: 917 + type: CableApcExtension + components: + - pos: 6.5,-12.5 + parent: 10 + type: Transform +- uid: 918 + type: CableApcExtension + components: + - pos: 6.5,-13.5 + parent: 10 + type: Transform +- uid: 919 + type: CableApcExtension + components: + - pos: 6.5,-14.5 + parent: 10 + type: Transform +- uid: 920 + type: CableApcExtension + components: + - pos: 6.5,-15.5 + parent: 10 + type: Transform +- uid: 921 + type: CableApcExtension + components: + - pos: 6.5,-16.5 + parent: 10 + type: Transform +- uid: 922 + type: CableApcExtension + components: + - pos: 7.5,9.5 + parent: 10 + type: Transform +- uid: 923 + type: CableApcExtension + components: + - pos: 7.5,8.5 + parent: 10 + type: Transform +- uid: 924 + type: CableApcExtension + components: + - pos: 7.5,7.5 + parent: 10 + type: Transform +- uid: 925 + type: CableApcExtension + components: + - pos: 7.5,6.5 + parent: 10 + type: Transform +- uid: 926 + type: CableApcExtension + components: + - pos: 7.5,5.5 + parent: 10 + type: Transform +- uid: 927 + type: CableApcExtension + components: + - pos: 7.5,4.5 + parent: 10 + type: Transform +- uid: 928 + type: CableApcExtension + components: + - pos: 7.5,3.5 + parent: 10 + type: Transform +- uid: 929 + type: CableApcExtension + components: + - pos: 7.5,2.5 + parent: 10 + type: Transform +- uid: 930 + type: CableApcExtension + components: + - pos: 7.5,1.5 + parent: 10 + type: Transform +- uid: 931 + type: CableApcExtension + components: + - pos: 7.5,0.5 + parent: 10 + type: Transform +- uid: 932 + type: CableApcExtension + components: + - pos: 7.5,-0.5 + parent: 10 + type: Transform +- uid: 933 + type: CableApcExtension + components: + - pos: 7.5,-1.5 + parent: 10 + type: Transform +- uid: 934 + type: CableApcExtension + components: + - pos: 7.5,-2.5 + parent: 10 + type: Transform +- uid: 935 + type: CableApcExtension + components: + - pos: 7.5,-3.5 + parent: 10 + type: Transform +- uid: 936 + type: CableApcExtension + components: + - pos: 7.5,-4.5 + parent: 10 + type: Transform +- uid: 937 + type: CableApcExtension + components: + - pos: 7.5,-5.5 + parent: 10 + type: Transform +- uid: 938 + type: CableApcExtension + components: + - pos: 5.5,-10.5 + parent: 10 + type: Transform +- uid: 939 + type: CableApcExtension + components: + - pos: 5.5,-11.5 + parent: 10 + type: Transform +- uid: 940 + type: CableApcExtension + components: + - pos: 5.5,-12.5 + parent: 10 + type: Transform +- uid: 941 + type: CableApcExtension + components: + - pos: 5.5,-13.5 + parent: 10 + type: Transform +- uid: 942 + type: CableApcExtension + components: + - pos: 5.5,-14.5 + parent: 10 + type: Transform +- uid: 943 + type: CableApcExtension + components: + - pos: 5.5,-15.5 + parent: 10 + type: Transform +- uid: 944 + type: CableApcExtension + components: + - pos: 5.5,-16.5 + parent: 10 + type: Transform +- uid: 945 + type: CableApcExtension + components: + - pos: 6.5,9.5 + parent: 10 + type: Transform +- uid: 946 + type: CableApcExtension + components: + - pos: 6.5,8.5 + parent: 10 + type: Transform +- uid: 947 + type: CableApcExtension + components: + - pos: 6.5,7.5 + parent: 10 + type: Transform +- uid: 948 + type: CableApcExtension + components: + - pos: 6.5,6.5 + parent: 10 + type: Transform +- uid: 949 + type: CableApcExtension + components: + - pos: 6.5,5.5 + parent: 10 + type: Transform +- uid: 950 + type: CableApcExtension + components: + - pos: 6.5,4.5 + parent: 10 + type: Transform +- uid: 951 + type: CableApcExtension + components: + - pos: 6.5,3.5 + parent: 10 + type: Transform +- uid: 952 + type: CableApcExtension + components: + - pos: 6.5,2.5 + parent: 10 + type: Transform +- uid: 953 + type: CableApcExtension + components: + - pos: 6.5,1.5 + parent: 10 + type: Transform +- uid: 954 + type: CableApcExtension + components: + - pos: 6.5,0.5 + parent: 10 + type: Transform +- uid: 955 + type: CableApcExtension + components: + - pos: 6.5,-0.5 + parent: 10 + type: Transform +- uid: 956 + type: CableApcExtension + components: + - pos: 6.5,-1.5 + parent: 10 + type: Transform +- uid: 957 + type: CableApcExtension + components: + - pos: 6.5,-2.5 + parent: 10 + type: Transform +- uid: 958 + type: CableApcExtension + components: + - pos: 6.5,-3.5 + parent: 10 + type: Transform +- uid: 959 + type: CableApcExtension + components: + - pos: 6.5,-4.5 + parent: 10 + type: Transform +- uid: 960 + type: CableApcExtension + components: + - pos: 6.5,-5.5 + parent: 10 + type: Transform +- uid: 961 + type: CableApcExtension + components: + - pos: 6.5,-6.5 + parent: 10 + type: Transform +- uid: 962 + type: CableApcExtension + components: + - pos: 7.5,-16.5 + parent: 10 + type: Transform +- uid: 963 + type: CableApcExtension + components: + - pos: 8.5,9.5 + parent: 10 + type: Transform +- uid: 964 + type: CableApcExtension + components: + - pos: 8.5,8.5 + parent: 10 + type: Transform +- uid: 965 + type: CableApcExtension + components: + - pos: 8.5,7.5 + parent: 10 + type: Transform +- uid: 966 + type: CableApcExtension + components: + - pos: 10.5,6.5 + parent: 10 + type: Transform +- uid: 967 + type: CableApcExtension + components: + - pos: 10.5,5.5 + parent: 10 + type: Transform +- uid: 968 + type: CableApcExtension + components: + - pos: 10.5,4.5 + parent: 10 + type: Transform +- uid: 969 + type: CableApcExtension + components: + - pos: 10.5,3.5 + parent: 10 + type: Transform +- uid: 970 + type: CableApcExtension + components: + - pos: 10.5,2.5 + parent: 10 + type: Transform +- uid: 971 + type: CableApcExtension + components: + - pos: 10.5,1.5 + parent: 10 + type: Transform +- uid: 972 + type: CableApcExtension + components: + - pos: 9.5,5.5 + parent: 10 + type: Transform +- uid: 973 + type: CableApcExtension + components: + - pos: 9.5,4.5 + parent: 10 + type: Transform +- uid: 974 + type: CableApcExtension + components: + - pos: 9.5,3.5 + parent: 10 + type: Transform +- uid: 975 + type: CableApcExtension + components: + - pos: 9.5,2.5 + parent: 10 + type: Transform +- uid: 976 + type: CableApcExtension + components: + - pos: 9.5,1.5 + parent: 10 + type: Transform +- uid: 977 + type: CableApcExtension + components: + - pos: 9.5,0.5 + parent: 10 + type: Transform +- uid: 978 + type: CableApcExtension + components: + - pos: 9.5,-0.5 + parent: 10 + type: Transform +- uid: 979 + type: CableApcExtension + components: + - pos: 9.5,-1.5 + parent: 10 + type: Transform +- uid: 980 + type: CableApcExtension + components: + - pos: 9.5,-2.5 + parent: 10 + type: Transform +- uid: 981 + type: CableApcExtension + components: + - pos: 9.5,-3.5 + parent: 10 + type: Transform +- uid: 982 + type: CableApcExtension + components: + - pos: 9.5,-4.5 + parent: 10 + type: Transform +- uid: 983 + type: CableApcExtension + components: + - pos: 9.5,-5.5 + parent: 10 + type: Transform +- uid: 984 + type: CableApcExtension + components: + - pos: 9.5,-6.5 + parent: 10 + type: Transform +- uid: 985 + type: CableApcExtension + components: + - pos: 9.5,-7.5 + parent: 10 + type: Transform +- uid: 986 + type: CableApcExtension + components: + - pos: 9.5,-8.5 + parent: 10 + type: Transform +- uid: 987 + type: CableApcExtension + components: + - pos: 9.5,-9.5 + parent: 10 + type: Transform +- uid: 988 + type: CableApcExtension + components: + - pos: 9.5,-10.5 + parent: 10 + type: Transform +- uid: 989 + type: CableApcExtension + components: + - pos: 9.5,-11.5 + parent: 10 + type: Transform +- uid: 990 + type: CableApcExtension + components: + - pos: 9.5,-12.5 + parent: 10 + type: Transform +- uid: 991 + type: CableApcExtension + components: + - pos: 9.5,-13.5 + parent: 10 + type: Transform +- uid: 992 + type: CableApcExtension + components: + - pos: 9.5,-14.5 + parent: 10 + type: Transform +- uid: 993 + type: CableApcExtension + components: + - pos: 9.5,-15.5 + parent: 10 + type: Transform +- uid: 994 + type: CableApcExtension + components: + - pos: 9.5,-16.5 + parent: 10 + type: Transform +- uid: 995 + type: CableApcExtension + components: + - pos: 10.5,9.5 + parent: 10 + type: Transform +- uid: 996 + type: CableApcExtension + components: + - pos: 10.5,8.5 + parent: 10 + type: Transform +- uid: 997 + type: CableApcExtension + components: + - pos: 10.5,7.5 + parent: 10 + type: Transform +- uid: 998 + type: CableApcExtension + components: + - pos: 8.5,6.5 + parent: 10 + type: Transform +- uid: 999 + type: CableApcExtension + components: + - pos: 8.5,5.5 + parent: 10 + type: Transform +- uid: 1000 + type: CableApcExtension + components: + - pos: 8.5,4.5 + parent: 10 + type: Transform +- uid: 1001 + type: CableApcExtension + components: + - pos: 8.5,3.5 + parent: 10 + type: Transform +- uid: 1002 + type: CableApcExtension + components: + - pos: 8.5,2.5 + parent: 10 + type: Transform +- uid: 1003 + type: CableApcExtension + components: + - pos: 8.5,1.5 + parent: 10 + type: Transform +- uid: 1004 + type: CableApcExtension + components: + - pos: 8.5,0.5 + parent: 10 + type: Transform +- uid: 1005 + type: CableApcExtension + components: + - pos: 8.5,-0.5 + parent: 10 + type: Transform +- uid: 1006 + type: CableApcExtension + components: + - pos: 8.5,-1.5 + parent: 10 + type: Transform +- uid: 1007 + type: CableApcExtension + components: + - pos: 8.5,-2.5 + parent: 10 + type: Transform +- uid: 1008 + type: CableApcExtension + components: + - pos: 8.5,-3.5 + parent: 10 + type: Transform +- uid: 1009 + type: CableApcExtension + components: + - pos: 8.5,-4.5 + parent: 10 + type: Transform +- uid: 1010 + type: CableApcExtension + components: + - pos: 8.5,-5.5 + parent: 10 + type: Transform +- uid: 1011 + type: CableApcExtension + components: + - pos: 8.5,-6.5 + parent: 10 + type: Transform +- uid: 1012 + type: CableApcExtension + components: + - pos: 8.5,-7.5 + parent: 10 + type: Transform +- uid: 1013 + type: CableApcExtension + components: + - pos: 8.5,-8.5 + parent: 10 + type: Transform +- uid: 1014 + type: CableApcExtension + components: + - pos: 8.5,-9.5 + parent: 10 + type: Transform +- uid: 1015 + type: CableApcExtension + components: + - pos: 8.5,-10.5 + parent: 10 + type: Transform +- uid: 1016 + type: CableApcExtension + components: + - pos: 8.5,-11.5 + parent: 10 + type: Transform +- uid: 1017 + type: CableApcExtension + components: + - pos: 8.5,-12.5 + parent: 10 + type: Transform +- uid: 1018 + type: CableApcExtension + components: + - pos: 8.5,-13.5 + parent: 10 + type: Transform +- uid: 1019 + type: CableApcExtension + components: + - pos: 8.5,-14.5 + parent: 10 + type: Transform +- uid: 1020 + type: CableApcExtension + components: + - pos: 8.5,-15.5 + parent: 10 + type: Transform +- uid: 1021 + type: CableApcExtension + components: + - pos: 8.5,-16.5 + parent: 10 + type: Transform +- uid: 1022 + type: CableApcExtension + components: + - pos: 9.5,9.5 + parent: 10 + type: Transform +- uid: 1023 + type: CableApcExtension + components: + - pos: 9.5,8.5 + parent: 10 + type: Transform +- uid: 1024 + components: + - pos: -14.968191,-14.713851 + parent: 9 + type: Transform + - index: 1 + type: MapGrid + - angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + type: Physics + - fixtures: [] + type: Fixtures + - gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + type: Gravity + - chunkCollection: {} + type: DecalGrid + - tiles: + -9,-1: 0 + -8,-1: 0 + -7,-1: 0 + -6,-1: 0 + -5,-1: 0 + -4,-1: 0 + -3,-1: 0 + -2,-1: 0 + -1,-1: 0 + -16,-1: 0 + -15,-1: 0 + -14,-1: 0 + -13,-1: 0 + -12,-1: 0 + -11,-1: 0 + -10,-1: 0 + -24,-1: 0 + -23,-1: 0 + -22,-1: 0 + -21,-1: 0 + -20,-1: 0 + -19,-1: 0 + -18,-1: 0 + -17,-1: 0 + -24,0: 0 + -24,1: 0 + -24,2: 0 + -24,3: 0 + -24,4: 0 + -24,5: 0 + -24,6: 0 + -24,7: 0 + -24,8: 0 + -24,9: 0 + -24,10: 0 + -24,11: 0 + -24,12: 0 + -24,13: 0 + -24,14: 0 + -24,15: 0 + -23,0: 0 + -23,1: 0 + -23,2: 0 + -23,3: 0 + -23,4: 0 + -23,5: 0 + -23,6: 0 + -23,7: 0 + -23,8: 0 + -23,9: 0 + -23,10: 0 + -23,11: 0 + -23,12: 0 + -23,13: 0 + -23,14: 0 + -23,15: 0 + -22,0: 0 + -22,1: 0 + -22,2: 0 + -22,3: 0 + -22,4: 0 + -22,5: 0 + -22,6: 0 + -22,7: 0 + -22,8: 0 + -22,9: 0 + -22,10: 0 + -22,11: 0 + -22,12: 0 + -22,13: 0 + -22,14: 0 + -22,15: 0 + -21,0: 0 + -21,1: 0 + -21,2: 0 + -21,3: 0 + -21,4: 0 + -21,5: 0 + -21,6: 0 + -21,7: 0 + -21,8: 0 + -21,9: 0 + -21,10: 0 + -21,11: 0 + -21,12: 0 + -21,13: 0 + -21,14: 0 + -21,15: 0 + -20,0: 0 + -20,1: 0 + -20,2: 0 + -20,3: 0 + -20,4: 0 + -20,5: 0 + -20,6: 0 + -20,7: 0 + -20,8: 0 + -20,9: 0 + -20,10: 0 + -20,11: 0 + -20,12: 0 + -20,13: 0 + -20,14: 0 + -20,15: 0 + -19,0: 0 + -19,1: 0 + -19,2: 0 + -19,3: 0 + -19,4: 0 + -19,5: 0 + -19,6: 0 + -19,7: 0 + -19,8: 0 + -19,9: 0 + -19,10: 0 + -19,11: 0 + -19,12: 0 + -19,13: 0 + -19,14: 0 + -19,15: 0 + -18,0: 0 + -18,1: 0 + -18,2: 0 + -18,3: 0 + -18,4: 0 + -18,5: 0 + -18,6: 0 + -18,7: 0 + -18,8: 0 + -18,9: 0 + -18,10: 0 + -18,11: 0 + -18,12: 0 + -18,13: 0 + -18,14: 0 + -18,15: 0 + -17,0: 0 + -17,1: 0 + -17,2: 0 + -17,3: 0 + -17,4: 0 + -17,5: 0 + -17,6: 0 + -17,7: 0 + -17,8: 0 + -17,9: 0 + -17,10: 0 + -17,11: 0 + -17,12: 0 + -17,13: 0 + -17,14: 0 + -17,15: 0 + -16,0: 0 + -16,1: 0 + -16,2: 0 + -16,3: 0 + -16,4: 0 + -16,5: 0 + -16,6: 0 + -16,7: 0 + -16,8: 0 + -16,9: 0 + -16,10: 0 + -16,11: 0 + -16,12: 0 + -16,13: 0 + -16,14: 0 + -16,15: 0 + -15,0: 0 + -15,1: 0 + -15,2: 0 + -15,3: 0 + -15,4: 0 + -15,5: 0 + -15,6: 0 + -15,7: 0 + -15,8: 0 + -15,9: 0 + -15,10: 0 + -15,11: 0 + -15,12: 0 + -15,13: 0 + -15,14: 0 + -15,15: 0 + -14,0: 0 + -14,1: 0 + -14,2: 0 + -14,3: 0 + -14,4: 0 + -14,5: 0 + -14,6: 0 + -14,7: 0 + -14,8: 0 + -14,9: 0 + -14,10: 0 + -14,11: 0 + -14,12: 0 + -14,13: 0 + -14,14: 0 + -14,15: 0 + -13,0: 0 + -13,1: 0 + -13,2: 0 + -13,3: 0 + -13,4: 0 + -13,5: 0 + -13,6: 0 + -13,7: 0 + -13,8: 0 + -13,9: 0 + -13,10: 0 + -13,11: 0 + -13,12: 0 + -13,13: 0 + -13,14: 0 + -13,15: 0 + -12,0: 0 + -12,1: 0 + -12,2: 0 + -12,3: 0 + -12,4: 0 + -12,5: 0 + -12,6: 0 + -12,7: 0 + -12,8: 0 + -12,9: 0 + -12,10: 0 + -12,11: 0 + -12,12: 0 + -12,13: 0 + -12,14: 0 + -12,15: 0 + -11,0: 0 + -11,1: 0 + -11,2: 0 + -11,3: 0 + -11,4: 0 + -11,5: 0 + -11,6: 0 + -11,7: 0 + -11,8: 0 + -11,9: 0 + -11,10: 0 + -11,11: 0 + -11,12: 0 + -11,13: 0 + -11,14: 0 + -11,15: 0 + -10,0: 0 + -10,1: 0 + -10,2: 0 + -10,3: 0 + -10,4: 0 + -10,5: 0 + -10,6: 0 + -10,7: 0 + -10,8: 0 + -10,9: 0 + -10,10: 0 + -10,11: 0 + -10,12: 0 + -10,13: 0 + -10,14: 0 + -10,15: 0 + -9,0: 0 + -9,1: 0 + -9,2: 0 + -9,3: 0 + -9,4: 0 + -9,5: 0 + -9,6: 0 + -9,7: 0 + -9,8: 0 + -9,9: 0 + -9,10: 0 + -9,11: 0 + -9,12: 0 + -9,13: 0 + -9,14: 0 + -9,15: 0 + -8,0: 0 + -8,1: 0 + -8,2: 0 + -8,3: 0 + -8,4: 0 + -8,5: 0 + -8,6: 0 + -8,7: 0 + -8,8: 0 + -8,9: 0 + -8,10: 0 + -8,11: 0 + -8,12: 0 + -8,13: 0 + -8,14: 0 + -8,15: 0 + -7,0: 0 + -7,1: 0 + -7,2: 0 + -7,3: 0 + -7,4: 0 + -7,5: 0 + -7,6: 0 + -7,7: 0 + -7,8: 0 + -7,9: 0 + -7,10: 0 + -7,11: 0 + -7,12: 0 + -7,13: 0 + -7,14: 0 + -7,15: 0 + -6,0: 0 + -6,1: 0 + -6,2: 0 + -6,3: 0 + -6,4: 0 + -6,5: 0 + -6,6: 0 + -6,7: 0 + -6,8: 0 + -6,9: 0 + -6,10: 0 + -6,11: 0 + -6,12: 0 + -6,13: 0 + -6,14: 0 + -6,15: 0 + -5,0: 0 + -5,1: 0 + -5,2: 0 + -5,3: 0 + -5,4: 0 + -5,5: 0 + -5,6: 0 + -5,7: 0 + -5,8: 0 + -5,9: 0 + -5,10: 0 + -5,11: 0 + -5,12: 0 + -5,13: 0 + -5,14: 0 + -5,15: 0 + -4,0: 0 + -4,1: 0 + -4,2: 0 + -4,3: 0 + -4,4: 0 + -4,5: 0 + -4,6: 0 + -4,7: 0 + -4,8: 0 + -4,9: 0 + -4,10: 0 + -4,11: 0 + -4,12: 0 + -4,13: 0 + -4,14: 0 + -4,15: 0 + -3,0: 0 + -3,1: 0 + -3,2: 0 + -3,3: 0 + -3,4: 0 + -3,5: 0 + -3,6: 0 + -3,7: 0 + -3,8: 0 + -3,9: 0 + -3,10: 0 + -3,11: 0 + -3,12: 0 + -3,13: 0 + -3,14: 0 + -3,15: 0 + -2,0: 0 + -2,1: 0 + -2,2: 0 + -2,3: 0 + -2,4: 0 + -2,5: 0 + -2,6: 0 + -2,7: 0 + -2,8: 0 + -2,9: 0 + -2,10: 0 + -2,11: 0 + -2,12: 0 + -2,13: 0 + -2,14: 0 + -2,15: 0 + -1,0: 0 + -1,1: 0 + -1,2: 0 + -1,3: 0 + -1,4: 0 + -1,5: 0 + -1,6: 0 + -1,7: 0 + -1,8: 0 + -1,9: 0 + -1,10: 0 + -1,11: 0 + -1,12: 0 + -1,13: 0 + -1,14: 0 + -1,15: 0 + -16,16: 0 + -16,17: 0 + -16,18: 0 + -16,19: 0 + -16,20: 0 + -16,21: 0 + -16,22: 0 + -16,23: 0 + -16,24: 0 + -16,25: 0 + -15,16: 0 + -15,17: 0 + -15,18: 0 + -15,19: 0 + -15,20: 0 + -15,21: 0 + -15,22: 0 + -15,23: 0 + -15,24: 0 + -15,25: 0 + -14,16: 0 + -14,17: 0 + -14,18: 0 + -14,19: 0 + -14,20: 0 + -14,21: 0 + -14,22: 0 + -14,23: 0 + -14,24: 0 + -14,25: 0 + -13,16: 0 + -13,17: 0 + -13,18: 0 + -13,19: 0 + -13,20: 0 + -13,21: 0 + -13,22: 0 + -13,23: 0 + -13,24: 0 + -13,25: 0 + -12,16: 0 + -12,17: 0 + -12,18: 0 + -12,19: 0 + -12,20: 0 + -12,21: 0 + -12,22: 0 + -12,23: 0 + -12,24: 0 + -12,25: 0 + -11,16: 0 + -11,17: 0 + -11,18: 0 + -11,19: 0 + -11,20: 0 + -11,21: 0 + -11,22: 0 + -11,23: 0 + -11,24: 0 + -11,25: 0 + -10,16: 0 + -10,17: 0 + -10,18: 0 + -10,19: 0 + -10,20: 0 + -10,21: 0 + -10,22: 0 + -10,23: 0 + -10,24: 0 + -10,25: 0 + -9,16: 0 + -9,17: 0 + -9,18: 0 + -9,19: 0 + -9,20: 0 + -9,21: 0 + -9,22: 0 + -9,23: 0 + -9,24: 0 + -9,25: 0 + -8,16: 0 + -8,17: 0 + -8,18: 0 + -8,19: 0 + -8,20: 0 + -8,21: 0 + -8,22: 0 + -8,23: 0 + -8,24: 0 + -8,25: 0 + -7,16: 0 + -7,17: 0 + -7,18: 0 + -7,19: 0 + -7,20: 0 + -7,21: 0 + -7,22: 0 + -7,23: 0 + -7,24: 0 + -7,25: 0 + -6,16: 0 + -6,17: 0 + -6,18: 0 + -6,19: 0 + -6,20: 0 + -6,21: 0 + -6,22: 0 + -6,23: 0 + -6,24: 0 + -6,25: 0 + -5,16: 0 + -5,17: 0 + -5,18: 0 + -5,19: 0 + -5,20: 0 + -5,21: 0 + -5,22: 0 + -5,23: 0 + -5,24: 0 + -5,25: 0 + -4,16: 0 + -4,17: 0 + -4,18: 0 + -4,19: 0 + -4,20: 0 + -4,21: 0 + -4,22: 0 + -4,23: 0 + -4,24: 0 + -4,25: 0 + -3,16: 0 + -3,17: 0 + -3,18: 0 + -3,19: 0 + -3,20: 0 + -3,21: 0 + -3,22: 0 + -3,23: 0 + -3,24: 0 + -3,25: 0 + -2,16: 0 + -2,17: 0 + -2,18: 0 + -2,19: 0 + -2,20: 0 + -2,21: 0 + -2,22: 0 + -2,23: 0 + -2,24: 0 + -2,25: 0 + -1,16: 0 + -1,17: 0 + -1,18: 0 + -1,19: 0 + -1,20: 0 + -1,21: 0 + -1,22: 0 + -1,23: 0 + -1,24: 0 + -1,25: 0 + -24,16: 0 + -24,17: 0 + -24,18: 0 + -24,19: 0 + -24,20: 0 + -24,21: 0 + -24,22: 0 + -24,23: 0 + -24,24: 0 + -24,25: 0 + -23,16: 0 + -23,17: 0 + -23,18: 0 + -23,19: 0 + -23,20: 0 + -23,21: 0 + -23,22: 0 + -23,23: 0 + -23,24: 0 + -23,25: 0 + -22,16: 0 + -22,17: 0 + -22,18: 0 + -22,19: 0 + -22,20: 0 + -22,21: 0 + -22,22: 0 + -22,23: 0 + -22,24: 0 + -22,25: 0 + -21,16: 0 + -21,17: 0 + -21,18: 0 + -21,19: 0 + -21,20: 0 + -21,21: 0 + -21,22: 0 + -21,23: 0 + -21,24: 0 + -21,25: 0 + -20,16: 0 + -20,17: 0 + -20,18: 0 + -20,19: 0 + -20,20: 0 + -20,21: 0 + -20,22: 0 + -20,23: 0 + -20,24: 0 + -20,25: 0 + -19,16: 0 + -19,17: 0 + -19,18: 0 + -19,19: 0 + -19,20: 0 + -19,21: 0 + -19,22: 0 + -19,23: 0 + -19,24: 0 + -19,25: 0 + -18,16: 0 + -18,17: 0 + -18,18: 0 + -18,19: 0 + -18,20: 0 + -18,21: 0 + -18,22: 0 + -18,23: 0 + -18,24: 0 + -18,25: 0 + -17,16: 0 + -17,17: 0 + -17,18: 0 + -17,19: 0 + -17,20: 0 + -17,21: 0 + -17,22: 0 + -17,23: 0 + -17,24: 0 + -17,25: 0 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + type: GridAtmosphere +- uid: 1025 + type: CableApcExtension + components: + - pos: 9.5,6.5 + parent: 10 + type: Transform +- uid: 1026 + type: CableApcExtension + components: + - pos: 10.5,0.5 + parent: 10 + type: Transform +- uid: 1027 + type: CableApcExtension + components: + - pos: 10.5,-0.5 + parent: 10 + type: Transform +- uid: 1028 + type: CableApcExtension + components: + - pos: 10.5,-1.5 + parent: 10 + type: Transform +- uid: 1029 + type: CableApcExtension + components: + - pos: 10.5,-2.5 + parent: 10 + type: Transform +- uid: 1030 + type: CableApcExtension + components: + - pos: 11.5,0.5 + parent: 10 + type: Transform +- uid: 1031 + type: CableApcExtension + components: + - pos: 11.5,-0.5 + parent: 10 + type: Transform +- uid: 1032 + type: CableApcExtension + components: + - pos: 11.5,-1.5 + parent: 10 + type: Transform +- uid: 1033 + type: CableApcExtension + components: + - pos: 11.5,-2.5 + parent: 10 + type: Transform +- uid: 1034 + type: CableApcExtension + components: + - pos: 11.5,-3.5 + parent: 10 + type: Transform +- uid: 1035 + type: CableApcExtension + components: + - pos: 11.5,-4.5 + parent: 10 + type: Transform +- uid: 1036 + type: CableApcExtension + components: + - pos: 11.5,-5.5 + parent: 10 + type: Transform +- uid: 1037 + type: CableApcExtension + components: + - pos: 11.5,-6.5 + parent: 10 + type: Transform +- uid: 1038 + type: CableApcExtension + components: + - pos: 11.5,-7.5 + parent: 10 + type: Transform +- uid: 1039 + type: CableApcExtension + components: + - pos: 11.5,-8.5 + parent: 10 + type: Transform +- uid: 1040 + type: CableApcExtension + components: + - pos: 11.5,-9.5 + parent: 10 + type: Transform +- uid: 1041 + type: CableApcExtension + components: + - pos: 11.5,-10.5 + parent: 10 + type: Transform +- uid: 1042 + type: CableApcExtension + components: + - pos: 11.5,-11.5 + parent: 10 + type: Transform +- uid: 1043 + type: CableApcExtension + components: + - pos: 11.5,-12.5 + parent: 10 + type: Transform +- uid: 1044 + type: CableApcExtension + components: + - pos: 11.5,-13.5 + parent: 10 + type: Transform +- uid: 1045 + type: CableApcExtension + components: + - pos: 11.5,-14.5 + parent: 10 + type: Transform +- uid: 1046 + type: CableApcExtension + components: + - pos: 11.5,-15.5 + parent: 10 + type: Transform +- uid: 1047 + type: CableApcExtension + components: + - pos: 11.5,-16.5 + parent: 10 + type: Transform +- uid: 1048 + type: CableApcExtension + components: + - pos: 12.5,9.5 + parent: 10 + type: Transform +- uid: 1049 + type: CableApcExtension + components: + - pos: 12.5,8.5 + parent: 10 + type: Transform +- uid: 1050 + type: CableApcExtension + components: + - pos: 12.5,7.5 + parent: 10 + type: Transform +- uid: 1051 + type: CableApcExtension + components: + - pos: 12.5,6.5 + parent: 10 + type: Transform +- uid: 1052 + type: CableApcExtension + components: + - pos: 12.5,5.5 + parent: 10 + type: Transform +- uid: 1053 + type: CableApcExtension + components: + - pos: 12.5,4.5 + parent: 10 + type: Transform +- uid: 1054 + type: CableApcExtension + components: + - pos: 12.5,3.5 + parent: 10 + type: Transform +- uid: 1055 + type: CableApcExtension + components: + - pos: 12.5,2.5 + parent: 10 + type: Transform +- uid: 1056 + type: CableApcExtension + components: + - pos: 12.5,1.5 + parent: 10 + type: Transform +- uid: 1057 + type: CableApcExtension + components: + - pos: 12.5,0.5 + parent: 10 + type: Transform +- uid: 1058 + type: CableApcExtension + components: + - pos: 12.5,-0.5 + parent: 10 + type: Transform +- uid: 1059 + type: CableApcExtension + components: + - pos: 12.5,-1.5 + parent: 10 + type: Transform +- uid: 1060 + type: CableApcExtension + components: + - pos: 12.5,-2.5 + parent: 10 + type: Transform +- uid: 1061 + type: CableApcExtension + components: + - pos: 12.5,-3.5 + parent: 10 + type: Transform +- uid: 1062 + type: CableApcExtension + components: + - pos: 12.5,-4.5 + parent: 10 + type: Transform +- uid: 1063 + type: CableApcExtension + components: + - pos: 12.5,-5.5 + parent: 10 + type: Transform +- uid: 1064 + type: CableApcExtension + components: + - pos: 12.5,-6.5 + parent: 10 + type: Transform +- uid: 1065 + type: CableApcExtension + components: + - pos: 12.5,-7.5 + parent: 10 + type: Transform +- uid: 1066 + type: CableApcExtension + components: + - pos: 12.5,-8.5 + parent: 10 + type: Transform +- uid: 1067 + type: CableApcExtension + components: + - pos: 10.5,-3.5 + parent: 10 + type: Transform +- uid: 1068 + type: CableApcExtension + components: + - pos: 10.5,-4.5 + parent: 10 + type: Transform +- uid: 1069 + type: CableApcExtension + components: + - pos: 10.5,-5.5 + parent: 10 + type: Transform +- uid: 1070 + type: CableApcExtension + components: + - pos: 10.5,-6.5 + parent: 10 + type: Transform +- uid: 1071 + type: CableApcExtension + components: + - pos: 10.5,-7.5 + parent: 10 + type: Transform +- uid: 1072 + type: CableApcExtension + components: + - pos: 10.5,-8.5 + parent: 10 + type: Transform +- uid: 1073 + type: CableApcExtension + components: + - pos: 10.5,-9.5 + parent: 10 + type: Transform +- uid: 1074 + type: CableApcExtension + components: + - pos: 10.5,-10.5 + parent: 10 + type: Transform +- uid: 1075 + type: CableApcExtension + components: + - pos: 10.5,-11.5 + parent: 10 + type: Transform +- uid: 1076 + type: CableApcExtension + components: + - pos: 10.5,-12.5 + parent: 10 + type: Transform +- uid: 1077 + type: CableApcExtension + components: + - pos: 10.5,-13.5 + parent: 10 + type: Transform +- uid: 1078 + type: CableApcExtension + components: + - pos: 10.5,-14.5 + parent: 10 + type: Transform +- uid: 1079 + type: CableApcExtension + components: + - pos: 10.5,-15.5 + parent: 10 + type: Transform +- uid: 1080 + type: CableApcExtension + components: + - pos: 10.5,-16.5 + parent: 10 + type: Transform +- uid: 1081 + type: CableApcExtension + components: + - pos: 11.5,9.5 + parent: 10 + type: Transform +- uid: 1082 + type: CableApcExtension + components: + - pos: 11.5,8.5 + parent: 10 + type: Transform +- uid: 1083 + type: CableApcExtension + components: + - pos: 11.5,7.5 + parent: 10 + type: Transform +- uid: 1084 + type: CableApcExtension + components: + - pos: 11.5,6.5 + parent: 10 + type: Transform +- uid: 1085 + type: CableApcExtension + components: + - pos: 11.5,5.5 + parent: 10 + type: Transform +- uid: 1086 + type: CableApcExtension + components: + - pos: 11.5,4.5 + parent: 10 + type: Transform +- uid: 1087 + type: CableApcExtension + components: + - pos: 11.5,3.5 + parent: 10 + type: Transform +- uid: 1088 + type: CableApcExtension + components: + - pos: 11.5,2.5 + parent: 10 + type: Transform +- uid: 1089 + type: CableApcExtension + components: + - pos: 11.5,1.5 + parent: 10 + type: Transform +- uid: 1090 + type: CableApcExtension + components: + - pos: 12.5,-9.5 + parent: 10 + type: Transform +- uid: 1091 + type: CableApcExtension + components: + - pos: 12.5,-10.5 + parent: 10 + type: Transform +- uid: 1092 + type: CableApcExtension + components: + - pos: 12.5,-11.5 + parent: 10 + type: Transform +- uid: 1093 + type: CableApcExtension + components: + - pos: 12.5,-12.5 + parent: 10 + type: Transform +- uid: 1094 + type: CableApcExtension + components: + - pos: 14.5,-13.5 + parent: 10 + type: Transform +- uid: 1095 + type: CableApcExtension + components: + - pos: 14.5,-14.5 + parent: 10 + type: Transform +- uid: 1096 + type: CableApcExtension + components: + - pos: 14.5,-15.5 + parent: 10 + type: Transform +- uid: 1097 + type: CableApcExtension + components: + - pos: 14.5,-16.5 + parent: 10 + type: Transform +- uid: 1098 + type: CableApcExtension + components: + - pos: 15.5,9.5 + parent: 10 + type: Transform +- uid: 1099 + type: CableApcExtension + components: + - pos: 15.5,8.5 + parent: 10 + type: Transform +- uid: 1100 + type: CableApcExtension + components: + - pos: 12.5,-15.5 + parent: 10 + type: Transform +- uid: 1101 + type: CableApcExtension + components: + - pos: 12.5,-16.5 + parent: 10 + type: Transform +- uid: 1102 + type: CableApcExtension + components: + - pos: 13.5,9.5 + parent: 10 + type: Transform +- uid: 1103 + type: CableApcExtension + components: + - pos: 13.5,8.5 + parent: 10 + type: Transform +- uid: 1104 + type: CableApcExtension + components: + - pos: 13.5,7.5 + parent: 10 + type: Transform +- uid: 1105 + type: CableApcExtension + components: + - pos: 13.5,6.5 + parent: 10 + type: Transform +- uid: 1106 + type: CableApcExtension + components: + - pos: 13.5,5.5 + parent: 10 + type: Transform +- uid: 1107 + type: CableApcExtension + components: + - pos: 13.5,4.5 + parent: 10 + type: Transform +- uid: 1108 + type: CableApcExtension + components: + - pos: 13.5,3.5 + parent: 10 + type: Transform +- uid: 1109 + type: CableApcExtension + components: + - pos: 13.5,2.5 + parent: 10 + type: Transform +- uid: 1110 + type: CableApcExtension + components: + - pos: 13.5,1.5 + parent: 10 + type: Transform +- uid: 1111 + type: CableApcExtension + components: + - pos: 13.5,0.5 + parent: 10 + type: Transform +- uid: 1112 + type: CableApcExtension + components: + - pos: 13.5,-0.5 + parent: 10 + type: Transform +- uid: 1113 + type: CableApcExtension + components: + - pos: 13.5,-1.5 + parent: 10 + type: Transform +- uid: 1114 + type: CableApcExtension + components: + - pos: 13.5,-2.5 + parent: 10 + type: Transform +- uid: 1115 + type: CableApcExtension + components: + - pos: 13.5,-3.5 + parent: 10 + type: Transform +- uid: 1116 + type: CableApcExtension + components: + - pos: 13.5,-4.5 + parent: 10 + type: Transform +- uid: 1117 + type: CableApcExtension + components: + - pos: 13.5,-5.5 + parent: 10 + type: Transform +- uid: 1118 + type: CableApcExtension + components: + - pos: 13.5,-6.5 + parent: 10 + type: Transform +- uid: 1119 + type: CableApcExtension + components: + - pos: 13.5,-7.5 + parent: 10 + type: Transform +- uid: 1120 + type: CableApcExtension + components: + - pos: 13.5,-8.5 + parent: 10 + type: Transform +- uid: 1121 + type: CableApcExtension + components: + - pos: 13.5,-9.5 + parent: 10 + type: Transform +- uid: 1122 + type: CableApcExtension + components: + - pos: 13.5,-10.5 + parent: 10 + type: Transform +- uid: 1123 + type: CableApcExtension + components: + - pos: 13.5,-11.5 + parent: 10 + type: Transform +- uid: 1124 + type: CableApcExtension + components: + - pos: 13.5,-12.5 + parent: 10 + type: Transform +- uid: 1125 + type: CableApcExtension + components: + - pos: 13.5,-13.5 + parent: 10 + type: Transform +- uid: 1126 + type: CableApcExtension + components: + - pos: 13.5,-14.5 + parent: 10 + type: Transform +- uid: 1127 + type: CableApcExtension + components: + - pos: 13.5,-15.5 + parent: 10 + type: Transform +- uid: 1128 + type: CableApcExtension + components: + - pos: 13.5,-16.5 + parent: 10 + type: Transform +- uid: 1129 + type: CableApcExtension + components: + - pos: 14.5,9.5 + parent: 10 + type: Transform +- uid: 1130 + type: CableApcExtension + components: + - pos: 14.5,8.5 + parent: 10 + type: Transform +- uid: 1131 + type: CableApcExtension + components: + - pos: 14.5,7.5 + parent: 10 + type: Transform +- uid: 1132 + type: CableApcExtension + components: + - pos: 14.5,6.5 + parent: 10 + type: Transform +- uid: 1133 + type: CableApcExtension + components: + - pos: 14.5,5.5 + parent: 10 + type: Transform +- uid: 1134 + type: CableApcExtension + components: + - pos: 14.5,4.5 + parent: 10 + type: Transform +- uid: 1135 + type: CableApcExtension + components: + - pos: 14.5,3.5 + parent: 10 + type: Transform +- uid: 1136 + type: CableApcExtension + components: + - pos: 14.5,2.5 + parent: 10 + type: Transform +- uid: 1137 + type: CableApcExtension + components: + - pos: 14.5,1.5 + parent: 10 + type: Transform +- uid: 1138 + type: CableApcExtension + components: + - pos: 14.5,0.5 + parent: 10 + type: Transform +- uid: 1139 + type: CableApcExtension + components: + - pos: 14.5,-0.5 + parent: 10 + type: Transform +- uid: 1140 + type: CableApcExtension + components: + - pos: 14.5,-1.5 + parent: 10 + type: Transform +- uid: 1141 + type: CableApcExtension + components: + - pos: 14.5,-2.5 + parent: 10 + type: Transform +- uid: 1142 + type: CableApcExtension + components: + - pos: 14.5,-3.5 + parent: 10 + type: Transform +- uid: 1143 + type: CableApcExtension + components: + - pos: 14.5,-4.5 + parent: 10 + type: Transform +- uid: 1144 + type: CableApcExtension + components: + - pos: 14.5,-5.5 + parent: 10 + type: Transform +- uid: 1145 + type: CableApcExtension + components: + - pos: 14.5,-6.5 + parent: 10 + type: Transform +- uid: 1146 + type: CableApcExtension + components: + - pos: 14.5,-7.5 + parent: 10 + type: Transform +- uid: 1147 + type: CableApcExtension + components: + - pos: 14.5,-8.5 + parent: 10 + type: Transform +- uid: 1148 + type: CableApcExtension + components: + - pos: 14.5,-9.5 + parent: 10 + type: Transform +- uid: 1149 + type: CableApcExtension + components: + - pos: 14.5,-10.5 + parent: 10 + type: Transform +- uid: 1150 + type: CableApcExtension + components: + - pos: 14.5,-11.5 + parent: 10 + type: Transform +- uid: 1151 + type: CableApcExtension + components: + - pos: 14.5,-12.5 + parent: 10 + type: Transform +- uid: 1152 + type: CableApcExtension + components: + - pos: 12.5,-13.5 + parent: 10 + type: Transform +- uid: 1153 + type: CableApcExtension + components: + - pos: 12.5,-14.5 + parent: 10 + type: Transform +- uid: 1154 + type: CableApcExtension + components: + - pos: 15.5,7.5 + parent: 10 + type: Transform +- uid: 1155 + type: CableApcExtension + components: + - pos: 15.5,6.5 + parent: 10 + type: Transform +- uid: 1156 + type: CableApcExtension + components: + - pos: 15.5,5.5 + parent: 10 + type: Transform +- uid: 1157 + type: CableApcExtension + components: + - pos: 15.5,4.5 + parent: 10 + type: Transform +- uid: 1158 + type: CableApcExtension + components: + - pos: 15.5,3.5 + parent: 10 + type: Transform +- uid: 1159 + type: CableApcExtension + components: + - pos: 15.5,2.5 + parent: 10 + type: Transform +- uid: 1160 + type: CableApcExtension + components: + - pos: 15.5,1.5 + parent: 10 + type: Transform +- uid: 1161 + type: CableApcExtension + components: + - pos: 15.5,0.5 + parent: 10 + type: Transform +- uid: 1162 + type: CableApcExtension + components: + - pos: 15.5,-0.5 + parent: 10 + type: Transform +- uid: 1163 + type: CableApcExtension + components: + - pos: 15.5,-1.5 + parent: 10 + type: Transform +- uid: 1164 + type: CableApcExtension + components: + - pos: 15.5,-2.5 + parent: 10 + type: Transform +- uid: 1165 + type: CableApcExtension + components: + - pos: 15.5,-3.5 + parent: 10 + type: Transform +- uid: 1166 + type: CableApcExtension + components: + - pos: 15.5,-4.5 + parent: 10 + type: Transform +- uid: 1167 + type: CableApcExtension + components: + - pos: 15.5,-5.5 + parent: 10 + type: Transform +- uid: 1168 + type: CableApcExtension + components: + - pos: 15.5,-6.5 + parent: 10 + type: Transform +- uid: 1169 + type: CableApcExtension + components: + - pos: 15.5,-7.5 + parent: 10 + type: Transform +- uid: 1170 + type: CableApcExtension + components: + - pos: 15.5,-8.5 + parent: 10 + type: Transform +- uid: 1171 + type: CableApcExtension + components: + - pos: 15.5,-9.5 + parent: 10 + type: Transform +- uid: 1172 + type: CableApcExtension + components: + - pos: 15.5,-10.5 + parent: 10 + type: Transform +- uid: 1173 + type: CableApcExtension + components: + - pos: 15.5,-11.5 + parent: 10 + type: Transform +- uid: 1174 + type: CableApcExtension + components: + - pos: 15.5,-12.5 + parent: 10 + type: Transform +- uid: 1175 + type: CableApcExtension + components: + - pos: 15.5,-13.5 + parent: 10 + type: Transform +- uid: 1176 + type: CableApcExtension + components: + - pos: 15.5,-14.5 + parent: 10 + type: Transform +- uid: 1177 + type: CableApcExtension + components: + - pos: 15.5,-15.5 + parent: 10 + type: Transform +- uid: 1178 + type: CableApcExtension + components: + - pos: 15.5,-16.5 + parent: 10 + type: Transform +- uid: 1179 + type: CableApcExtension + components: + - pos: 15.5,-0.5 + parent: 10 + type: Transform +- uid: 1180 + type: AlwaysPoweredWallLight + components: + - pos: 2.5,8.5 + parent: 10 + type: Transform +- uid: 1181 + type: AlwaysPoweredWallLight + components: + - pos: 5.5,5.5 + parent: 10 + type: Transform +- uid: 1182 + type: AlwaysPoweredWallLight + components: + - pos: -2.5,5.5 + parent: 10 + type: Transform +- uid: 1183 + type: AlwaysPoweredWallLight + components: + - pos: -3.5,3.5 + parent: 10 + type: Transform +- uid: 1184 + type: AlwaysPoweredWallLight + components: + - pos: -1.5,-3.5 + parent: 10 + type: Transform +- uid: 1185 + type: AlwaysPoweredWallLight + components: + - pos: -6.5,-2.5 + parent: 10 + type: Transform +- uid: 1186 + type: AlwaysPoweredWallLight + components: + - pos: -8.5,-9.5 + parent: 10 + type: Transform +- uid: 1187 + type: AlwaysPoweredWallLight + components: + - pos: -9.5,8.5 + parent: 10 + type: Transform +- uid: 1188 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: -5.5,2.5 + parent: 10 + type: Transform +- uid: 1189 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: -9.5,-3.5 + parent: 10 + type: Transform +- uid: 1190 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: -9.5,-1.5 + parent: 10 + type: Transform +- uid: 1191 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: -0.5,2.5 + parent: 10 + type: Transform +- uid: 1192 + type: AlwaysPoweredWallLight + components: + - pos: -1.5,-5.5 + parent: 10 + type: Transform +- uid: 1193 + type: AlwaysPoweredWallLight + components: + - pos: 6.5,-6.5 + parent: 10 + type: Transform +- uid: 1194 + type: AlwaysPoweredWallLight + components: + - pos: 8.5,-2.5 + parent: 10 + type: Transform +- uid: 1195 + type: AlwaysPoweredWallLight + components: + - pos: 12.5,-8.5 + parent: 10 + type: Transform +- uid: 1196 + type: AlwaysPoweredWallLight + components: + - pos: 12.5,-10.5 + parent: 10 + type: Transform +- uid: 1197 + type: AlwaysPoweredWallLight + components: + - pos: 7.5,-10.5 + parent: 10 + type: Transform +- uid: 1198 + type: AlwaysPoweredWallLight + components: + - pos: 3.5,-11.5 + parent: 10 + type: Transform +- uid: 1199 + type: AlwaysPoweredWallLight + components: + - pos: 2.5,-13.5 + parent: 10 + type: Transform +- uid: 1200 + type: AlwaysPoweredWallLight + components: + - pos: 0.5,-15.5 + parent: 10 + type: Transform +- uid: 1201 + type: AlwaysPoweredWallLight + components: + - pos: -7.5,-13.5 + parent: 10 + type: Transform +- uid: 1202 + type: AlwaysPoweredWallLight + components: + - pos: -6.5,-9.5 + parent: 10 + type: Transform +- uid: 1203 + type: AlwaysPoweredWallLight + components: + - pos: 4.5,-1.5 + parent: 10 + type: Transform +- uid: 1204 + type: AlwaysPoweredWallLight + components: + - pos: -1.5,8.5 + parent: 10 + type: Transform +- uid: 1205 + type: AlwaysPoweredWallLight + components: + - pos: -5.5,8.5 + parent: 10 + type: Transform +- uid: 1206 + type: AlwaysPoweredWallLight + components: + - pos: 13.5,8.5 + parent: 10 + type: Transform +- uid: 1207 + type: AlwaysPoweredWallLight + components: + - pos: 9.5,8.5 + parent: 10 + type: Transform +- uid: 1208 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: 9.5,3.5 + parent: 10 + type: Transform +- uid: 1209 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: 11.5,5.5 + parent: 10 + type: Transform +- uid: 1210 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: 14.5,3.5 + parent: 10 + type: Transform +- uid: 1211 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: 14.5,-0.5 + parent: 10 + type: Transform +- uid: 1212 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: 12.5,-0.5 + parent: 10 + type: Transform +- uid: 1213 + type: AlwaysPoweredWallLight + components: + - pos: 9.5,1.5 + parent: 10 + type: Transform +- uid: 1214 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: 12.5,-5.5 + parent: 10 + type: Transform +- uid: 1215 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: 3.5,-5.5 + parent: 10 + type: Transform +- uid: 1216 + type: AlwaysPoweredWallLight + components: + - pos: 1.5,-5.5 + parent: 10 + type: Transform +- uid: 1217 + type: AlwaysPoweredWallLight + components: + - pos: -5.5,-11.5 + parent: 10 + type: Transform +- uid: 1218 + type: AlwaysPoweredWallLight + components: + - pos: -8.5,-6.5 + parent: 10 + type: Transform +- uid: 1219 + type: AlwaysPoweredLightExterior + components: + - pos: 3.5,3.5 + parent: 10 + type: Transform +- uid: 1220 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: -4.5,-5.5 + parent: 10 + type: Transform +- uid: 1221 + type: AlwaysPoweredWallLight + components: + - rot: 3.141592653589793 rad + pos: 5.5,-3.5 + parent: 10 + type: Transform +- uid: 1222 + type: AlwaysPoweredWallLight + components: + - pos: 8.5,-4.5 + parent: 10 + type: Transform +- uid: 1223 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: 3.5,-3.5 + parent: 10 + type: Transform +- uid: 1224 + type: AlwaysPoweredWallLight + components: + - pos: -1.5,-1.5 + parent: 10 + type: Transform +- uid: 1225 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: 8.5,-6.5 + parent: 10 + type: Transform +- uid: 1226 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: 14.5,-6.5 + parent: 10 + type: Transform +- uid: 1227 + type: AlwaysPoweredWallLight + components: + - rot: 3.141592653589793 rad + pos: 12.5,-2.5 + parent: 10 + type: Transform +- uid: 1228 + type: AlwaysPoweredWallLight + components: + - pos: 1.5,0.5 + parent: 10 + type: Transform +- uid: 1229 + type: AlwaysPoweredWallLight + components: + - pos: 5.5,0.5 + parent: 10 + type: Transform +- uid: 1230 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: -8.5,3.5 + parent: 10 + type: Transform +- uid: 1231 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: -3.5,-11.5 + parent: 10 + type: Transform +- uid: 1232 + type: AlwaysPoweredWallLight + components: + - rot: 1.5707963267948966 rad + pos: -3.5,-14.5 + parent: 10 + type: Transform +- uid: 1233 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: -5.5,-13.5 + parent: 10 + type: Transform +- uid: 1234 + type: AlwaysPoweredWallLight + components: + - pos: 6.5,-14.5 + parent: 10 + type: Transform +- uid: 1235 + type: AlwaysPoweredWallLight + components: + - rot: -1.5707963267948966 rad + pos: 14.5,-13.5 + parent: 10 + type: Transform +- uid: 1236 + type: AlwaysPoweredWallLight + components: + - pos: 13.5,-15.5 + parent: 10 + type: Transform +- uid: 1237 + type: AlwaysPoweredWallLight + components: + - pos: 10.5,-10.5 + parent: 10 + type: Transform +- uid: 1238 + type: AlwaysPoweredLightLED + components: + - rot: -1.5707963267948966 rad + pos: -6.5,-0.5 + parent: 10 + type: Transform +- uid: 1239 + type: AlwaysPoweredLightLED + components: + - rot: -1.5707963267948966 rad + pos: 7.5,3.5 + parent: 10 + type: Transform +- uid: 1240 + type: AlwaysPoweredWallLight + components: + - pos: 12.5,1.5 + parent: 10 + type: Transform +- uid: 1241 + type: AlwaysPoweredWallLight + components: + - rot: 3.141592653589793 rad + pos: 14.5,7.5 + parent: 10 + type: Transform +- uid: 1242 + type: AlwaysPoweredWallLight + components: + - rot: 3.141592653589793 rad + pos: 12.5,7.5 + parent: 10 + type: Transform +- uid: 1243 + type: AlwaysPoweredWallLight + components: + - pos: -3.5,8.5 + parent: 10 + type: Transform +- uid: 1244 + type: AlwaysPoweredWallLight + components: + - rot: 3.141592653589793 rad + pos: -7.5,7.5 + parent: 10 + type: Transform +- uid: 1245 + type: PlushieLizard + components: + - pos: -6.5760937,-9.476422 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1246 + type: PlushieSpaceLizard + components: + - pos: 11.724928,5.429369 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1247 + type: PlushieSpaceLizard + components: + - pos: -3.543882,8.401216 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1248 + type: PlushieSpaceLizard + components: + - pos: 11.493368,-5.4476123 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1249 + type: PlushieSpaceLizard + components: + - pos: -2.5631237,3.462329 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1250 + type: PlushieSpaceLizard + components: + - pos: -8.535989,1.4602432 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1251 + type: PlushieSpaceLizard + components: + - pos: 14.597689,7.5790386 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1252 + type: PlushieSpaceLizard + components: + - pos: 7.4887495,8.204691 + parent: 10 + type: Transform + - canCollide: False + type: Physics +- uid: 1253 + type: AlwaysPoweredLightSodium + components: + - pos: 7.5,8.5 + parent: 10 + type: Transform +- uid: 1254 + type: APCHyperCapacity + components: + - pos: -2.5,4.5 + parent: 10 + type: Transform +- uid: 1255 + type: APCHyperCapacity + components: + - pos: -10.5,1.5 + parent: 10 + type: Transform +- uid: 1256 + type: APCHyperCapacity + components: + - pos: -7.5,2.5 + parent: 10 + type: Transform +- uid: 1257 + type: APCHyperCapacity + components: + - pos: 5.5,6.5 + parent: 10 + type: Transform +- uid: 1258 + type: DebugAPC + components: + - pos: -0.5,1.5 + parent: 10 + type: Transform +- uid: 1259 + type: DebugAPC + components: + - pos: -0.5,-2.5 + parent: 10 + type: Transform +- uid: 1260 + type: APCHyperCapacity + components: + - pos: -0.5,-8.5 + parent: 10 + type: Transform +- uid: 1261 + type: APCHyperCapacity + components: + - pos: -5.5,6.5 + parent: 10 + type: Transform +- uid: 1262 + type: APCHyperCapacity + components: + - pos: -7.5,-12.5 + parent: 10 + type: Transform +- uid: 1263 + type: APCHyperCapacity + components: + - pos: -8.5,-4.5 + parent: 10 + type: Transform +- uid: 1264 + type: APCHyperCapacity + components: + - pos: 12.5,6.5 + parent: 10 + type: Transform +- uid: 1265 + type: APCHyperCapacity + components: + - pos: 10.5,2.5 + parent: 10 + type: Transform +- uid: 1266 + type: APCHyperCapacity + components: + - pos: -6.5,-16.5 + parent: 10 + type: Transform +- uid: 1267 + type: APCHyperCapacity + components: + - pos: -0.5,-12.5 + parent: 10 + type: Transform +- uid: 1268 + type: APCHyperCapacity + components: + - pos: 6.5,-5.5 + parent: 10 + type: Transform +- uid: 1269 + type: APCHyperCapacity + components: + - pos: 5.5,-2.5 + parent: 10 + type: Transform +- uid: 1270 + type: APCHyperCapacity + components: + - pos: -10.5,2.5 + parent: 10 + type: Transform +- uid: 1271 + type: APCHyperCapacity + components: + - pos: -10.5,3.5 + parent: 10 + type: Transform +- uid: 1272 + type: APCHyperCapacity + components: + - pos: -10.5,4.5 + parent: 10 + type: Transform +- uid: 1273 + type: APCHyperCapacity + components: + - pos: -10.5,5.5 + parent: 10 + type: Transform +- uid: 1274 + type: APCHyperCapacity + components: + - pos: -7.5,4.5 + parent: 10 + type: Transform +- uid: 1275 + type: APCHyperCapacity + components: + - pos: -7.5,3.5 + parent: 10 + type: Transform +- uid: 1276 + type: APCHyperCapacity + components: + - pos: -7.5,1.5 + parent: 10 + type: Transform +- uid: 1277 + type: APCHyperCapacity + components: + - pos: -8.5,0.5 + parent: 10 + type: Transform +- uid: 1278 + type: APCHyperCapacity + components: + - pos: 5.5,4.5 + parent: 10 + type: Transform +- uid: 1279 + type: APCHyperCapacity + components: + - pos: -7.5,0.5 + parent: 10 + type: Transform +- uid: 1280 + type: APCHyperCapacity + components: + - pos: 3.5,4.5 + parent: 10 + type: Transform +- uid: 1281 + type: APCHyperCapacity + components: + - pos: 15.5,2.5 + parent: 10 + type: Transform +- uid: 1282 + type: APCHyperCapacity + components: + - pos: 9.5,-1.5 + parent: 10 + type: Transform +- uid: 1283 + type: APCHyperCapacity + components: + - pos: 10.5,-9.5 + parent: 10 + type: Transform +- uid: 1284 + type: APCHyperCapacity + components: + - pos: 7.5,-13.5 + parent: 10 + type: Transform +- uid: 1285 + type: APCHyperCapacity + components: + - pos: 2.5,-14.5 + parent: 10 + type: Transform +- uid: 1286 + type: APCHyperCapacity + components: + - pos: -4.5,-14.5 + parent: 10 + type: Transform +- uid: 1287 + type: APCHyperCapacity + components: + - pos: 0.5,-10.5 + parent: 10 + type: Transform +- uid: 1288 + type: APCHyperCapacity + components: + - pos: 1.5,-10.5 + parent: 10 + type: Transform +- uid: 1289 + type: APCHyperCapacity + components: + - pos: 2.5,-10.5 + parent: 10 + type: Transform +- uid: 1290 + type: PathfindPoint + components: + - pos: -4.5,21.5 + parent: 1024 + type: Transform +- uid: 1291 + type: PathfindPoint + components: + - pos: -19.5,3.5 + parent: 1024 + type: Transform +- uid: 1292 + type: PathfindPoint + components: + - pos: -4.5,3.5 + parent: 1024 + type: Transform +- uid: 1293 + type: PathfindPoint + components: + - pos: -19.5,21.5 + parent: 1024 + type: Transform +... diff --git a/Resources/Prototypes/Entities/Markers/npc.yml b/Resources/Prototypes/Entities/Markers/npc.yml new file mode 100644 index 0000000000..8f9840e84c --- /dev/null +++ b/Resources/Prototypes/Entities/Markers/npc.yml @@ -0,0 +1,8 @@ +- type: entity + id: PathfindPoint + parent: MarkerBase + name: pathfind point + components: + - type: NPCPathfindPoint + - type: Sprite + state: pink diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 3f691ea378..a25d764bf6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -139,10 +139,8 @@ Piercing: 1 - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - UnarmedAttackHostiles - - Idle + - type: HTN + rootTask: SimpleHostileCompound - type: AiFactionTag factions: - SimpleHostile @@ -665,9 +663,8 @@ Blunt: 10 - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: AiFactionTag factions: - SimpleHostile @@ -1147,9 +1144,8 @@ baseSprintSpeed : 7 - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: AiFactionTag factions: - Syndicate @@ -1312,10 +1308,8 @@ - Xeno - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: GhostTakeoverAvailable makeSentient: true name: ghost-role-information-giant-spider-name diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml b/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml index 421cb7d380..05970072a2 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/bear.yml @@ -6,10 +6,6 @@ components: - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles - type: AiFactionTag factions: - SimpleHostile diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index 894cab4984..cc8fe851e4 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -7,10 +7,8 @@ components: - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: AiFactionTag factions: - SimpleHostile @@ -154,3 +152,5 @@ makeSentient: true name: Sentient Carp description: Help the dragon flood the station with carps! + - type: HTN + rootTask: DragonCarpCompound diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml b/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml index 9c5ce19a41..37876c818e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/dummy_npcs.yml @@ -8,7 +8,3 @@ components: - type: InputMover - type: MobMover - - type: UtilityNPC - startingGear: PassengerGear - behaviorSets: - - PathingDummy diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml index 9bfbf528e2..f10b8d3d75 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml @@ -6,12 +6,6 @@ components: - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Clothing - - Hunger - - Thirst - - Idle - type: Loadout prototype: PassengerGear - type: AiFactionTag @@ -24,15 +18,13 @@ id: MobSpirate description: Yarr! components: + - type: AiFactionTag + factions: + - Syndicate - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Clothing - - Hunger - - Thirst - - Idle - - Spirate + - type: HTN + rootTask: RangedCombatCompound - type: entity parent: MobHuman diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index a38bb308ad..96195560cf 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -9,9 +9,6 @@ - FootstepSound - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - UnarmedAttackHostiles - type: AiFactionTag factions: - SimpleHostile diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index b2ef6daf01..08c6aab32c 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -84,9 +84,8 @@ Slash: 5 - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: AiFactionTag factions: - SimpleHostile diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index a195044155..cd4c6f7f0b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -165,10 +165,8 @@ baseSprintSpeed : 3.5 - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: Reactive groups: Flammable: [Touch] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 21f29bceef..d2605294c3 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -39,9 +39,6 @@ netsync: false - type: Recyclable safe: false - - type: UtilityNPC - behaviorSets: - - Idle - type: AiFactionTag factions: - SimpleNeutral @@ -103,9 +100,6 @@ - type: SpamEmitSound sound: collection: BikeHorn - - type: UtilityNPC - behaviorSets: - - Idle - type: Sprite drawdepth: Mobs sprite: Mobs/Silicon/Bots/honkbot.rsi @@ -148,9 +142,6 @@ drawdepth: Mobs sprite: Mobs/Silicon/Bots/cleanbot.rsi state: cleanbot - - type: UtilityNPC - behaviorSets: - - CleanBot - type: Drain range: 1 unitsDestroyedPerSecond: 6 @@ -163,6 +154,12 @@ solutions: drainBuffer: maxVol: 30 + - type: MovementSpeedModifier + baseWalkSpeed: 2 + baseSprintSpeed: 3 + - type: NoSlip + - type: HTN + rootTask: CleanbotCompound - type: DrainableSolution solution: drainBuffer - type: InteractionPopup @@ -177,15 +174,14 @@ name: medibot description: No substitute for a doctor, but better than nothing. components: - - type: UtilityNPC - behaviorSets: - - MediBot - type: Medibot - type: Sprite drawdepth: Mobs sprite: Mobs/Silicon/Bots/medibot.rsi state: medibot - type: Speech + - type: HTN + rootTask: MedibotCompound - type: Construction graph: MediBot node: bot diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml index 8b83d9298c..9fe6ec4570 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml @@ -14,11 +14,8 @@ Acidic: [Touch, Ingestion] - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - # - Clothing - - Idle - # No hunger and thirst for space mobs (need a way to eat station tiles for space carps) + - type: HTN + rootTask: IdleCompound - type: Input context: "human" - type: AiFactionTag @@ -161,11 +158,6 @@ components: - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - # - Hunger TODO: eating on the floor and fix weird AI endless stomach - # - Thirst - type: Hunger - type: Thirst - type: Barotrauma diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml index 52a4b9d5a5..f1b1b9f8f1 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml @@ -6,10 +6,6 @@ components: - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles - type: AiFactionTag factions: - SimpleHostile diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index bd5d0328c6..d3208bc570 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -12,10 +12,8 @@ canDisarm: true - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles + - type: HTN + rootTask: XenoCompound - type: Tool speed: 0.3 qualities: @@ -351,10 +349,8 @@ gender: male - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - - UnarmedAttackHostiles + - type: HTN + rootTask: SimpleHostileCompound - type: AiFactionTag factions: - Xeno diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 5a964b8269..734298c4b5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -9,6 +9,11 @@ makeSentient: true name: Space dragon description: Call in 3 carp rifts and take over this quadrant! You have only 5 minutes in between each rift before you will disappear. + - type: HTN + rootTask: XenoCompound + - type: AiFactionTag + factions: + - Dragon - type: Speech - type: CombatMode disarmAction: diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index d545b72218..f57b1ef57f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -45,9 +45,6 @@ Slash: 7 - type: InputMover - type: MobMover - - type: UtilityNPC - behaviorSets: - - Idle - type: AiFactionTag factions: - SimpleNeutral diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml new file mode 100644 index 0000000000..2e1a6bbb65 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml @@ -0,0 +1,77 @@ +- type: entity + id: WeaponTurretSyndicateBroken + name: ballistic turret (broken) + suffix: Syndicate + parent: BaseStructure + description: A ballistic machine gun auto-turret. + components: + - type: Clickable + - type: InteractionOutline + - type: Sprite + netsync: false + sprite: Objects/Weapons/Guns/turrets.rsi + drawdepth: WallMountedItems + layers: + - state: syndie_broken + +- type: entity + id: WeaponTurretSyndicate + name: ballistic turret + parent: WeaponTurretSyndicateBroken + components: + - type: Actions + - type: ContainerContainer + containers: + ballistic-ammo: !type:Container + - type: Sprite + layers: + - state: syndie_lethal + - type: InteractionPopup + interactDelay: 0.2 + successChance: 0.8 + interactSuccessString: petting-success-generic + interactFailureString: petting-failure-generic + interactSuccessSound: + path: /Audio/Effects/double_beep.ogg + - type: CombatMode + combatToggleAction: + enabled: false + autoPopulate: false + name: action-name-combat + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 300 + # TODO: Construction graph + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/metalbreak.ogg + - !type:SpawnEntitiesBehavior + spawn: + SheetSteel1: + min: 3 + max: 5 + - type: Gun + fireRate: 6 + selectedMode: FullAuto + availableModes: + - FullAuto + soundGunshot: /Audio/Weapons/Guns/Gunshots/gun_sentry.ogg + # TODO: Power ammo provider? + - type: BallisticAmmoProvider + proto: CartridgeCaselessRifle + capacity: 500 + - type: HTN + rootTask: TurretCompound + blackboard: + SoundTargetInLOS: !type:SoundPathSpecifier + path: /Audio/Effects/double_beep.ogg + - type: AiFactionTag + factions: + - Syndicate diff --git a/Resources/Prototypes/NPCs/attack.yml b/Resources/Prototypes/NPCs/attack.yml new file mode 100644 index 0000000000..7e64751786 --- /dev/null +++ b/Resources/Prototypes/NPCs/attack.yml @@ -0,0 +1,109 @@ +# -- Ranged -- +# Tries to shoot a target in LOS in range. +- type: htnCompound + id: TurretCompound + branches: + - tasks: + - id: PickRangedTargetPrimitive + - id: RangedAttackTargetPrimitive + - tasks: + - id: IdleSpinCompound + +# Tries to shoot a target at range. +- type: htnCompound + id: RangedCombatCompound + branches: + - tasks: + - id: PickRangedTargetPrimitive + - id: RangedAttackTargetCompound + +# Tries to ranged attack our target. +- type: htnCompound + id: RangedAttackTargetCompound + preconditions: + - !type:KeyExistsPrecondition + key: CombatTarget + branches: + # Keep hitting them if they're in LOS + - tasks: + - id: RangedAttackTargetPrimitive + + # Move to range and hit them + - tasks: + - id: MoveToCombatTargetPrimitive + - id: RangedAttackTargetPrimitive + + +- type: htnPrimitive + id: PickRangedTargetPrimitive + operator: !type:PickRangedTargetOperator + +# Attacks the specified target if they're in LOS. +- type: htnPrimitive + id: RangedAttackTargetPrimitive + operator: !type:RangedOperator + targetKey: CombatTarget + preconditions: + - !type:KeyExistsPrecondition + key: CombatTarget + - !type:TargetInRangePrecondition + targetKey: CombatTarget + # TODO: Non-scuffed + rangeKey: RangedRange + - !type:TargetInLOSPrecondition + targetKey: CombatTarget + rangeKey: RangedRange + + +# -- Melee -- +# Selects a target in melee and tries to attack it. +- type: htnCompound + id: MeleeCombatCompound + branches: + # Unarmed combat + - tasks: + - id: PickMeleeTargetPrimitive + - id: MeleeAttackTargetCompound + +# Tries to melee attack our target. +- type: htnCompound + id: MeleeAttackTargetCompound + preconditions: + - !type:KeyExistsPrecondition + key: CombatTarget + branches: + # Keep hitting them if they're in range + - tasks: + - id: MeleeAttackTargetPrimitive + + # Move to melee range and hit them + - tasks: + - id: MoveToCombatTargetPrimitive + - id: MeleeAttackTargetPrimitive + + +- type: htnPrimitive + id: PickMeleeTargetPrimitive + operator: !type:PickMeleeTargetOperator + +# Attacks the specified target if they're in range. +- type: htnPrimitive + id: MeleeAttackTargetPrimitive + operator: !type:MeleeOperator + targetKey: CombatTarget + preconditions: + - !type:KeyExistsPrecondition + key: CombatTarget + - !type:TargetInRangePrecondition + targetKey: CombatTarget + rangeKey: MeleeRange + +# Moves the owner into range of the combat target. +- type: htnPrimitive + id: MoveToCombatTargetPrimitive + operator: !type:MoveToOperator + pathfindInPlanning: true + removeKeyOnFinish: false + targetKey: CombatTargetCoordinates + pathfindKey: CombatTargetPathfind + rangeKey: MeleeRange diff --git a/Resources/Prototypes/NPCs/behavior_sets.yml b/Resources/Prototypes/NPCs/behavior_sets.yml deleted file mode 100644 index 801cb4d7bf..0000000000 --- a/Resources/Prototypes/NPCs/behavior_sets.yml +++ /dev/null @@ -1,57 +0,0 @@ -- type: behaviorSet - id: Clothing - actions: - - EquipAnyHeadExp - - EquipAnyOuterClothingExp - - EquipAnyGlovesExp - - EquipAnyShoesExp - - PickUpAnyNearbyHeadExp - - PickUpAnyNearbyOuterClothingExp - - PickUpAnyNearbyGlovesExp - - PickUpAnyNearbyShoesExp - -- type: behaviorSet - id: Hunger - actions: - - PickUpNearbyFoodExp - - UseFoodInInventoryExp - -- type: behaviorSet - id: Idle - actions: - - CloseLastEntityStorage - - WanderAndWait - -- type: behaviorSet - id: PathingDummy - actions: - - MoveRightAndLeftTen - -- type: behaviorSet - id: CleanBot - actions: - - BufferNearbyPuddlesExp - - WanderAndWait - -- type: behaviorSet - id: MediBot - actions: - - InjectNearbyExp - -- type: behaviorSet - id: Spirate - actions: - - EquipMeleeExp - - PickUpMeleeWeaponExp - - MeleeAttackNearbyExp - -- type: behaviorSet - id: Thirst - actions: - - PickUpNearbyDrinkExp - - UseDrinkInInventoryExp - -- type: behaviorSet - id: UnarmedAttackHostiles - actions: - - UnarmedAttackNearbyHostilesExp diff --git a/Resources/Prototypes/NPCs/cleanbot.yml b/Resources/Prototypes/NPCs/cleanbot.yml new file mode 100644 index 0000000000..97d64de659 --- /dev/null +++ b/Resources/Prototypes/NPCs/cleanbot.yml @@ -0,0 +1,31 @@ +- type: htnCompound + id: CleanbotCompound + branches: + - tasks: + - id: BufferNearbyPuddlesCompound + - tasks: + - id: IdleCompound + +# Picks a random puddle in range to move to and idle +- type: htnCompound + id: BufferNearbyPuddlesCompound + branches: + - tasks: + - id: PickPuddlePrimitive + - id: MoveToAccessiblePrimitive + - id: SetIdleTimePrimitive + - id: WaitIdleTimePrimitive + + +- type: htnPrimitive + id: PickPuddlePrimitive + operator: !type:PickAccessibleComponentOperator + rangeKey: BufferRange + targetKey: MovementTarget + component: Puddle + +- type: htnPrimitive + id: SetIdleTimePrimitive + operator: !type:SetFloatOperator + targetKey: IdleTime + amount: 3 diff --git a/Resources/Prototypes/NPCs/follow.yml b/Resources/Prototypes/NPCs/follow.yml new file mode 100644 index 0000000000..e505a45f6d --- /dev/null +++ b/Resources/Prototypes/NPCs/follow.yml @@ -0,0 +1,55 @@ +# There are limitations to this ATM. The pathfinder is too slow to check accessibility well +# and reachable only takes in an entity so sometimes the follow idle spot is outside of the follow range + +# Follows the specified target. +- type: htnCompound + id: FollowCompound + branches: + # Head to follow target + - tasks: + - id: FollowPrimitive + # Keep idling near follow target + - tasks: + - id: WaitFollowPrimitive + # Pick a new idle spot near the follow target + - tasks: + - id: PickAccessibleNearFollowPrimitive + - id: IdleNearFollowPrimitive + - id: RandomIdleTimePrimitive + - id: WaitFollowPrimitive + + +- type: htnPrimitive + id: WaitFollowPrimitive + operator: !type:WaitOperator + key: IdleTime + preconditions: + - !type:KeyExistsPrecondition + key: IdleTime + - !type:CoordinatesInRangePrecondition + targetKey: FollowTarget + rangeKey: FollowRange + +- type: htnPrimitive + id: PickAccessibleNearFollowPrimitive + operator: !type:PickAccessibleOperator + # originKey: FollowTarget + rangeKey: FollowCloseRange + targetKey: FollowIdleTarget + +- type: htnPrimitive + id: IdleNearFollowPrimitive + operator: !type:MoveToOperator + targetKey: FollowIdleTarget + +- type: htnPrimitive + id: FollowPrimitive + operator: !type:MoveToOperator + pathfindInPlanning: true + targetKey: FollowTarget + rangeKey: FollowCloseRange + removeKeyOnFinish: false + preconditions: + - !type:CoordinatesNotInRangePrecondition + targetKey: FollowTarget + rangeKey: FollowRange diff --git a/Resources/Prototypes/NPCs/idle.yml b/Resources/Prototypes/NPCs/idle.yml new file mode 100644 index 0000000000..d4e0d12988 --- /dev/null +++ b/Resources/Prototypes/NPCs/idle.yml @@ -0,0 +1,68 @@ +# Picks a random location for the NPC to move to and idle. +- type: htnCompound + id: IdleCompound + branches: + - tasks: + - id: WaitIdleTimePrimitive + # Pick a new spot and wait there. + - tasks: + - id: PickAccessiblePrimitive + - id: MoveToAccessiblePrimitive + - id: RandomIdleTimePrimitive + - id: WaitIdleTimePrimitive + preconditions: + - !type:BuckledPrecondition + isBuckled: false + - !type:PulledPrecondition + isPulled: false + +# Spin to a random rotation and idle. +- type: htnCompound + id: IdleSpinCompound + branches: + - tasks: + - id: WaitIdleTimePrimitive + # Pick a new angle and spin there + - tasks: + - id: PickRandomRotationPrimitive + - id: RotateToTargetPrimitive + - id: RandomIdleTimePrimitive + - id: WaitIdleTimePrimitive + + +# Primitives +- type: htnPrimitive + id: PickRandomRotationPrimitive + operator: !type:PickRandomRotationOperator + targetKey: RotateTarget + +- type: htnPrimitive + id: RotateToTargetPrimitive + operator: !type:RotateToTargetOperator + targetKey: RotateTarget + +- type: htnPrimitive + id: PickAccessiblePrimitive + operator: !type:PickAccessibleOperator + rangeKey: IdleRange + targetKey: MovementTarget + +- type: htnPrimitive + id: MoveToAccessiblePrimitive + operator: !type:MoveToOperator + pathfindInPlanning: false + +- type: htnPrimitive + id: RandomIdleTimePrimitive + operator: !type:RandomOperator + targetKey: IdleTime + minKey: MinimumIdleTime + maxKey: MaximumIdleTime + +- type: htnPrimitive + id: WaitIdleTimePrimitive + operator: !type:WaitOperator + key: IdleTime + preconditions: + - !type:KeyExistsPrecondition + key: IdleTime diff --git a/Resources/Prototypes/NPCs/medibot.yml b/Resources/Prototypes/NPCs/medibot.yml new file mode 100644 index 0000000000..493cc45508 --- /dev/null +++ b/Resources/Prototypes/NPCs/medibot.yml @@ -0,0 +1,41 @@ +- type: htnCompound + id: MedibotCompound + branches: + - tasks: + - id: InjectNearbyCompound + - tasks: + - id: IdleCompound + +- type: htnCompound + id: InjectNearbyCompound + branches: + - tasks: + - id: PickNearbyInjectablePrimitive + - id: MedibotSpeakPrimitive + - id: MoveToAccessiblePrimitive + - id: SetIdleTimePrimitive + - id: WaitIdleTimePrimitive + - id: MedibotInjectPrimitive + + +- type: htnPrimitive + id: MedibotSpeakPrimitive + operator: !type:SpeakOperator + speech: medibot-start-inject + +- type: htnPrimitive + id: PickNearbyInjectablePrimitive + operator: !type:PickNearbyInjectableOperator + targetKey: InjectTarget + targetMoveKey: MovementTarget + +- type: htnPrimitive + id: MedibotInjectPrimitive + operator: !type:MedibotInjectOperator + targetKey: InjectTarget + +- type: htnPrimitive + id: SetIdleTimePrimitive + operator: !type:SetFloatOperator + targetKey: IdleTime + amount: 3 diff --git a/Resources/Prototypes/NPCs/mob.yml b/Resources/Prototypes/NPCs/mob.yml new file mode 100644 index 0000000000..f7410ce4e9 --- /dev/null +++ b/Resources/Prototypes/NPCs/mob.yml @@ -0,0 +1,18 @@ +# Really you should write your own +- type: htnCompound + id: SimpleHostileCompound + branches: + - tasks: + - id: MeleeCombatCompound + - tasks: + - id: IdleCompound + +- type: htnCompound + id: DragonCarpCompound + branches: + - tasks: + - id: MeleeCombatCompound + - tasks: + - id: FollowCompound + - tasks: + - id: IdleCompound diff --git a/Resources/Prototypes/NPCs/test.yml b/Resources/Prototypes/NPCs/test.yml new file mode 100644 index 0000000000..f1e2399099 --- /dev/null +++ b/Resources/Prototypes/NPCs/test.yml @@ -0,0 +1,21 @@ +# Selects a random pathfind point and tries to move there. +- type: htnCompound + id: MoveToPathfindPointCompound + branches: + - tasks: + - id: PickPathfindPointPrimitive + - id: MoveToAccessiblePrimitive + + +- type: htnPrimitive + id: PickPathfindPointPrimitive + operator: !type:PickPathfindPointOperator + +- type: entity + id: MobPathfindDummy + name: Pathfind dummy + suffix: NPC + parent: MobXenoRouny + components: + - type: HTN + rootTask: MoveToPathfindPointCompound diff --git a/Resources/Prototypes/NPCs/xeno.yml b/Resources/Prototypes/NPCs/xeno.yml new file mode 100644 index 0000000000..fca253c29c --- /dev/null +++ b/Resources/Prototypes/NPCs/xeno.yml @@ -0,0 +1,7 @@ +- type: htnCompound + id: XenoCompound + branches: + - tasks: + - id: MeleeCombatCompound + - tasks: + - id: IdleCompound diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index 1a60f55763..9599aa1955 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -1,3 +1,10 @@ +- type: aiFaction + id: Dragon + hostile: + - NanoTrasen + - Syndicate + - Xeno + - type: aiFaction id: NanoTrasen hostile: diff --git a/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/meta.json new file mode 100644 index 0000000000..2cd1e9ecb6 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/42e0275d860551197687c571c689c270ff423288", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "syndie_off", + "directions": 8 + }, + { + "name": "syndie_lethal", + "directions": 8 + }, + { + "name": "syndie_broken" + }, + { + "name": "syndie_stun", + "directions": 8 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_broken.png b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_broken.png new file mode 100644 index 0000000000..492c91fb0e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_broken.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_lethal.png b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_lethal.png new file mode 100644 index 0000000000..4ad70b1d02 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_lethal.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_off.png b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_off.png new file mode 100644 index 0000000000..22823f2048 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_off.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_stun.png b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_stun.png new file mode 100644 index 0000000000..4ad70b1d02 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/turrets.rsi/syndie_stun.png differ