ECS AME (#16779)
This commit is contained in:
@@ -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<AMEControllerVisualsComponent>
|
|
||||||
{
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<AMEControllerVisualsComponent, ComponentInit>(OnComponentInit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnComponentInit(EntityUid uid, AMEControllerVisualsComponent component, ComponentInit args)
|
|
||||||
{
|
|
||||||
if (TryComp<SpriteComponent>(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<string>(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
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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<AMEShieldingVisualsComponent>
|
|
||||||
{
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
SubscribeLocalEvent<AMEShieldingVisualsComponent, ComponentInit>(OnComponentInit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnComponentInit(EntityUid uid, AMEShieldingVisualsComponent component, ComponentInit args)
|
|
||||||
{
|
|
||||||
if (TryComp<SpriteComponent>(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<string>(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<string>(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,
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.AME.Components;
|
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class AMEControllerVisualsComponent : Component
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.AME.Components;
|
|
||||||
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class AMEShieldingVisualsComponent : Component
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
using Content.Shared.Chemistry.Dispenser;
|
using Content.Shared.Ame;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
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]
|
[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();
|
base.Open();
|
||||||
|
|
||||||
_window = new AMEWindow(this);
|
_window = new AmeWindow(this);
|
||||||
_window.OnClose += Close;
|
_window.OnClose += Close;
|
||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
}
|
}
|
||||||
@@ -35,11 +33,11 @@ namespace Content.Client.AME.UI
|
|||||||
{
|
{
|
||||||
base.UpdateState(state);
|
base.UpdateState(state);
|
||||||
|
|
||||||
var castState = (AMEControllerBoundUserInterfaceState) state;
|
var castState = (AmeControllerBoundUserInterfaceState) state;
|
||||||
_window?.UpdateState(castState); //Update window state
|
_window?.UpdateState(castState); //Update window state
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ButtonPressed(UiButton button, int dispenseIndex = -1)
|
public void ButtonPressed(UiButton button)
|
||||||
{
|
{
|
||||||
SendMessage(new UiButtonPressedMessage(button));
|
SendMessage(new UiButtonPressedMessage(button));
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,15 @@
|
|||||||
using Content.Client.UserInterface;
|
using Content.Client.UserInterface;
|
||||||
|
using Content.Shared.Ame;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
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]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class AMEWindow : DefaultWindow
|
public sealed partial class AmeWindow : DefaultWindow
|
||||||
{
|
{
|
||||||
public AMEWindow(AMEControllerBoundUserInterface ui)
|
public AmeWindow(AmeControllerBoundUserInterface ui)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
@@ -31,7 +26,7 @@ namespace Content.Client.AME.UI
|
|||||||
/// <param name="state">State data sent by the server.</param>
|
/// <param name="state">State data sent by the server.</param>
|
||||||
public void UpdateState(BoundUserInterfaceState state)
|
public void UpdateState(BoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
var castState = (AMEControllerBoundUserInterfaceState) state;
|
var castState = (AmeControllerBoundUserInterfaceState) state;
|
||||||
|
|
||||||
// Disable all buttons if not powered
|
// Disable all buttons if not powered
|
||||||
if (Contents.Children != null)
|
if (Contents.Children != null)
|
||||||
@@ -21,7 +21,7 @@ using Content.Client.Stylesheets;
|
|||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Client.Voting;
|
using Content.Client.Voting;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.AME;
|
using Content.Shared.Ame;
|
||||||
using Content.Shared.Gravity;
|
using Content.Shared.Gravity;
|
||||||
using Content.Shared.Localizations;
|
using Content.Shared.Localizations;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
@@ -91,7 +91,7 @@ namespace Content.Client.Entry
|
|||||||
|
|
||||||
// Do not add to these, they are legacy.
|
// Do not add to these, they are legacy.
|
||||||
_componentFactory.RegisterClass<SharedGravityGeneratorComponent>();
|
_componentFactory.RegisterClass<SharedGravityGeneratorComponent>();
|
||||||
_componentFactory.RegisterClass<SharedAMEControllerComponent>();
|
_componentFactory.RegisterClass<SharedAmeControllerComponent>();
|
||||||
// Do not add to the above, they are legacy
|
// Do not add to the above, they are legacy
|
||||||
|
|
||||||
_prototypeManager.RegisterIgnore("utilityQuery");
|
_prototypeManager.RegisterIgnore("utilityQuery");
|
||||||
|
|||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Node group class for handling the Antimatter Engine's console and parts.
|
|
||||||
/// </summary>
|
|
||||||
[NodeGroup(NodeGroupID.AMEngine)]
|
|
||||||
public sealed class AMENodeGroup : BaseNodeGroup
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[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<AMEShieldComponent> _cores = new();
|
|
||||||
|
|
||||||
public int CoreCount => _cores.Count;
|
|
||||||
|
|
||||||
public override void LoadNodes(List<Node> 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<TransformComponent>(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<AMEShieldComponent>(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<ExplosionSystem>().TriggerExplosive(MasterController.Owner, radius: radius, delete: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<AMEControllerComponent, PowerChangedEvent>(OnAMEPowerChange);
|
|
||||||
SubscribeLocalEvent<AMEControllerComponent, InteractUsingEvent>(OnInteractUsing);
|
|
||||||
SubscribeLocalEvent<AMEPartComponent, InteractUsingEvent>(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<AMEControllerComponent>())
|
|
||||||
{
|
|
||||||
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<AMEFuelContainerComponent?>(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<HandsComponent>(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<AMEShieldComponent>(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<ContainerSlot>(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<AMEFuelContainerComponent?>(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<AMEFuelContainerComponent>(jar);
|
|
||||||
return new AMEControllerBoundUserInterfaceState(Powered, IsMasterController(), _injecting, HasJar, jarComponent.FuelAmount, InjectionAmount, GetCoreCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether the player entity is able to use the controller.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="playerEntity">The player entity.</param>
|
|
||||||
/// <returns>Returns true if the entity can use the controller, and false if it cannot.</returns>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles ui messages from the client. For things such as button presses
|
|
||||||
/// which interact with the world and require server action.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">A user interface message from the client.</param>
|
|
||||||
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<SharedHandsSystem>().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<string>(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<AMENodeGroup>()
|
|
||||||
.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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
namespace Content.Server.AME.Components;
|
|
||||||
|
|
||||||
// TODO: network and put in shared
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class AMEFuelContainerComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of fuel in the jar.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("fuelAmount")]
|
|
||||||
public int FuelAmount = 1000;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum fuel capacity of the jar.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("fuelCapacity")]
|
|
||||||
public int FuelCapacity = 1000;
|
|
||||||
}
|
|
||||||
@@ -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<EntityPrototype>))]
|
|
||||||
public string QualityNeeded = "Pulsing";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<IEntityManager>();
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
211
Content.Server/Ame/AmeNodeGroup.cs
Normal file
211
Content.Server/Ame/AmeNodeGroup.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Node group class for handling the Antimatter Engine's console and parts.
|
||||||
|
/// </summary>
|
||||||
|
[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!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private EntityUid? _masterController;
|
||||||
|
|
||||||
|
public EntityUid? MasterController => _masterController;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The set of AME shielding units that currently count as cores for the AME.
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<EntityUid> _cores = new();
|
||||||
|
|
||||||
|
public int CoreCount => _cores.Count;
|
||||||
|
|
||||||
|
public override void LoadNodes(List<Node> groupNodes)
|
||||||
|
{
|
||||||
|
base.LoadNodes(groupNodes);
|
||||||
|
|
||||||
|
EntityUid? gridEnt = null;
|
||||||
|
|
||||||
|
var ameControllerSystem = _entMan.System<AmeControllerSystem>();
|
||||||
|
var ameShieldingSystem = _entMan.System<AmeShieldingSystem>();
|
||||||
|
|
||||||
|
var shieldQuery = _entMan.GetEntityQuery<AmeShieldComponent>();
|
||||||
|
var controllerQuery = _entMan.GetEntityQuery<AmeControllerComponent>();
|
||||||
|
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
|
||||||
|
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<AmeControllerComponent>(_masterController, out var controller))
|
||||||
|
{
|
||||||
|
injectionAmount = controller.InjectionAmount;
|
||||||
|
injecting = controller.Injecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
var injectionStrength = CoreCount > 0 ? injectionAmount / CoreCount : 0;
|
||||||
|
|
||||||
|
var coreSystem = _entMan.System<AmeShieldingSystem>();
|
||||||
|
foreach (var coreUid in _cores)
|
||||||
|
{
|
||||||
|
coreSystem.UpdateCoreVisuals(coreUid, injectionStrength, injecting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float InjectFuel(int fuel, out bool overloading)
|
||||||
|
{
|
||||||
|
overloading = false;
|
||||||
|
|
||||||
|
var shieldQuery = _entMan.GetEntityQuery<AmeShieldComponent>();
|
||||||
|
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<AmeShieldComponent>();
|
||||||
|
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<AmeControllerComponent>(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<ExplosionSystem>().TriggerExplosive(MasterController.Value, radius: radius, delete: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Content.Server/Ame/Components/AmeControllerComponent.cs
Normal file
81
Content.Server/Ame/Components/AmeControllerComponent.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The component used to make an entity the controller/fuel injector port of an AntiMatter Engine.
|
||||||
|
/// Connects to adjacent entities with this component or <see cref="AmeShieldComponent"/> to make an AME.
|
||||||
|
/// </summary>
|
||||||
|
[Access(typeof(AmeControllerSystem), typeof(AmeNodeGroup))]
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AmeControllerComponent : SharedAmeControllerComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The id of the container used to store the current fuel container for the AME.
|
||||||
|
/// </summary>
|
||||||
|
public const string FuelContainerId = "AmeFuel";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The container for the fuel canisters used by the AME.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public ContainerSlot JarSlot = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not the AME controller is currently injecting animatter into the reactor.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("injecting")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool Injecting = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much antimatter the AME controller is set to inject into the reactor per update.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("injectionAmount")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int InjectionAmount = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How stable the reactor currently is.
|
||||||
|
/// When this falls to <= 0 the reactor explodes.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("stability")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int Stability = 100;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound used when pressing buttons in the UI.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("clickSound")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public SoundSpecifier ClickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound used when injecting antimatter into the AME.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("injectSound")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Effects/bang.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last time this could have injected fuel into the AME.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("lastUpdate")]
|
||||||
|
public TimeSpan LastUpdate = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next time this will try to inject fuel into the AME.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("nextUpdate")]
|
||||||
|
public TimeSpan NextUpdate = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The the amount of time that passes between injection attempts.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("updatePeriod")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan UpdatePeriod = TimeSpan.FromSeconds(10.0);
|
||||||
|
}
|
||||||
23
Content.Server/Ame/Components/AmeFuelContainerComponent.cs
Normal file
23
Content.Server/Ame/Components/AmeFuelContainerComponent.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Content.Server.Ame.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An antimatter containment cell used to handle the fuel for the AME.
|
||||||
|
/// TODO: network and put in shared
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AmeFuelContainerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of fuel in the jar.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("fuelAmount")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int FuelAmount = 1000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum fuel capacity of the jar.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("fuelCapacity")]
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int FuelCapacity = 1000;
|
||||||
|
}
|
||||||
24
Content.Server/Ame/Components/AmePartComponent.cs
Normal file
24
Content.Server/Ame/Components/AmePartComponent.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Packaged AME machinery that can be deployed to construct an AME.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AmePartComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The sound played when the AME shielding is unpacked.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("unwrapSound")]
|
||||||
|
public SoundSpecifier UnwrapSound = new SoundPathSpecifier("/Audio/Effects/unwrap.ogg");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tool quality required to deploy the packaged AME shielding.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||||
|
public string QualityNeeded = "Pulsing";
|
||||||
|
}
|
||||||
26
Content.Server/Ame/Components/AmeShieldComponent.cs
Normal file
26
Content.Server/Ame/Components/AmeShieldComponent.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Content.Server.Ame.EntitySystems;
|
||||||
|
using Content.Shared.Ame;
|
||||||
|
|
||||||
|
namespace Content.Server.Ame.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The component used to make an entity part of the bulk machinery of an AntiMatter Engine.
|
||||||
|
/// Connects to adjacent entities with this component or <see cref="AmeControllerComponent"/> to make an AME.
|
||||||
|
/// </summary>
|
||||||
|
[Access(typeof(AmeShieldingSystem), typeof(AmeNodeGroup))]
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AmeShieldComponent : SharedAmeShieldComponent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this AME shield counts as a core for the AME or not.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public bool IsCore = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current integrity of the AME shield.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("integrity")]
|
||||||
|
[ViewVariables]
|
||||||
|
public int CoreIntegrity = 100;
|
||||||
|
}
|
||||||
326
Content.Server/Ame/EntitySystems/AmeControllerSystem.cs
Normal file
326
Content.Server/Ame/EntitySystems/AmeControllerSystem.cs
Normal file
@@ -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<AmeControllerComponent, ComponentStartup>(OnComponentStartup);
|
||||||
|
SubscribeLocalEvent<AmeControllerComponent, InteractUsingEvent>(OnInteractUsing);
|
||||||
|
SubscribeLocalEvent<AmeControllerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||||
|
SubscribeLocalEvent<AmeControllerComponent, UiButtonPressedMessage>(OnUiButtonPressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
var curTime = _gameTiming.CurTime;
|
||||||
|
var query = EntityQueryEnumerator<AmeControllerComponent, NodeContainerComponent>();
|
||||||
|
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<AmeFuelContainerComponent>(controller.JarSlot.ContainedEntity, out var fuelJar))
|
||||||
|
{
|
||||||
|
var availableInject = Math.Min(controller.InjectionAmount, fuelJar.FuelAmount);
|
||||||
|
var powerOutput = group.InjectFuel(availableInject, out var overloading);
|
||||||
|
if (TryComp<PowerSupplierComponent>(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<ApcPowerReceiverComponent>(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<AmeFuelContainerComponent>(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<AmeNodeGroup>()
|
||||||
|
.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<PowerSupplierComponent>(uid, out var powerOut))
|
||||||
|
powerOut.MaxSupply = 0;
|
||||||
|
|
||||||
|
UpdateUi(uid, controller);
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
if (!HasComp<MindContainerComponent>(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<MindContainerComponent>(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<ContainerSlot>(uid, AmeControllerComponent.FuelContainerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractUsing(EntityUid uid, AmeControllerComponent comp, InteractUsingEvent args)
|
||||||
|
{
|
||||||
|
if (!HasComp<HandsComponent>(args.User))
|
||||||
|
{
|
||||||
|
_popupSystem.PopupEntity(Loc.GetString("ame-controller-component-interact-using-no-hands-text"), uid, args.User);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HasComp<AmeFuelContainerComponent?>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the player entity is able to use the controller.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="playerEntity">The player entity.</param>
|
||||||
|
/// <returns>Returns true if the entity can use the controller, and false if it cannot.</returns>
|
||||||
|
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<ApcPowerReceiverComponent>(uid, out var powerSource) && !powerSource.Powered)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
using Content.Server.AME.Components;
|
using Content.Server.Ame.Components;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
|
|
||||||
namespace Content.Server.AME;
|
namespace Content.Server.Ame.EntitySystems;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds fuel level info to examine on fuel jars and handles network state.
|
/// Adds fuel level info to examine on fuel jars and handles network state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AMESystem
|
public sealed class AmeFuelSystem : EntitySystem
|
||||||
{
|
{
|
||||||
private void InitializeFuel()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
SubscribeLocalEvent<AMEFuelContainerComponent, ExaminedEvent>(OnFuelExamined);
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AmeFuelContainerComponent, ExaminedEvent>(OnFuelExamined);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFuelExamined(EntityUid uid, AMEFuelContainerComponent comp, ExaminedEvent args)
|
private void OnFuelExamined(EntityUid uid, AmeFuelContainerComponent comp, ExaminedEvent args)
|
||||||
{
|
{
|
||||||
if (!args.IsInDetailsRange)
|
if (!args.IsInDetailsRange)
|
||||||
return;
|
return;
|
||||||
51
Content.Server/Ame/EntitySystems/AmePartSystem.cs
Normal file
51
Content.Server/Ame/EntitySystems/AmePartSystem.cs
Normal file
@@ -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<AmePartComponent, InteractUsingEvent>(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<AmeShieldComponent>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Content.Server/Ame/EntitySystems/AmeShieldingSystem.cs
Normal file
41
Content.Server/Ame/EntitySystems/AmeShieldingSystem.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
71
Content.Shared/Ame/SharedAmeControllerComponent.cs
Normal file
71
Content.Shared/Ame/SharedAmeControllerComponent.cs
Normal file
@@ -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,
|
||||||
|
}
|
||||||
23
Content.Shared/Ame/SharedAmeShieldComponent.cs
Normal file
23
Content.Shared/Ame/SharedAmeShieldComponent.cs
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
components:
|
components:
|
||||||
- type: StorageFill
|
- type: StorageFill
|
||||||
contents:
|
contents:
|
||||||
- id: AMEPart
|
- id: AmePart
|
||||||
amount: 9
|
amount: 9
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
components:
|
components:
|
||||||
- type: StorageFill
|
- type: StorageFill
|
||||||
contents:
|
contents:
|
||||||
- id: AMEJar
|
- id: AmeJar
|
||||||
amount: 3
|
amount: 3
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
components:
|
components:
|
||||||
- type: StorageFill
|
- type: StorageFill
|
||||||
contents:
|
contents:
|
||||||
- id: AMEControllerUnanchored
|
- id: AmeControllerUnanchored
|
||||||
|
|
||||||
# Singularity
|
# Singularity
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
- type: MachineBoard
|
- type: MachineBoard
|
||||||
prototype: ParticleAcceleratorFuelChamberUnfinished
|
prototype: ParticleAcceleratorFuelChamberUnfinished
|
||||||
componentRequirements:
|
componentRequirements:
|
||||||
AMEFuelContainer:
|
AmeFuelContainer:
|
||||||
Amount: 1
|
Amount: 1
|
||||||
DefaultPrototype: AMEJar
|
DefaultPrototype: AmeJar
|
||||||
ExamineName: AME Fuel Jar
|
ExamineName: AME Fuel Jar
|
||||||
materialRequirements:
|
materialRequirements:
|
||||||
Glass: 10
|
Glass: 10
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
id: AMEJar
|
id: AmeJar
|
||||||
name: AME fuel jar
|
name: AME fuel jar
|
||||||
description: A hermetically sealed jar containing antimatter for use in an antimatter reactor.
|
description: A hermetically sealed jar containing antimatter for use in an antimatter reactor.
|
||||||
components:
|
components:
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Power/AME/ame_jar.rsi
|
sprite: Objects/Power/AME/ame_jar.rsi
|
||||||
state: jar
|
state: jar
|
||||||
- type: AMEFuelContainer
|
- type: AmeFuelContainer
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
id: AMEPart
|
id: AmePart
|
||||||
name: AME part
|
name: AME part
|
||||||
description: A flatpack used for constructing an antimatter engine reactor. Use a multitool to unpack it.
|
description: A flatpack used for constructing an antimatter engine reactor. Use a multitool to unpack it.
|
||||||
components:
|
components:
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Power/AME/ame_part.rsi
|
sprite: Objects/Power/AME/ame_part.rsi
|
||||||
state: box
|
state: box
|
||||||
- type: AMEPart
|
- type: AmePart
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
id: AMEController
|
id: AmeController
|
||||||
name: AME controller
|
name: AME controller
|
||||||
description: It's a controller for the antimatter engine.
|
description: It's a controller for the antimatter engine.
|
||||||
placement:
|
placement:
|
||||||
@@ -11,6 +11,11 @@
|
|||||||
snapCardinals: true
|
snapCardinals: true
|
||||||
sprite: Structures/Power/Generation/ame.rsi
|
sprite: Structures/Power/Generation/ame.rsi
|
||||||
state: control
|
state: control
|
||||||
|
layers:
|
||||||
|
- state: control
|
||||||
|
- state: control_on
|
||||||
|
map: ["display"]
|
||||||
|
visible: false
|
||||||
- type: Physics
|
- type: Physics
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
fixtures:
|
fixtures:
|
||||||
@@ -44,19 +49,26 @@
|
|||||||
noRot: true
|
noRot: true
|
||||||
- type: Anchorable
|
- type: Anchorable
|
||||||
- type: Pullable
|
- type: Pullable
|
||||||
- type: AMEController
|
- type: AmeController
|
||||||
- type: Explosive
|
- type: Explosive
|
||||||
explosionType: Default
|
explosionType: Default
|
||||||
intensitySlope: 5
|
intensitySlope: 5
|
||||||
maxIntensity: 60
|
maxIntensity: 60
|
||||||
- type: ActivatableUI
|
- type: ActivatableUI
|
||||||
key: enum.AMEControllerUiKey.Key
|
key: enum.AmeControllerUiKey.Key
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
interfaces:
|
interfaces:
|
||||||
- key: enum.AMEControllerUiKey.Key
|
- key: enum.AmeControllerUiKey.Key
|
||||||
type: AMEControllerBoundUserInterface
|
type: AmeControllerBoundUserInterface
|
||||||
- type: Appearance
|
- 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
|
- type: NodeContainer
|
||||||
examinable: true
|
examinable: true
|
||||||
nodes:
|
nodes:
|
||||||
@@ -72,7 +84,7 @@
|
|||||||
supplyRate: 0
|
supplyRate: 0
|
||||||
- type: ContainerContainer
|
- type: ContainerContainer
|
||||||
containers:
|
containers:
|
||||||
AMEController-fuelJarContainer: !type:ContainerSlot {}
|
AmeFuel: !type:ContainerSlot {}
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides: [ AME, Power ]
|
guides: [ AME, Power ]
|
||||||
- type: Electrified
|
- type: Electrified
|
||||||
@@ -85,8 +97,8 @@
|
|||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
noSpawn: true
|
noSpawn: true
|
||||||
parent: AMEController
|
parent: AmeController
|
||||||
id: AMEControllerUnanchored
|
id: AmeControllerUnanchored
|
||||||
suffix: Unanchored
|
suffix: Unanchored
|
||||||
components:
|
components:
|
||||||
- type: Transform
|
- type: Transform
|
||||||
@@ -95,7 +107,7 @@
|
|||||||
bodyType: Dynamic
|
bodyType: Dynamic
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: AMEShielding
|
id: AmeShielding
|
||||||
name: AME shielding
|
name: AME shielding
|
||||||
description: Keeps the antimatter in and the matter out.
|
description: Keeps the antimatter in and the matter out.
|
||||||
placement:
|
placement:
|
||||||
@@ -107,6 +119,15 @@
|
|||||||
drawdepth: Walls
|
drawdepth: Walls
|
||||||
sprite: Structures/Power/Generation/ame.rsi
|
sprite: Structures/Power/Generation/ame.rsi
|
||||||
state: shield_0
|
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: Physics
|
||||||
- type: Fixtures
|
- type: Fixtures
|
||||||
fixtures:
|
fixtures:
|
||||||
@@ -141,7 +162,7 @@
|
|||||||
mode: CardinalFlags
|
mode: CardinalFlags
|
||||||
base: shield_
|
base: shield_
|
||||||
key: ame_shield
|
key: ame_shield
|
||||||
- type: AMEShield
|
- type: AmeShield
|
||||||
- type: NodeContainer
|
- type: NodeContainer
|
||||||
nodes:
|
nodes:
|
||||||
ame:
|
ame:
|
||||||
@@ -153,9 +174,19 @@
|
|||||||
energy: 0.5
|
energy: 0.5
|
||||||
color: "#00AAFF"
|
color: "#00AAFF"
|
||||||
- type: Appearance
|
- 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
|
- type: Construction
|
||||||
graph: AMEShielding
|
graph: AmeShielding
|
||||||
node: ameShielding
|
node: ameShielding
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides: [ AME, Power ]
|
guides: [ AME, Power ]
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
- type: constructionGraph
|
- type: constructionGraph
|
||||||
id: AMEShielding
|
id: AmeShielding
|
||||||
start: start
|
start: start
|
||||||
graph:
|
graph:
|
||||||
- node: start
|
- node: start
|
||||||
|
|
||||||
- node: ameShielding
|
- node: ameShielding
|
||||||
entity: AMEShielding
|
entity: AmeShielding
|
||||||
edges:
|
edges:
|
||||||
- to: start
|
- to: start
|
||||||
completed:
|
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.
|
- !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"
|
message: "An AME shielding was deconstructed"
|
||||||
- !type:SpawnPrototype
|
- !type:SpawnPrototype
|
||||||
prototype: AMEPart
|
prototype: AmePart
|
||||||
amount: 1
|
amount: 1
|
||||||
- !type:DeleteEntity
|
- !type:DeleteEntity
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ The AME is one of the simplest engines available. You put together the multi-til
|
|||||||
## Construction
|
## Construction
|
||||||
<Box>Required parts:</Box>
|
<Box>Required parts:</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<GuideEntityEmbed Entity="AMEController"/>
|
<GuideEntityEmbed Entity="AmeController"/>
|
||||||
<GuideEntityEmbed Entity="AMEPart"/>
|
<GuideEntityEmbed Entity="AmePart"/>
|
||||||
<GuideEntityEmbed Entity="AMEJar"/>
|
<GuideEntityEmbed Entity="AmeJar"/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ FoodCondimentBottleSmallHotsauce: FoodCondimentBottleHotsauce
|
|||||||
FoodBakedCookieFortune: FoodSnackCookieFortune
|
FoodBakedCookieFortune: FoodSnackCookieFortune
|
||||||
GunSafeSubMachineGunVector: GunSafeSubMachineGunDrozd
|
GunSafeSubMachineGunVector: GunSafeSubMachineGunDrozd
|
||||||
|
|
||||||
|
# 2023-05-24
|
||||||
|
AMEController: AmeController
|
||||||
|
AMEJar: AmeJar
|
||||||
|
AMEPart: AmePart
|
||||||
|
AMEShielding: AmeShielding
|
||||||
|
|
||||||
# 2023-05-29
|
# 2023-05-29
|
||||||
OrGate: null
|
OrGate: null
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user