This commit is contained in:
TemporalOroboros
2023-06-28 05:02:06 -07:00
committed by GitHub
parent 638026878e
commit d9de405859
35 changed files with 965 additions and 1005 deletions

View 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);
}
}

View 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);
}

View 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;
}

View 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";
}

View 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;
}

View 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;
}
}

View 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)));
}
}

View 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);
}
}

View 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);
}
}