From 3266c94eac0dea5bdfc4b8cd814ed7b0a980b580 Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Fri, 19 Dec 2025 19:18:12 +0100 Subject: [PATCH] Unify BatteryComponent and PredictedBatteryComponent (#41867) * unify * cleanup and merge conflicts * floating points --------- Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> --- .../Silicons/Borgs/BorgMenu.xaml.cs | 4 +- Content.Client/Silicons/Borgs/BorgSystem.cs | 2 +- .../Tests/Power/PowerTest.cs | 62 ++++-- .../Tests/Power/StationPowerTests.cs | 5 +- .../Systems/AdminVerbSystem.Tools.cs | 57 +----- .../Construction/Completions/BuildMech.cs | 2 +- .../EntitySystems/EmergencyLightSystem.cs | 2 +- .../EntitySystems/HandheldLightSystem.cs | 4 +- Content.Server/Mech/Systems/MechSystem.cs | 10 +- .../Ninja/Systems/BatteryDrainerSystem.cs | 18 +- .../Ninja/Systems/ItemCreatorSystem.cs | 2 +- .../Ninja/Systems/NinjaSuitSystem.cs | 6 +- .../Ninja/Systems/SpaceNinjaSystem.cs | 4 +- .../Ninja/Systems/StunProviderSystem.cs | 2 +- .../EntitySystems/BatteryInterfaceSystem.cs | 9 +- .../Power/EntitySystems/BatterySystem.API.cs | 155 -------------- .../Power/EntitySystems/BatterySystem.cs | 113 +++-------- .../PowerMonitoringConsoleSystem.cs | 4 +- .../Power/EntitySystems/PowerNetSystem.cs | 7 +- .../Power/EntitySystems/RiggableSystem.cs | 27 +-- Content.Server/Power/SMES/SmesSystem.cs | 5 +- .../Power/SetBatteryPercentCommand.cs | 6 +- Content.Server/PowerSink/PowerSinkSystem.cs | 4 +- .../Radio/EntitySystems/JammerSystem.cs | 4 +- .../SensorMonitoring/BatterySensorSystem.cs | 5 +- Content.Server/Silicons/Borgs/BorgSystem.cs | 2 +- .../Silicons/StationAi/StationAiSystem.cs | 9 +- .../DamagedSiliconAccentSystem.cs | 2 +- .../Stunnable/Systems/StunbatonSystem.cs | 16 +- .../Tesla/EntitySystem/TeslaCoilSystem.cs | 2 +- .../Artifact/XAE/XAEChargeBatterySystem.cs | 9 - Content.Shared/Power/ChargeEvents.cs | 44 +--- .../Power/Components/BatteryComponent.cs | 90 ++++++-- .../BatterySelfRechargerComponent.cs | 23 +-- ...omponent.cs => BatteryVisualsComponent.cs} | 10 +- .../Components/ExaminableBatteryComponent.cs | 2 +- .../Components/PredictedBatteryComponent.cs | 94 --------- .../PredictedBatterySelfRechargerComponent.cs | 33 --- .../Power/EntitySystems/ChargerSystem.cs | 6 +- .../EntitySystems/PredictedBatterySystem.cs | 182 ----------------- ...stem.API.cs => SharedBatterySystem.API.cs} | 46 ++--- .../EntitySystems/SharedBatterySystem.cs | 192 +++++++++++++----- .../Components/PowerCellComponent.cs | 2 +- .../PowerCell/PowerCellSystem.API.cs | 16 +- .../PowerCell/PowerCellSystem.Relay.cs | 4 +- Content.Shared/PowerCell/PowerCellSystem.cs | 14 +- .../ActivatableUISystem.Power.cs | 4 +- .../BatteryAmmoProviderComponent.cs | 2 +- .../Ranged/Systems/SharedGunSystem.Battery.cs | 24 +-- .../Prototypes/Catalog/Bounties/bounties.yml | 1 - .../Entities/Clothing/Hands/gloves.yml | 2 +- .../Clothing/Head/base_clothinghead.yml | 4 +- .../Entities/Clothing/OuterClothing/armor.yml | 4 +- .../Entities/Debugging/debug_sweps.yml | 2 +- .../Entities/Mobs/NPCs/behonker.yml | 4 +- .../Entities/Mobs/NPCs/lavaland.yml | 4 +- .../Entities/Mobs/NPCs/living_light.yml | 4 +- .../Entities/Mobs/NPCs/miscellaneous.yml | 4 +- .../Entities/Mobs/Player/silicon.yml | 5 + .../Entities/Objects/Power/powercells.yml | 46 ++--- .../Entities/Objects/Power/powersink.yml | 5 + .../Specific/Medical/healthanalyzer.yml | 2 +- .../Objects/Tools/handheld_mass_scanner.yml | 2 +- .../Weapons/Guns/Battery/battery_guns.yml | 24 +-- .../Weapons/Guns/Turrets/turrets_base.yml | 5 + .../Objects/Weapons/Guns/pneumatic_cannon.yml | 4 +- .../Objects/Weapons/Melee/stunprod.yml | 2 +- .../Entities/Objects/Weapons/security.yml | 2 +- .../Structures/Lighting/base_lighting.yml | 5 + .../Power/Generation/Tesla/coil.yml | 5 + .../Entities/Structures/Power/apc.yml | 5 + .../Entities/Structures/Power/debug_power.yml | 10 + .../Entities/Structures/Power/smes.yml | 5 + .../Entities/Structures/Power/substation.yml | 5 + 74 files changed, 549 insertions(+), 959 deletions(-) delete mode 100644 Content.Server/Power/EntitySystems/BatterySystem.API.cs rename Content.Shared/Power/Components/{PredictedBatteryVisualsComponent.cs => BatteryVisualsComponent.cs} (75%) delete mode 100644 Content.Shared/Power/Components/PredictedBatteryComponent.cs delete mode 100644 Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs delete mode 100644 Content.Shared/Power/EntitySystems/PredictedBatterySystem.cs rename Content.Shared/Power/EntitySystems/{PredictedBatterySystem.API.cs => SharedBatterySystem.API.cs} (79%) diff --git a/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs b/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs index 0acc3a2646..eee45bacc8 100644 --- a/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs +++ b/Content.Client/Silicons/Borgs/BorgMenu.xaml.cs @@ -21,7 +21,7 @@ public sealed partial class BorgMenu : FancyWindow [Dependency] private readonly IEntityManager _entity = default!; private readonly NameModifierSystem _nameModifier; private readonly PowerCellSystem _powerCell; - private readonly PredictedBatterySystem _battery; + private readonly SharedBatterySystem _battery; public Action? BrainButtonPressed; public Action? EjectBatteryButtonPressed; @@ -44,7 +44,7 @@ public sealed partial class BorgMenu : FancyWindow _nameModifier = _entity.System(); _powerCell = _entity.System(); - _battery = _entity.System(); + _battery = _entity.System(); _maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength); diff --git a/Content.Client/Silicons/Borgs/BorgSystem.cs b/Content.Client/Silicons/Borgs/BorgSystem.cs index a4ca3725dc..517027d082 100644 --- a/Content.Client/Silicons/Borgs/BorgSystem.cs +++ b/Content.Client/Silicons/Borgs/BorgSystem.cs @@ -18,7 +18,7 @@ public sealed partial class BorgSystem : SharedBorgSystem [Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _player = default!; diff --git a/Content.IntegrationTests/Tests/Power/PowerTest.cs b/Content.IntegrationTests/Tests/Power/PowerTest.cs index 85bd366697..a28f646ef8 100644 --- a/Content.IntegrationTests/Tests/Power/PowerTest.cs +++ b/Content.IntegrationTests/Tests/Power/PowerTest.cs @@ -54,6 +54,7 @@ namespace Content.IntegrationTests.Tests.Power nodeGroupID: HVPower - type: PowerNetworkBattery - type: Battery + netsync: false - type: BatteryCharger - type: entity @@ -68,6 +69,7 @@ namespace Content.IntegrationTests.Tests.Power nodeGroupID: HVPower - type: PowerNetworkBattery - type: Battery + netsync: false - type: BatteryDischarger - type: entity @@ -85,6 +87,7 @@ namespace Content.IntegrationTests.Tests.Power nodeGroupID: HVPower - type: PowerNetworkBattery - type: Battery + netsync: false - type: BatteryDischarger node: output - type: BatteryCharger @@ -110,6 +113,7 @@ namespace Content.IntegrationTests.Tests.Power maxSupply: 1000 supplyRampTolerance: 1000 - type: Battery + netsync: false maxCharge: 1000 startingCharge: 1000 - type: Transform @@ -119,6 +123,7 @@ namespace Content.IntegrationTests.Tests.Power id: ApcDummy components: - type: Battery + netsync: false maxCharge: 10000 startingCharge: 10000 - type: PowerNetworkBattery @@ -380,6 +385,8 @@ namespace Content.IntegrationTests.Tests.Power const float startingCharge = 100_000; PowerNetworkBatteryComponent netBattery = default!; + EntityUid generatorEnt = default!; + EntityUid consumerEnt = default!; BatteryComponent battery = default!; PowerConsumerComponent consumer = default!; @@ -395,8 +402,8 @@ namespace Content.IntegrationTests.Tests.Power entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates()); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); + generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates()); + consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); netBattery = entityManager.GetComponent(generatorEnt); battery = entityManager.GetComponent(generatorEnt); @@ -441,7 +448,8 @@ namespace Content.IntegrationTests.Tests.Power // Trivial integral to calculate expected power spent. const double spentExpected = (200 + 100) / 2.0 * 0.25; - Assert.That(battery.CurrentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev)); + var currentCharge = batterySys.GetCharge((generatorEnt, battery)); + Assert.That(currentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev)); }); }); @@ -460,7 +468,8 @@ namespace Content.IntegrationTests.Tests.Power // Trivial integral to calculate expected power spent. const double spentExpected = (400 + 100) / 2.0 * 0.75 + 400 * 0.25; - Assert.That(battery.CurrentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev)); + var currentCharge = batterySys.GetCharge((generatorEnt, battery)); + Assert.That(currentCharge, Is.EqualTo(startingCharge - spentExpected).Within(tickDev)); }); }); @@ -576,6 +585,8 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); var mapSys = entityManager.System(); + EntityUid generatorEnt = default!; + EntityUid batteryEnt = default!; PowerSupplierComponent supplier = default!; BatteryComponent battery = default!; @@ -591,8 +602,8 @@ namespace Content.IntegrationTests.Tests.Power entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); } - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); - var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); + batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.Owner.ToCoordinates(0, 2)); supplier = entityManager.GetComponent(generatorEnt); var netBattery = entityManager.GetComponent(batteryEnt); @@ -615,7 +626,8 @@ namespace Content.IntegrationTests.Tests.Power { // half a second @ 500 W = 250 // 50% efficiency, so 125 J stored total. - Assert.That(battery.CurrentCharge, Is.EqualTo(125).Within(0.1)); + var currentCharge = batterySys.GetCharge((batteryEnt, battery)); + Assert.That(currentCharge, Is.EqualTo(125).Within(0.1)); Assert.That(supplier.CurrentSupply, Is.EqualTo(500).Within(0.1)); }); }); @@ -633,6 +645,9 @@ namespace Content.IntegrationTests.Tests.Power var gameTiming = server.ResolveDependency(); var batterySys = entityManager.System(); var mapSys = entityManager.System(); + EntityUid batteryEnt = default!; + EntityUid supplyEnt = default!; + EntityUid consumerEnt = default!; PowerConsumerComponent consumer = default!; PowerSupplierComponent supplier = default!; PowerNetworkBatteryComponent netBattery = default!; @@ -653,9 +668,9 @@ namespace Content.IntegrationTests.Tests.Power var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); + batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumer = entityManager.GetComponent(consumerEnt); supplier = entityManager.GetComponent(supplyEnt); @@ -694,7 +709,8 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(200).Within(0.1)); const int expectedSpent = 200; - Assert.That(battery.CurrentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev)); + var currentCharge = batterySys.GetCharge((batteryEnt, battery)); + Assert.That(currentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev)); }); }); @@ -711,6 +727,9 @@ namespace Content.IntegrationTests.Tests.Power var gameTiming = server.ResolveDependency(); var batterySys = entityManager.System(); var mapSys = entityManager.System(); + EntityUid batteryEnt = default!; + EntityUid supplyEnt = default!; + EntityUid consumerEnt = default!; PowerConsumerComponent consumer = default!; PowerSupplierComponent supplier = default!; PowerNetworkBatteryComponent netBattery = default!; @@ -731,9 +750,9 @@ namespace Content.IntegrationTests.Tests.Power var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); entityManager.GetComponent(terminal).LocalRotation = Angle.FromDegrees(180); - var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); - var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); - var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); + batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); + supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumer = entityManager.GetComponent(consumerEnt); supplier = entityManager.GetComponent(supplyEnt); @@ -772,7 +791,8 @@ namespace Content.IntegrationTests.Tests.Power Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(400).Within(0.1)); const int expectedSpent = 400; - Assert.That(battery.CurrentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev)); + var currentCharge = batterySys.GetCharge((batteryEnt, battery)); + Assert.That(currentCharge, Is.EqualTo(battery.MaxCharge - expectedSpent).Within(tickDev)); }); }); @@ -1223,6 +1243,9 @@ namespace Content.IntegrationTests.Tests.Power var entityManager = server.ResolveDependency(); var batterySys = entityManager.System(); var mapSys = entityManager.System(); + EntityUid generatorEnt = default!; + EntityUid substationEnt = default!; + EntityUid apcEnt = default!; PowerNetworkBatteryComponent substationNetBattery = default!; BatteryComponent apcBattery = default!; @@ -1242,9 +1265,9 @@ namespace Content.IntegrationTests.Tests.Power entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 1)); entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 2)); - var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); - var substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.Owner.ToCoordinates(0, 1)); - var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 2)); + generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); + substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.Owner.ToCoordinates(0, 1)); + apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 2)); var generatorSupplier = entityManager.GetComponent(generatorEnt); substationNetBattery = entityManager.GetComponent(substationEnt); @@ -1262,8 +1285,9 @@ namespace Content.IntegrationTests.Tests.Power { Assert.Multiple(() => { + var currentCharge = batterySys.GetCharge((apcEnt, apcBattery)); Assert.That(substationNetBattery.CurrentSupply, Is.GreaterThan(0)); //substation should be providing power - Assert.That(apcBattery.CurrentCharge, Is.GreaterThan(0)); //apc battery should have gained charge + Assert.That(currentCharge, Is.GreaterThan(0)); //apc battery should have gained charge }); }); diff --git a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs index 542a4645c6..9d79abf480 100644 --- a/Content.IntegrationTests/Tests/Power/StationPowerTests.cs +++ b/Content.IntegrationTests/Tests/Power/StationPowerTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.GameTicking; using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; using Content.Server.Power.NodeGroups; using Content.Server.Power.Pow3r; using Content.Shared.Maps; @@ -48,6 +49,7 @@ public sealed class StationPowerTests var entMan = server.EntMan; var protoMan = server.ProtoMan; var ticker = entMan.System(); + var batterySys = entMan.System(); // Load the map await server.WaitAssertion(() => @@ -71,7 +73,8 @@ public sealed class StationPowerTests if (node.NodeGroup is not IBasePowerNet group) continue; networks.TryGetValue(group.NetworkNode, out var charge); - networks[group.NetworkNode] = charge + battery.CurrentCharge; + var currentCharge = batterySys.GetCharge((uid, battery)); + networks[group.NetworkNode] = charge + currentCharge; } var totalStartingCharge = networks.MaxBy(n => n.Value).Value; diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs index bd214578ea..f30e0ef728 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs @@ -4,7 +4,6 @@ using System.Numerics; using Content.Server.Cargo.Components; using Content.Server.Doors.Systems; using Content.Server.Hands.Systems; -using Content.Server.Power.EntitySystems; using Content.Server.Stack; using Content.Server.Station.Systems; using Content.Server.Weapons.Ranged.Systems; @@ -51,8 +50,7 @@ public sealed partial class AdminVerbSystem [Dependency] private readonly AdminTestArenaSystem _adminTestArenaSystem = default!; [Dependency] private readonly StationJobsSystem _stationJobsSystem = default!; [Dependency] private readonly JointSystem _jointSystem = default!; - [Dependency] private readonly BatterySystem _batterySystem = default!; - [Dependency] private readonly PredictedBatterySystem _predictedBatterySystem = default!; + [Dependency] private readonly SharedBatterySystem _batterySystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly GunSystem _gun = default!; @@ -161,57 +159,6 @@ public sealed partial class AdminVerbSystem args.Verbs.Add(makeVulnerable); } - if (TryComp(args.Target, out var pBattery)) - { - Verb refillBattery = new() - { - Text = Loc.GetString("admin-verbs-refill-battery"), - Category = VerbCategory.Tricks, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")), - Act = () => - { - _predictedBatterySystem.SetCharge((args.Target, pBattery), pBattery.MaxCharge); - }, - Impact = LogImpact.Medium, - Message = Loc.GetString("admin-trick-refill-battery-description"), - Priority = (int)TricksVerbPriorities.RefillBattery, - }; - args.Verbs.Add(refillBattery); - - Verb drainBattery = new() - { - Text = Loc.GetString("admin-verbs-drain-battery"), - Category = VerbCategory.Tricks, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")), - Act = () => - { - _predictedBatterySystem.SetCharge((args.Target, pBattery), 0); - }, - Impact = LogImpact.Medium, - Priority = (int)TricksVerbPriorities.DrainBattery, - }; - args.Verbs.Add(drainBattery); - - Verb infiniteBattery = new() - { - Text = Loc.GetString("admin-verbs-infinite-battery"), - Category = VerbCategory.Tricks, - Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/infinite_battery.png")), - Act = () => - { - var recharger = EnsureComp(args.Target); - recharger.AutoRechargeRate = pBattery.MaxCharge; // Instant refill. - recharger.AutoRechargePauseTime = TimeSpan.Zero; // No delay. - Dirty(args.Target, recharger); - _predictedBatterySystem.RefreshChargeRate((args.Target, pBattery)); - }, - Impact = LogImpact.Medium, - Message = Loc.GetString("admin-trick-infinite-battery-object-description"), - Priority = (int)TricksVerbPriorities.InfiniteBattery, - }; - args.Verbs.Add(infiniteBattery); - } - if (TryComp(args.Target, out var battery)) { Verb refillBattery = new() @@ -254,6 +201,8 @@ public sealed partial class AdminVerbSystem var recharger = EnsureComp(args.Target); recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill. recharger.AutoRechargePauseTime = TimeSpan.Zero; // No delay. + Dirty(args.Target, recharger); + _batterySystem.RefreshChargeRate((args.Target, battery)); }, Impact = LogImpact.Medium, Message = Loc.GetString("admin-trick-infinite-battery-object-description"), diff --git a/Content.Server/Construction/Completions/BuildMech.cs b/Content.Server/Construction/Completions/BuildMech.cs index 5f1e40c347..c0b5921db9 100644 --- a/Content.Server/Construction/Completions/BuildMech.cs +++ b/Content.Server/Construction/Completions/BuildMech.cs @@ -48,7 +48,7 @@ public sealed partial class BuildMech : IGraphAction var cell = container.ContainedEntities[0]; - if (!entityManager.TryGetComponent(cell, out var batteryComponent)) + if (!entityManager.TryGetComponent(cell, out var batteryComponent)) { Logger.Warning($"Mech construct entity {uid} had an invalid entity in container \"{Container}\"! Aborting build mech action."); return; diff --git a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs index eddef87853..824afbc789 100644 --- a/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs +++ b/Content.Server/Light/EntitySystems/EmergencyLightSystem.cs @@ -153,7 +153,7 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem } else { - _battery.SetCharge((entity.Owner, battery), battery.CurrentCharge + entity.Comp.ChargingWattage * frameTime * entity.Comp.ChargingEfficiency); + _battery.ChangeCharge((entity.Owner, battery), entity.Comp.ChargingWattage * frameTime * entity.Comp.ChargingEfficiency); if (_battery.IsFull((entity.Owner, battery))) { if (TryComp(entity.Owner, out var receiver)) diff --git a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs index da024ee075..e4e947b548 100644 --- a/Content.Server/Light/EntitySystems/HandheldLightSystem.cs +++ b/Content.Server/Light/EntitySystems/HandheldLightSystem.cs @@ -24,7 +24,7 @@ namespace Content.Server.Light.EntitySystems [Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPointLightSystem _lights = default!; @@ -147,7 +147,7 @@ namespace Content.Server.Light.EntitySystems } // TODO: Very important: Make this charge rate based instead of instantly removing charge each update step. - // See PredictedBatteryComponent + // See BatteryComponent public override void Update(float frameTime) { var toRemove = new RemQueue>(); diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 80169cb2ed..61aa1aa1cc 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -33,7 +33,7 @@ public sealed partial class MechSystem : SharedMechSystem { [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly AtmosphereSystem _atmosphere = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; @@ -88,7 +88,7 @@ public sealed partial class MechSystem : SharedMechSystem if (TryComp(uid, out var panel) && !panel.Open) return; - if (component.BatterySlot.ContainedEntity == null && TryComp(args.Used, out var battery)) + if (component.BatterySlot.ContainedEntity == null && TryComp(args.Used, out var battery)) { InsertBattery(uid, args.Used, component, battery); _actionBlocker.UpdateCanMove(uid); @@ -109,7 +109,7 @@ public sealed partial class MechSystem : SharedMechSystem private void OnInsertBattery(EntityUid uid, MechComponent component, EntInsertedIntoContainerMessage args) { - if (args.Container != component.BatterySlot || !TryComp(args.Entity, out var battery)) + if (args.Container != component.BatterySlot || !TryComp(args.Entity, out var battery)) return; component.Energy = _battery.GetCharge((args.Entity, battery)); @@ -337,7 +337,7 @@ public sealed partial class MechSystem : SharedMechSystem if (battery == null) return false; - if (!TryComp(battery, out var batteryComp)) + if (!TryComp(battery, out var batteryComp)) return false; _battery.SetCharge((battery.Value, batteryComp), _battery.GetCharge((battery.Value, batteryComp)) + delta.Float()); @@ -353,7 +353,7 @@ public sealed partial class MechSystem : SharedMechSystem return true; } - public void InsertBattery(EntityUid uid, EntityUid toInsert, MechComponent? component = null, PredictedBatteryComponent? battery = null) + public void InsertBattery(EntityUid uid, EntityUid toInsert, MechComponent? component = null, BatteryComponent? battery = null) { if (!Resolve(uid, ref component, false)) return; diff --git a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs index f6d9d7bec7..98fae3ae13 100644 --- a/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs +++ b/Content.Server/Ninja/Systems/BatteryDrainerSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Ninja.Events; using Content.Server.Power.Components; -using Content.Server.Power.EntitySystems; using Content.Shared.DoAfter; using Content.Shared.Interaction; using Content.Shared.Ninja.Components; @@ -17,8 +16,7 @@ namespace Content.Server.Ninja.Systems; /// public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem { - [Dependency] private readonly BatterySystem _battery = default!; - [Dependency] private readonly PredictedBatterySystem _predictedBattery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; @@ -80,20 +78,20 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem protected override bool TryDrainPower(Entity ent, EntityUid target) { var (uid, comp) = ent; - if (comp.BatteryUid == null || !TryComp(comp.BatteryUid.Value, out var battery)) + if (comp.BatteryUid == null || !TryComp(comp.BatteryUid.Value, out var battery)) return false; if (!TryComp(target, out var targetBattery) || !TryComp(target, out var pnb)) return false; - if (MathHelper.CloseToPercent(targetBattery.CurrentCharge, 0)) + var available = _battery.GetCharge((target, targetBattery)); + if (MathHelper.CloseToPercent(available, 0)) { _popup.PopupEntity(Loc.GetString("battery-drainer-empty", ("battery", target)), uid, uid, PopupType.Medium); return false; } - var available = targetBattery.CurrentCharge; - var required = battery.MaxCharge - _predictedBattery.GetCharge((comp.BatteryUid.Value, battery)); + var required = battery.MaxCharge - _battery.GetCharge((comp.BatteryUid.Value, battery)); // higher tier storages can charge more var maxDrained = pnb.MaxSupply * comp.DrainTime; var input = Math.Min(Math.Min(available, required / comp.DrainEfficiency), maxDrained); @@ -101,15 +99,13 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem return false; var output = input * comp.DrainEfficiency; - // PowerCells use PredictedBatteryComponent - // SMES, substations and APCs use BatteryComponent - _predictedBattery.ChangeCharge((comp.BatteryUid.Value, battery), output); + _battery.ChangeCharge((comp.BatteryUid.Value, battery), output); // TODO: create effect message or something Spawn("EffectSparks", Transform(target).Coordinates); _audio.PlayPvs(comp.SparkSound, target); _popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid); // repeat the doafter until battery is full - return !_predictedBattery.IsFull((comp.BatteryUid.Value, battery)); + return !_battery.IsFull((comp.BatteryUid.Value, battery)); } } diff --git a/Content.Server/Ninja/Systems/ItemCreatorSystem.cs b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs index 227fdea789..cca80f2e42 100644 --- a/Content.Server/Ninja/Systems/ItemCreatorSystem.cs +++ b/Content.Server/Ninja/Systems/ItemCreatorSystem.cs @@ -9,7 +9,7 @@ namespace Content.Server.Ninja.Systems; public sealed class ItemCreatorSystem : SharedItemCreatorSystem { - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; diff --git a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs index 8ebd56ea7c..f6c4643cad 100644 --- a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs +++ b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs @@ -62,7 +62,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery)) return; - if (!TryComp(args.EntityUid, out var inserting)) + if (!TryComp(args.EntityUid, out var inserting)) { args.Cancel(); return; @@ -88,11 +88,11 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem } // this function assigns a score to a power cell depending on the capacity, to be used when comparing which cell is better. - private float GetCellScore(EntityUid uid, PredictedBatteryComponent battcomp) + private float GetCellScore(EntityUid uid, BatteryComponent battcomp) { // if a cell is able to automatically recharge, boost the score drastically depending on the recharge rate, // this is to ensure a ninja can still upgrade to a micro reactor cell even if they already have a medium or high. - if (TryComp(uid, out var selfcomp)) + if (TryComp(uid, out var selfcomp)) return battcomp.MaxCharge + selfcomp.AutoRechargeRate * AutoRechargeValue; return battcomp.MaxCharge; } diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs index f2fc9abfcc..4f3a1873f5 100644 --- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs +++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs @@ -24,7 +24,7 @@ namespace Content.Server.Ninja.Systems; public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem { [Dependency] private readonly AlertsSystem _alerts = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly CodeConditionSystem _codeCondition = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly SharedMindSystem _mind = default!; @@ -91,7 +91,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem /// /// Get the battery component in a ninja's suit, if it's worn. /// - public bool GetNinjaBattery(EntityUid user, [NotNullWhen(true)] out EntityUid? batteryUid, [NotNullWhen(true)] out PredictedBatteryComponent? batteryComp) + public bool GetNinjaBattery(EntityUid user, [NotNullWhen(true)] out EntityUid? batteryUid, [NotNullWhen(true)] out BatteryComponent? batteryComp) { if (TryComp(user, out var ninja) && ninja.Suit != null diff --git a/Content.Server/Ninja/Systems/StunProviderSystem.cs b/Content.Server/Ninja/Systems/StunProviderSystem.cs index 49d7ab5e85..387844b6b1 100644 --- a/Content.Server/Ninja/Systems/StunProviderSystem.cs +++ b/Content.Server/Ninja/Systems/StunProviderSystem.cs @@ -17,7 +17,7 @@ namespace Content.Server.Ninja.Systems; /// public sealed class StunProviderSystem : SharedStunProviderSystem { - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; diff --git a/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs b/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs index 83ff28646d..d2c95065db 100644 --- a/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs +++ b/Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Power.Components; using Content.Shared.Database; using Content.Shared.Power; using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Robust.Server.GameObjects; namespace Content.Server.Power.EntitySystems; @@ -24,6 +25,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem { [Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = null!; + [Dependency] private readonly SharedBatterySystem _battery = null!; public override void Initialize() { @@ -48,7 +50,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem var netBattery = Comp(ent); netBattery.CanCharge = args.On; - _adminLog.Add(LogType.Action,$"{ToPrettyString(args.Actor):actor} set input breaker to {args.On} on {ToPrettyString(ent):target}"); + _adminLog.Add(LogType.Action, $"{ToPrettyString(args.Actor):actor} set input breaker to {args.On} on {ToPrettyString(ent):target}"); } private void HandleSetOutputBreaker(Entity ent, ref BatterySetOutputBreakerMessage args) @@ -56,7 +58,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem var netBattery = Comp(ent); netBattery.CanDischarge = args.On; - _adminLog.Add(LogType.Action,$"{ToPrettyString(args.Actor):actor} set output breaker to {args.On} on {ToPrettyString(ent):target}"); + _adminLog.Add(LogType.Action, $"{ToPrettyString(args.Actor):actor} set output breaker to {args.On} on {ToPrettyString(ent):target}"); } private void HandleSetChargeRate(Entity ent, ref BatterySetChargeRateMessage args) @@ -90,13 +92,14 @@ public sealed class BatteryInterfaceSystem : EntitySystem if (!_uiSystem.IsUiOpen(uid, BatteryUiKey.Key)) return; + var currentCharge = _battery.GetCharge((uid, battery)); _uiSystem.SetUiState( uid, BatteryUiKey.Key, new BatteryBuiState { Capacity = battery.MaxCharge, - Charge = battery.CurrentCharge, + Charge = currentCharge, CanCharge = netBattery.CanCharge, CanDischarge = netBattery.CanDischarge, CurrentReceiving = netBattery.CurrentReceiving, diff --git a/Content.Server/Power/EntitySystems/BatterySystem.API.cs b/Content.Server/Power/EntitySystems/BatterySystem.API.cs deleted file mode 100644 index 5995758d3d..0000000000 --- a/Content.Server/Power/EntitySystems/BatterySystem.API.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Content.Shared.Power; -using Content.Shared.Power.Components; -using Content.Shared.Power.EntitySystems; - -namespace Content.Server.Power.EntitySystems; - -/// -/// Responsible for . -/// Unpredicted equivalent of . -/// If you make changes to this make sure to keep the two consistent. -/// -public sealed partial class BatterySystem -{ - public override float ChangeCharge(Entity ent, float amount) - { - if (!Resolve(ent, ref ent.Comp)) - return 0; - - var newValue = Math.Clamp(ent.Comp.CurrentCharge + amount, 0, ent.Comp.MaxCharge); - var delta = newValue - ent.Comp.CurrentCharge; - - if (delta == 0f) - return delta; - - ent.Comp.CurrentCharge = newValue; - - TrySetChargeCooldown(ent.Owner); - - var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, delta, ent.Comp.MaxCharge); - RaiseLocalEvent(ent, ref ev); - return delta; - } - - public override float UseCharge(Entity ent, float amount) - { - if (amount <= 0f || !Resolve(ent, ref ent.Comp) || ent.Comp.CurrentCharge == 0) - return 0f; - - return ChangeCharge(ent, -amount); - } - - public override bool TryUseCharge(Entity ent, float amount) - { - if (!Resolve(ent, ref ent.Comp, false) || amount > ent.Comp.CurrentCharge) - return false; - - UseCharge(ent, amount); - return true; - } - - public override void SetCharge(Entity ent, float value) - { - if (!Resolve(ent, ref ent.Comp)) - return; - - var oldCharge = ent.Comp.CurrentCharge; - ent.Comp.CurrentCharge = MathHelper.Clamp(value, 0, ent.Comp.MaxCharge); - if (MathHelper.CloseTo(ent.Comp.CurrentCharge, oldCharge) && - !(oldCharge != ent.Comp.CurrentCharge && ent.Comp.CurrentCharge == ent.Comp.MaxCharge)) - { - return; - } - - 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.CurrentCharge - oldCharge, ent.Comp.MaxCharge); - RaiseLocalEvent(ent, ref ev); - } - - /// - /// Gets the battery's current charge. - /// - public float GetCharge(Entity ent) - { - if (!Resolve(ent, ref ent.Comp, false)) - return 0f; - - return ent.Comp.CurrentCharge; - } - - /// - /// Gets number of remaining uses for the given charge cost. - /// - public int GetRemainingUses(Entity ent, float cost) - { - if (cost <= 0) - return 0; - - if (!Resolve(ent, ref ent.Comp)) - return 0; - - return (int)(ent.Comp.CurrentCharge / cost); - } - - /// - /// Gets number of maximum uses at full charge for the given charge cost. - /// - public int GetMaxUses(Entity ent, float cost) - { - if (cost <= 0) - return 0; - - if (!Resolve(ent, ref ent.Comp)) - return 0; - - return (int)(ent.Comp.MaxCharge / cost); - } - - public override void TrySetChargeCooldown(Entity ent) - { - if (!Resolve(ent, ref ent.Comp, false)) - return; - - if (ent.Comp.AutoRechargePauseTime == TimeSpan.Zero) - return; // no recharge pause - - if (_timing.CurTime + ent.Comp.AutoRechargePauseTime <= ent.Comp.NextAutoRecharge) - return; // the current pause is already longer - - SetChargeCooldown(ent, ent.Comp.AutoRechargePauseTime); - } - - public override void SetChargeCooldown(Entity ent, TimeSpan cooldown) - { - if (!Resolve(ent, ref ent.Comp)) - return; - - ent.Comp.NextAutoRecharge = _timing.CurTime + cooldown; - } - - /// - /// Returns whether the battery is full. - /// - public bool IsFull(Entity ent) - { - if (!Resolve(ent, ref ent.Comp)) - return false; - - return ent.Comp.CurrentCharge >= ent.Comp.MaxCharge; - } -} diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs index 1a88672bc9..a9b750b6d4 100644 --- a/Content.Server/Power/EntitySystems/BatterySystem.cs +++ b/Content.Server/Power/EntitySystems/BatterySystem.cs @@ -1,85 +1,58 @@ using Content.Server.Power.Components; -using Content.Shared.Cargo; -using Content.Shared.Examine; -using Content.Shared.Power; using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; using Content.Shared.Rejuvenate; -using JetBrains.Annotations; using Robust.Shared.Utility; -using Robust.Shared.Timing; namespace Content.Server.Power.EntitySystems; -/// -/// Responsible for . -/// Unpredicted equivalent of . -/// If you make changes to this make sure to keep the two consistent. -/// -[UsedImplicitly] -public sealed partial class BatterySystem : SharedBatterySystem +public sealed class BatterySystem : SharedBatterySystem { - [Dependency] private readonly IGameTiming _timing = default!; - public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent(OnBatteryRejuvenate); SubscribeLocalEvent(OnNetBatteryRejuvenate); - SubscribeLocalEvent(CalculateBatteryPrice); - SubscribeLocalEvent(OnChangeCharge); - SubscribeLocalEvent(OnGetCharge); - SubscribeLocalEvent(PreSync); SubscribeLocalEvent(PostSync); } - private void OnInit(Entity ent, ref ComponentInit args) - { - DebugTools.Assert(!HasComp(ent), $"{ent} has both BatteryComponent and PredictedBatteryComponent"); - } - private void OnNetBatteryRejuvenate(Entity ent, ref RejuvenateEvent args) - { - ent.Comp.NetworkBattery.CurrentStorage = ent.Comp.NetworkBattery.Capacity; - } - private void OnBatteryRejuvenate(Entity ent, ref RejuvenateEvent args) - { - SetCharge(ent.AsNullable(), ent.Comp.MaxCharge); - } - - private void OnExamine(Entity ent, ref ExaminedEvent args) + protected override void OnStartup(Entity ent, ref ComponentStartup args) { - if (!args.IsInDetailsRange) + // Debug assert to prevent anyone from killing their networking performance by dirtying a battery's charge every single tick. + // This checks for components that interact with the power network, have a charge rate that ramps up over time and therefore + // have to set the charge in an update loop instead of using a subscription. + // This is usually the case for APCs, SMES, battery powered turrets or similar. + // For those entities you should disable net sync for the battery in your prototype, using + /// + /// - type: Battery + /// netSync: false + /// + /// This disables networking and prediction for this battery. + if (!ent.Comp.NetSyncEnabled) return; - if (!HasComp(ent)) - return; + DebugTools.Assert(!HasComp(ent), $"{ToPrettyString(ent.Owner)} has a predicted battery connected to the power net. Disable net sync!"); + DebugTools.Assert(!HasComp(ent), $"{ToPrettyString(ent.Owner)} has a predicted battery connected to the power net. Disable net sync!"); + DebugTools.Assert(!HasComp(ent), $"{ToPrettyString(ent.Owner)} has a predicted battery connected to the power net. Disable net sync!"); + } - var chargePercentRounded = 0; - if (ent.Comp.MaxCharge != 0) - chargePercentRounded = (int)(100 * ent.Comp.CurrentCharge / ent.Comp.MaxCharge); - args.PushMarkup( - Loc.GetString( - "examinable-battery-component-examine-detail", - ("percent", chargePercentRounded), - ("markupPercentColor", "green") - ) - ); + private void OnNetBatteryRejuvenate(Entity ent, ref RejuvenateEvent args) + { + ent.Comp.NetworkBattery.CurrentStorage = ent.Comp.NetworkBattery.Capacity; } private void PreSync(NetworkBatteryPreSync ev) { // Ignoring entity pausing. If the entity was paused, neither component's data should have been changed. var enumerator = AllEntityQuery(); - while (enumerator.MoveNext(out var netBat, out var bat)) + while (enumerator.MoveNext(out var uid, out var netBat, out var bat)) { - DebugTools.Assert(bat.CurrentCharge <= bat.MaxCharge && bat.CurrentCharge >= 0); + var currentCharge = GetCharge((uid, bat)); + DebugTools.Assert(currentCharge <= bat.MaxCharge && currentCharge >= 0); netBat.NetworkBattery.Capacity = bat.MaxCharge; - netBat.NetworkBattery.CurrentStorage = bat.CurrentCharge; + netBat.NetworkBattery.CurrentStorage = currentCharge; } } @@ -92,42 +65,4 @@ public sealed partial class BatterySystem : SharedBatterySystem SetCharge((uid, bat), netBat.NetworkBattery.CurrentStorage); } } - - /// - /// Gets the price for the power contained in an entity's battery. - /// - private void CalculateBatteryPrice(Entity ent, ref PriceCalculationEvent args) - { - args.Price += ent.Comp.CurrentCharge * ent.Comp.PricePerJoule; - } - - private void OnChangeCharge(Entity ent, ref ChangeChargeEvent args) - { - if (args.ResidualValue == 0) - return; - - args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue); - } - - private void OnGetCharge(Entity entity, ref GetChargeEvent args) - { - args.CurrentCharge += entity.Comp.CurrentCharge; - args.MaxCharge += entity.Comp.MaxCharge; - } - - public override void Update(float frameTime) - { - var query = EntityQueryEnumerator(); - var curTime = _timing.CurTime; - while (query.MoveNext(out var uid, out var comp, out var bat)) - { - if (!comp.AutoRecharge || IsFull((uid, bat))) - continue; - - if (comp.NextAutoRecharge > curTime) - continue; - - SetCharge((uid, bat), bat.CurrentCharge + comp.AutoRechargeRate * frameTime); - } - } } diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index 25757360b3..e0344f4d6c 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Pinpointer; using Content.Shared.Station.Components; using Content.Shared.Power; using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; @@ -22,6 +23,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori { [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly SharedMapSystem _sharedMapSystem = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; // Note: this data does not need to be saved private Dictionary> _gridPowerCableChunks = new(); @@ -510,7 +512,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori if (effectiveMax == 0) effectiveMax = 1; - return battery.CurrentCharge / effectiveMax; + return _battery.GetCharge((uid, battery)) / effectiveMax; } private void GetSourcesForNode(EntityUid uid, Node node, out List sources) diff --git a/Content.Server/Power/EntitySystems/PowerNetSystem.cs b/Content.Server/Power/EntitySystems/PowerNetSystem.cs index 5b6b922ddf..1c3f76fdee 100644 --- a/Content.Server/Power/EntitySystems/PowerNetSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerNetSystem.cs @@ -358,17 +358,18 @@ namespace Content.Server.Power.EntitySystems if (requireBattery) { - _battery.SetCharge((uid, battery), battery.CurrentCharge - apcBattery.IdleLoad * frameTime); + _battery.ChangeCharge((uid, battery), -apcBattery.IdleLoad * frameTime); } // Otherwise try to charge the battery else if (powered && !_battery.IsFull((uid, battery))) { apcReceiver.Load += apcBattery.BatteryRechargeRate * apcBattery.BatteryRechargeEfficiency; - _battery.SetCharge((uid, battery), battery.CurrentCharge + apcBattery.BatteryRechargeRate * frameTime); + _battery.ChangeCharge((uid, battery), apcBattery.BatteryRechargeRate * frameTime); } // Enable / disable the battery if the state changed - var enableBattery = requireBattery && battery.CurrentCharge > 0; + var currentCharge = _battery.GetCharge((uid, battery)); + var enableBattery = requireBattery && currentCharge > 0; if (apcBattery.Enabled != enableBattery) { diff --git a/Content.Server/Power/EntitySystems/RiggableSystem.cs b/Content.Server/Power/EntitySystems/RiggableSystem.cs index 390e75eeb8..0bff0f4ba8 100644 --- a/Content.Server/Power/EntitySystems/RiggableSystem.cs +++ b/Content.Server/Power/EntitySystems/RiggableSystem.cs @@ -18,7 +18,7 @@ public sealed class RiggableSystem : EntitySystem { [Dependency] private readonly ExplosionSystem _explosionSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly PredictedBatterySystem _predictedBattery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; public override void Initialize() { @@ -27,7 +27,6 @@ public sealed class RiggableSystem : EntitySystem SubscribeLocalEvent(OnMicrowaved); SubscribeLocalEvent(OnSolutionChanged); SubscribeLocalEvent(OnChargeChanged); - SubscribeLocalEvent(OnChargeChanged); } private void OnRejuvenate(Entity entity, ref RejuvenateEvent args) @@ -39,16 +38,7 @@ public sealed class RiggableSystem : EntitySystem { if (TryComp(entity, out var batteryComponent)) { - if (batteryComponent.CurrentCharge == 0f) - return; - - Explode(entity, batteryComponent.CurrentCharge); - args.Handled = true; - } - - if (TryComp(entity, out var predictedBatteryComponent)) - { - var charge = _predictedBattery.GetCharge((entity, predictedBatteryComponent)); + var charge = _battery.GetCharge((entity, batteryComponent)); if (charge == 0f) return; @@ -80,20 +70,7 @@ public sealed class RiggableSystem : EntitySystem QueueDel(uid); } - // non-predicted batteries private void OnChargeChanged(Entity ent, ref ChargeChangedEvent args) - { - if (!ent.Comp.IsRigged) - return; - - if (args.Charge == 0f) - return; // No charge to cause an explosion. - - Explode(ent, args.Charge); - } - - // predicted batteries - private void OnChargeChanged(Entity ent, ref PredictedBatteryChargeChangedEvent args) { if (!ent.Comp.IsRigged) return; diff --git a/Content.Server/Power/SMES/SmesSystem.cs b/Content.Server/Power/SMES/SmesSystem.cs index 15c40b3c92..5a8169e1c9 100644 --- a/Content.Server/Power/SMES/SmesSystem.cs +++ b/Content.Server/Power/SMES/SmesSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Power; using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.Rounding; using Content.Shared.SMES; using JetBrains.Annotations; @@ -14,6 +15,7 @@ internal sealed class SmesSystem : EntitySystem { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; public override void Initialize() { @@ -61,7 +63,8 @@ internal sealed class SmesSystem : EntitySystem if (!Resolve(uid, ref battery, false)) return 0; - return ContentHelpers.RoundToLevels(battery.CurrentCharge, battery.MaxCharge, 6); + var currentCharge = _battery.GetCharge((uid, battery)); + return ContentHelpers.RoundToLevels(currentCharge, battery.MaxCharge, 6); } private ChargeState CalcChargeState(EntityUid uid, PowerNetworkBatteryComponent? netBattery = null) diff --git a/Content.Server/Power/SetBatteryPercentCommand.cs b/Content.Server/Power/SetBatteryPercentCommand.cs index bd48e6cd97..f13a1b820e 100644 --- a/Content.Server/Power/SetBatteryPercentCommand.cs +++ b/Content.Server/Power/SetBatteryPercentCommand.cs @@ -1,5 +1,4 @@ using Content.Server.Administration; -using Content.Server.Power.EntitySystems; using Content.Shared.Administration; using Content.Shared.Power.Components; using Content.Shared.Power.EntitySystems; @@ -10,8 +9,7 @@ namespace Content.Server.Power [AdminCommand(AdminFlags.Debug)] public sealed class SetBatteryPercentCommand : LocalizedEntityCommands { - [Dependency] private readonly BatterySystem _batterySystem = default!; - [Dependency] private readonly PredictedBatterySystem _predictedBatterySystem = default!; + [Dependency] private readonly SharedBatterySystem _batterySystem = default!; public override string Command => "setbatterypercent"; @@ -39,8 +37,6 @@ namespace Content.Server.Power if (EntityManager.TryGetComponent(id, out var battery)) _batterySystem.SetCharge((id.Value, battery), battery.MaxCharge * percent / 100); - else if (EntityManager.TryGetComponent(id, out var pBattery)) - _predictedBatterySystem.SetCharge((id.Value, pBattery), pBattery.MaxCharge * percent / 100); else { shell.WriteLine(Loc.GetString($"cmd-setbatterypercent-battery-not-found", ("id", id))); diff --git a/Content.Server/PowerSink/PowerSinkSystem.cs b/Content.Server/PowerSink/PowerSinkSystem.cs index 328bff89f4..c8ea58a395 100644 --- a/Content.Server/PowerSink/PowerSinkSystem.cs +++ b/Content.Server/PowerSink/PowerSinkSystem.cs @@ -68,7 +68,7 @@ namespace Content.Server.PowerSink _battery.ChangeCharge((entity, battery), networkLoad.NetworkLoad.ReceivingPower * frameTime); - var currentBatteryThreshold = battery.CurrentCharge / battery.MaxCharge; + var currentBatteryThreshold = _battery.GetChargeLevel((entity, battery)); // Check for warning message threshold if (!component.SentImminentExplosionWarningMessage && @@ -90,7 +90,7 @@ namespace Content.Server.PowerSink } // Check for explosion - if (battery.CurrentCharge < battery.MaxCharge) + if (!_battery.IsFull((entity, battery))) continue; if (component.ExplosionTime == null) diff --git a/Content.Server/Radio/EntitySystems/JammerSystem.cs b/Content.Server/Radio/EntitySystems/JammerSystem.cs index eaa42d744c..dead85f51f 100644 --- a/Content.Server/Radio/EntitySystems/JammerSystem.cs +++ b/Content.Server/Radio/EntitySystems/JammerSystem.cs @@ -11,7 +11,7 @@ namespace Content.Server.Radio.EntitySystems; public sealed class JammerSystem : SharedJammerSystem { [Dependency] private readonly PowerCellSystem _powerCell = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedDeviceNetworkJammerSystem _jammer = default!; @@ -25,7 +25,7 @@ public sealed class JammerSystem : SharedJammerSystem } // TODO: Very important: Make this charge rate based instead of updating every single tick - // See PredictedBatteryComponent + // See BatteryComponent public override void Update(float frameTime) { var query = EntityQueryEnumerator(); diff --git a/Content.Server/SensorMonitoring/BatterySensorSystem.cs b/Content.Server/SensorMonitoring/BatterySensorSystem.cs index bd94868c5f..2c7666ba96 100644 --- a/Content.Server/SensorMonitoring/BatterySensorSystem.cs +++ b/Content.Server/SensorMonitoring/BatterySensorSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Power.Components; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Events; using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; namespace Content.Server.SensorMonitoring; @@ -11,6 +12,7 @@ public sealed class BatterySensorSystem : EntitySystem public const string DeviceNetworkCommandSyncData = "bat_sync_data"; [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; public override void Initialize() { @@ -26,13 +28,14 @@ public sealed class BatterySensorSystem : EntitySystem { case DeviceNetworkCommandSyncData: var battery = Comp(uid); + var currentCharge = _battery.GetCharge((uid, battery)); var netBattery = Comp(uid); var payload = new NetworkPayload { [DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData, [DeviceNetworkCommandSyncData] = new BatterySensorData( - battery.CurrentCharge, + currentCharge, battery.MaxCharge, netBattery.CurrentReceiving, netBattery.MaxChargeRate, diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index 6f37d55013..925050f28e 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -25,7 +25,7 @@ public sealed partial class BorgSystem : SharedBorgSystem [Dependency] private readonly TriggerSystem _trigger = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index e52400d3de..c4afd83b2d 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Destructible; using Content.Server.Ghost; using Content.Server.Mind; using Content.Server.Power.Components; -using Content.Server.Power.EntitySystems; using Content.Server.Roles; using Content.Server.Spawners.Components; using Content.Server.Spawners.EntitySystems; @@ -21,6 +20,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.Power; +using Content.Shared.Power.EntitySystems; using Content.Shared.Power.Components; using Content.Shared.Rejuvenate; using Content.Shared.Roles; @@ -49,7 +49,7 @@ public sealed class StationAiSystem : SharedStationAiSystem [Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly DestructibleSystem _destructible = default!; - [Dependency] private readonly BatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly StationSystem _station = default!; @@ -220,6 +220,7 @@ public sealed class StationAiSystem : SharedStationAiSystem UpdateDamagedAccent(entity); } + // TODO: This should just read the current damage and charge when speaking instead of updating the component all the time even if we don't even use it. private void UpdateDamagedAccent(Entity ent) { if (!TryGetHeld((ent.Owner, ent.Comp), out var held)) @@ -229,7 +230,7 @@ public sealed class StationAiSystem : SharedStationAiSystem return; if (TryComp(ent, out var battery)) - accent.OverrideChargeLevel = battery.CurrentCharge / battery.MaxCharge; + accent.OverrideChargeLevel = _battery.GetChargeLevel((ent.Owner, battery)); if (TryComp(ent, out var damageable)) accent.OverrideTotalDamage = damageable.TotalDamage; @@ -251,7 +252,7 @@ public sealed class StationAiSystem : SharedStationAiSystem if (!_proto.TryIndex(_batteryAlert, out var proto)) return; - var chargePercent = battery.CurrentCharge / battery.MaxCharge; + var chargePercent = _battery.GetChargeLevel((ent.Owner, battery)); var chargeLevel = Math.Round(chargePercent * proto.MaxSeverity); _alerts.ShowAlert(held.Value, _batteryAlert, (short)Math.Clamp(chargeLevel, 0, proto.MaxSeverity)); diff --git a/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs b/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs index e5db13ca84..e9bbfdf528 100644 --- a/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs +++ b/Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs @@ -13,7 +13,7 @@ namespace Content.Server.Speech.EntitySystems; public sealed class DamagedSiliconAccentSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly DestructibleSystem _destructibleSystem = default!; diff --git a/Content.Server/Stunnable/Systems/StunbatonSystem.cs b/Content.Server/Stunnable/Systems/StunbatonSystem.cs index b1bae0127c..90d2dc7588 100644 --- a/Content.Server/Stunnable/Systems/StunbatonSystem.cs +++ b/Content.Server/Stunnable/Systems/StunbatonSystem.cs @@ -18,7 +18,7 @@ namespace Content.Server.Stunnable.Systems { [Dependency] private readonly RiggableSystem _riggableSystem = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly ItemToggleSystem _itemToggle = default!; public override void Initialize() @@ -28,13 +28,13 @@ namespace Content.Server.Stunnable.Systems SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnSolutionChange); SubscribeLocalEvent(OnStaminaHitAttempt); - SubscribeLocalEvent(OnChargeChanged); + SubscribeLocalEvent(OnChargeChanged); } private void OnStaminaHitAttempt(Entity entity, ref StaminaDamageOnHitAttemptEvent args) { if (!_itemToggle.IsActivated(entity.Owner) || - !TryComp(entity.Owner, out var battery) || !_battery.TryUseCharge((entity.Owner, battery), entity.Comp.EnergyPerUse)) + !TryComp(entity.Owner, out var battery) || !_battery.TryUseCharge((entity.Owner, battery), entity.Comp.EnergyPerUse)) { args.Cancelled = true; } @@ -47,7 +47,7 @@ namespace Content.Server.Stunnable.Systems : Loc.GetString("comp-stunbaton-examined-off"); args.PushMarkup(onMsg); - if (TryComp(entity.Owner, out var battery)) + if (TryComp(entity.Owner, out var battery)) { var count = _battery.GetRemainingUses((entity.Owner, battery), entity.Comp.EnergyPerUse); args.PushMarkup(Loc.GetString("melee-battery-examine", ("color", "yellow"), ("count", count))); @@ -58,7 +58,7 @@ namespace Content.Server.Stunnable.Systems { base.TryTurnOn(entity, ref args); - if (!TryComp(entity, out var battery) || _battery.GetCharge((entity, battery)) < entity.Comp.EnergyPerUse) + if (!TryComp(entity, out var battery) || _battery.GetCharge((entity, battery)) < entity.Comp.EnergyPerUse) { args.Cancelled = true; if (args.User != null) @@ -79,7 +79,7 @@ namespace Content.Server.Stunnable.Systems { // Explode if baton is activated and rigged. if (!TryComp(entity, out var riggable) || - !TryComp(entity, out var battery)) + !TryComp(entity, out var battery)) return; if (_itemToggle.IsActivated(entity.Owner) && riggable.IsRigged) @@ -96,9 +96,9 @@ namespace Content.Server.Stunnable.Systems }); } - private void OnChargeChanged(Entity entity, ref PredictedBatteryChargeChangedEvent args) + private void OnChargeChanged(Entity entity, ref ChargeChangedEvent args) { - if (TryComp(entity.Owner, out var battery) && + if (TryComp(entity.Owner, out var battery) && _battery.GetCharge((entity.Owner, battery)) < entity.Comp.EnergyPerUse) { _itemToggle.TryDeactivate(entity.Owner, predicted: false); diff --git a/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs b/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs index 988ade86ce..d418ea5a18 100644 --- a/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs +++ b/Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs @@ -24,7 +24,7 @@ public sealed class TeslaCoilSystem : EntitySystem { if (TryComp(coil, out var batteryComponent)) { - _battery.SetCharge((coil, batteryComponent), batteryComponent.CurrentCharge + coil.Comp.ChargeFromLightning); + _battery.ChangeCharge((coil, batteryComponent), coil.Comp.ChargeFromLightning); } } } diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs index c3f4926a12..437e082581 100644 --- a/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs +++ b/Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs @@ -13,29 +13,20 @@ namespace Content.Server.Xenoarchaeology.Artifact.XAE; public sealed class XAEChargeBatterySystem : BaseXAESystem { [Dependency] private readonly BatterySystem _battery = default!; - [Dependency] private readonly PredictedBatterySystem _predictedBattery = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; /// Pre-allocated and re-used collection. private readonly HashSet> _batteryEntities = new(); - private readonly HashSet> _pBatteryEntities = new(); /// protected override void OnActivated(Entity ent, ref XenoArtifactNodeActivatedEvent args) { _batteryEntities.Clear(); - _pBatteryEntities.Clear(); _lookup.GetEntitiesInRange(args.Coordinates, ent.Comp.Radius, _batteryEntities); foreach (var battery in _batteryEntities) { _battery.SetCharge(battery.AsNullable(), battery.Comp.MaxCharge); } - - _lookup.GetEntitiesInRange(args.Coordinates, ent.Comp.Radius, _pBatteryEntities); - foreach (var pBattery in _pBatteryEntities) - { - _predictedBattery.SetCharge(pBattery.AsNullable(), pBattery.Comp.MaxCharge); - } } } diff --git a/Content.Shared/Power/ChargeEvents.cs b/Content.Shared/Power/ChargeEvents.cs index d6a9c66071..ff602f9dcb 100644 --- a/Content.Shared/Power/ChargeEvents.cs +++ b/Content.Shared/Power/ChargeEvents.cs @@ -1,40 +1,17 @@ using Content.Shared.Power.Components; +using Content.Shared.Power.EntitySystems; using Content.Shared.PowerCell.Components; namespace Content.Shared.Power; /// -/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage). -/// Only raised for entities with . +/// Raised when a battery's charge, charge rate or capacity was updated (capacity affects relative charge percentage). +/// If a battery uses to (dis)charge this is NOT raised every single tick, but only when the charge rate is updated. +/// For instantaneous charge changes using , or similar this DOES get raised, but +/// you should avoid doing so in update loops if the component has net sync enabled. /// [ByRefEvent] -public readonly record struct ChargeChangedEvent(float Charge, float Delta, float MaxCharge) -{ - /// - /// The new charge of the battery. - /// - public readonly float Charge = Charge; - - /// - /// The amount the charge was changed by. - /// - public readonly float Delta = Delta; - - /// - /// The maximum charge of the battery. - /// - public readonly float MaxCharge = MaxCharge; -} - -/// -/// Raised when a predicted battery's charge or capacity changes (capacity affects relative charge percentage). -/// Unlike this is not raised repeatedly each time the charge changes, but only when the charge rate is changed -/// or a charge amount was added or removed instantaneously. The current charge can be inferred from the time of the last update and the charge and -/// charge rate at that time. -/// Only raised for entities with . -/// -[ByRefEvent] -public readonly record struct PredictedBatteryChargeChangedEvent(float CurrentCharge, float Delta, float CurrentChargeRate, float MaxCharge) +public readonly record struct ChargeChangedEvent(float CurrentCharge, float Delta, float CurrentChargeRate, float MaxCharge) { /// /// The new charge of the battery. @@ -60,15 +37,14 @@ public readonly record struct PredictedBatteryChargeChangedEvent(float CurrentCh /// /// Raised when a battery changes its state between full, empty, or neither. -/// Used only for . +/// Useful to detect when a battery is empty or fully charged (since ChargeChangedEvent does not get raised every tick for batteries with a constant charge rate). /// [ByRefEvent] -public record struct PredictedBatteryStateChangedEvent(BatteryState OldState, BatteryState NewState); +public record struct BatteryStateChangedEvent(BatteryState OldState, BatteryState NewState); /// /// Raised to calculate a predicted battery's recharge rate. /// Subscribe to this to offset its current charge rate. -/// Used only for . /// [ByRefEvent] public record struct RefreshChargeRateEvent(float MaxCharge) @@ -80,7 +56,7 @@ public record struct RefreshChargeRateEvent(float MaxCharge) /// /// Event that supports multiple battery types. /// Raised when it is necessary to get information about battery charges. -/// Works with either , , or . +/// Works with either or . /// If there are multiple batteries then the results will be summed up. /// [ByRefEvent] @@ -93,7 +69,7 @@ public record struct GetChargeEvent /// /// Method event that supports multiple battery types. /// Raised when it is necessary to change the current battery charge by some value. -/// Works with either , , or . +/// Works with either or . /// If there are multiple batteries then they will be changed in order of subscription until the total value was reached. /// [ByRefEvent] diff --git a/Content.Shared/Power/Components/BatteryComponent.cs b/Content.Shared/Power/Components/BatteryComponent.cs index 396896a6c5..343333f6db 100644 --- a/Content.Shared/Power/Components/BatteryComponent.cs +++ b/Content.Shared/Power/Components/BatteryComponent.cs @@ -1,34 +1,98 @@ using Content.Shared.Power.EntitySystems; +using Content.Shared.PowerCell.Components; using Content.Shared.Guidebook; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Power.Components; /// -/// Battery node on the pow3r network. Needs other components to connect to actual networks. -/// Use this for batteries that cannot be predicted. -/// Use otherwise. +/// Used for any sort of battery that stores electical power. +/// Can be used as a battery node on the pow3r network. Needs other components to connect to actual networks, see PowerNetworkBatteryComponent. +/// Also used for power cells using or battery powered guns with intrinsic battery. /// -[RegisterComponent] -[Virtual] +/// +/// IMPORTANT: If your battery has an update loop setting the charge every single tick you should set to false +/// in your prototype to prevent it from getting networked every single tick. However, this will disable prediction. +/// This is mostly needed for anything connected to the power network (APCs, SMES, turrets with battery), as their power supply ramps up over time. +/// Everything else that only has a constant charge rate (e.g. charging/discharging a battery at a certain wattage) or instantaneous power draw (e.g. shooting a gun) is fine being networked. +/// However, you should write your systems to avoid using update loops and instead change the battery's charge rate using and +/// the current charge will automatically be inferred if you use . +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] [Access(typeof(SharedBatterySystem))] -public partial class BatteryComponent : Component +public sealed partial class BatteryComponent : Component { /// - /// Maximum charge of the battery in joules (i.e. watt seconds) + /// Maximum charge of the battery in joules (ie. watt seconds) /// - [DataField] + [DataField, AutoNetworkedField, ViewVariables] [GuidebookData] public float MaxCharge; /// - /// Current charge of the battery in joules (ie. watt seconds) + /// The price per one joule. Default is 1 speso for 10kJ. + /// + [DataField] + public float PricePerJoule = 0.0001f; + + /// + /// Time stamp of the last networked update. /// - [DataField("startingCharge")] // TODO: rename this datafield to currentCharge - public float CurrentCharge; + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] + [AutoNetworkedField, AutoPausedField, ViewVariables] + public TimeSpan LastUpdate = TimeSpan.Zero; /// - /// The price per one joule. Default is 1 speso for 10kJ. + /// The intial charge to be set on map init. /// [DataField] - public float PricePerJoule = 0.0001f; + public float StartingCharge; + + /// + /// The charge at the last update in joules (i.e. watt seconds). + /// + [DataField, AutoNetworkedField, ViewVariables] + public float LastCharge; + + /// + /// The current charge rate in watt. + /// + /// + /// Not a datafield as this is only cached and recalculated on component startup. + /// + [ViewVariables, AutoNetworkedField] + public float ChargeRate; + + /// + /// The current charge state of the battery. + /// Used to track state changes for raising . + /// + /// + /// Not a datafield as this is only cached and recalculated in an update loop. + /// + [ViewVariables, AutoNetworkedField] + public BatteryState State = BatteryState.Neither; } + +/// +/// Charge level status of the battery. +/// +[Serializable, NetSerializable] +public enum BatteryState : byte +{ + /// + /// Full charge. + /// + Full, + /// + /// No charge. + /// + Empty, + /// + /// Neither full nor empty. + /// + Neither, +} + diff --git a/Content.Shared/Power/Components/BatterySelfRechargerComponent.cs b/Content.Shared/Power/Components/BatterySelfRechargerComponent.cs index 7a5665ae82..e18ad9d3b0 100644 --- a/Content.Shared/Power/Components/BatterySelfRechargerComponent.cs +++ b/Content.Shared/Power/Components/BatterySelfRechargerComponent.cs @@ -1,3 +1,4 @@ +using Robust.Shared.GameStates; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Shared.Power.Components; @@ -5,33 +6,27 @@ namespace Content.Shared.Power.Components; /// /// Self-recharging battery. /// To be used in combination with . -/// For use instead. /// -[RegisterComponent, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState, AutoGenerateComponentPause] public sealed partial class BatterySelfRechargerComponent : Component { - /// - /// Is the component currently enabled? - /// - [DataField] - public bool AutoRecharge = true; - /// /// At what rate does the entity automatically recharge? In watts. /// - [DataField] + [DataField, AutoNetworkedField, ViewVariables] public float AutoRechargeRate; /// - /// How long should the entity stop automatically recharging if charge is used? + /// How long should the entity stop automatically recharging if a charge is used? /// - [DataField] - public TimeSpan AutoRechargePauseTime = TimeSpan.FromSeconds(0); + [DataField, AutoNetworkedField] + public TimeSpan AutoRechargePauseTime = TimeSpan.Zero; /// /// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoPausedField] - public TimeSpan NextAutoRecharge = TimeSpan.FromSeconds(0); + [AutoNetworkedField, AutoPausedField, ViewVariables] + public TimeSpan? NextAutoRecharge = TimeSpan.FromSeconds(0); } diff --git a/Content.Shared/Power/Components/PredictedBatteryVisualsComponent.cs b/Content.Shared/Power/Components/BatteryVisualsComponent.cs similarity index 75% rename from Content.Shared/Power/Components/PredictedBatteryVisualsComponent.cs rename to Content.Shared/Power/Components/BatteryVisualsComponent.cs index f7ea9338ab..120dd8591f 100644 --- a/Content.Shared/Power/Components/PredictedBatteryVisualsComponent.cs +++ b/Content.Shared/Power/Components/BatteryVisualsComponent.cs @@ -5,11 +5,11 @@ using Robust.Shared.Serialization; namespace Content.Shared.Power.Components; /// -/// Marker component that makes an entity with update its appearance data for use with visualizers. +/// Marker component that makes an entity with update its appearance data for use with visualizers. /// Also works with an entity with and will relay the state of the inserted powercell. /// [RegisterComponent, NetworkedComponent] -public sealed partial class PredictedBatteryVisualsComponent : Component; +public sealed partial class BatteryVisualsComponent : Component; /// /// Keys for the appearance data. @@ -37,15 +37,15 @@ public enum BatteryVisuals : byte public enum BatteryChargingState : byte { /// - /// PredictedBatteryComponent.ChargeRate > 0 + /// BatteryComponent.ChargeRate > 0 /// Charging, /// - /// PredictedBatteryComponent.ChargeRate < 0 + /// BatteryComponent.ChargeRate < 0 /// Decharging, /// - /// PredictedBatteryComponent.ChargeRate == 0 + /// BatteryComponent.ChargeRate == 0 /// Constant, } diff --git a/Content.Shared/Power/Components/ExaminableBatteryComponent.cs b/Content.Shared/Power/Components/ExaminableBatteryComponent.cs index 59d0b8792b..b49680db0d 100644 --- a/Content.Shared/Power/Components/ExaminableBatteryComponent.cs +++ b/Content.Shared/Power/Components/ExaminableBatteryComponent.cs @@ -4,7 +4,7 @@ namespace Content.Shared.Power.Components; /// /// Allows the charge of a battery to be seen by examination. -/// Works with either or . +/// Requires . /// [RegisterComponent, NetworkedComponent] public sealed partial class ExaminableBatteryComponent : Component; diff --git a/Content.Shared/Power/Components/PredictedBatteryComponent.cs b/Content.Shared/Power/Components/PredictedBatteryComponent.cs deleted file mode 100644 index 0db53245eb..0000000000 --- a/Content.Shared/Power/Components/PredictedBatteryComponent.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Content.Shared.Power.EntitySystems; -using Content.Shared.Guidebook; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Shared.Power.Components; - -/// -/// Predicted equivalent to . -/// Use this for electrical power storages that only have a constant charge rate or instantaneous power draw. -/// Devices being directly charged by the power network do not fulfill that requirement as their power supply ramps up over time. -/// -/// -/// We cannot simply network since it would get dirtied every single tick when it updates. -/// This component solves this by requiring a constant charge rate and having the client infer the current charge from the rate -/// and the timestamp the charge was last networked at. This can possibly be expanded in the future by adding a second time derivative. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] -[Access(typeof(PredictedBatterySystem))] -public sealed partial class PredictedBatteryComponent : Component -{ - /// - /// Maximum charge of the battery in joules (ie. watt seconds) - /// - [DataField, AutoNetworkedField, ViewVariables] - [GuidebookData] - public float MaxCharge; - - /// - /// The price per one joule. Default is 1 speso for 10kJ. - /// - [DataField] - public float PricePerJoule = 0.0001f; - - /// - /// Time stamp of the last networked update. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoNetworkedField, AutoPausedField, ViewVariables] - public TimeSpan LastUpdate = TimeSpan.Zero; - - /// - /// The intial charge to be set on map init. - /// - [DataField] - public float StartingCharge; - - /// - /// The charge at the last update in joules (i.e. watt seconds). - /// - [DataField, AutoNetworkedField, ViewVariables] - public float LastCharge; - - /// - /// The current charge rate in watt. - /// - /// - /// Not a datafield as this is only cached and recalculated on component startup. - /// - [ViewVariables, AutoNetworkedField] - public float ChargeRate; - - /// - /// The current charge state of the battery. - /// Used to track state changes for raising . - /// - /// - /// Not a datafield as this is only cached and recalculated in an update loop. - /// - [ViewVariables, AutoNetworkedField] - public BatteryState State = BatteryState.Neither; -} - -/// -/// Charge level status of the battery. -/// -[Serializable, NetSerializable] -public enum BatteryState : byte -{ - /// - /// Full charge. - /// - Full, - /// - /// No charge. - /// - Empty, - /// - /// Neither full nor empty. - /// - Neither, -} - diff --git a/Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs b/Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs deleted file mode 100644 index 449a4e1dc6..0000000000 --- a/Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Shared.Power.Components; - -/// -/// Self-recharging battery. -/// To be used in combination with . -/// For use instead. -/// -[RegisterComponent, NetworkedComponent] -[AutoGenerateComponentState, AutoGenerateComponentPause] -public sealed partial class PredictedBatterySelfRechargerComponent : Component -{ - /// - /// At what rate does the entity automatically recharge? In watts. - /// - [DataField, AutoNetworkedField, ViewVariables] - public float AutoRechargeRate; - - /// - /// How long should the entity stop automatically recharging if a charge is used? - /// - [DataField, AutoNetworkedField] - public TimeSpan AutoRechargePauseTime = TimeSpan.Zero; - - /// - /// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] - [AutoNetworkedField, AutoPausedField, ViewVariables] - public TimeSpan? NextAutoRecharge = TimeSpan.FromSeconds(0); -} diff --git a/Content.Shared/Power/EntitySystems/ChargerSystem.cs b/Content.Shared/Power/EntitySystems/ChargerSystem.cs index b561eedcbb..00429067c4 100644 --- a/Content.Shared/Power/EntitySystems/ChargerSystem.cs +++ b/Content.Shared/Power/EntitySystems/ChargerSystem.cs @@ -13,7 +13,7 @@ namespace Content.Shared.Power.EntitySystems; public sealed class ChargerSystem : EntitySystem { - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly SharedPowerReceiverSystem _receiver = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly SharedContainerSystem _container = default!; @@ -35,7 +35,7 @@ public sealed class ChargerSystem : EntitySystem SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnEmpRemoved); SubscribeLocalEvent(OnRefreshChargeRate); - SubscribeLocalEvent(OnStatusChanged); + SubscribeLocalEvent(OnStatusChanged); } private void OnStartup(Entity ent, ref ComponentStartup args) @@ -176,7 +176,7 @@ public sealed class ChargerSystem : EntitySystem args.NewChargeRate += chargerComp.ChargeRate; } - private void OnStatusChanged(Entity ent, ref PredictedBatteryStateChangedEvent args) + private void OnStatusChanged(Entity ent, ref BatteryStateChangedEvent args) { // If the battery is full update the visuals and power draw of the charger. diff --git a/Content.Shared/Power/EntitySystems/PredictedBatterySystem.cs b/Content.Shared/Power/EntitySystems/PredictedBatterySystem.cs deleted file mode 100644 index 760d7277c6..0000000000 --- a/Content.Shared/Power/EntitySystems/PredictedBatterySystem.cs +++ /dev/null @@ -1,182 +0,0 @@ -using Content.Shared.Cargo; -using Content.Shared.Emp; -using Content.Shared.Examine; -using Content.Shared.Power.Components; -using Content.Shared.Rejuvenate; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Content.Shared.Power.EntitySystems; - -/// -/// Responsible for . -/// Predicted equivalent of . -/// If you make changes to this make sure to keep the two consistent. -/// -public sealed partial class PredictedBatterySystem : EntitySystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnMapInit); - SubscribeLocalEvent(OnEmpPulse); - SubscribeLocalEvent(OnRejuvenate); - SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent(CalculateBatteryPrice); - SubscribeLocalEvent(OnChangeCharge); - SubscribeLocalEvent(OnGetCharge); - SubscribeLocalEvent(OnRefreshChargeRate); - SubscribeLocalEvent(OnRechargerStartup); - SubscribeLocalEvent(OnRechargerRemove); - SubscribeLocalEvent(OnVisualsChargeChanged); - SubscribeLocalEvent(OnVisualsStateChanged); - } - - private void OnInit(Entity ent, ref ComponentInit args) - { - DebugTools.Assert(!HasComp(ent), $"{ent} has both BatteryComponent and PredictedBatteryComponent"); - } - - private void OnStartup(Entity ent, ref ComponentStartup args) - { - // In case a recharging component was added before the battery component itself. - // Doing this only on map init is not enough because the charge rate is not a datafield, but cached, so it would get lost when reloading the game. - // If we would make it a datafield then the integration tests would complain about modifying it before map init. - RefreshChargeRate(ent.AsNullable()); - } - - private void OnMapInit(Entity ent, ref MapInitEvent args) - { - SetCharge(ent.AsNullable(), ent.Comp.StartingCharge); - RefreshChargeRate(ent.AsNullable()); - } - - private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) - { - SetCharge(ent.AsNullable(), ent.Comp.MaxCharge); - } - - private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) - { - args.Affected = true; - UseCharge(ent.AsNullable(), args.EnergyConsumption); - } - - private void OnExamine(Entity ent, ref ExaminedEvent args) - { - if (!args.IsInDetailsRange) - return; - - if (!HasComp(ent)) - return; - - var chargePercentRounded = 0; - var currentCharge = GetCharge(ent.AsNullable()); - if (ent.Comp.MaxCharge != 0) - chargePercentRounded = (int)(100 * currentCharge / ent.Comp.MaxCharge); - args.PushMarkup( - Loc.GetString( - "examinable-battery-component-examine-detail", - ("percent", chargePercentRounded), - ("markupPercentColor", "green") - ) - ); - } - - /// - /// Gets the price for the power contained in an entity's battery. - /// - private void CalculateBatteryPrice(Entity ent, ref PriceCalculationEvent args) - { - args.Price += GetCharge(ent.AsNullable()) * ent.Comp.PricePerJoule; - } - - private void OnChangeCharge(Entity ent, ref ChangeChargeEvent args) - { - if (args.ResidualValue == 0) - return; - - args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue); - } - - private void OnGetCharge(Entity ent, ref GetChargeEvent args) - { - args.CurrentCharge += GetCharge(ent.AsNullable()); - args.MaxCharge += ent.Comp.MaxCharge; - } - - private void OnRefreshChargeRate(Entity ent, ref RefreshChargeRateEvent args) - { - if (_timing.CurTime < ent.Comp.NextAutoRecharge) - return; // Still on cooldown - - args.NewChargeRate += ent.Comp.AutoRechargeRate; - } - - public override void Update(float frameTime) - { - var curTime = _timing.CurTime; - - // Update self-recharging cooldowns. - var rechargerQuery = EntityQueryEnumerator(); - while (rechargerQuery.MoveNext(out var uid, out var recharger, out var battery)) - { - if (recharger.NextAutoRecharge == null || curTime < recharger.NextAutoRecharge) - continue; - - recharger.NextAutoRecharge = null; // Don't refresh every tick. - Dirty(uid, recharger); - RefreshChargeRate((uid, battery)); // Cooldown is over, apply the new recharge rate. - } - - // Raise events when the battery is full or empty so that other systems can react and visuals can get updated. - // This is not doing that many calculations, it only has to get the current charge and only raises events if something did change. - // If this turns out to be too expensive and shows up on grafana consider updating it less often. - var batteryQuery = EntityQueryEnumerator(); - while (batteryQuery.MoveNext(out var uid, out var battery)) - { - if (battery.ChargeRate == 0f) - continue; // No need to check if it's constant. - - UpdateState((uid, battery)); - } - } - - private void OnRechargerStartup(Entity ent, ref ComponentStartup args) - { - // In case this component is added after the battery component. - RefreshChargeRate(ent.Owner); - } - - private void OnRechargerRemove(Entity ent, ref ComponentRemove args) - { - // We use ComponentRemove to make sure this component no longer subscribes to the refresh event. - RefreshChargeRate(ent.Owner); - } - - private void OnVisualsChargeChanged(Entity ent, ref PredictedBatteryChargeChangedEvent args) - { - // Update the appearance data for the charge rate. - // We have a separate component for this to not duplicate the networking cost unless we actually use it. - var state = BatteryChargingState.Constant; - if (args.CurrentChargeRate > 0f) - state = BatteryChargingState.Charging; - else if (args.CurrentChargeRate < 0f) - state = BatteryChargingState.Decharging; - - _appearance.SetData(ent.Owner, BatteryVisuals.Charging, state); - } - - private void OnVisualsStateChanged(Entity ent, ref PredictedBatteryStateChangedEvent args) - { - // Update the appearance data for the fill level (empty, full, in-between). - // We have a separate component for this to not duplicate the networking cost unless we actually use it. - _appearance.SetData(ent.Owner, BatteryVisuals.State, args.NewState); - } -} diff --git a/Content.Shared/Power/EntitySystems/PredictedBatterySystem.API.cs b/Content.Shared/Power/EntitySystems/SharedBatterySystem.API.cs similarity index 79% rename from Content.Shared/Power/EntitySystems/PredictedBatterySystem.API.cs rename to Content.Shared/Power/EntitySystems/SharedBatterySystem.API.cs index e1d685daeb..d51023f3eb 100644 --- a/Content.Shared/Power/EntitySystems/PredictedBatterySystem.API.cs +++ b/Content.Shared/Power/EntitySystems/SharedBatterySystem.API.cs @@ -4,11 +4,11 @@ using JetBrains.Annotations; namespace Content.Shared.Power.EntitySystems; /// -/// Responsible for . +/// Responsible for . /// Predicted equivalent of . /// If you make changes to this make sure to keep the two consistent. /// -public sealed partial class PredictedBatterySystem +public abstract partial class SharedBatterySystem { /// /// Changes the battery's charge by the given amount @@ -17,7 +17,7 @@ public sealed partial class PredictedBatterySystem /// /// The actually changed amount. [PublicAPI] - public float ChangeCharge(Entity ent, float amount) + public float ChangeCharge(Entity ent, float amount) { if (!Resolve(ent, ref ent.Comp)) return 0; @@ -36,7 +36,7 @@ public sealed partial class PredictedBatterySystem TrySetChargeCooldown(ent.Owner); - var changedEv = new PredictedBatteryChargeChangedEvent(newValue, delta, ent.Comp.ChargeRate, ent.Comp.MaxCharge); + var changedEv = new ChargeChangedEvent(newValue, delta, ent.Comp.ChargeRate, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref changedEv); // Raise events if the battery status changed between full, empty, or neither. @@ -50,7 +50,7 @@ public sealed partial class PredictedBatterySystem /// /// The actually changed amount. [PublicAPI] - public float UseCharge(Entity ent, float amount) + public float UseCharge(Entity ent, float amount) { if (amount <= 0f) return 0f; @@ -65,7 +65,7 @@ public sealed partial class PredictedBatterySystem /// /// If the full amount was able to be removed. [PublicAPI] - public bool TryUseCharge(Entity ent, float amount) + public bool TryUseCharge(Entity ent, float amount) { if (!Resolve(ent, ref ent.Comp, false) || amount > GetCharge(ent)) return false; @@ -78,7 +78,7 @@ public sealed partial class PredictedBatterySystem /// Sets the battery's charge. /// [PublicAPI] - public void SetCharge(Entity ent, float value) + public void SetCharge(Entity ent, float value) { if (!Resolve(ent, ref ent.Comp)) return; @@ -95,7 +95,7 @@ public sealed partial class PredictedBatterySystem ent.Comp.LastUpdate = curTime; Dirty(ent); - var ev = new PredictedBatteryChargeChangedEvent(newValue, delta, ent.Comp.ChargeRate, ent.Comp.MaxCharge); + var ev = new ChargeChangedEvent(newValue, delta, ent.Comp.ChargeRate, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref ev); // Raise events if the battery status changed between full, empty, or neither. @@ -106,7 +106,7 @@ public sealed partial class PredictedBatterySystem /// Sets the battery's maximum charge. /// [PublicAPI] - public void SetMaxCharge(Entity ent, float value) + public void SetMaxCharge(Entity ent, float value) { if (!Resolve(ent, ref ent.Comp)) return; @@ -122,7 +122,7 @@ public sealed partial class PredictedBatterySystem ent.Comp.LastUpdate = curTime; Dirty(ent); - var ev = new PredictedBatteryChargeChangedEvent(ent.Comp.LastCharge, ent.Comp.LastCharge - oldCharge, ent.Comp.ChargeRate, ent.Comp.MaxCharge); + var ev = new ChargeChangedEvent(ent.Comp.LastCharge, ent.Comp.LastCharge - oldCharge, ent.Comp.ChargeRate, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref ev); // Raise events if the battery status changed between full, empty, or neither. @@ -133,7 +133,7 @@ public sealed partial class PredictedBatterySystem /// Updates the battery's charge state and sends an event if it changed. /// [PublicAPI] - public void UpdateState(Entity ent) + public void UpdateState(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return; @@ -144,7 +144,7 @@ public sealed partial class PredictedBatterySystem var charge = GetCharge(ent); - if (charge == ent.Comp.MaxCharge) + if (charge >= ent.Comp.MaxCharge) newState = BatteryState.Full; else if (charge == 0f) newState = BatteryState.Empty; @@ -155,7 +155,7 @@ public sealed partial class PredictedBatterySystem ent.Comp.State = newState; Dirty(ent); - var changedEv = new PredictedBatteryStateChangedEvent(oldState, newState); + var changedEv = new BatteryStateChangedEvent(oldState, newState); RaiseLocalEvent(ent, ref changedEv); } @@ -163,7 +163,7 @@ public sealed partial class PredictedBatterySystem /// Gets the battery's current charge. /// [PublicAPI] - public float GetCharge(Entity ent) + public float GetCharge(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return 0f; @@ -179,7 +179,7 @@ public sealed partial class PredictedBatterySystem /// Gets the fraction of charge remaining (0–1). /// [PublicAPI] - public float GetChargeLevel(Entity ent) + public float GetChargeLevel(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return 0f; @@ -194,7 +194,7 @@ public sealed partial class PredictedBatterySystem /// Gets number of remaining uses for the given charge cost. /// [PublicAPI] - public int GetRemainingUses(Entity ent, float cost) + public int GetRemainingUses(Entity ent, float cost) { if (cost <= 0) return 0; @@ -209,7 +209,7 @@ public sealed partial class PredictedBatterySystem /// Gets number of maximum uses at full charge for the given charge cost. /// [PublicAPI] - public int GetMaxUses(Entity ent, float cost) + public int GetMaxUses(Entity ent, float cost) { if (cost <= 0) return 0; @@ -220,13 +220,13 @@ public sealed partial class PredictedBatterySystem return (int)(ent.Comp.MaxCharge / cost); } - /// /// Refreshes the battery's current charge rate by raising a . + /// Subscribe to that event to add to or subtract from the total charge rate. /// /// The new charge rate. [PublicAPI] - public float RefreshChargeRate(Entity ent) + public float RefreshChargeRate(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return 0f; @@ -241,7 +241,7 @@ public sealed partial class PredictedBatterySystem Dirty(ent); // Inform other systems about the new rate; - var changedEv = new PredictedBatteryChargeChangedEvent(ent.Comp.LastCharge, 0f, ent.Comp.ChargeRate, ent.Comp.MaxCharge); + var changedEv = new ChargeChangedEvent(ent.Comp.LastCharge, 0f, ent.Comp.ChargeRate, ent.Comp.MaxCharge); RaiseLocalEvent(ent, ref changedEv); return refreshEv.NewChargeRate; @@ -252,7 +252,7 @@ public sealed partial class PredictedBatterySystem /// Uses the cooldown time given in the component. /// [PublicAPI] - public void TrySetChargeCooldown(Entity ent) + public void TrySetChargeCooldown(Entity ent) { if (!Resolve(ent, ref ent.Comp, false)) return; @@ -270,7 +270,7 @@ public sealed partial class PredictedBatterySystem /// Puts the entity's self recharge on cooldown for the specified time. /// [PublicAPI] - public void SetChargeCooldown(Entity ent, TimeSpan cooldown) + public void SetChargeCooldown(Entity ent, TimeSpan cooldown) { if (!Resolve(ent, ref ent.Comp)) return; @@ -284,7 +284,7 @@ public sealed partial class PredictedBatterySystem /// Returns whether the battery is full. /// [PublicAPI] - public bool IsFull(Entity ent) + public bool IsFull(Entity ent) { if (!Resolve(ent, ref ent.Comp)) return false; diff --git a/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs b/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs index 317dcb129e..e150fd60d8 100644 --- a/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs +++ b/Content.Shared/Power/EntitySystems/SharedBatterySystem.cs @@ -1,83 +1,177 @@ +using Content.Shared.Cargo; using Content.Shared.Emp; +using Content.Shared.Examine; using Content.Shared.Power.Components; -using JetBrains.Annotations; +using Content.Shared.Rejuvenate; +using Robust.Shared.Timing; namespace Content.Shared.Power.EntitySystems; -public abstract class SharedBatterySystem : EntitySystem +/// +/// Responsible for . +/// +public abstract partial class SharedBatterySystem : EntitySystem { + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnEmpPulse); + SubscribeLocalEvent(OnRejuvenate); + SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(CalculateBatteryPrice); + SubscribeLocalEvent(OnChangeCharge); + SubscribeLocalEvent(OnGetCharge); + SubscribeLocalEvent(OnRefreshChargeRate); + SubscribeLocalEvent(OnRechargerStartup); + SubscribeLocalEvent(OnRechargerRemove); + SubscribeLocalEvent(OnVisualsChargeChanged); + SubscribeLocalEvent(OnVisualsStateChanged); + } + + protected virtual void OnStartup(Entity ent, ref ComponentStartup args) + { + // In case a recharging component was added before the battery component itself. + // Doing this only on map init is not enough because the charge rate is not a datafield, but cached, so it would get lost when reloading the game. + // If we would make it a datafield then the integration tests would complain about modifying it before map init. + // Don't do this in case the battery is not net synced, because then the client would raise events overwriting the networked server state on spawn. + if (ent.Comp.NetSyncEnabled) + RefreshChargeRate(ent.AsNullable()); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + SetCharge(ent.AsNullable(), ent.Comp.StartingCharge); + RefreshChargeRate(ent.AsNullable()); + } + + private void OnRejuvenate(Entity ent, ref RejuvenateEvent args) + { + SetCharge(ent.AsNullable(), ent.Comp.MaxCharge); } private void OnEmpPulse(Entity ent, ref EmpPulseEvent args) { args.Affected = true; UseCharge(ent.AsNullable(), args.EnergyConsumption); - // Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP. - TrySetChargeCooldown(ent.Owner); } - /// - /// Changes the battery's charge by the given amount - /// and resets the self-recharge cooldown if it exists. - /// A positive value will add charge, a negative value will remove charge. - /// - /// The actually changed amount. - [PublicAPI] - public virtual float ChangeCharge(Entity ent, float amount) + private void OnExamine(Entity ent, ref ExaminedEvent args) { - return 0f; + if (!args.IsInDetailsRange) + return; + + if (!HasComp(ent)) + return; + + var chargePercentRounded = 0; + var currentCharge = GetCharge(ent.AsNullable()); + if (ent.Comp.MaxCharge != 0) + chargePercentRounded = (int)(100 * currentCharge / ent.Comp.MaxCharge); + args.PushMarkup( + Loc.GetString( + "examinable-battery-component-examine-detail", + ("percent", chargePercentRounded), + ("markupPercentColor", "green") + ) + ); } /// - /// Removes the given amount of charge from the battery - /// and resets the self-recharge cooldown if it exists. + /// Gets the price for the power contained in an entity's battery. /// - /// The actually changed amount. - [PublicAPI] - public virtual float UseCharge(Entity ent, float amount) + private void CalculateBatteryPrice(Entity ent, ref PriceCalculationEvent args) { - return 0f; + args.Price += GetCharge(ent.AsNullable()) * ent.Comp.PricePerJoule; } - /// - /// If sufficient charge is available on the battery, use it. Otherwise, don't. - /// Resets the self-recharge cooldown if it exists. - /// Always returns false on the client. - /// - /// If the full amount was able to be removed. - [PublicAPI] - public virtual bool TryUseCharge(Entity ent, float amount) + private void OnChangeCharge(Entity ent, ref ChangeChargeEvent args) { - return false; + if (args.ResidualValue == 0) + return; + + args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue); } - /// - /// Sets the battery's charge. - /// - [PublicAPI] - public virtual void SetCharge(Entity ent, float value) { } + private void OnGetCharge(Entity ent, ref GetChargeEvent args) + { + args.CurrentCharge += GetCharge(ent.AsNullable()); + args.MaxCharge += ent.Comp.MaxCharge; + } - /// - /// Sets the battery's maximum charge. - /// - [PublicAPI] - public virtual void SetMaxCharge(Entity ent, float value) { } + private void OnRefreshChargeRate(Entity ent, ref RefreshChargeRateEvent args) + { + if (_timing.CurTime < ent.Comp.NextAutoRecharge) + return; // Still on cooldown - /// - /// Checks if the entity has a self recharge and puts it on cooldown if applicable. - /// Uses the cooldown time given in the component. - /// - [PublicAPI] - public virtual void TrySetChargeCooldown(Entity ent) { } + args.NewChargeRate += ent.Comp.AutoRechargeRate; + } - /// - /// Puts the entity's self recharge on cooldown for the specified time. - /// - [PublicAPI] - public virtual void SetChargeCooldown(Entity ent, TimeSpan cooldown) { } + public override void Update(float frameTime) + { + var curTime = _timing.CurTime; + + // Update self-recharging cooldowns. + var rechargerQuery = EntityQueryEnumerator(); + while (rechargerQuery.MoveNext(out var uid, out var recharger, out var battery)) + { + if (recharger.NextAutoRecharge == null || curTime < recharger.NextAutoRecharge) + continue; + + recharger.NextAutoRecharge = null; // Don't refresh every tick. + Dirty(uid, recharger); + RefreshChargeRate((uid, battery)); // Cooldown is over, apply the new recharge rate. + } + + // Raise events when the battery is full or empty so that other systems can react and visuals can get updated. + // This is not doing that many calculations, it only has to get the current charge and only raises events if something did change. + // If this turns out to be too expensive and shows up on grafana consider updating it less often. + var batteryQuery = EntityQueryEnumerator(); + while (batteryQuery.MoveNext(out var uid, out var battery)) + { + if (battery.ChargeRate == 0f) + continue; // No need to check if it's constant. + + UpdateState((uid, battery)); + } + } + + private void OnRechargerStartup(Entity ent, ref ComponentStartup args) + { + // In case this component is added after the battery component. + // Don't do this in case the battery is not net synced, because then the client would raise events overwriting the networked server state on spawn. + if (ent.Comp.NetSyncEnabled) + RefreshChargeRate(ent.Owner); + } + + private void OnRechargerRemove(Entity ent, ref ComponentRemove args) + { + // We use ComponentRemove to make sure this component no longer subscribes to the refresh event. + RefreshChargeRate(ent.Owner); + } + + private void OnVisualsChargeChanged(Entity ent, ref ChargeChangedEvent args) + { + // Update the appearance data for the charge rate. + // We have a separate component for this to not duplicate the networking cost unless we actually use it. + var state = BatteryChargingState.Constant; + if (args.CurrentChargeRate > 0f) + state = BatteryChargingState.Charging; + else if (args.CurrentChargeRate < 0f) + state = BatteryChargingState.Decharging; + + _appearance.SetData(ent.Owner, BatteryVisuals.Charging, state); + } + + private void OnVisualsStateChanged(Entity ent, ref BatteryStateChangedEvent args) + { + // Update the appearance data for the fill level (empty, full, in-between). + // We have a separate component for this to not duplicate the networking cost unless we actually use it. + _appearance.SetData(ent.Owner, BatteryVisuals.State, args.NewState); + } } diff --git a/Content.Shared/PowerCell/Components/PowerCellComponent.cs b/Content.Shared/PowerCell/Components/PowerCellComponent.cs index fdada10958..8db75f63ba 100644 --- a/Content.Shared/PowerCell/Components/PowerCellComponent.cs +++ b/Content.Shared/PowerCell/Components/PowerCellComponent.cs @@ -5,7 +5,7 @@ namespace Content.Shared.PowerCell.Components; /// /// This component enables power-cell related interactions (e.g. EntityWhitelists, cell sizes, examine, rigging). -/// The actual power functionality is provided by the . +/// The actual power functionality is provided by the . /// [RegisterComponent, NetworkedComponent] public sealed partial class PowerCellComponent : Component; diff --git a/Content.Shared/PowerCell/PowerCellSystem.API.cs b/Content.Shared/PowerCell/PowerCellSystem.API.cs index 15376bda89..36590c7983 100644 --- a/Content.Shared/PowerCell/PowerCellSystem.API.cs +++ b/Content.Shared/PowerCell/PowerCellSystem.API.cs @@ -31,7 +31,7 @@ public sealed partial class PowerCellSystem [PublicAPI] public bool TryGetBatteryFromSlot( Entity ent, - [NotNullWhen(true)] out Entity? battery) + [NotNullWhen(true)] out Entity? battery) { if (!Resolve(ent, ref ent.Comp, false)) { @@ -45,7 +45,7 @@ public sealed partial class PowerCellSystem return false; } - if (!TryComp(slot.Item, out var batteryComp)) + if (!TryComp(slot.Item, out var batteryComp)) { battery = null; return false; @@ -57,15 +57,15 @@ public sealed partial class PowerCellSystem /// /// First tries to get a battery from the entity's power cell slot. - /// If that fails check if the entity itself is a battery with . + /// If that fails check if the entity itself is a battery with . /// [PublicAPI] - public bool TryGetBatteryFromSlotOrEntity(Entity ent, [NotNullWhen(true)] out Entity? battery) + public bool TryGetBatteryFromSlotOrEntity(Entity ent, [NotNullWhen(true)] out Entity? battery) { if (TryGetBatteryFromSlot(ent, out battery)) return true; - if (TryComp(ent, out var batteryComp)) + if (TryComp(ent, out var batteryComp)) { battery = (ent.Owner, batteryComp); return true; @@ -76,13 +76,13 @@ public sealed partial class PowerCellSystem } /// - /// First checks if the entity itself is a battery with . + /// First checks if the entity itself is a battery with . /// If that fails it will try to get a battery from the entity's power cell slot instead. /// [PublicAPI] - public bool TryGetBatteryFromEntityOrSlot(Entity ent, [NotNullWhen(true)] out Entity? battery) + public bool TryGetBatteryFromEntityOrSlot(Entity ent, [NotNullWhen(true)] out Entity? battery) { - if (TryComp(ent, out var batteryComp)) + if (TryComp(ent, out var batteryComp)) { battery = (ent.Owner, batteryComp); return true; diff --git a/Content.Shared/PowerCell/PowerCellSystem.Relay.cs b/Content.Shared/PowerCell/PowerCellSystem.Relay.cs index ca4bc2a661..75e436becc 100644 --- a/Content.Shared/PowerCell/PowerCellSystem.Relay.cs +++ b/Content.Shared/PowerCell/PowerCellSystem.Relay.cs @@ -16,8 +16,8 @@ public sealed partial class PowerCellSystem SubscribeLocalEvent(RelayToCell); SubscribeLocalEvent(RelayToCellSlot); // Prevent the ninja from EMPing its own battery - SubscribeLocalEvent(RelayToCellSlot); - SubscribeLocalEvent(RelayToCellSlot); // For shutting down devices if the battery is empty + SubscribeLocalEvent(RelayToCellSlot); + SubscribeLocalEvent(RelayToCellSlot); // For shutting down devices if the battery is empty SubscribeLocalEvent(RelayToCellSlot); // Allow devices to charge/drain inserted batteries } diff --git a/Content.Shared/PowerCell/PowerCellSystem.cs b/Content.Shared/PowerCell/PowerCellSystem.cs index 41d6c094ca..8f4950c5d2 100644 --- a/Content.Shared/PowerCell/PowerCellSystem.cs +++ b/Content.Shared/PowerCell/PowerCellSystem.cs @@ -15,7 +15,7 @@ public sealed partial class PowerCellSystem : EntitySystem [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly PredictedBatterySystem _battery = default!; + [Dependency] private readonly SharedBatterySystem _battery = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() @@ -27,7 +27,7 @@ public sealed partial class PowerCellSystem : EntitySystem SubscribeLocalEvent(OnCellSlotInserted); SubscribeLocalEvent(OnCellSlotRemoved); SubscribeLocalEvent(OnCellSlotExamined); - SubscribeLocalEvent(OnCellSlotStateChanged); + SubscribeLocalEvent(OnCellSlotStateChanged); SubscribeLocalEvent(OnCellExamined); @@ -65,7 +65,7 @@ public sealed partial class PowerCellSystem : EntitySystem _battery.RefreshChargeRate(args.Entity); // Only update the visuals if we actually use them. - if (!HasComp(ent)) + if (!HasComp(ent)) return; // Set the data to that of the power cell @@ -94,7 +94,7 @@ public sealed partial class PowerCellSystem : EntitySystem _battery.RefreshChargeRate(args.Entity); // Only update the visuals if we actually use them. - if (!HasComp(ent)) + if (!HasComp(ent)) return; // Set the appearance to empty. @@ -103,7 +103,7 @@ public sealed partial class PowerCellSystem : EntitySystem } - private void OnCellSlotStateChanged(Entity ent, ref PredictedBatteryStateChangedEvent args) + private void OnCellSlotStateChanged(Entity ent, ref BatteryStateChangedEvent args) { if (args.NewState != BatteryState.Empty) return; @@ -123,11 +123,11 @@ public sealed partial class PowerCellSystem : EntitySystem private void OnCellExamined(Entity ent, ref ExaminedEvent args) { - if (TryComp(ent, out var battery)) + if (TryComp(ent, out var battery)) OnBatteryExamined((ent.Owner, battery), ref args); } - private void OnBatteryExamined(Entity ent, ref ExaminedEvent args) + private void OnBatteryExamined(Entity ent, ref ExaminedEvent args) { var chargePercent = _battery.GetChargeLevel(ent.AsNullable()) * 100; args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{chargePercent:F0}"))); diff --git a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs index f4b866044c..0c80ff802d 100644 --- a/Content.Shared/UserInterface/ActivatableUISystem.Power.cs +++ b/Content.Shared/UserInterface/ActivatableUISystem.Power.cs @@ -16,7 +16,7 @@ public sealed partial class ActivatableUISystem SubscribeLocalEvent(OnToggled); SubscribeLocalEvent(OnBatteryOpened); SubscribeLocalEvent(OnBatteryClosed); - SubscribeLocalEvent(OnBatteryStateChanged); + SubscribeLocalEvent(OnBatteryStateChanged); SubscribeLocalEvent(OnBatteryOpenAttempt); } @@ -57,7 +57,7 @@ public sealed partial class ActivatableUISystem _toggle.TryDeactivate(uid); } - private void OnBatteryStateChanged(Entity ent, ref PredictedBatteryStateChangedEvent args) + private void OnBatteryStateChanged(Entity ent, ref BatteryStateChangedEvent args) { // Deactivate when empty. if (args.NewState != BatteryState.Empty) diff --git a/Content.Shared/Weapons/Ranged/Components/BatteryAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/BatteryAmmoProviderComponent.cs index 3378d82b44..63b680066f 100644 --- a/Content.Shared/Weapons/Ranged/Components/BatteryAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/BatteryAmmoProviderComponent.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Weapons.Ranged.Components; /// /// Ammo provider that uses electric charge from a battery to provide ammunition to a weapon. -/// This works with both and +/// Works in combination with . /// [RegisterComponent, NetworkedComponent] [AutoGenerateComponentState(raiseAfterAutoHandleState: true), AutoGenerateComponentPause] diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs index 24cfe35051..f92cea07d3 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs @@ -23,7 +23,6 @@ public abstract partial class SharedGunSystem SubscribeLocalEvent(OnBatteryExamine); SubscribeLocalEvent(OnBatteryDamageExamine); SubscribeLocalEvent(OnPowerCellChanged); - SubscribeLocalEvent(OnPredictedChargeChanged); SubscribeLocalEvent(OnChargeChanged); } @@ -86,10 +85,10 @@ public abstract partial class SharedGunSystem /// public void TakeCharge(Entity ent, int shots = 1) { - // Take charge from either the BatteryComponent, PredictedBatteryComponent or PowerCellSlotComponent. + // Take charge from either the BatteryComponent or PowerCellSlotComponent. var ev = new ChangeChargeEvent(-ent.Comp.FireCost * shots); RaiseLocalEvent(ent, ref ev); - // UpdateShots is already called by the resulting PredictedBatteryChargeChangedEvent or ChargeChangedEvent + // UpdateShots is already called by the resulting ChargeChangedEvent } private (EntityUid? Entity, IShootable) GetShootable(BatteryAmmoProviderComponent component, EntityCoordinates coordinates) @@ -140,9 +139,8 @@ public abstract partial class SharedGunSystem UpdateShots(ent); } - // For predicted batteries. // If the entity is has a PowerCellSlotComponent then this event is relayed from the power cell to the slot entity. - private void OnPredictedChargeChanged(Entity ent, ref PredictedBatteryChargeChangedEvent args) + private void OnChargeChanged(Entity ent, ref ChargeChangedEvent args) { // Update the visuals and charge counter UI. UpdateShots(ent); @@ -150,14 +148,6 @@ public abstract partial class SharedGunSystem UpdateNextUpdate(ent, args.CurrentCharge, args.MaxCharge, args.CurrentChargeRate); } - // For unpredicted batteries. - private void OnChargeChanged(Entity ent, ref ChargeChangedEvent args) - { - // Update the visuals and charge counter UI. - UpdateShots(ent); - // No need to queue an update here since unpredicted batteries already update periodically as they charge/discharge. - } - private void UpdateNextUpdate(Entity ent, float currentCharge, float maxCharge, float currentChargeRate) { // Don't queue any updates if charge is constant. @@ -179,12 +169,15 @@ public abstract partial class SharedGunSystem // Shots are only chached, not a DataField, so we need to refresh this when the game is loaded. private void OnBatteryStartup(Entity ent, ref ComponentStartup args) { + if (_netManager.IsClient && !IsClientSide(ent.Owner)) + return; // Don't overwrite the server state in cases where the battery is not predicted. + UpdateShots(ent); } /// /// Gets the current and maximum amount of shots from this entity's battery. - /// This works for BatteryComponent, PredictedBatteryComponent and PowercellSlotComponent. + /// This works for BatteryComponent and PowercellSlotComponent. /// public (int, int) GetShots(Entity ent) { @@ -197,8 +190,7 @@ public abstract partial class SharedGunSystem } /// - /// Update loop for refreshing the ammo counter for charging/draining predicted batteries. - /// This is not needed for unpredicted batteries since those already raise ChargeChangedEvent periodically. + /// Update loop for refreshing the ammo counter for charging/draining batteries. /// private void UpdateBattery(float frameTime) { diff --git a/Resources/Prototypes/Catalog/Bounties/bounties.yml b/Resources/Prototypes/Catalog/Bounties/bounties.yml index 00f53f443c..137a494674 100644 --- a/Resources/Prototypes/Catalog/Bounties/bounties.yml +++ b/Resources/Prototypes/Catalog/Bounties/bounties.yml @@ -519,7 +519,6 @@ whitelist: components: - Battery - - PredictedBattery - type: cargoBounty id: BountyLaserGun diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 5dfc94a1f7..05dda23f0a 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -619,7 +619,7 @@ damage: 35 sound: /Audio/Weapons/egloves.ogg - type: LandAtCursor # it deals stamina damage when thrown - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - type: GuideHelp diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index 2d320c868c..3b85ce94c9 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -243,10 +243,10 @@ startValue: 0.1 endValue: 2.0 isLooped: true - - type: PredictedBattery + - type: Battery maxCharge: 600 #lights drain 3/s but recharge of 2 makes this 1/s. Therefore 600 is 10 minutes of light. startingCharge: 600 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 2 #recharge of 2 makes total drain 1w / s so max charge is 1:1 with time. Time to fully charge should be 5 minutes. Having recharge gives light an extended flicker period which gives you some warning to return to light area. - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index 1c23d18a97..341404af2b 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -296,10 +296,10 @@ startValue: 0.1 endValue: 2.0 isLooped: true - - type: PredictedBattery + - type: Battery maxCharge: 600 startingCharge: 600 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 2 - type: Item size: Normal diff --git a/Resources/Prototypes/Entities/Debugging/debug_sweps.yml b/Resources/Prototypes/Entities/Debugging/debug_sweps.yml index 6a8c8a5d02..557a36646f 100644 --- a/Resources/Prototypes/Entities/Debugging/debug_sweps.yml +++ b/Resources/Prototypes/Entities/Debugging/debug_sweps.yml @@ -18,7 +18,7 @@ - type: BatteryAmmoProvider proto: DebugLaser fireCost: 1 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 1000 - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml index 5c575da396..c0220cee13 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml @@ -22,10 +22,10 @@ - type: BatteryAmmoProvider proto: RedLaser fireCost: 62.5 - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 40 - type: Gun fireRate: 0.75 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml b/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml index 4a205c9cb0..1f3faf2155 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml @@ -53,9 +53,9 @@ - type: BatteryAmmoProvider proto: WatcherBolt fireCost: 50 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 50 - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - type: Gun diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml index d080f8c45f..fc7058b68e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml @@ -170,10 +170,10 @@ - type: BatteryAmmoProvider proto: RedLaser fireCost: 140 - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 50 - type: Gun fireRate: 0.3 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml index ac4f476a35..d6727c7d88 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml @@ -50,9 +50,9 @@ - type: BatteryAmmoProvider proto: RedLightLaser fireCost: 50 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 50 - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - type: Gun diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 9547a0e38b..898cee8524 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -241,6 +241,11 @@ powerLoad: 500 - type: ExtensionCableReceiver - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false maxCharge: 300000 startingCharge: 300000 - type: ApcPowerReceiverBattery diff --git a/Resources/Prototypes/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/Entities/Objects/Power/powercells.yml index a896e3a6e2..0c318dd20d 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powercells.yml @@ -5,7 +5,7 @@ components: - type: Item storedRotation: -90 - - type: PredictedBattery + - type: Battery pricePerJoule: 0.15 - type: PowerCell - type: Explosive @@ -32,7 +32,7 @@ - PowerCell - type: Riggable - type: Appearance - - type: PredictedBatteryVisuals + - type: BatteryVisuals - type: GenericVisualizer visuals: enum.BatteryVisuals.State: @@ -50,7 +50,7 @@ - type: Sprite layers: - state: potato - - type: PredictedBattery + - type: Battery maxCharge: 70 startingCharge: 70 - type: Tag @@ -74,7 +74,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 360 startingCharge: 360 - type: Tag @@ -94,7 +94,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery maxCharge: 360 startingCharge: 0 @@ -104,7 +104,7 @@ name: small-capacity nuclear power cell description: A self rechargeable power cell, designed for fast recharge rate at the expense of capacity. components: - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 36 # 10 seconds to recharge autoRechargePauseTime: 30 @@ -122,7 +122,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 720 startingCharge: 720 @@ -139,7 +139,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery maxCharge: 720 startingCharge: 0 @@ -157,7 +157,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 1080 startingCharge: 1080 @@ -174,7 +174,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery maxCharge: 1080 startingCharge: 0 @@ -192,7 +192,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 1800 startingCharge: 1800 @@ -209,7 +209,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery maxCharge: 1800 startingCharge: 0 @@ -227,10 +227,10 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 720 startingCharge: 720 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 12 # takes 1 minute to charge itself back to full - type: entity @@ -246,7 +246,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery startingCharge: 0 - type: entity @@ -262,10 +262,10 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 1200 startingCharge: 1200 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 40 # Power cage (big heavy power cell for big devices) @@ -309,7 +309,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 1400 startingCharge: 1400 @@ -327,7 +327,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 2700 startingCharge: 2700 @@ -345,7 +345,7 @@ - map: [ "enum.PowerCellVisualLayers.Unshaded" ] state: o2 shader: unshaded - - type: PredictedBattery + - type: Battery maxCharge: 6200 startingCharge: 6200 @@ -363,7 +363,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery maxCharge: 1400 startingCharge: 0 @@ -381,7 +381,7 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery startingCharge: 0 - type: entity @@ -398,5 +398,5 @@ state: o2 shader: unshaded visible: false - - type: PredictedBattery + - type: Battery startingCharge: 0 diff --git a/Resources/Prototypes/Entities/Objects/Power/powersink.yml b/Resources/Prototypes/Entities/Objects/Power/powersink.yml index 089610883c..7e448e06f7 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powersink.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powersink.yml @@ -40,6 +40,11 @@ acts: [ "Destruction" ] - type: PowerSink - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false maxCharge: 250000000 pricePerJoule: 0.000009 - type: ExaminableBattery diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml index 9fe4d377d7..59efee2e69 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml @@ -48,7 +48,7 @@ parent: [ HandheldHealthAnalyzerUnpowered, PowerCellSlotSmallItem] suffix: "" components: - - type: PredictedBatteryVisuals + - type: BatteryVisuals - type: PowerCellDraw drawRate: 1.2 #Calculated for 5 minutes on a small cell - type: ToggleCellDraw diff --git a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml index 086901b145..e49c6b2c09 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml @@ -19,7 +19,7 @@ maxRange: 256 followEntity: true - type: Appearance - - type: PredictedBatteryVisuals + - type: BatteryVisuals - type: GenericVisualizer visuals: enum.BatteryVisuals.State: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index 1ad4ef01c0..9e4a1a644d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -20,7 +20,7 @@ - SemiAuto soundGunshot: path: /Audio/Weapons/Guns/Gunshots/laser.ogg - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - type: StaticPrice @@ -220,7 +220,7 @@ - type: BatteryAmmoProvider proto: RedLaser fireCost: 62.5 - - type: PredictedBattery + - type: Battery maxCharge: 500 startingCharge: 500 - type: Tag @@ -312,7 +312,7 @@ - type: BatteryAmmoProvider proto: Pulse fireCost: 200 - - type: PredictedBattery + - type: Battery maxCharge: 2000 startingCharge: 2000 - type: Tag @@ -354,7 +354,7 @@ - type: BatteryAmmoProvider proto: Pulse fireCost: 200 - - type: PredictedBattery + - type: Battery maxCharge: 5000 startingCharge: 5000 @@ -390,7 +390,7 @@ - type: BatteryAmmoProvider proto: Pulse fireCost: 100 - - type: PredictedBattery + - type: Battery maxCharge: 40000 startingCharge: 40000 @@ -457,7 +457,7 @@ - type: BatteryAmmoProvider proto: AntiParticlesProjectile fireCost: 500 - - type: PredictedBattery + - type: Battery maxCharge: 10000 startingCharge: 10000 @@ -682,7 +682,7 @@ - type: BatteryAmmoProvider proto: RedMediumLaser fireCost: 100 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 40 - type: MagazineVisuals magState: mag @@ -730,7 +730,7 @@ - type: BatteryAmmoProvider proto: RedMediumLaser fireCost: 100 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 30 - type: MagazineVisuals magState: mag @@ -829,7 +829,7 @@ - type: BatteryAmmoProvider proto: RedMediumLaser fireCost: 100 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 40 - type: StaticPrice price: 750 @@ -878,7 +878,7 @@ sprite: Objects/Weapons/Guns/Battery/inhands_64x.rsi heldPrefix: energy - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - - type: PredictedBattery + - type: Battery maxCharge: 480 startingCharge: 480 @@ -925,7 +925,7 @@ - proto: BulletDisabler fireCost: 62.5 pacifismAllowedMode: true - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 48 autoRechargePauseTime: 10 @@ -967,7 +967,7 @@ fireCost: 100 - proto: BoltTempgunHot fireCost: 100 - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - type: StaticPrice diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_base.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_base.yml index dc4be40c82..ca56ae0c6c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_base.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_base.yml @@ -124,6 +124,11 @@ proto: BulletEnergyTurretLaser fireCost: 100 - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false maxCharge: 2000 startingCharge: 2000 - type: ApcPowerReceiverBattery diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml index 2a197e2685..7b2d96854a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml @@ -129,10 +129,10 @@ - type: BatteryAmmoProvider proto: FoodPieBananaCream fireCost: 30 - - type: PredictedBattery + - type: Battery maxCharge: 90 startingCharge: 90 - - type: PredictedBatterySelfRecharger + - type: BatterySelfRecharger autoRechargeRate: 1 - type: AmmoCounter - type: Item diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml index 27857e24a1..295a782190 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml @@ -42,7 +42,7 @@ damage: 35 sound: /Audio/Weapons/egloves.ogg - type: LandAtCursor # it deals stamina damage when thrown - - type: PredictedBattery + - type: Battery maxCharge: 360 startingCharge: 360 - type: UseDelay diff --git a/Resources/Prototypes/Entities/Objects/Weapons/security.yml b/Resources/Prototypes/Entities/Objects/Weapons/security.yml index 217286a5c5..2f6ac834ba 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/security.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/security.yml @@ -47,7 +47,7 @@ damage: 35 sound: /Audio/Weapons/egloves.ogg - type: LandAtCursor # it deals stamina damage when thrown - - type: PredictedBattery + - type: Battery maxCharge: 1000 startingCharge: 1000 - type: UseDelay diff --git a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml index e769d81fbb..e930e105e1 100644 --- a/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml +++ b/Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml @@ -451,6 +451,11 @@ - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false maxCharge: 30000 startingCharge: 0 - type: EmergencyLight diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml index 1def040b09..e7f6082734 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml @@ -56,6 +56,11 @@ sprite: Structures/Power/Generation/Tesla/coil.rsi state: coil - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false maxCharge: 5000000 startingCharge: 0 - type: BatteryDischarger diff --git a/Resources/Prototypes/Entities/Structures/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml index ec8b86e173..4efc5d0fe6 100644 --- a/Resources/Prototypes/Entities/Structures/Power/apc.yml +++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml @@ -56,6 +56,11 @@ - type: Appearance - type: ApcVisuals - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false maxCharge: 50000 startingCharge: 0 - type: ExaminableBattery diff --git a/Resources/Prototypes/Entities/Structures/Power/debug_power.yml b/Resources/Prototypes/Entities/Structures/Power/debug_power.yml index 5706b802a5..e4268a25c5 100644 --- a/Resources/Prototypes/Entities/Structures/Power/debug_power.yml +++ b/Resources/Prototypes/Entities/Structures/Power/debug_power.yml @@ -73,6 +73,11 @@ sprite: Structures/Power/power.rsi state: provider - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false - type: NodeContainer nodes: input: @@ -104,6 +109,11 @@ sprite: Structures/Power/power.rsi state: provider - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false - type: NodeContainer nodes: input: diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml index 3572fe6a39..c29f7a972c 100644 --- a/Resources/Prototypes/Entities/Structures/Power/smes.yml +++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml @@ -33,6 +33,11 @@ - type: Smes - type: Appearance - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false startingCharge: 0 maxCharge: 8000000 - type: ExaminableBattery diff --git a/Resources/Prototypes/Entities/Structures/Power/substation.yml b/Resources/Prototypes/Entities/Structures/Power/substation.yml index f2d86a3900..a2b5a0a006 100644 --- a/Resources/Prototypes/Entities/Structures/Power/substation.yml +++ b/Resources/Prototypes/Entities/Structures/Power/substation.yml @@ -5,6 +5,11 @@ components: # Core power behavior - type: Battery + # Very important: + # Disable networking to prevent the battery getting continuously dirtied every tick as it interacts with the power network. + # This can not be done by changing the charge rate as the power supply ramps up over time. + # This disables prediction for this battery. + netsync: false - type: ExaminableBattery - type: NodeContainer examinable: true -- 2.52.0