mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 13:37:47 +03:00
* Power Supply and Load Stuff (#2505) # Description Changes how `ApcPowerReceiverComponent` works a bit. Separated the `Load` variable into main and side power loads. If main power demand is not met, the machine is considered unpowered. Side power demand is "optional", as can be met only partially (or not at all) and the device will continue to operate. Depending on the device, this may have different negative effects on its operaton. such as lights dimming and weapon rechargers not charging at full speed. This was first intended to fix an annoying bug with `ChargerComponent` and `ApcPowerReceiverBatteryComponent`, that made the powernet spaz out for a while if their power demand was too high. This is now fixed. --- <details><summary><h1>Media</h1></summary> <p> <details><summary>Before (heavy flashing lights)</summary> <p> https://github.com/user-attachments/assets/de7fb84f-54d0-4c8a-ba9e-7a97e8489980 </p> </details> <details><summary>After</summary> <p> https://github.com/user-attachments/assets/9cece608-24f7-4ec9-95cd-0c719c7beddb </p> </details> </p> </details> --- # Changelog 🆑 - fix: Chargers and energy turrets no longer make the lights flash rapidly if their power draw is too high - add: Lights dim if the powernet they're connected to is overloaded * больно много жрёт --------- Co-authored-by: VMSolidus <evilexecutive@gmail.com>
303 lines
11 KiB
C#
303 lines
11 KiB
C#
using Content.Server.Power.Components;
|
|
using Content.Server.Emp;
|
|
using Content.Server.PowerCell;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.Power;
|
|
using Content.Shared.PowerCell.Components;
|
|
using Content.Shared.Emp;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.Containers;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Storage.Components;
|
|
using Robust.Server.Containers;
|
|
using Content.Shared.Whitelist;
|
|
|
|
namespace Content.Server.Power.EntitySystems;
|
|
|
|
[UsedImplicitly]
|
|
internal sealed class ChargerSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly ContainerSystem _container = default!;
|
|
[Dependency] private readonly PowerCellSystem _powerCell = default!;
|
|
[Dependency] private readonly BatterySystem _battery = default!;
|
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
|
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
|
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
SubscribeLocalEvent<ChargerComponent, ComponentStartup>(OnStartup);
|
|
SubscribeLocalEvent<ChargerComponent, PowerChangedEvent>(OnPowerChanged);
|
|
SubscribeLocalEvent<ChargerComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
|
SubscribeLocalEvent<ChargerComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
|
SubscribeLocalEvent<ChargerComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
|
|
SubscribeLocalEvent<ChargerComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
|
|
SubscribeLocalEvent<ChargerComponent, ExaminedEvent>(OnChargerExamine);
|
|
|
|
SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
|
|
}
|
|
|
|
private void OnStartup(EntityUid uid, ChargerComponent component, ComponentStartup args)
|
|
{
|
|
UpdateStatus(uid, component);
|
|
}
|
|
|
|
private void OnChargerExamine(EntityUid uid, ChargerComponent component, ExaminedEvent args)
|
|
{
|
|
using (args.PushGroup(nameof(ChargerComponent)))
|
|
{
|
|
// rate at which the charger charges
|
|
args.PushMarkup(Loc.GetString("charger-examine", ("color", "yellow"), ("chargeRate", (int) component.ChargeRate)));
|
|
|
|
// try to get contents of the charger
|
|
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
|
|
return;
|
|
|
|
if (HasComp<PowerCellSlotComponent>(uid))
|
|
return;
|
|
|
|
// if charger is empty and not a power cell type charger, add empty message
|
|
// power cells have their own empty message by default, for things like flash lights
|
|
if (container.ContainedEntities.Count == 0)
|
|
{
|
|
args.PushMarkup(Loc.GetString("charger-empty"));
|
|
}
|
|
else
|
|
{
|
|
// add how much each item is charged it
|
|
foreach (var contained in container.ContainedEntities)
|
|
{
|
|
if (!TryComp<BatteryComponent>(contained, out var battery))
|
|
continue;
|
|
|
|
var chargePercentage = (battery.CurrentCharge / battery.MaxCharge) * 100;
|
|
args.PushMarkup(Loc.GetString("charger-content", ("chargePercentage", (int) chargePercentage)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
var query = EntityQueryEnumerator<ActiveChargerComponent, ChargerComponent, ApcPowerReceiverComponent, ContainerManagerComponent>();
|
|
while (query.MoveNext(out var uid, out _, out var charger, out var apcReceiver, out var containerComp))
|
|
{
|
|
if (!_container.TryGetContainer(uid, charger.SlotId, out var container, containerComp))
|
|
continue;
|
|
|
|
if (charger.Status == CellChargerStatus.Empty || charger.Status == CellChargerStatus.Charged || container.ContainedEntities.Count == 0)
|
|
continue;
|
|
|
|
foreach (var contained in container.ContainedEntities)
|
|
{
|
|
TransferPower(uid, contained, charger, apcReceiver.SideLoadFraction, frameTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnPowerChanged(EntityUid uid, ChargerComponent component, ref PowerChangedEvent args)
|
|
{
|
|
UpdateStatus(uid, component);
|
|
}
|
|
|
|
private void OnInserted(EntityUid uid, ChargerComponent component, EntInsertedIntoContainerMessage args)
|
|
{
|
|
if (!component.Initialized)
|
|
return;
|
|
|
|
if (args.Container.ID != component.SlotId)
|
|
return;
|
|
|
|
UpdateStatus(uid, component);
|
|
}
|
|
|
|
private void OnRemoved(EntityUid uid, ChargerComponent component, EntRemovedFromContainerMessage args)
|
|
{
|
|
if (args.Container.ID != component.SlotId)
|
|
return;
|
|
|
|
UpdateStatus(uid, component);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the entity being inserted is actually rechargeable.
|
|
/// </summary>
|
|
private void OnInsertAttempt(EntityUid uid, ChargerComponent component, ContainerIsInsertingAttemptEvent args)
|
|
{
|
|
if (!component.Initialized)
|
|
return;
|
|
|
|
if (args.Container.ID != component.SlotId)
|
|
return;
|
|
|
|
if (!TryComp<PowerCellSlotComponent>(args.EntityUid, out var cellSlot))
|
|
return;
|
|
|
|
if (!cellSlot.FitsInCharger)
|
|
args.Cancel();
|
|
}
|
|
|
|
private void OnEntityStorageInsertAttempt(EntityUid uid, ChargerComponent component, ref InsertIntoEntityStorageAttemptEvent args)
|
|
{
|
|
if (!component.Initialized || args.Cancelled)
|
|
return;
|
|
|
|
if (!TryComp<PowerCellSlotComponent>(uid, out var cellSlot))
|
|
return;
|
|
|
|
if (!cellSlot.FitsInCharger)
|
|
args.Cancelled = true;
|
|
}
|
|
|
|
private void UpdateStatus(EntityUid uid, ChargerComponent component)
|
|
{
|
|
var status = GetStatus(uid, component);
|
|
TryComp(uid, out AppearanceComponent? appearance);
|
|
|
|
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
|
|
return;
|
|
|
|
_appearance.SetData(uid, CellVisual.Occupied, container.ContainedEntities.Count != 0, appearance);
|
|
if (component.Status == status || !TryComp(uid, out ApcPowerReceiverComponent? receiver))
|
|
return;
|
|
|
|
component.Status = status;
|
|
|
|
if (component.Status == CellChargerStatus.Charging)
|
|
{
|
|
AddComp<ActiveChargerComponent>(uid);
|
|
}
|
|
else
|
|
{
|
|
RemComp<ActiveChargerComponent>(uid);
|
|
}
|
|
|
|
switch (component.Status)
|
|
{
|
|
case CellChargerStatus.Off:
|
|
receiver.SideLoad = 0;
|
|
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance);
|
|
break;
|
|
case CellChargerStatus.Empty:
|
|
receiver.SideLoad = 0;
|
|
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance);
|
|
break;
|
|
case CellChargerStatus.Charging:
|
|
receiver.SideLoad = component.ChargeRate * component.ChargeEfficiency;
|
|
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance);
|
|
break;
|
|
case CellChargerStatus.Charged:
|
|
receiver.SideLoad = 0;
|
|
_appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance);
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args)
|
|
{
|
|
args.Affected = true;
|
|
args.Disabled = true;
|
|
}
|
|
|
|
private CellChargerStatus GetStatus(EntityUid uid, ChargerComponent component)
|
|
{
|
|
if (!component.Portable)
|
|
{
|
|
if (!TryComp(uid, out TransformComponent? transformComponent) || !transformComponent.Anchored)
|
|
return CellChargerStatus.Off;
|
|
}
|
|
|
|
if (!TryComp(uid, out ApcPowerReceiverComponent? apcPowerReceiverComponent))
|
|
return CellChargerStatus.Off;
|
|
|
|
if (!component.Portable && !apcPowerReceiverComponent.Powered)
|
|
return CellChargerStatus.Off;
|
|
|
|
if (HasComp<EmpDisabledComponent>(uid))
|
|
return CellChargerStatus.Off;
|
|
|
|
if (!_container.TryGetContainer(uid, component.SlotId, out var container))
|
|
return CellChargerStatus.Off;
|
|
|
|
if (container.ContainedEntities.Count == 0)
|
|
return CellChargerStatus.Empty;
|
|
|
|
if (!SearchForBattery(container.ContainedEntities[0], out var heldEnt, out var heldBattery))
|
|
return CellChargerStatus.Off;
|
|
|
|
if (_battery.IsFull(heldEnt.Value, heldBattery))
|
|
return CellChargerStatus.Charged;
|
|
|
|
return CellChargerStatus.Charging;
|
|
}
|
|
|
|
private void TransferPower(EntityUid uid, EntityUid targetEntity, ChargerComponent component, float powerDemandFraction, float frameTime)
|
|
{
|
|
if (!TryComp(uid, out ApcPowerReceiverComponent? receiverComponent))
|
|
return;
|
|
|
|
if (!receiverComponent.Powered)
|
|
return;
|
|
|
|
if (_whitelistSystem.IsWhitelistFail(component.Whitelist, targetEntity))
|
|
return;
|
|
|
|
if (!SearchForBattery(targetEntity, out var batteryUid, out var heldBattery))
|
|
return;
|
|
|
|
_battery.SetCharge(batteryUid.Value, heldBattery.CurrentCharge + component.ChargeRate * powerDemandFraction * frameTime, heldBattery);
|
|
// Just so the sprite won't be set to 99.99999% visibility
|
|
if (heldBattery.MaxCharge - heldBattery.CurrentCharge < 0.01)
|
|
{
|
|
_battery.SetCharge(batteryUid.Value, heldBattery.MaxCharge, heldBattery);
|
|
}
|
|
|
|
UpdateStatus(uid, component);
|
|
}
|
|
|
|
// Goobstation - Modsuits - Changed charger logic to work with suits in cyborg charger
|
|
private bool SearchForBattery(EntityUid uid, [NotNullWhen(true)] out EntityUid? batteryUid, [NotNullWhen(true)] out BatteryComponent? component)
|
|
{
|
|
batteryUid = null;
|
|
component = null;
|
|
|
|
// try get a battery directly on the inserted entity
|
|
if (TryComp(uid, out component))
|
|
{
|
|
batteryUid = uid;
|
|
return true;
|
|
}
|
|
|
|
// Try to get the battery by checking for a power cell slot on the inserted entity
|
|
if (_powerCell.TryGetBatteryFromSlot(uid, out batteryUid, out component))
|
|
return true;
|
|
|
|
if (TryComp<InventoryComponent>(uid, out var inventory))
|
|
{
|
|
var relayEv = new FindInventoryBatteryEvent();
|
|
_inventorySystem.RelayEvent((uid, inventory), ref relayEv);
|
|
|
|
if (relayEv.FoundBattery != null)
|
|
{
|
|
batteryUid = relayEv.FoundBattery.Value.Owner;
|
|
component = relayEv.FoundBattery.Value.Comp;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Goobstation - Modsuits stuff
|
|
[ByRefEvent]
|
|
public record struct FindInventoryBatteryEvent() : IInventoryRelayEvent
|
|
{
|
|
public SlotFlags TargetSlots { get; } = SlotFlags.WITHOUT_POCKET;
|
|
|
|
public Entity<BatteryComponent>? FoundBattery { get; set; }
|
|
}
|