diff --git a/Content.Client/AME/AMEControllerVisualizerSystem.cs b/Content.Client/AME/AMEControllerVisualizerSystem.cs deleted file mode 100644 index c80963fc64..0000000000 --- a/Content.Client/AME/AMEControllerVisualizerSystem.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Content.Client.AME.Components; -using Robust.Client.GameObjects; -using static Content.Shared.AME.SharedAMEControllerComponent; - -namespace Content.Client.AME; - -public sealed class AMEControllerVisualizerSystem : VisualizerSystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentInit); - } - - private void OnComponentInit(EntityUid uid, AMEControllerVisualsComponent component, ComponentInit args) - { - if (TryComp(uid, out var sprite)) - { - sprite.LayerMapSet(AMEControllerVisualLayers.Display, sprite.AddLayerState("control_on")); - sprite.LayerSetVisible(AMEControllerVisualLayers.Display, false); - } - } - - protected override void OnAppearanceChange(EntityUid uid, AMEControllerVisualsComponent component, ref AppearanceChangeEvent args) - { - base.OnAppearanceChange(uid, component, ref args); - - if (args.Sprite == null - || !AppearanceSystem.TryGetData(uid, AMEControllerVisuals.DisplayState, out var state, args.Component)) - { - return; - } - - switch (state) - { - case "on": - args.Sprite.LayerSetState(AMEControllerVisualLayers.Display, "control_on"); - args.Sprite.LayerSetVisible(AMEControllerVisualLayers.Display, true); - break; - case "critical": - args.Sprite.LayerSetState(AMEControllerVisualLayers.Display, "control_critical"); - args.Sprite.LayerSetVisible(AMEControllerVisualLayers.Display, true); - break; - case "fuck": - args.Sprite.LayerSetState(AMEControllerVisualLayers.Display, "control_fuck"); - args.Sprite.LayerSetVisible(AMEControllerVisualLayers.Display, true); - break; - case "off": - args.Sprite.LayerSetVisible(AMEControllerVisualLayers.Display, false); - break; - default: - args.Sprite.LayerSetVisible(AMEControllerVisualLayers.Display, false); - break; - } - } -} - -public enum AMEControllerVisualLayers : byte -{ - Display -} - diff --git a/Content.Client/AME/AMEShieldingVisualizerSystem.cs b/Content.Client/AME/AMEShieldingVisualizerSystem.cs deleted file mode 100644 index 7e225cc4ad..0000000000 --- a/Content.Client/AME/AMEShieldingVisualizerSystem.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Content.Client.AME.Components; -using Robust.Client.GameObjects; -using static Content.Shared.AME.SharedAMEShieldComponent; - -namespace Content.Client.AME; - -public sealed class AMEShieldingVisualizerSystem : VisualizerSystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnComponentInit); - } - - private void OnComponentInit(EntityUid uid, AMEShieldingVisualsComponent component, ComponentInit args) - { - if (TryComp(uid, out var sprite)) - { - sprite.LayerMapSet(AMEShieldingVisualsLayer.Core, sprite.AddLayerState("core")); - sprite.LayerSetVisible(AMEShieldingVisualsLayer.Core, false); - sprite.LayerMapSet(AMEShieldingVisualsLayer.CoreState, sprite.AddLayerState("core_weak")); - sprite.LayerSetVisible(AMEShieldingVisualsLayer.CoreState, false); - } - } - - protected override void OnAppearanceChange(EntityUid uid, AMEShieldingVisualsComponent component, ref AppearanceChangeEvent args) - { - if (args.Sprite == null) - return; - - if (AppearanceSystem.TryGetData(uid, AMEShieldVisuals.Core, out var core, args.Component)) - { - if (core == "isCore") - { - args.Sprite.LayerSetState(AMEShieldingVisualsLayer.Core, "core"); - args.Sprite.LayerSetVisible(AMEShieldingVisualsLayer.Core, true); - - } - else - { - args.Sprite.LayerSetVisible(AMEShieldingVisualsLayer.Core, false); - } - } - - if (AppearanceSystem.TryGetData(uid, AMEShieldVisuals.CoreState, out var coreState, args.Component)) - { - switch (coreState) - { - case "weak": - args.Sprite.LayerSetState(AMEShieldingVisualsLayer.CoreState, "core_weak"); - args.Sprite.LayerSetVisible(AMEShieldingVisualsLayer.CoreState, true); - break; - case "strong": - args.Sprite.LayerSetState(AMEShieldingVisualsLayer.CoreState, "core_strong"); - args.Sprite.LayerSetVisible(AMEShieldingVisualsLayer.CoreState, true); - break; - case "off": - args.Sprite.LayerSetVisible(AMEShieldingVisualsLayer.CoreState, false); - break; - } - } - } -} - -public enum AMEShieldingVisualsLayer : byte -{ - Core, - CoreState, -} diff --git a/Content.Client/AME/Components/AMEControllerVisualsComponent.cs b/Content.Client/AME/Components/AMEControllerVisualsComponent.cs deleted file mode 100644 index 282ddb3576..0000000000 --- a/Content.Client/AME/Components/AMEControllerVisualsComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Robust.Shared.GameObjects; - -namespace Content.Client.AME.Components; - -[RegisterComponent] -public sealed class AMEControllerVisualsComponent : Component -{ -} diff --git a/Content.Client/AME/Components/AMEShieldingVisualsComponent.cs b/Content.Client/AME/Components/AMEShieldingVisualsComponent.cs deleted file mode 100644 index 4254d809f3..0000000000 --- a/Content.Client/AME/Components/AMEShieldingVisualsComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Robust.Shared.GameObjects; - -namespace Content.Client.AME.Components; - -[RegisterComponent] -public sealed class AMEShieldingVisualsComponent : Component -{ -} diff --git a/Content.Client/AME/UI/AMEControllerBoundUserInterface.cs b/Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs similarity index 68% rename from Content.Client/AME/UI/AMEControllerBoundUserInterface.cs rename to Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs index 45bcce40a2..a99a69747f 100644 --- a/Content.Client/AME/UI/AMEControllerBoundUserInterface.cs +++ b/Content.Client/Ame/UI/AmeControllerBoundUserInterface.cs @@ -1,17 +1,15 @@ -using Content.Shared.Chemistry.Dispenser; +using Content.Shared.Ame; using JetBrains.Annotations; using Robust.Client.GameObjects; -using Robust.Shared.GameObjects; -using static Content.Shared.AME.SharedAMEControllerComponent; -namespace Content.Client.AME.UI +namespace Content.Client.Ame.UI { [UsedImplicitly] - public sealed class AMEControllerBoundUserInterface : BoundUserInterface + public sealed class AmeControllerBoundUserInterface : BoundUserInterface { - private AMEWindow? _window; + private AmeWindow? _window; - public AMEControllerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) + public AmeControllerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) { } @@ -19,7 +17,7 @@ namespace Content.Client.AME.UI { base.Open(); - _window = new AMEWindow(this); + _window = new AmeWindow(this); _window.OnClose += Close; _window.OpenCentered(); } @@ -35,11 +33,11 @@ namespace Content.Client.AME.UI { base.UpdateState(state); - var castState = (AMEControllerBoundUserInterfaceState) state; + var castState = (AmeControllerBoundUserInterfaceState) state; _window?.UpdateState(castState); //Update window state } - public void ButtonPressed(UiButton button, int dispenseIndex = -1) + public void ButtonPressed(UiButton button) { SendMessage(new UiButtonPressedMessage(button)); } diff --git a/Content.Client/AME/UI/AMEWindow.xaml b/Content.Client/Ame/UI/AmeWindow.xaml similarity index 100% rename from Content.Client/AME/UI/AMEWindow.xaml rename to Content.Client/Ame/UI/AmeWindow.xaml diff --git a/Content.Client/AME/UI/AMEWindow.xaml.cs b/Content.Client/Ame/UI/AmeWindow.xaml.cs similarity index 83% rename from Content.Client/AME/UI/AMEWindow.xaml.cs rename to Content.Client/Ame/UI/AmeWindow.xaml.cs index da7abc7587..0ad8880bec 100644 --- a/Content.Client/AME/UI/AMEWindow.xaml.cs +++ b/Content.Client/Ame/UI/AmeWindow.xaml.cs @@ -1,20 +1,15 @@ using Content.Client.UserInterface; +using Content.Shared.Ame; using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using static Content.Shared.AME.SharedAMEControllerComponent; -namespace Content.Client.AME.UI +namespace Content.Client.Ame.UI { [GenerateTypedNameReferences] - public sealed partial class AMEWindow : DefaultWindow + public sealed partial class AmeWindow : DefaultWindow { - public AMEWindow(AMEControllerBoundUserInterface ui) + public AmeWindow(AmeControllerBoundUserInterface ui) { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); @@ -31,7 +26,7 @@ namespace Content.Client.AME.UI /// State data sent by the server. public void UpdateState(BoundUserInterfaceState state) { - var castState = (AMEControllerBoundUserInterfaceState) state; + var castState = (AmeControllerBoundUserInterfaceState) state; // Disable all buttons if not powered if (Contents.Children != null) diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 1c9df27d33..3b7baa0a48 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -21,7 +21,7 @@ using Content.Client.Stylesheets; using Content.Client.Viewport; using Content.Client.Voting; using Content.Shared.Administration; -using Content.Shared.AME; +using Content.Shared.Ame; using Content.Shared.Gravity; using Content.Shared.Localizations; using Robust.Client; @@ -91,7 +91,7 @@ namespace Content.Client.Entry // Do not add to these, they are legacy. _componentFactory.RegisterClass(); - _componentFactory.RegisterClass(); + _componentFactory.RegisterClass(); // Do not add to the above, they are legacy _prototypeManager.RegisterIgnore("utilityQuery"); diff --git a/Content.Server/AME/AMENodeGroup.cs b/Content.Server/AME/AMENodeGroup.cs deleted file mode 100644 index 444d3cc47b..0000000000 --- a/Content.Server/AME/AMENodeGroup.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System.Linq; -using Content.Server.Administration.Systems; -using Content.Server.AME.Components; -using Content.Server.Chat.Managers; -using Content.Server.Explosion.EntitySystems; -using Content.Server.NodeContainer.NodeGroups; -using Content.Server.NodeContainer.Nodes; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Random; - -namespace Content.Server.AME -{ - /// - /// Node group class for handling the Antimatter Engine's console and parts. - /// - [NodeGroup(NodeGroupID.AMEngine)] - public sealed class AMENodeGroup : BaseNodeGroup - { - /// - /// The AME controller which is currently in control of this node group. - /// This could be tracked a few different ways, but this is most convenient, - /// since any part connected to the node group can easily find the master. - /// - [ViewVariables] - private AMEControllerComponent? _masterController; - - [Dependency] private readonly IChatManager _chat = default!; - [Dependency] private readonly IEntityManager _entMan = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - - public AMEControllerComponent? MasterController => _masterController; - - private readonly List _cores = new(); - - public int CoreCount => _cores.Count; - - public override void LoadNodes(List groupNodes) - { - base.LoadNodes(groupNodes); - - MapGridComponent? grid = null; - - foreach (var node in groupNodes) - { - var nodeOwner = node.Owner; - if (_entMan.TryGetComponent(nodeOwner, out AMEShieldComponent? shield)) - { - var xform = _entMan.GetComponent(nodeOwner); - if (xform.GridUid != grid?.Owner && !_mapManager.TryGetGrid(xform.GridUid, out grid)) - continue; - - if (grid == null) - continue; - - var nodeNeighbors = grid.GetCellsInSquareArea(xform.Coordinates, 1) - .Where(entity => entity != nodeOwner && _entMan.HasComponent(entity)); - - if (nodeNeighbors.Count() >= 8) - { - _cores.Add(shield); - shield.SetCore(); - // Core visuals will be updated later. - } - else - { - shield.UnsetCore(); - } - } - } - - // Separate to ensure core count is correctly updated. - foreach (var node in groupNodes) - { - var nodeOwner = node.Owner; - if (_entMan.TryGetComponent(nodeOwner, out AMEControllerComponent? controller)) - { - if (_masterController == null) - { - // Has to be the first one, as otherwise IsMasterController will return true on them all for this first update. - _masterController = controller; - } - controller.OnAMENodeGroupUpdate(); - } - } - - UpdateCoreVisuals(); - } - - public void UpdateCoreVisuals() - { - var injectionAmount = 0; - var injecting = false; - - if (_masterController != null) - { - injectionAmount = _masterController.InjectionAmount; - injecting = _masterController.Injecting; - } - - var injectionStrength = CoreCount > 0 ? injectionAmount / CoreCount : 0; - - foreach (AMEShieldComponent core in _cores) - { - core.UpdateCoreVisuals(injectionStrength, injecting); - } - } - - public float InjectFuel(int fuel, out bool overloading) - { - overloading = false; - if(fuel > 0 && CoreCount > 0) - { - var safeFuelLimit = CoreCount * 2; - if (fuel > safeFuelLimit) - { - // The AME is being overloaded. - // Note about these maths: I would assume the general idea here is to make larger engines less safe to overload. - // In other words, yes, those are supposed to be CoreCount, not safeFuelLimit. - var instability = 0; - var overloadVsSizeResult = fuel - CoreCount; - - // fuel > safeFuelLimit: Slow damage. Can safely run at this level for burst periods if the engine is small and someone is keeping an eye on it. - if (_random.Prob(0.5f)) - instability = 1; - // overloadVsSizeResult > 5: - if (overloadVsSizeResult > 5) - instability = 5; - // overloadVsSizeResult > 10: This will explode in at most 5 injections. - if (overloadVsSizeResult > 10) - instability = 20; - - // Apply calculated instability - if (instability != 0) - { - overloading = true; - var integrityCheck = 100; - foreach(AMEShieldComponent core in _cores) - { - var oldIntegrity = core.CoreIntegrity; - core.CoreIntegrity -= instability; - - if (oldIntegrity > 95 - && core.CoreIntegrity <= 95 - && core.CoreIntegrity < integrityCheck) - integrityCheck = core.CoreIntegrity; - } - - // Admin alert - if (integrityCheck != 100 && _masterController != null) - _chat.SendAdminAlert($"AME overloading: {_entMan.ToPrettyString(_masterController.Owner)}"); - } - } - // Note the float conversions. The maths will completely fail if not done using floats. - // Oh, and don't ever stuff the result of this in an int. Seriously. - return (((float) fuel) / CoreCount) * fuel * 20000; - } - return 0; - } - - public int GetTotalStability() - { - if(CoreCount < 1) { return 100; } - var stability = 0; - - foreach(AMEShieldComponent core in _cores) - { - stability += core.CoreIntegrity; - } - - stability = stability / CoreCount; - - return stability; - } - - public void ExplodeCores() - { - if(_cores.Count < 1 || MasterController == null) { return; } - - float radius = 0; - - /* - * todo: add an exact to the shielding and make this find the core closest to the controller - * so they chain explode, after helpers have been added to make it not cancer - */ - - foreach (AMEShieldComponent core in _cores) - { - radius += MasterController.InjectionAmount; - } - - radius *= 2; - radius = Math.Min(radius, 8); - EntitySystem.Get().TriggerExplosive(MasterController.Owner, radius: radius, delete: false); - } - } -} diff --git a/Content.Server/AME/AMESystem.cs b/Content.Server/AME/AMESystem.cs deleted file mode 100644 index 3bc9956d60..0000000000 --- a/Content.Server/AME/AMESystem.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Linq; -using Content.Server.Administration.Logs; -using Content.Server.AME.Components; -using Content.Server.Popups; -using Content.Server.Power.Components; -using Content.Server.Tools; -using Content.Shared.Database; -using Content.Shared.Hands.Components; -using Content.Shared.Interaction; -using Content.Shared.Popups; -using JetBrains.Annotations; -using Robust.Shared.Audio; -using Robust.Shared.Map; -using Robust.Shared.Player; - -namespace Content.Server.AME -{ - [UsedImplicitly] - public sealed partial class AMESystem : EntitySystem - { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly ToolSystem _toolSystem = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - private float _accumulatedFrameTime; - - private const float UpdateCooldown = 10f; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnAMEPowerChange); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnPartInteractUsing); - - InitializeFuel(); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - // TODO: Won't exactly work with replays I guess? - _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime >= UpdateCooldown) - { - foreach (var comp in EntityManager.EntityQuery()) - { - comp.OnUpdate(frameTime); - } - _accumulatedFrameTime -= UpdateCooldown; - } - } - - private static void OnAMEPowerChange(EntityUid uid, AMEControllerComponent component, ref PowerChangedEvent args) - { - component.UpdateUserInterface(); - } - - private void OnInteractUsing(EntityUid uid, AMEControllerComponent component, InteractUsingEvent args) - { - if (!TryComp(args.User, out HandsComponent? hands)) - { - _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-no-hands-text"), uid, args.User); - return; - } - - if (HasComp(args.Used)) - { - if (component.HasJar) - { - _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-already-has-jar"), uid, args.User); - } - - else - { - component.JarSlot.Insert(args.Used); - _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-success"), uid, - args.User, PopupType.Medium); - component.UpdateUserInterface(); - } - } - else - { - _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-fail"), uid, args.User); - } - } - - private void OnPartInteractUsing(EntityUid uid, AMEPartComponent component, InteractUsingEvent args) - { - if (!HasComp(args.User)) - { - _popupSystem.PopupEntity(Loc.GetString("ame-part-component-interact-using-no-hands"), uid, args.User); - return; - } - - if (!_toolSystem.HasQuality(args.Used, component.QualityNeeded)) - return; - - if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridUid(EntityManager), out var mapGrid)) - return; // No AME in space. - - var snapPos = mapGrid.TileIndicesFor(args.ClickLocation); - if (mapGrid.GetAnchoredEntities(snapPos).Any(sc => HasComp(sc))) - { - _popupSystem.PopupEntity(Loc.GetString("ame-part-component-shielding-already-present"), uid, args.User); - return; - } - - var ent = EntityManager.SpawnEntity("AMEShielding", mapGrid.GridTileToLocal(snapPos)); - - _adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(args.User):player} unpacked {ToPrettyString(ent)} at {Transform(ent).Coordinates} from {ToPrettyString(uid)}"); - - SoundSystem.Play(component.UnwrapSound.GetSound(), Filter.Pvs(uid), uid); - - EntityManager.QueueDeleteEntity(uid); - } - } -} diff --git a/Content.Server/AME/Components/AMEControllerComponent.cs b/Content.Server/AME/Components/AMEControllerComponent.cs deleted file mode 100644 index 4285ff5fbd..0000000000 --- a/Content.Server/AME/Components/AMEControllerComponent.cs +++ /dev/null @@ -1,298 +0,0 @@ -using System.Linq; -using Content.Server.Administration.Logs; -using Content.Server.Administration.Systems; -using Content.Server.Chat.Managers; -using Content.Server.Mind.Components; -using Content.Server.NodeContainer; -using Content.Server.Power.Components; -using Content.Server.UserInterface; -using Content.Shared.AME; -using Content.Shared.Database; -using Content.Shared.Hands.EntitySystems; -using Robust.Server.GameObjects; -using Robust.Shared.Audio; -using Robust.Shared.Containers; -using Robust.Shared.Player; - -namespace Content.Server.AME.Components -{ - [RegisterComponent] - public sealed class AMEControllerComponent : SharedAMEControllerComponent - { - [Dependency] private readonly IEntityManager _entities = default!; - [Dependency] private readonly IEntitySystemManager _sysMan = default!; - [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IChatManager _chat = default!; - - [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(AMEControllerUiKey.Key); - private bool _injecting; - [ViewVariables] public bool Injecting => _injecting; - [ViewVariables] public int InjectionAmount; - - private AppearanceComponent? _appearance; - private PowerSupplierComponent? _powerSupplier; - [DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); - [DataField("injectSound")] private SoundSpecifier _injectSound = new SoundPathSpecifier("/Audio/Effects/bang.ogg"); - - private bool Powered => !_entities.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered; - - [ViewVariables] - private int _stability = 100; - - public ContainerSlot JarSlot = default!; - [ViewVariables] public bool HasJar => JarSlot.ContainedEntity != null; - - protected override void Initialize() - { - base.Initialize(); - - if (UserInterface != null) - { - UserInterface.OnReceiveMessage += OnUiReceiveMessage; - } - - _entities.TryGetComponent(Owner, out _appearance); - - _entities.TryGetComponent(Owner, out _powerSupplier); - - _injecting = false; - InjectionAmount = 2; - // TODO: Fix this bad name. I'd update maps but then people get mad. - JarSlot = ContainerHelpers.EnsureContainer(Owner, $"AMEController-fuelJarContainer"); - } - - internal void OnUpdate(float frameTime) - { - if (!_injecting) - { - return; - } - - var group = GetAMENodeGroup(); - - if (group == null) - { - return; - } - - if (JarSlot.ContainedEntity is not {Valid: true} jar) - return; - - _entities.TryGetComponent(jar, out var fuelJar); - if (fuelJar != null && _powerSupplier != null) - { - var availableInject = fuelJar.FuelAmount >= InjectionAmount ? InjectionAmount : fuelJar.FuelAmount; - _powerSupplier.MaxSupply = group.InjectFuel(availableInject, out var overloading); - fuelJar.FuelAmount -= availableInject; - InjectSound(overloading); - UpdateUserInterface(); - } - - _stability = group.GetTotalStability(); - - UpdateDisplay(_stability); - - if (_stability <= 0) { group.ExplodeCores(); } - - } - - // Used to update core count - public void OnAMENodeGroupUpdate() - { - UpdateUserInterface(); - } - - private AMEControllerBoundUserInterfaceState GetUserInterfaceState() - { - if (JarSlot.ContainedEntity is not {Valid: true} jar) - { - return new AMEControllerBoundUserInterfaceState(Powered, IsMasterController(), false, HasJar, 0, InjectionAmount, GetCoreCount()); - } - - var jarComponent = _entities.GetComponent(jar); - return new AMEControllerBoundUserInterfaceState(Powered, IsMasterController(), _injecting, HasJar, jarComponent.FuelAmount, InjectionAmount, GetCoreCount()); - } - - /// - /// Checks whether the player entity is able to use the controller. - /// - /// The player entity. - /// Returns true if the entity can use the controller, and false if it cannot. - private bool PlayerCanUseController(EntityUid playerEntity, bool needsPower = true) - { - //Need player entity to check if they are still able to use the dispenser - if (playerEntity == default) - return false; - - //Check if device is powered - if (needsPower && !Powered) - return false; - - return true; - } - - public void UpdateUserInterface() - { - var state = GetUserInterfaceState(); - UserInterface?.SetState(state); - } - - /// - /// Handles ui messages from the client. For things such as button presses - /// which interact with the world and require server action. - /// - /// A user interface message from the client. - private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj) - { - if (obj.Session.AttachedEntity is not {Valid: true} player) - { - return; - } - - var msg = (UiButtonPressedMessage) obj.Message; - var needsPower = msg.Button switch - { - UiButton.Eject => false, - _ => true, - }; - - if (!PlayerCanUseController(player, needsPower)) - return; - - switch (msg.Button) - { - case UiButton.Eject: - TryEject(player); - break; - case UiButton.ToggleInjection: - ToggleInjection(); - break; - case UiButton.IncreaseFuel: - InjectionAmount += 2; - break; - case UiButton.DecreaseFuel: - InjectionAmount = InjectionAmount > 0 ? InjectionAmount -= 2 : 0; - break; - } - - // Logging - _entities.TryGetComponent(player, out MindContainerComponent? mindContainerComponent); - if (mindContainerComponent != null) - { - var humanReadableState = _injecting ? "Inject" : "Not inject"; - - if (msg.Button == UiButton.IncreaseFuel || msg.Button == UiButton.DecreaseFuel) - _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindContainerComponent.Owner):player} has set the AME to inject {InjectionAmount} while set to {humanReadableState}"); - - if (msg.Button == UiButton.ToggleInjection) - _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindContainerComponent.Owner):player} has set the AME to {humanReadableState}"); - - // Admin alert - if (GetCoreCount() * 2 == InjectionAmount - 2 && msg.Button == UiButton.IncreaseFuel) - _chat.SendAdminAlert(player, $"increased AME over safe limit to {InjectionAmount}", mindContainerComponent); - } - - GetAMENodeGroup()?.UpdateCoreVisuals(); - - UpdateUserInterface(); - ClickSound(); - } - - private void TryEject(EntityUid user) - { - if (!HasJar || _injecting) - return; - - if (JarSlot.ContainedEntity is not {Valid: true} jar) - return; - - JarSlot.Remove(jar); - UpdateUserInterface(); - - _sysMan.GetEntitySystem().PickupOrDrop(user, jar); - } - - private void ToggleInjection() - { - if (!_injecting) - { - _appearance?.SetData(AMEControllerVisuals.DisplayState, "on"); - } - else - { - _appearance?.SetData(AMEControllerVisuals.DisplayState, "off"); - if (_powerSupplier != null) - { - _powerSupplier.MaxSupply = 0; - } - } - _injecting = !_injecting; - UpdateUserInterface(); - } - - - private void UpdateDisplay(int stability) - { - if (_appearance == null) { return; } - - _appearance.TryGetData(AMEControllerVisuals.DisplayState, out var state); - - var newState = "on"; - if (stability < 50) { newState = "critical"; } - if (stability < 10) { newState = "fuck"; } - - if (state != newState) - { - _appearance?.SetData(AMEControllerVisuals.DisplayState, newState); - } - - } - - private AMENodeGroup? GetAMENodeGroup() - { - _entities.TryGetComponent(Owner, out NodeContainerComponent? nodeContainer); - - var engineNodeGroup = nodeContainer?.Nodes.Values - .Select(node => node.NodeGroup) - .OfType() - .FirstOrDefault(); - - return engineNodeGroup; - } - - private bool IsMasterController() - { - if (GetAMENodeGroup()?.MasterController == this) - { - return true; - } - - return false; - } - - private int GetCoreCount() - { - var coreCount = 0; - var group = GetAMENodeGroup(); - - if (group != null) - { - coreCount = group.CoreCount; - } - - return coreCount; - } - - - private void ClickSound() - { - SoundSystem.Play(_clickSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default.WithVolume(-2f)); - } - - private void InjectSound(bool overloading) - { - SoundSystem.Play(_injectSound.GetSound(), Filter.Pvs(Owner), Owner, AudioParams.Default.WithVolume(overloading ? 10f : 0f)); - } - } - -} diff --git a/Content.Server/AME/Components/AMEFuelContainerComponent.cs b/Content.Server/AME/Components/AMEFuelContainerComponent.cs deleted file mode 100644 index e3ef04fb26..0000000000 --- a/Content.Server/AME/Components/AMEFuelContainerComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Content.Server.AME.Components; - -// TODO: network and put in shared -[RegisterComponent] -public sealed class AMEFuelContainerComponent : Component -{ - /// - /// The amount of fuel in the jar. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("fuelAmount")] - public int FuelAmount = 1000; - - /// - /// The maximum fuel capacity of the jar. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("fuelCapacity")] - public int FuelCapacity = 1000; -} diff --git a/Content.Server/AME/Components/AMEPartComponent.cs b/Content.Server/AME/Components/AMEPartComponent.cs deleted file mode 100644 index 925e2bc69e..0000000000 --- a/Content.Server/AME/Components/AMEPartComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.AME.Components -{ - [RegisterComponent] - public sealed class AMEPartComponent : Component - { - [DataField("unwrapSound")] - public SoundSpecifier UnwrapSound = new SoundPathSpecifier("/Audio/Effects/unwrap.ogg"); - - [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string QualityNeeded = "Pulsing"; - } -} diff --git a/Content.Server/AME/Components/AMEShieldComponent.cs b/Content.Server/AME/Components/AMEShieldComponent.cs deleted file mode 100644 index 48fb5e21bd..0000000000 --- a/Content.Server/AME/Components/AMEShieldComponent.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Content.Shared.AME; -using Robust.Server.GameObjects; - -namespace Content.Server.AME.Components -{ - [RegisterComponent] - public sealed class AMEShieldComponent : SharedAMEShieldComponent - { - - private bool _isCore = false; - - [ViewVariables] - public int CoreIntegrity = 100; - - private AppearanceComponent? _appearance; - private PointLightComponent? _pointLight; - - protected override void Initialize() - { - base.Initialize(); - var entMan = IoCManager.Resolve(); - entMan.TryGetComponent(Owner, out _appearance); - entMan.TryGetComponent(Owner, out _pointLight); - } - - public void SetCore() - { - if(_isCore) { return; } - _isCore = true; - _appearance?.SetData(AMEShieldVisuals.Core, "isCore"); - } - - public void UnsetCore() - { - _isCore = false; - _appearance?.SetData(AMEShieldVisuals.Core, "isNotCore"); - UpdateCoreVisuals(0, false); - } - - public void UpdateCoreVisuals(int injectionStrength, bool injecting) - { - if (!injecting) - { - _appearance?.SetData(AMEShieldVisuals.CoreState, "off"); - if (_pointLight != null) { _pointLight.Enabled = false; } - return; - } - - if (_pointLight != null) - { - _pointLight.Radius = Math.Clamp(injectionStrength, 1, 12); - _pointLight.Enabled = true; - } - - if (injectionStrength > 2) - { - _appearance?.SetData(AMEShieldVisuals.CoreState, "strong"); - return; - } - - _appearance?.SetData(AMEShieldVisuals.CoreState, "weak"); - } - } -} diff --git a/Content.Server/Ame/AmeNodeGroup.cs b/Content.Server/Ame/AmeNodeGroup.cs new file mode 100644 index 0000000000..ab29d7037f --- /dev/null +++ b/Content.Server/Ame/AmeNodeGroup.cs @@ -0,0 +1,211 @@ +using System.Linq; +using Content.Server.Ame.Components; +using Content.Server.Ame.EntitySystems; +using Content.Server.Chat.Managers; +using Content.Server.Explosion.EntitySystems; +using Content.Server.NodeContainer.NodeGroups; +using Content.Server.NodeContainer.Nodes; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Ame; + +/// +/// Node group class for handling the Antimatter Engine's console and parts. +/// +[NodeGroup(NodeGroupID.AMEngine)] +public sealed class AmeNodeGroup : BaseNodeGroup +{ + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + /// + /// The AME controller which is currently in control of this node group. + /// This could be tracked a few different ways, but this is most convenient, + /// since any part connected to the node group can easily find the master. + /// + [ViewVariables] + private EntityUid? _masterController; + + public EntityUid? MasterController => _masterController; + + /// + /// The set of AME shielding units that currently count as cores for the AME. + /// + private readonly List _cores = new(); + + public int CoreCount => _cores.Count; + + public override void LoadNodes(List groupNodes) + { + base.LoadNodes(groupNodes); + + EntityUid? gridEnt = null; + + var ameControllerSystem = _entMan.System(); + var ameShieldingSystem = _entMan.System(); + + var shieldQuery = _entMan.GetEntityQuery(); + var controllerQuery = _entMan.GetEntityQuery(); + var xformQuery = _entMan.GetEntityQuery(); + foreach (var node in groupNodes) + { + var nodeOwner = node.Owner; + if (!shieldQuery.TryGetComponent(nodeOwner, out var shield)) + continue; + if (!xformQuery.TryGetComponent(nodeOwner, out var xform)) + continue; + if (!_mapManager.TryGetGrid(xform.GridUid, out var grid)) + continue; + + if (gridEnt == null) + gridEnt = xform.GridUid; + else if (gridEnt != xform.GridUid) + continue; + + var nodeNeighbors = grid.GetCellsInSquareArea(xform.Coordinates, 1) + .Where(entity => entity != nodeOwner && shieldQuery.HasComponent(entity)); + + if (nodeNeighbors.Count() >= 8) + { + _cores.Add(nodeOwner); + ameShieldingSystem.SetCore(nodeOwner, true, shield); + // Core visuals will be updated later. + } + else + { + ameShieldingSystem.SetCore(nodeOwner, false, shield); + } + } + + // Separate to ensure core count is correctly updated. + foreach (var node in groupNodes) + { + var nodeOwner = node.Owner; + if (!controllerQuery.TryGetComponent(nodeOwner, out var controller)) + continue; + + if (_masterController == null) + _masterController = nodeOwner; + + ameControllerSystem.UpdateUi(nodeOwner, controller); + } + + UpdateCoreVisuals(); + } + + public void UpdateCoreVisuals() + { + var injectionAmount = 0; + var injecting = false; + + if (_entMan.TryGetComponent(_masterController, out var controller)) + { + injectionAmount = controller.InjectionAmount; + injecting = controller.Injecting; + } + + var injectionStrength = CoreCount > 0 ? injectionAmount / CoreCount : 0; + + var coreSystem = _entMan.System(); + foreach (var coreUid in _cores) + { + coreSystem.UpdateCoreVisuals(coreUid, injectionStrength, injecting); + } + } + + public float InjectFuel(int fuel, out bool overloading) + { + overloading = false; + + var shieldQuery = _entMan.GetEntityQuery(); + if (fuel <= 0 || CoreCount <= 0) + return 0; + + var safeFuelLimit = CoreCount * 2; + + // Note the float conversions. The maths will completely fail if not done using floats. + // Oh, and don't ever stuff the result of this in an int. Seriously. + var floatFuel = (float) fuel; + var floatCores = (float) CoreCount; + var powerOutput = 20000f * floatFuel * floatFuel / floatCores; + if (fuel <= safeFuelLimit) + return powerOutput; + + // The AME is being overloaded. + // Note about these maths: I would assume the general idea here is to make larger engines less safe to overload. + // In other words, yes, those are supposed to be CoreCount, not safeFuelLimit. + var instability = 0; + var overloadVsSizeResult = fuel - CoreCount; + + // fuel > safeFuelLimit: Slow damage. Can safely run at this level for burst periods if the engine is small and someone is keeping an eye on it. + if (_random.Prob(0.5f)) + instability = 1; + // overloadVsSizeResult > 5: + if (overloadVsSizeResult > 5) + instability = 5; + // overloadVsSizeResult > 10: This will explode in at most 5 injections. + if (overloadVsSizeResult > 10) + instability = 20; + + // Apply calculated instability + if (instability == 0) + return powerOutput; + + overloading = true; + var integrityCheck = 100; + foreach (var coreUid in _cores) + { + if (!shieldQuery.TryGetComponent(coreUid, out var core)) + continue; + + var oldIntegrity = core.CoreIntegrity; + core.CoreIntegrity -= instability; + + if (oldIntegrity > 95 + && core.CoreIntegrity <= 95 + && core.CoreIntegrity < integrityCheck) + integrityCheck = core.CoreIntegrity; + } + + // Admin alert + if (integrityCheck != 100 && _masterController.HasValue) + _chat.SendAdminAlert($"AME overloading: {_entMan.ToPrettyString(_masterController.Value)}"); + + return powerOutput; + } + + public int GetTotalStability() + { + if (CoreCount < 1) + return 100; + + var stability = 0; + var coreQuery = _entMan.GetEntityQuery(); + foreach (var coreUid in _cores) + { + if (coreQuery.TryGetComponent(coreUid, out var core)) + stability += core.CoreIntegrity; + } + + stability /= CoreCount; + + return stability; + } + + public void ExplodeCores() + { + if (_cores.Count < 1 + || !_entMan.TryGetComponent(MasterController, out var controller)) + return; + + /* + * todo: add an exact to the shielding and make this find the core closest to the controller + * so they chain explode, after helpers have been added to make it not cancer + */ + var radius = Math.Min(2 * CoreCount * controller.InjectionAmount, 8f); + _entMan.System().TriggerExplosive(MasterController.Value, radius: radius, delete: false); + } +} diff --git a/Content.Server/Ame/Components/AmeControllerComponent.cs b/Content.Server/Ame/Components/AmeControllerComponent.cs new file mode 100644 index 0000000000..e9e8e48b6c --- /dev/null +++ b/Content.Server/Ame/Components/AmeControllerComponent.cs @@ -0,0 +1,81 @@ +using Content.Server.Ame.EntitySystems; +using Content.Shared.Ame; +using Robust.Shared.Audio; +using Robust.Shared.Containers; + +namespace Content.Server.Ame.Components; + +/// +/// The component used to make an entity the controller/fuel injector port of an AntiMatter Engine. +/// Connects to adjacent entities with this component or to make an AME. +/// +[Access(typeof(AmeControllerSystem), typeof(AmeNodeGroup))] +[RegisterComponent] +public sealed class AmeControllerComponent : SharedAmeControllerComponent +{ + /// + /// The id of the container used to store the current fuel container for the AME. + /// + public const string FuelContainerId = "AmeFuel"; + + /// + /// The container for the fuel canisters used by the AME. + /// + [ViewVariables] + public ContainerSlot JarSlot = default!; + + /// + /// Whether or not the AME controller is currently injecting animatter into the reactor. + /// + [DataField("injecting")] + [ViewVariables(VVAccess.ReadWrite)] + public bool Injecting = false; + + /// + /// How much antimatter the AME controller is set to inject into the reactor per update. + /// + [DataField("injectionAmount")] + [ViewVariables(VVAccess.ReadWrite)] + public int InjectionAmount = 2; + + /// + /// How stable the reactor currently is. + /// When this falls to <= 0 the reactor explodes. + /// + [DataField("stability")] + [ViewVariables(VVAccess.ReadWrite)] + public int Stability = 100; + + /// + /// The sound used when pressing buttons in the UI. + /// + [DataField("clickSound")] + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); + + /// + /// The sound used when injecting antimatter into the AME. + /// + [DataField("injectSound")] + [ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Effects/bang.ogg"); + + /// + /// The last time this could have injected fuel into the AME. + /// + [DataField("lastUpdate")] + public TimeSpan LastUpdate = default!; + + /// + /// The next time this will try to inject fuel into the AME. + /// + [DataField("nextUpdate")] + public TimeSpan NextUpdate = default!; + + /// + /// The the amount of time that passes between injection attempts. + /// + [DataField("updatePeriod")] + [ViewVariables(VVAccess.ReadWrite)] + public TimeSpan UpdatePeriod = TimeSpan.FromSeconds(10.0); +} diff --git a/Content.Server/Ame/Components/AmeFuelContainerComponent.cs b/Content.Server/Ame/Components/AmeFuelContainerComponent.cs new file mode 100644 index 0000000000..70f3d55c7d --- /dev/null +++ b/Content.Server/Ame/Components/AmeFuelContainerComponent.cs @@ -0,0 +1,23 @@ +namespace Content.Server.Ame.Components; + +/// +/// An antimatter containment cell used to handle the fuel for the AME. +/// TODO: network and put in shared +/// +[RegisterComponent] +public sealed class AmeFuelContainerComponent : Component +{ + /// + /// The amount of fuel in the jar. + /// + [DataField("fuelAmount")] + [ViewVariables(VVAccess.ReadWrite)] + public int FuelAmount = 1000; + + /// + /// The maximum fuel capacity of the jar. + /// + [DataField("fuelCapacity")] + [ViewVariables(VVAccess.ReadWrite)] + public int FuelCapacity = 1000; +} diff --git a/Content.Server/Ame/Components/AmePartComponent.cs b/Content.Server/Ame/Components/AmePartComponent.cs new file mode 100644 index 0000000000..3642da3913 --- /dev/null +++ b/Content.Server/Ame/Components/AmePartComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Ame.Components; + +/// +/// Packaged AME machinery that can be deployed to construct an AME. +/// +[RegisterComponent] +public sealed class AmePartComponent : Component +{ + /// + /// The sound played when the AME shielding is unpacked. + /// + [DataField("unwrapSound")] + public SoundSpecifier UnwrapSound = new SoundPathSpecifier("/Audio/Effects/unwrap.ogg"); + + /// + /// The tool quality required to deploy the packaged AME shielding. + /// + [DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string QualityNeeded = "Pulsing"; +} diff --git a/Content.Server/Ame/Components/AmeShieldComponent.cs b/Content.Server/Ame/Components/AmeShieldComponent.cs new file mode 100644 index 0000000000..83dd1e2fad --- /dev/null +++ b/Content.Server/Ame/Components/AmeShieldComponent.cs @@ -0,0 +1,26 @@ +using Content.Server.Ame.EntitySystems; +using Content.Shared.Ame; + +namespace Content.Server.Ame.Components; + +/// +/// The component used to make an entity part of the bulk machinery of an AntiMatter Engine. +/// Connects to adjacent entities with this component or to make an AME. +/// +[Access(typeof(AmeShieldingSystem), typeof(AmeNodeGroup))] +[RegisterComponent] +public sealed class AmeShieldComponent : SharedAmeShieldComponent +{ + /// + /// Whether or not this AME shield counts as a core for the AME or not. + /// + [ViewVariables] + public bool IsCore = false; + + /// + /// The current integrity of the AME shield. + /// + [DataField("integrity")] + [ViewVariables] + public int CoreIntegrity = 100; +} diff --git a/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs new file mode 100644 index 0000000000..72cd8804c0 --- /dev/null +++ b/Content.Server/Ame/EntitySystems/AmeControllerSystem.cs @@ -0,0 +1,326 @@ +using Content.Server.Administration.Logs; +using Content.Server.Ame.Components; +using Content.Server.Chat.Managers; +using Content.Server.Mind.Components; +using Content.Server.NodeContainer; +using Content.Server.Power.Components; +using Content.Shared.Ame; +using Content.Shared.Database; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Robust.Server.Containers; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.Timing; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Server.Ame.EntitySystems; + +public sealed class AmeControllerSystem : EntitySystem +{ + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly ContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentStartup); + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnUiButtonPressed); + } + + public override void Update(float frameTime) + { + var curTime = _gameTiming.CurTime; + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var controller, out var nodes)) + { + if (controller.NextUpdate <= curTime) + UpdateController(uid, curTime, controller, nodes); + } + } + + private void UpdateController(EntityUid uid, TimeSpan curTime, AmeControllerComponent? controller = null, NodeContainerComponent? nodes = null) + { + if (!Resolve(uid, ref controller)) + return; + + controller.LastUpdate = curTime; + controller.NextUpdate = curTime + controller.UpdatePeriod; + + if (!controller.Injecting) + return; + if (!TryGetAMENodeGroup(uid, out var group, nodes)) + return; + + if (TryComp(controller.JarSlot.ContainedEntity, out var fuelJar)) + { + var availableInject = Math.Min(controller.InjectionAmount, fuelJar.FuelAmount); + var powerOutput = group.InjectFuel(availableInject, out var overloading); + if (TryComp(uid, out var powerOutlet)) + powerOutlet.MaxSupply = powerOutput; + fuelJar.FuelAmount -= availableInject; + _audioSystem.PlayPvs(controller.InjectSound, uid, AudioParams.Default.WithVolume(overloading ? 10f : 0f)); + UpdateUi(uid, controller); + } + + controller.Stability = group.GetTotalStability(); + + UpdateDisplay(uid, controller.Stability, controller); + + if (controller.Stability <= 0) + group.ExplodeCores(); + } + + public void UpdateUi(EntityUid uid, AmeControllerComponent? controller = null) + { + if (!Resolve(uid, ref controller)) + return; + + if (!_userInterfaceSystem.TryGetUi(uid, AmeControllerUiKey.Key, out var bui)) + return; + + var state = GetUiState(uid, controller); + _userInterfaceSystem.SetUiState(bui, state); + } + + private AmeControllerBoundUserInterfaceState GetUiState(EntityUid uid, AmeControllerComponent controller) + { + var powered = !TryComp(uid, out var powerSource) || powerSource.Powered; + var coreCount = TryGetAMENodeGroup(uid, out var group) ? group.CoreCount : 0; + + var hasJar = Exists(controller.JarSlot.ContainedEntity); + if (!hasJar || !TryComp(controller.JarSlot.ContainedEntity, out var jar)) + return new AmeControllerBoundUserInterfaceState(powered, IsMasterController(uid), false, hasJar, 0, controller.InjectionAmount, coreCount); + + return new AmeControllerBoundUserInterfaceState(powered, IsMasterController(uid), controller.Injecting, hasJar, jar.FuelAmount, controller.InjectionAmount, coreCount); + } + + private bool IsMasterController(EntityUid uid) + { + return TryGetAMENodeGroup(uid, out var group) && group.MasterController == uid; + } + + private bool TryGetAMENodeGroup(EntityUid uid, [MaybeNullWhen(false)] out AmeNodeGroup group, NodeContainerComponent? nodes = null) + { + if (!Resolve(uid, ref nodes)) + { + group = null; + return false; + } + + group = nodes.Nodes.Values + .Select(node => node.NodeGroup) + .OfType() + .FirstOrDefault(); + + return group != null; + } + + public void TryEject(EntityUid uid, EntityUid? user = null, AmeControllerComponent? controller = null) + { + if (!Resolve(uid, ref controller)) + return; + if (controller.Injecting) + return; + + var jar = controller.JarSlot.ContainedEntity; + if (!Exists(jar)) + return; + + controller.JarSlot.Remove(jar!.Value); + UpdateUi(uid, controller); + if (Exists(user)) + _handsSystem.PickupOrDrop(user, jar!.Value); + } + + public void SetInjecting(EntityUid uid, bool value, EntityUid? user = null, AmeControllerComponent? controller = null) + { + if (!Resolve(uid, ref controller)) + return; + if (controller.Injecting == value) + return; + + controller.Injecting = value; + _appearanceSystem.SetData(uid, AmeControllerVisuals.DisplayState, value ? AmeControllerState.On : AmeControllerState.Off); + if (!value && TryComp(uid, out var powerOut)) + powerOut.MaxSupply = 0; + + UpdateUi(uid, controller); + + // Logging + if (!HasComp(user)) + return; + + var humanReadableState = value ? "Inject" : "Not inject"; + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to {humanReadableState}"); + } + + public void ToggleInjecting(EntityUid uid, EntityUid? user = null, AmeControllerComponent? controller = null) + { + if (!Resolve(uid, ref controller)) + return; + SetInjecting(uid, !controller.Injecting, user, controller); + } + + public void SetInjectionAmount(EntityUid uid, int value, EntityUid? user = null, AmeControllerComponent? controller = null) + { + if (!Resolve(uid, ref controller)) + return; + if (controller.InjectionAmount == value) + return; + + var oldValue = controller.InjectionAmount; + controller.InjectionAmount = value; + + UpdateUi(uid, controller); + + // Logging + if (!TryComp(user, out var mindContainer)) + return; + + var humanReadableState = controller.Injecting ? "Inject" : "Not inject"; + _adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{EntityManager.ToPrettyString(user.Value):player} has set the AME to inject {controller.InjectionAmount} while set to {humanReadableState}"); + + // Admin alert + var safeLimit = 0; + if (TryGetAMENodeGroup(uid, out var group)) + safeLimit = group.CoreCount * 2; + + if (oldValue <= safeLimit && value > safeLimit) + _chatManager.SendAdminAlert(user.Value, $"increased AME over safe limit to {controller.InjectionAmount}", mindContainer); + } + + public void AdjustInjectionAmount(EntityUid uid, int delta, int min = 0, int max = int.MaxValue, EntityUid? user = null, AmeControllerComponent? controller = null) + { + if (Resolve(uid, ref controller)) + SetInjectionAmount(uid, MathHelper.Clamp(controller.InjectionAmount + delta, min, max), user, controller); + } + + private void UpdateDisplay(EntityUid uid, int stability, AmeControllerComponent? controller = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref controller, ref appearance)) + return; + + _appearanceSystem.SetData( + uid, + AmeControllerVisuals.DisplayState, + stability switch + { + < 10 => AmeControllerState.Fuck, + < 50 => AmeControllerState.Critical, + _ => AmeControllerState.On, + }, + appearance + ); + } + + private void OnComponentStartup(EntityUid uid, AmeControllerComponent comp, ComponentStartup args) + { + // TODO: Fix this bad name. I'd update maps but then people get mad. + comp.JarSlot = _containerSystem.EnsureContainer(uid, AmeControllerComponent.FuelContainerId); + } + + private void OnInteractUsing(EntityUid uid, AmeControllerComponent comp, InteractUsingEvent args) + { + if (!HasComp(args.User)) + { + _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-no-hands-text"), uid, args.User); + return; + } + + if (!HasComp(args.Used)) + { + _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-fail"), uid, args.User); + return; + } + + if (Exists(comp.JarSlot.ContainedEntity)) + { + _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-already-has-jar"), uid, args.User); + return; + } + + comp.JarSlot.Insert(args.Used); + _popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-success"), uid, args.User, PopupType.Medium); + + UpdateUi(uid, comp); + } + + private void OnPowerChanged(EntityUid uid, AmeControllerComponent comp, ref PowerChangedEvent args) + { + UpdateUi(uid, comp); + } + + private void OnUiButtonPressed(EntityUid uid, AmeControllerComponent comp, UiButtonPressedMessage msg) + { + var user = msg.Session.AttachedEntity; + if (!Exists(user)) + return; + + var needsPower = msg.Button switch + { + UiButton.Eject => false, + _ => true, + }; + + if (!PlayerCanUseController(uid, user!.Value, needsPower, comp)) + return; + + _audioSystem.PlayPvs(comp.ClickSound, uid, AudioParams.Default.WithVolume(-2f)); + switch (msg.Button) + { + case UiButton.Eject: + TryEject(uid, user: user, controller: comp); + break; + case UiButton.ToggleInjection: + ToggleInjecting(uid, user: user, controller: comp); + break; + case UiButton.IncreaseFuel: + AdjustInjectionAmount(uid, +1, user: user, controller: comp); + break; + case UiButton.DecreaseFuel: + AdjustInjectionAmount(uid, -1, user: user, controller: comp); + break; + } + + if (TryGetAMENodeGroup(uid, out var group)) + group.UpdateCoreVisuals(); + + UpdateUi(uid, comp); + } + + /// + /// Checks whether the player entity is able to use the controller. + /// + /// The player entity. + /// Returns true if the entity can use the controller, and false if it cannot. + private bool PlayerCanUseController(EntityUid uid, EntityUid playerEntity, bool needsPower = true, AmeControllerComponent? controller = null) + { + if (!Resolve(uid, ref controller)) + return false; + + //Need player entity to check if they are still able to use the dispenser + if (!Exists(playerEntity)) + return false; + + //Check if device is powered + if (needsPower && TryComp(uid, out var powerSource) && !powerSource.Powered) + return false; + + return true; + } +} diff --git a/Content.Server/AME/AMESystem.Fuel.cs b/Content.Server/Ame/EntitySystems/AmeFuelSystem.cs similarity index 66% rename from Content.Server/AME/AMESystem.Fuel.cs rename to Content.Server/Ame/EntitySystems/AmeFuelSystem.cs index e2b7cd0a88..a7971b449e 100644 --- a/Content.Server/AME/AMESystem.Fuel.cs +++ b/Content.Server/Ame/EntitySystems/AmeFuelSystem.cs @@ -1,19 +1,21 @@ -using Content.Server.AME.Components; +using Content.Server.Ame.Components; using Content.Shared.Examine; -namespace Content.Server.AME; +namespace Content.Server.Ame.EntitySystems; /// /// Adds fuel level info to examine on fuel jars and handles network state. /// -public sealed partial class AMESystem +public sealed class AmeFuelSystem : EntitySystem { - private void InitializeFuel() + public override void Initialize() { - SubscribeLocalEvent(OnFuelExamined); + base.Initialize(); + + SubscribeLocalEvent(OnFuelExamined); } - private void OnFuelExamined(EntityUid uid, AMEFuelContainerComponent comp, ExaminedEvent args) + private void OnFuelExamined(EntityUid uid, AmeFuelContainerComponent comp, ExaminedEvent args) { if (!args.IsInDetailsRange) return; diff --git a/Content.Server/Ame/EntitySystems/AmePartSystem.cs b/Content.Server/Ame/EntitySystems/AmePartSystem.cs new file mode 100644 index 0000000000..54a379f693 --- /dev/null +++ b/Content.Server/Ame/EntitySystems/AmePartSystem.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Content.Server.Administration.Logs; +using Content.Server.Ame.Components; +using Content.Server.Popups; +using Content.Server.Tools; +using Content.Shared.Database; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using Robust.Shared.Map; + +namespace Content.Server.Ame.EntitySystems; + +public sealed class AmePartSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly ToolSystem _toolSystem = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPartInteractUsing); + } + + private void OnPartInteractUsing(EntityUid uid, AmePartComponent component, InteractUsingEvent args) + { + if (!_toolSystem.HasQuality(args.Used, component.QualityNeeded)) + return; + + if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridUid(EntityManager), out var mapGrid)) + return; // No AME in space. + + var snapPos = mapGrid.TileIndicesFor(args.ClickLocation); + if (mapGrid.GetAnchoredEntities(snapPos).Any(sc => HasComp(sc))) + { + _popupSystem.PopupEntity(Loc.GetString("ame-part-component-shielding-already-present"), uid, args.User); + return; + } + + var ent = Spawn("AmeShielding", mapGrid.GridTileToLocal(snapPos)); + + _adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(args.User):player} unpacked {ToPrettyString(ent)} at {Transform(ent).Coordinates} from {ToPrettyString(uid)}"); + + _audioSystem.PlayPvs(component.UnwrapSound, uid); + + QueueDel(uid); + } +} diff --git a/Content.Server/Ame/EntitySystems/AmeShieldingSystem.cs b/Content.Server/Ame/EntitySystems/AmeShieldingSystem.cs new file mode 100644 index 0000000000..c8ebaf53e8 --- /dev/null +++ b/Content.Server/Ame/EntitySystems/AmeShieldingSystem.cs @@ -0,0 +1,41 @@ +using Content.Server.Ame.Components; +using Content.Shared.Ame; +using Robust.Server.GameObjects; + +namespace Content.Server.Ame.EntitySystems; + +public sealed class AmeShieldingSystem : EntitySystem +{ + [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly PointLightSystem _pointLightSystem = default!; + + public void SetCore(EntityUid uid, bool value, AmeShieldComponent? shield = null) + { + if (!Resolve(uid, ref shield)) + return; + if (value == shield.IsCore) + return; + + shield.IsCore = value; + _appearanceSystem.SetData(uid, AmeShieldVisuals.Core, value); + if (!value) + UpdateCoreVisuals(uid, 0, false, shield); + } + + public void UpdateCoreVisuals(EntityUid uid, int injectionStrength, bool injecting, AmeShieldComponent? shield = null) + { + if (!Resolve(uid, ref shield)) + return; + + if (!injecting) + { + _appearanceSystem.SetData(uid, AmeShieldVisuals.CoreState, AmeCoreState.Off); + _pointLightSystem.SetEnabled(uid, false); + return; + } + + _pointLightSystem.SetRadius(uid, Math.Clamp(injectionStrength, 1, 12)); + _pointLightSystem.SetEnabled(uid, true); + _appearanceSystem.SetData(uid, AmeShieldVisuals.CoreState, injectionStrength > 2 ? AmeCoreState.Strong : AmeCoreState.Weak); + } +} diff --git a/Content.Shared/AME/SharedAMEControllerComponent.cs b/Content.Shared/AME/SharedAMEControllerComponent.cs deleted file mode 100644 index e782b4fcc9..0000000000 --- a/Content.Shared/AME/SharedAMEControllerComponent.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.AME -{ - [Virtual] - public class SharedAMEControllerComponent : Component - { - [Serializable, NetSerializable] - public sealed class AMEControllerBoundUserInterfaceState : BoundUserInterfaceState - { - public readonly bool HasPower; - public readonly bool IsMaster; - public readonly bool Injecting; - public readonly bool HasFuelJar; - public readonly int FuelAmount; - public readonly int InjectionAmount; - public readonly int CoreCount; - - public AMEControllerBoundUserInterfaceState(bool hasPower, bool isMaster, bool injecting, bool hasFuelJar, int fuelAmount, int injectionAmount, int coreCount) - { - HasPower = hasPower; - IsMaster = isMaster; - Injecting = injecting; - HasFuelJar = hasFuelJar; - FuelAmount = fuelAmount; - InjectionAmount = injectionAmount; - CoreCount = coreCount; - } - } - - [Serializable, NetSerializable] - public sealed class UiButtonPressedMessage : BoundUserInterfaceMessage - { - public readonly UiButton Button; - - public UiButtonPressedMessage(UiButton button) - { - Button = button; - } - } - - [Serializable, NetSerializable] - public enum AMEControllerUiKey - { - Key - } - - public enum UiButton - { - Eject, - ToggleInjection, - IncreaseFuel, - DecreaseFuel, - } - - [Serializable, NetSerializable] - public enum AMEControllerVisuals - { - DisplayState, - } - } -} diff --git a/Content.Shared/AME/SharedAMEShieldComponent.cs b/Content.Shared/AME/SharedAMEShieldComponent.cs deleted file mode 100644 index 31ebcb25df..0000000000 --- a/Content.Shared/AME/SharedAMEShieldComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.AME -{ - [Virtual] - public class SharedAMEShieldComponent : Component - { - [Serializable, NetSerializable] - public enum AMEShieldVisuals - { - Core, - CoreState - } - - public enum AMECoreState - { - Off, - Weak, - Strong - } - } -} diff --git a/Content.Shared/Ame/SharedAmeControllerComponent.cs b/Content.Shared/Ame/SharedAmeControllerComponent.cs new file mode 100644 index 0000000000..73797af268 --- /dev/null +++ b/Content.Shared/Ame/SharedAmeControllerComponent.cs @@ -0,0 +1,71 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Ame; + +[Virtual] +public class SharedAmeControllerComponent : Component +{ +} + +[Serializable, NetSerializable] +public sealed class AmeControllerBoundUserInterfaceState : BoundUserInterfaceState +{ + public readonly bool HasPower; + public readonly bool IsMaster; + public readonly bool Injecting; + public readonly bool HasFuelJar; + public readonly int FuelAmount; + public readonly int InjectionAmount; + public readonly int CoreCount; + + public AmeControllerBoundUserInterfaceState(bool hasPower, bool isMaster, bool injecting, bool hasFuelJar, int fuelAmount, int injectionAmount, int coreCount) + { + HasPower = hasPower; + IsMaster = isMaster; + Injecting = injecting; + HasFuelJar = hasFuelJar; + FuelAmount = fuelAmount; + InjectionAmount = injectionAmount; + CoreCount = coreCount; + } +} + +[Serializable, NetSerializable] +public sealed class UiButtonPressedMessage : BoundUserInterfaceMessage +{ + public readonly UiButton Button; + + public UiButtonPressedMessage(UiButton button) + { + Button = button; + } +} + +[Serializable, NetSerializable] +public enum AmeControllerUiKey +{ + Key +} + +public enum UiButton +{ + Eject, + ToggleInjection, + IncreaseFuel, + DecreaseFuel, +} + +[Serializable, NetSerializable] +public enum AmeControllerVisuals +{ + DisplayState, +} + +[Serializable, NetSerializable] +public enum AmeControllerState +{ + On, + Critical, + Fuck, + Off, +} diff --git a/Content.Shared/Ame/SharedAmeShieldComponent.cs b/Content.Shared/Ame/SharedAmeShieldComponent.cs new file mode 100644 index 0000000000..92b86f9bc4 --- /dev/null +++ b/Content.Shared/Ame/SharedAmeShieldComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Ame; + +[Virtual] +public class SharedAmeShieldComponent : Component +{ +} + +[Serializable, NetSerializable] +public enum AmeShieldVisuals +{ + Core, + CoreState +} + +[Serializable, NetSerializable] +public enum AmeCoreState +{ + Off, + Weak, + Strong +} diff --git a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml index a3cbe17ac7..11e0224207 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/engines.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/engines.yml @@ -6,7 +6,7 @@ components: - type: StorageFill contents: - - id: AMEPart + - id: AmePart amount: 9 - type: entity @@ -15,7 +15,7 @@ components: - type: StorageFill contents: - - id: AMEJar + - id: AmeJar amount: 3 - type: entity @@ -24,7 +24,7 @@ components: - type: StorageFill contents: - - id: AMEControllerUnanchored + - id: AmeControllerUnanchored # Singularity diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml index eb040c0bc7..78e17e0745 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/particle_accelerator.yml @@ -23,9 +23,9 @@ - type: MachineBoard prototype: ParticleAcceleratorFuelChamberUnfinished componentRequirements: - AMEFuelContainer: + AmeFuelContainer: Amount: 1 - DefaultPrototype: AMEJar + DefaultPrototype: AmeJar ExamineName: AME Fuel Jar materialRequirements: Glass: 10 diff --git a/Resources/Prototypes/Entities/Objects/Power/antimatter_jar.yml b/Resources/Prototypes/Entities/Objects/Power/antimatter_jar.yml index a18d9bba79..8267903dbb 100644 --- a/Resources/Prototypes/Entities/Objects/Power/antimatter_jar.yml +++ b/Resources/Prototypes/Entities/Objects/Power/antimatter_jar.yml @@ -1,6 +1,6 @@ - type: entity parent: BaseItem - id: AMEJar + id: AmeJar name: AME fuel jar description: A hermetically sealed jar containing antimatter for use in an antimatter reactor. components: @@ -10,7 +10,7 @@ - type: Sprite sprite: Objects/Power/AME/ame_jar.rsi state: jar - - type: AMEFuelContainer + - type: AmeFuelContainer - type: StaticPrice price: 500 - type: GuideHelp diff --git a/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml b/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml index aee1953fbb..a393cad617 100644 --- a/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml +++ b/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml @@ -1,6 +1,6 @@ - type: entity parent: BaseItem - id: AMEPart + id: AmePart name: AME part description: A flatpack used for constructing an antimatter engine reactor. Use a multitool to unpack it. components: @@ -10,7 +10,7 @@ - type: Sprite sprite: Objects/Power/AME/ame_part.rsi state: box - - type: AMEPart + - type: AmePart - type: StaticPrice price: 500 - type: GuideHelp diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml index c39152934f..a356bfe285 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/ame.yml @@ -1,5 +1,5 @@ - type: entity - id: AMEController + id: AmeController name: AME controller description: It's a controller for the antimatter engine. placement: @@ -11,6 +11,11 @@ snapCardinals: true sprite: Structures/Power/Generation/ame.rsi state: control + layers: + - state: control + - state: control_on + map: ["display"] + visible: false - type: Physics - type: Fixtures fixtures: @@ -44,19 +49,26 @@ noRot: true - type: Anchorable - type: Pullable - - type: AMEController + - type: AmeController - type: Explosive explosionType: Default intensitySlope: 5 maxIntensity: 60 - type: ActivatableUI - key: enum.AMEControllerUiKey.Key + key: enum.AmeControllerUiKey.Key - type: UserInterface interfaces: - - key: enum.AMEControllerUiKey.Key - type: AMEControllerBoundUserInterface + - key: enum.AmeControllerUiKey.Key + type: AmeControllerBoundUserInterface - type: Appearance - - type: AMEControllerVisuals + - type: GenericVisualizer + visuals: + enum.AmeControllerVisuals.DisplayState: + display: + Off: { visible: false } + On: { state: control_on, visible: true } + Critical: { state: control_critical, visible: true } + Fuck: { state: control_fuck, visible: true } - type: NodeContainer examinable: true nodes: @@ -72,7 +84,7 @@ supplyRate: 0 - type: ContainerContainer containers: - AMEController-fuelJarContainer: !type:ContainerSlot {} + AmeFuel: !type:ContainerSlot {} - type: GuideHelp guides: [ AME, Power ] - type: Electrified @@ -85,8 +97,8 @@ - type: entity noSpawn: true - parent: AMEController - id: AMEControllerUnanchored + parent: AmeController + id: AmeControllerUnanchored suffix: Unanchored components: - type: Transform @@ -95,7 +107,7 @@ bodyType: Dynamic - type: entity - id: AMEShielding + id: AmeShielding name: AME shielding description: Keeps the antimatter in and the matter out. placement: @@ -107,6 +119,15 @@ drawdepth: Walls sprite: Structures/Power/Generation/ame.rsi state: shield_0 + layers: + - state: shield_0 + - state: core + map: ["core"] + visible: false + - state: core_weak + map: ["core_glow"] + shader: unshaded + visible: false - type: Physics - type: Fixtures fixtures: @@ -141,7 +162,7 @@ mode: CardinalFlags base: shield_ key: ame_shield - - type: AMEShield + - type: AmeShield - type: NodeContainer nodes: ame: @@ -153,9 +174,19 @@ energy: 0.5 color: "#00AAFF" - type: Appearance - - type: AMEShieldingVisuals + - type: GenericVisualizer + visuals: + enum.AmeShieldVisuals.Core: + core: + True: { visible: true } + False: { visible: false } + enum.AmeShieldVisuals.CoreState: + core_glow: + Off: { visible: false } + Weak: { state: core_weak, visible: true } + Strong: { state: core_strong, visible: true } - type: Construction - graph: AMEShielding + graph: AmeShielding node: ameShielding - type: GuideHelp guides: [ AME, Power ] diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/ame_shielding.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/ame_shielding.yml index 2f9c8a9715..98a4857be6 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/ame_shielding.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/ame_shielding.yml @@ -1,18 +1,18 @@ - type: constructionGraph - id: AMEShielding + id: AmeShielding start: start graph: - node: start - node: ameShielding - entity: AMEShielding + entity: AmeShielding edges: - to: start completed: - !type:AdminLog # I don't like logging it like this. The log should include the user, AMEShielding EntityID, and AMEPart EntityID, and there should also be a start of attempt log. message: "An AME shielding was deconstructed" - !type:SpawnPrototype - prototype: AMEPart + prototype: AmePart amount: 1 - !type:DeleteEntity steps: diff --git a/Resources/ServerInfo/Guidebook/Engineering/AME.xml b/Resources/ServerInfo/Guidebook/Engineering/AME.xml index 9de25b21aa..8f98c27ba9 100644 --- a/Resources/ServerInfo/Guidebook/Engineering/AME.xml +++ b/Resources/ServerInfo/Guidebook/Engineering/AME.xml @@ -6,9 +6,9 @@ The AME is one of the simplest engines available. You put together the multi-til ## Construction Required parts: - - - + + + To assemble an AME, start by wrenching down the controller on the far end of a HV wire. On most stations, there's catwalks to assist with this. From there, start putting down a 3x3 or larger square of AME parts in preparation for construction, making sure to maximize the number of "center" pieces that are surrounded on all 8 sides. diff --git a/Resources/migration.yml b/Resources/migration.yml index 6f400e854e..9f9fa7b6a9 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -53,6 +53,12 @@ FoodCondimentBottleSmallHotsauce: FoodCondimentBottleHotsauce FoodBakedCookieFortune: FoodSnackCookieFortune GunSafeSubMachineGunVector: GunSafeSubMachineGunDrozd +# 2023-05-24 +AMEController: AmeController +AMEJar: AmeJar +AMEPart: AmePart +AMEShielding: AmeShielding + # 2023-05-29 OrGate: null