ECS AME (#16779)
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
30
Content.Server/Ame/EntitySystems/AmeFuelSystem.cs
Normal file
30
Content.Server/Ame/EntitySystems/AmeFuelSystem.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Server.Ame.Components;
|
||||
using Content.Shared.Examine;
|
||||
|
||||
namespace Content.Server.Ame.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Adds fuel level info to examine on fuel jars and handles network state.
|
||||
/// </summary>
|
||||
public sealed class AmeFuelSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AmeFuelContainerComponent, ExaminedEvent>(OnFuelExamined);
|
||||
}
|
||||
|
||||
private void OnFuelExamined(EntityUid uid, AmeFuelContainerComponent comp, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
// less than 25%: amount < capacity / 4 = amount * 4 < capacity
|
||||
var low = comp.FuelAmount * 4 < comp.FuelCapacity;
|
||||
args.PushMarkup(Loc.GetString("ame-fuel-container-component-on-examine-detailed-message",
|
||||
("colorName", low ? "darkorange" : "orange"),
|
||||
("amount", comp.FuelAmount),
|
||||
("capacity", comp.FuelCapacity)));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user