From cfe3ef8efeaf13f4fedde293c7077b71ccf95010 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 22 Dec 2025 16:46:09 +0100 Subject: [PATCH] Sync charger and battery systems with upstream --- .../Power/EntitySystems/ChargerSystem.cs | 5 - .../Medical/HealthAnalyzerSystem.cs | 13 +- .../Power/EntitySystems/BatterySystem.API.cs | 8 +- .../Power/EntitySystems/ChargerSystem.cs | 259 ------------------ .../Power/EntitySystems/ChargerSystem.cs | 254 +++++++++++++++++ .../EntitySystems/SharedChargerSystem.cs | 20 -- 6 files changed, 270 insertions(+), 289 deletions(-) delete mode 100644 Content.Client/Power/EntitySystems/ChargerSystem.cs delete mode 100644 Content.Server/Power/EntitySystems/ChargerSystem.cs create mode 100644 Content.Shared/Power/EntitySystems/ChargerSystem.cs delete mode 100644 Content.Shared/Power/EntitySystems/SharedChargerSystem.cs diff --git a/Content.Client/Power/EntitySystems/ChargerSystem.cs b/Content.Client/Power/EntitySystems/ChargerSystem.cs deleted file mode 100644 index efadde30e0..0000000000 --- a/Content.Client/Power/EntitySystems/ChargerSystem.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Content.Shared.Power.EntitySystems; - -namespace Content.Client.Power.EntitySystems; - -public sealed class ChargerSystem : SharedChargerSystem; diff --git a/Content.Server/Medical/HealthAnalyzerSystem.cs b/Content.Server/Medical/HealthAnalyzerSystem.cs index 2e782e16f0..35d6c9f8f5 100644 --- a/Content.Server/Medical/HealthAnalyzerSystem.cs +++ b/Content.Server/Medical/HealthAnalyzerSystem.cs @@ -12,6 +12,7 @@ using Content.Shared.Item.ItemToggle.Components; using Content.Shared.MedicalScanner; using Content.Shared.Mobs.Components; using Content.Shared.Popups; +using Content.Shared.PowerCell.Components; using Content.Shared.Temperature.Components; using Content.Shared.Traits.Assorted; using Robust.Server.GameObjects; @@ -83,7 +84,11 @@ public sealed class HealthAnalyzerSystem : EntitySystem /// private void OnAfterInteract(Entity uid, ref AfterInteractEvent args) { - if (args.Target == null || !args.CanReach || !HasComp(args.Target) || !_cell.HasDrawCharge(uid, user: args.User)) + TryComp(uid, out PowerCellDrawComponent? draw); + TryComp(uid, out PowerCellSlotComponent? slot); + + if (args.Target == null || !args.CanReach || !HasComp(args.Target) || + !_cell.HasDrawCharge((uid.Owner, draw, slot), user: args.User)) return; _audio.PlayPvs(uid.Comp.ScanningBeginSound, uid); @@ -103,7 +108,11 @@ public sealed class HealthAnalyzerSystem : EntitySystem private void OnDoAfter(Entity uid, ref HealthAnalyzerDoAfterEvent args) { - if (args.Handled || args.Cancelled || args.Target == null || !_cell.HasDrawCharge(uid, user: args.User)) + TryComp(uid, out PowerCellDrawComponent? draw); + TryComp(uid, out PowerCellSlotComponent? slot); + + if (args.Handled || args.Cancelled || args.Target == null || + !_cell.HasDrawCharge((uid.Owner, draw, slot), user: args.User)) return; if (!uid.Comp.Silent) diff --git a/Content.Server/Power/EntitySystems/BatterySystem.API.cs b/Content.Server/Power/EntitySystems/BatterySystem.API.cs index 5683e7c133..5995758d3d 100644 --- a/Content.Server/Power/EntitySystems/BatterySystem.API.cs +++ b/Content.Server/Power/EntitySystems/BatterySystem.API.cs @@ -26,7 +26,7 @@ public sealed partial class BatterySystem TrySetChargeCooldown(ent.Owner); - var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.MaxCharge); + var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, delta, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref ev); return delta; } @@ -61,21 +61,23 @@ public sealed partial class BatterySystem return; } - var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.MaxCharge); + var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.CurrentCharge - oldCharge, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref ev); } + public override void SetMaxCharge(Entity ent, float value) { if (!Resolve(ent, ref ent.Comp)) return; var old = ent.Comp.MaxCharge; + var oldCharge = ent.Comp.CurrentCharge; ent.Comp.MaxCharge = Math.Max(value, 0); ent.Comp.CurrentCharge = Math.Min(ent.Comp.CurrentCharge, ent.Comp.MaxCharge); if (MathHelper.CloseTo(ent.Comp.MaxCharge, old)) return; - var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.MaxCharge); + var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.CurrentCharge - oldCharge, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref ev); } diff --git a/Content.Server/Power/EntitySystems/ChargerSystem.cs b/Content.Server/Power/EntitySystems/ChargerSystem.cs deleted file mode 100644 index 9c6b0907f7..0000000000 --- a/Content.Server/Power/EntitySystems/ChargerSystem.cs +++ /dev/null @@ -1,259 +0,0 @@ -using Content.Server.Power.Components; -using Content.Shared.Examine; -using Content.Shared.PowerCell; -using Content.Shared.Power; -using Content.Shared.Power.Components; -using Content.Shared.Power.EntitySystems; -using Content.Shared.PowerCell.Components; -using Content.Shared.Emp; -using JetBrains.Annotations; -using Robust.Shared.Containers; -using System.Diagnostics.CodeAnalysis; -using Content.Shared.Storage.Components; -using Robust.Server.Containers; -using Content.Shared.Whitelist; - -namespace Content.Server.Power.EntitySystems; - -[UsedImplicitly] -public sealed class ChargerSystem : SharedChargerSystem -{ - [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!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnPowerChanged); - SubscribeLocalEvent(OnInserted); - SubscribeLocalEvent(OnRemoved); - SubscribeLocalEvent(OnInsertAttempt); - SubscribeLocalEvent(OnEntityStorageInsertAttempt); - SubscribeLocalEvent(OnChargerExamine); - } - - 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(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(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(); - while (query.MoveNext(out var uid, out _, out var charger, 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, 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); - } - - /// - /// Verify that the entity being inserted is actually rechargeable. - /// - private void OnInsertAttempt(EntityUid uid, ChargerComponent component, ContainerIsInsertingAttemptEvent args) - { - if (!component.Initialized) - return; - - if (args.Container.ID != component.SlotId) - return; - - if (!TryComp(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(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(uid); - } - else - { - RemComp(uid); - } - - switch (component.Status) - { - case CellChargerStatus.Off: - receiver.Load = 0; - _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Off, appearance); - break; - case CellChargerStatus.Empty: - receiver.Load = 0; - _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Empty, appearance); - break; - case CellChargerStatus.Charging: - receiver.Load = component.ChargeRate; - _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charging, appearance); - break; - case CellChargerStatus.Charged: - receiver.Load = 0; - _appearance.SetData(uid, CellVisual.Light, CellChargerStatus.Charged, appearance); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - 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(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 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 * frameTime, heldBattery); - UpdateStatus(uid, component); - } - - private bool SearchForBattery(EntityUid uid, [NotNullWhen(true)] out EntityUid? batteryUid, [NotNullWhen(true)] out BatteryComponent? component) - { - // try get a battery directly on the inserted entity - if (!TryComp(uid, out component)) - { - // or by checking for a power cell slot on the inserted entity - return _powerCell.TryGetBatteryFromSlot(uid, out batteryUid, out component); - } - batteryUid = uid; - return true; - } -} diff --git a/Content.Shared/Power/EntitySystems/ChargerSystem.cs b/Content.Shared/Power/EntitySystems/ChargerSystem.cs new file mode 100644 index 0000000000..b561eedcbb --- /dev/null +++ b/Content.Shared/Power/EntitySystems/ChargerSystem.cs @@ -0,0 +1,254 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Emp; +using Content.Shared.Examine; +using Content.Shared.Power.Components; +using Content.Shared.PowerCell; +using Content.Shared.PowerCell.Components; +using Content.Shared.Storage.Components; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; +using Robust.Shared.Timing; + +namespace Content.Shared.Power.EntitySystems; + +public sealed class ChargerSystem : EntitySystem +{ + [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnInserted); + SubscribeLocalEvent(OnRemoved); + SubscribeLocalEvent(OnInsertAttempt); + SubscribeLocalEvent(OnEntityStorageInsertAttempt); + SubscribeLocalEvent(OnChargerExamine); + SubscribeLocalEvent(OnEmpPulse); + SubscribeLocalEvent(OnEmpRemoved); + SubscribeLocalEvent(OnRefreshChargeRate); + SubscribeLocalEvent(OnStatusChanged); + } + + private void OnStartup(Entity ent, ref ComponentStartup args) + { + UpdateStatus(ent); + } + + 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(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 (!_powerCell.TryGetBatteryFromEntityOrSlot(contained, out var battery)) + continue; + + var chargePercent = _battery.GetChargeLevel(battery.Value.AsNullable()) * 100; + args.PushMarkup(Loc.GetString("charger-content", ("chargePercent", (int)chargePercent))); + } + } + } + } + + private void OnPowerChanged(Entity ent, ref PowerChangedEvent args) + { + RefreshAllBatteries(ent); + UpdateStatus(ent); + } + + private void OnInserted(Entity ent, ref EntInsertedIntoContainerMessage args) + { + if (_timing.ApplyingState) + return; // Already networked in the same gamestate + + if (args.Container.ID != ent.Comp.SlotId) + return; + + AddComp(args.Entity); + if (_powerCell.TryGetBatteryFromEntityOrSlot(args.Entity, out var battery)) + _battery.RefreshChargeRate(battery.Value.AsNullable()); + UpdateStatus(ent); + } + + private void OnRemoved(Entity ent, ref EntRemovedFromContainerMessage args) + { + if (_timing.ApplyingState) + return; // Already networked in the same gamestate + + if (args.Container.ID != ent.Comp.SlotId) + return; + + RemComp(args.Entity); + if (_powerCell.TryGetBatteryFromEntityOrSlot(args.Entity, out var battery)) + _battery.RefreshChargeRate(battery.Value.AsNullable()); + UpdateStatus(ent); + } + + /// + /// Verify that the entity being inserted is actually rechargeable. + /// + private void OnInsertAttempt(EntityUid uid, ChargerComponent component, ContainerIsInsertingAttemptEvent args) + { + if (!component.Initialized) + return; + + if (args.Container.ID != component.SlotId) + return; + + if (!TryComp(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 (args.Container.ID != component.SlotId) + return; + + if (!TryComp(uid, out var cellSlot)) + return; + + if (!cellSlot.FitsInCharger) + args.Cancelled = true; + } + private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) + { + args.Affected = true; + args.Disabled = true; + RefreshAllBatteries(ent); + UpdateStatus(ent); + } + + private void OnEmpRemoved(Entity ent, ref EmpDisabledRemovedEvent args) + { + RefreshAllBatteries(ent); + UpdateStatus(ent); + } + + private void OnRefreshChargeRate(Entity ent, ref RefreshChargeRateEvent args) + { + var chargerUid = Transform(ent).ParentUid; + + if (HasComp(chargerUid)) + return; + + if (!TryComp(chargerUid, out var chargerComp)) + return; + + if (!chargerComp.Portable && !_receiver.IsPowered(chargerUid)) + return; + + if (_whitelist.IsWhitelistFail(chargerComp.Whitelist, ent.Owner)) + return; + + args.NewChargeRate += chargerComp.ChargeRate; + } + private void OnStatusChanged(Entity ent, ref PredictedBatteryStateChangedEvent args) + { + // If the battery is full update the visuals and power draw of the charger. + + var chargerUid = Transform(ent).ParentUid; + if (!TryComp(chargerUid, out var chargerComp)) + return; + + UpdateStatus((chargerUid, chargerComp)); + } + + private void RefreshAllBatteries(Entity ent) + { + // try to get contents of the charger + if (!_container.TryGetContainer(ent.Owner, ent.Comp.SlotId, out var container)) + return; + + foreach (var item in container.ContainedEntities) + { + if (_powerCell.TryGetBatteryFromEntityOrSlot(item, out var battery)) + _battery.RefreshChargeRate(battery.Value.AsNullable()); + } + } + + private void UpdateStatus(Entity ent) + { + TryComp(ent, out var appearance); + + if (!_container.TryGetContainer(ent.Owner, ent.Comp.SlotId, out var container)) + return; + + _appearance.SetData(ent.Owner, CellVisual.Occupied, container.ContainedEntities.Count != 0, appearance); + + var status = GetStatus(ent); + switch (status) + { + case CellChargerStatus.Charging: + // TODO: If someone ever adds chargers that can charge multiple batteries at once then set this to the total draw rate. + _receiver.SetLoad(ent.Owner, ent.Comp.ChargeRate); + break; + default: + // Don't set the load to 0 or the charger will be considered as powered even if the LV connection is unpowered. + // TODO: Fix this on an ApcPowerReceiver level. + _receiver.SetLoad(ent.Owner, ent.Comp.PassiveDraw); + break; + } + _appearance.SetData(ent.Owner, CellVisual.Light, status, appearance); + } + + private CellChargerStatus GetStatus(Entity ent) + { + if (!ent.Comp.Portable && !Transform(ent).Anchored) + return CellChargerStatus.Off; + + if (!ent.Comp.Portable && !_receiver.IsPowered(ent.Owner)) + return CellChargerStatus.Off; + + if (HasComp(ent)) + return CellChargerStatus.Off; + + if (!_container.TryGetContainer(ent.Owner, ent.Comp.SlotId, out var container)) + return CellChargerStatus.Off; + + if (container.ContainedEntities.Count == 0) + return CellChargerStatus.Empty; + + // Use the first stored battery for visuals. If someone ever makes a multi-slot charger then this will need to be changed. + if (!_powerCell.TryGetBatteryFromEntityOrSlot(container.ContainedEntities[0], out var battery)) + return CellChargerStatus.Off; + + if (_battery.IsFull(battery.Value.AsNullable())) + return CellChargerStatus.Charged; + + return CellChargerStatus.Charging; + } +} diff --git a/Content.Shared/Power/EntitySystems/SharedChargerSystem.cs b/Content.Shared/Power/EntitySystems/SharedChargerSystem.cs deleted file mode 100644 index a150436bef..0000000000 --- a/Content.Shared/Power/EntitySystems/SharedChargerSystem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Content.Shared.Emp; -using Content.Shared.Power.Components; - -namespace Content.Shared.Power.EntitySystems; - -public abstract class SharedChargerSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnEmpPulse); - } - - private void OnEmpPulse(EntityUid uid, ChargerComponent component, ref EmpPulseEvent args) - { - args.Affected = true; - args.Disabled = true; - } -}