]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Unify BatteryComponent and PredictedBatteryComponent (#41867)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Fri, 19 Dec 2025 18:18:12 +0000 (19:18 +0100)
committerGitHub <noreply@github.com>
Fri, 19 Dec 2025 18:18:12 +0000 (18:18 +0000)
* unify

* cleanup and merge conflicts

* floating points

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
74 files changed:
Content.Client/Silicons/Borgs/BorgMenu.xaml.cs
Content.Client/Silicons/Borgs/BorgSystem.cs
Content.IntegrationTests/Tests/Power/PowerTest.cs
Content.IntegrationTests/Tests/Power/StationPowerTests.cs
Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
Content.Server/Construction/Completions/BuildMech.cs
Content.Server/Light/EntitySystems/EmergencyLightSystem.cs
Content.Server/Light/EntitySystems/HandheldLightSystem.cs
Content.Server/Mech/Systems/MechSystem.cs
Content.Server/Ninja/Systems/BatteryDrainerSystem.cs
Content.Server/Ninja/Systems/ItemCreatorSystem.cs
Content.Server/Ninja/Systems/NinjaSuitSystem.cs
Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
Content.Server/Ninja/Systems/StunProviderSystem.cs
Content.Server/Power/EntitySystems/BatteryInterfaceSystem.cs
Content.Server/Power/EntitySystems/BatterySystem.API.cs [deleted file]
Content.Server/Power/EntitySystems/BatterySystem.cs
Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs
Content.Server/Power/EntitySystems/PowerNetSystem.cs
Content.Server/Power/EntitySystems/RiggableSystem.cs
Content.Server/Power/SMES/SmesSystem.cs
Content.Server/Power/SetBatteryPercentCommand.cs
Content.Server/PowerSink/PowerSinkSystem.cs
Content.Server/Radio/EntitySystems/JammerSystem.cs
Content.Server/SensorMonitoring/BatterySensorSystem.cs
Content.Server/Silicons/Borgs/BorgSystem.cs
Content.Server/Silicons/StationAi/StationAiSystem.cs
Content.Server/Speech/EntitySystems/DamagedSiliconAccentSystem.cs
Content.Server/Stunnable/Systems/StunbatonSystem.cs
Content.Server/Tesla/EntitySystem/TeslaCoilSystem.cs
Content.Server/Xenoarchaeology/Artifact/XAE/XAEChargeBatterySystem.cs
Content.Shared/Power/ChargeEvents.cs
Content.Shared/Power/Components/BatteryComponent.cs
Content.Shared/Power/Components/BatterySelfRechargerComponent.cs
Content.Shared/Power/Components/BatteryVisualsComponent.cs [moved from Content.Shared/Power/Components/PredictedBatteryVisualsComponent.cs with 75% similarity]
Content.Shared/Power/Components/ExaminableBatteryComponent.cs
Content.Shared/Power/Components/PredictedBatteryComponent.cs [deleted file]
Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs [deleted file]
Content.Shared/Power/EntitySystems/ChargerSystem.cs
Content.Shared/Power/EntitySystems/PredictedBatterySystem.cs [deleted file]
Content.Shared/Power/EntitySystems/SharedBatterySystem.API.cs [moved from Content.Shared/Power/EntitySystems/PredictedBatterySystem.API.cs with 79% similarity]
Content.Shared/Power/EntitySystems/SharedBatterySystem.cs
Content.Shared/PowerCell/Components/PowerCellComponent.cs
Content.Shared/PowerCell/PowerCellSystem.API.cs
Content.Shared/PowerCell/PowerCellSystem.Relay.cs
Content.Shared/PowerCell/PowerCellSystem.cs
Content.Shared/UserInterface/ActivatableUISystem.Power.cs
Content.Shared/Weapons/Ranged/Components/BatteryAmmoProviderComponent.cs
Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs
Resources/Prototypes/Catalog/Bounties/bounties.yml
Resources/Prototypes/Entities/Clothing/Hands/gloves.yml
Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml
Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
Resources/Prototypes/Entities/Debugging/debug_sweps.yml
Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml
Resources/Prototypes/Entities/Mobs/NPCs/lavaland.yml
Resources/Prototypes/Entities/Mobs/NPCs/living_light.yml
Resources/Prototypes/Entities/Mobs/NPCs/miscellaneous.yml
Resources/Prototypes/Entities/Mobs/Player/silicon.yml
Resources/Prototypes/Entities/Objects/Power/powercells.yml
Resources/Prototypes/Entities/Objects/Power/powersink.yml
Resources/Prototypes/Entities/Objects/Specific/Medical/healthanalyzer.yml
Resources/Prototypes/Entities/Objects/Tools/handheld_mass_scanner.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_base.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/stunprod.yml
Resources/Prototypes/Entities/Objects/Weapons/security.yml
Resources/Prototypes/Entities/Structures/Lighting/base_lighting.yml
Resources/Prototypes/Entities/Structures/Power/Generation/Tesla/coil.yml
Resources/Prototypes/Entities/Structures/Power/apc.yml
Resources/Prototypes/Entities/Structures/Power/debug_power.yml
Resources/Prototypes/Entities/Structures/Power/smes.yml
Resources/Prototypes/Entities/Structures/Power/substation.yml

index 0acc3a26468a21b75bfadbaa695cef28eaf94cd0..eee45bacc8aad9c179a06bc9be961533fd2289c1 100644 (file)
@@ -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<NameModifierSystem>();
         _powerCell = _entity.System<PowerCellSystem>();
-        _battery = _entity.System<PredictedBatterySystem>();
+        _battery = _entity.System<SharedBatterySystem>();
 
         _maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);
 
index a4ca3725dc641684d5aa108141815f95d0273568..517027d082181c3e1240f42ef8a07100b6707739 100644 (file)
@@ -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!;
index 85bd3666971874f892b1f3bcf33454678e671bf0..a28f646ef80a284e32810d4f3e8379fc8115c94a 100644 (file)
@@ -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<PowerNetworkBatteryComponent>(generatorEnt);
                 battery = entityManager.GetComponent<BatteryComponent>(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<IEntityManager>();
             var batterySys = entityManager.System<BatterySystem>();
             var mapSys = entityManager.System<SharedMapSystem>();
+            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<PowerSupplierComponent>(generatorEnt);
                 var netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(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<IGameTiming>();
             var batterySys = entityManager.System<BatterySystem>();
             var mapSys = entityManager.System<SharedMapSystem>();
+            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<TransformComponent>(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<PowerConsumerComponent>(consumerEnt);
                 supplier = entityManager.GetComponent<PowerSupplierComponent>(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<IGameTiming>();
             var batterySys = entityManager.System<BatterySystem>();
             var mapSys = entityManager.System<SharedMapSystem>();
+            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<TransformComponent>(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<PowerConsumerComponent>(consumerEnt);
                 supplier = entityManager.GetComponent<PowerSupplierComponent>(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<IEntityManager>();
             var batterySys = entityManager.System<BatterySystem>();
             var mapSys = entityManager.System<SharedMapSystem>();
+            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<PowerSupplierComponent>(generatorEnt);
                 substationNetBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(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
                 });
             });
 
index 542a4645c65af03041082421d155d71130c49b7e..9d79abf4809a26f4e0dfe0905cce42e6882208b5 100644 (file)
@@ -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<GameTicker>();
+        var batterySys = entMan.System<BatterySystem>();
 
         // 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;
 
index bd214578eaeae935d00dd4698ce9be3c2d71ea01..f30e0ef728052c606aa17f6b8bd883e2a1cf4103 100644 (file)
@@ -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<PredictedBatteryComponent>(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<PredictedBatterySelfRechargerComponent>(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<BatteryComponent>(args.Target, out var battery))
         {
             Verb refillBattery = new()
@@ -254,6 +201,8 @@ public sealed partial class AdminVerbSystem
                     var recharger = EnsureComp<BatterySelfRechargerComponent>(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"),
index 5f1e40c3477b41d7c5f986d85bdf28aefdb8bb03..c0b5921db9264e36d9c9f9c3f416a4e694df9ec5 100644 (file)
@@ -48,7 +48,7 @@ public sealed partial class BuildMech : IGraphAction
 
         var cell = container.ContainedEntities[0];
 
-        if (!entityManager.TryGetComponent<PredictedBatteryComponent>(cell, out var batteryComponent))
+        if (!entityManager.TryGetComponent<BatteryComponent>(cell, out var batteryComponent))
         {
             Logger.Warning($"Mech construct entity {uid} had an invalid entity in container \"{Container}\"! Aborting build mech action.");
             return;
index eddef878539fb09d0bda4e343354b8432f42e201..824afbc78904e206b329c89edf67be01e7120209 100644 (file)
@@ -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<ApcPowerReceiverComponent>(entity.Owner, out var receiver))
index da024ee07564bedad0ca437449a7b8ba4be2e967..e4e947b5487f3e10ad47c6253555ebd4ffde8e7d 100644 (file)
@@ -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<Entity<HandheldLightComponent>>();
index 80169cb2ed9b5cadbc2aa0d5a1d4ef2d2b479bf7..61aa1aa1cc22d57e049d91aef7a07c0ec3de2e12 100644 (file)
@@ -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<WiresPanelComponent>(uid, out var panel) && !panel.Open)
             return;
 
-        if (component.BatterySlot.ContainedEntity == null && TryComp<PredictedBatteryComponent>(args.Used, out var battery))
+        if (component.BatterySlot.ContainedEntity == null && TryComp<BatteryComponent>(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<PredictedBatteryComponent>(args.Entity, out var battery))
+        if (args.Container != component.BatterySlot || !TryComp<BatteryComponent>(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<PredictedBatteryComponent>(battery, out var batteryComp))
+        if (!TryComp<BatteryComponent>(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;
index f6d9d7bec744048ff72e4a573cffa3690e0ecf2e..98fae3ae1318b58ccf4adf47f3b05d9dad4e11ff 100644 (file)
@@ -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;
 /// </summary>
 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<BatteryDrainerComponent> ent, EntityUid target)
     {
         var (uid, comp) = ent;
-        if (comp.BatteryUid == null || !TryComp<PredictedBatteryComponent>(comp.BatteryUid.Value, out var battery))
+        if (comp.BatteryUid == null || !TryComp<BatteryComponent>(comp.BatteryUid.Value, out var battery))
             return false;
 
         if (!TryComp<BatteryComponent>(target, out var targetBattery) || !TryComp<PowerNetworkBatteryComponent>(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));
     }
 }
index 227fdea789999b8fad5b56936c03c04228ea3627..cca80f2e42bb8843922fda03f9185fbdffc1cdde 100644 (file)
@@ -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!;
 
index 8ebd56ea7c0e90647c25d3e3025db5225c404e60..f6c4643cad4e98bf7ad9b15eba4c290703076bf2 100644 (file)
@@ -62,7 +62,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
         if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery))
             return;
 
-        if (!TryComp<PredictedBatteryComponent>(args.EntityUid, out var inserting))
+        if (!TryComp<BatteryComponent>(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<PredictedBatterySelfRechargerComponent>(uid, out var selfcomp))
+        if (TryComp<BatterySelfRechargerComponent>(uid, out var selfcomp))
             return battcomp.MaxCharge + selfcomp.AutoRechargeRate * AutoRechargeValue;
         return battcomp.MaxCharge;
     }
index f2fc9abfccb5c1af67f4c6d1430910ad50d3d0df..4f3a1873f5c52cd4c2b30450e117b6456b24a7eb 100644 (file)
@@ -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
     /// <summary>
     /// Get the battery component in a ninja's suit, if it's worn.
     /// </summary>
-    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<SpaceNinjaComponent>(user, out var ninja)
             && ninja.Suit != null
index 49d7ab5e85c05c5acbe3d5ba21d322fc23c32443..387844b6b1ae70c6785874e32d629d0559a70c2f 100644 (file)
@@ -17,7 +17,7 @@ namespace Content.Server.Ninja.Systems;
 /// </summary>
 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!;
index 83ff28646dbaba51a648247b021ec374c698c24f..d2c95065db2e9e8b031acca1781bcae11c3ff5eb 100644 (file)
@@ -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<PowerNetworkBatteryComponent>(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<BatteryInterfaceComponent> ent, ref BatterySetOutputBreakerMessage args)
@@ -56,7 +58,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem
         var netBattery = Comp<PowerNetworkBatteryComponent>(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<BatteryInterfaceComponent> 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 (file)
index 5995758..0000000
+++ /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;
-
-/// <summary>
-/// Responsible for <see cref="BatteryComponent"/>.
-/// Unpredicted equivalent of <see cref="PredictedBatterySystem"/>.
-/// If you make changes to this make sure to keep the two consistent.
-/// </summary>
-public sealed partial class BatterySystem
-{
-    public override float ChangeCharge(Entity<BatteryComponent?> 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<BatteryComponent?> 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<BatteryComponent?> 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<BatteryComponent?> 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<BatteryComponent?> 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);
-    }
-
-    /// <summary>
-    /// Gets the battery's current charge.
-    /// </summary>
-    public float GetCharge(Entity<BatteryComponent?> ent)
-    {
-        if (!Resolve(ent, ref ent.Comp, false))
-            return 0f;
-
-        return ent.Comp.CurrentCharge;
-    }
-
-    /// <summary>
-    /// Gets number of remaining uses for the given charge cost.
-    /// </summary>
-    public int GetRemainingUses(Entity<BatteryComponent?> ent, float cost)
-    {
-        if (cost <= 0)
-            return 0;
-
-        if (!Resolve(ent, ref ent.Comp))
-            return 0;
-
-        return (int)(ent.Comp.CurrentCharge / cost);
-    }
-
-    /// <summary>
-    /// Gets number of maximum uses at full charge for the given charge cost.
-    /// </summary>
-    public int GetMaxUses(Entity<BatteryComponent?> 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<BatterySelfRechargerComponent?> 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<BatterySelfRechargerComponent?> ent, TimeSpan cooldown)
-    {
-        if (!Resolve(ent, ref ent.Comp))
-            return;
-
-        ent.Comp.NextAutoRecharge = _timing.CurTime + cooldown;
-    }
-
-    /// <summary>
-    /// Returns whether the battery is full.
-    /// </summary>
-    public bool IsFull(Entity<BatteryComponent?> ent)
-    {
-        if (!Resolve(ent, ref ent.Comp))
-            return false;
-
-        return ent.Comp.CurrentCharge >= ent.Comp.MaxCharge;
-    }
-}
index 1a88672bc935894dbca7a2442d31c1a3f1ece1d0..a9b750b6d40fbd54ff4eb9a206ba7ce7d4988fd6 100644 (file)
@@ -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;
 
-/// <summary>
-/// Responsible for <see cref="BatteryComponent"/>.
-/// Unpredicted equivalent of <see cref="PredictedBatterySystem"/>.
-/// If you make changes to this make sure to keep the two consistent.
-/// </summary>
-[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<BatteryComponent, ComponentInit>(OnInit);
-        SubscribeLocalEvent<BatteryComponent, ExaminedEvent>(OnExamine);
-        SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnBatteryRejuvenate);
         SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate);
-        SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
-        SubscribeLocalEvent<BatteryComponent, ChangeChargeEvent>(OnChangeCharge);
-        SubscribeLocalEvent<BatteryComponent, GetChargeEvent>(OnGetCharge);
-
         SubscribeLocalEvent<NetworkBatteryPreSync>(PreSync);
         SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync);
     }
 
-    private void OnInit(Entity<BatteryComponent> ent, ref ComponentInit args)
-    {
-        DebugTools.Assert(!HasComp<PredictedBatteryComponent>(ent), $"{ent} has both BatteryComponent and PredictedBatteryComponent");
-    }
-    private void OnNetBatteryRejuvenate(Entity<PowerNetworkBatteryComponent> ent, ref RejuvenateEvent args)
-    {
-        ent.Comp.NetworkBattery.CurrentStorage = ent.Comp.NetworkBattery.Capacity;
-    }
-    private void OnBatteryRejuvenate(Entity<BatteryComponent> ent, ref RejuvenateEvent args)
-    {
-        SetCharge(ent.AsNullable(), ent.Comp.MaxCharge);
-    }
-
-    private void OnExamine(Entity<BatteryComponent> ent, ref ExaminedEvent args)
+    protected override void OnStartup(Entity<BatteryComponent> 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 <see cref="RefreshChargeRateEvent"/> 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
+        /// <code>
+        /// - type: Battery
+        ///   netSync: false
+        /// </code>
+        /// This disables networking and prediction for this battery.
+        if (!ent.Comp.NetSyncEnabled)
             return;
 
-        if (!HasComp<ExaminableBatteryComponent>(ent))
-            return;
+        DebugTools.Assert(!HasComp<ApcPowerReceiverBatteryComponent>(ent), $"{ToPrettyString(ent.Owner)} has a predicted battery connected to the power net. Disable net sync!");
+        DebugTools.Assert(!HasComp<PowerNetworkBatteryComponent>(ent), $"{ToPrettyString(ent.Owner)} has a predicted battery connected to the power net. Disable net sync!");
+        DebugTools.Assert(!HasComp<PowerConsumerComponent>(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<PowerNetworkBatteryComponent> 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<PowerNetworkBatteryComponent, BatteryComponent>();
-        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);
         }
     }
-
-    /// <summary>
-    /// Gets the price for the power contained in an entity's battery.
-    /// </summary>
-    private void CalculateBatteryPrice(Entity<BatteryComponent> ent, ref PriceCalculationEvent args)
-    {
-        args.Price += ent.Comp.CurrentCharge * ent.Comp.PricePerJoule;
-    }
-
-    private void OnChangeCharge(Entity<BatteryComponent> ent, ref ChangeChargeEvent args)
-    {
-        if (args.ResidualValue == 0)
-            return;
-
-        args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue);
-    }
-
-    private void OnGetCharge(Entity<BatteryComponent> entity, ref GetChargeEvent args)
-    {
-        args.CurrentCharge += entity.Comp.CurrentCharge;
-        args.MaxCharge += entity.Comp.MaxCharge;
-    }
-
-    public override void Update(float frameTime)
-    {
-        var query = EntityQueryEnumerator<BatterySelfRechargerComponent, BatteryComponent>();
-        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);
-        }
-    }
 }
index 25757360b386fb2958be7dad46d916d5939a6ba2..e0344f4d6c9dba5cd1727be8f43a7007d87e266b 100644 (file)
@@ -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<EntityUid, Dictionary<Vector2i, PowerCableChunk>> _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<PowerMonitoringConsoleEntry> sources)
index 5b6b922ddf4b3b323135ab3215d2cf9ac33f3a40..1c3f76fdee45a55a2cafb7beaaa5907c75e553ef 100644 (file)
@@ -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)
                     {
index 390e75eeb8c4f249deead6b89c85995cd6b6ee63..0bff0f4ba818e7c2c137b58f12d462266e6366cf 100644 (file)
@@ -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<RiggableComponent, BeingMicrowavedEvent>(OnMicrowaved);
         SubscribeLocalEvent<RiggableComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
         SubscribeLocalEvent<RiggableComponent, ChargeChangedEvent>(OnChargeChanged);
-        SubscribeLocalEvent<RiggableComponent, PredictedBatteryChargeChangedEvent>(OnChargeChanged);
     }
 
     private void OnRejuvenate(Entity<RiggableComponent> entity, ref RejuvenateEvent args)
@@ -39,16 +38,7 @@ public sealed class RiggableSystem : EntitySystem
     {
         if (TryComp<BatteryComponent>(entity, out var batteryComponent))
         {
-            if (batteryComponent.CurrentCharge == 0f)
-                return;
-
-            Explode(entity, batteryComponent.CurrentCharge);
-            args.Handled = true;
-        }
-
-        if (TryComp<PredictedBatteryComponent>(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<RiggableComponent> 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<RiggableComponent> ent, ref PredictedBatteryChargeChangedEvent args)
     {
         if (!ent.Comp.IsRigged)
             return;
index 15c40b3c92351ee841225659f970b3adaae6c376..5a8169e1c92ae068ac34dc7d2144f16f7591fc73 100644 (file)
@@ -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)
index bd48e6cd977cf766aee62bd4c7d663eb36aac2e1..f13a1b820e4a77377e8e75ab9b0a9bd46b4faa1f 100644 (file)
@@ -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<BatteryComponent>(id, out var battery))
                 _batterySystem.SetCharge((id.Value, battery), battery.MaxCharge * percent / 100);
-            else if (EntityManager.TryGetComponent<PredictedBatteryComponent>(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)));
index 328bff89f4d6902ec5d65a7887886c4a1c948713..c8ea58a395171c097d6f78aec6032ffc4aef4eec 100644 (file)
@@ -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)
index eaa42d744c8ede2bfed3e2d4b467e2df78b6b629..dead85f51fa822d1371f66655dc9068ac07f6028 100644 (file)
@@ -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<ActiveRadioJammerComponent, RadioJammerComponent>();
index bd94868c5fc6f83c70d769809bd1d0d167515612..2c7666ba968f2da3a084ac51c530fb624f9fe6f6 100644 (file)
@@ -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<BatteryComponent>(uid);
+                var currentCharge = _battery.GetCharge((uid, battery));
                 var netBattery = Comp<PowerNetworkBatteryComponent>(uid);
 
                 var payload = new NetworkPayload
                 {
                     [DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData,
                     [DeviceNetworkCommandSyncData] = new BatterySensorData(
-                        battery.CurrentCharge,
+                        currentCharge,
                         battery.MaxCharge,
                         netBattery.CurrentReceiving,
                         netBattery.MaxChargeRate,
index 6f37d55013b84ccbdd56fef1b899f315555ffb93..925050f28eb907ed5f0eebaf92dd85ddfdab6d9a 100644 (file)
@@ -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!;
index e52400d3de4f5c9030a1b7b075435925a1c83492..c4afd83b2d7d00ee0b5df7bf5b759ff764a6abf6 100644 (file)
@@ -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<StationAiCoreComponent> ent)
     {
         if (!TryGetHeld((ent.Owner, ent.Comp), out var held))
@@ -229,7 +230,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
             return;
 
         if (TryComp<BatteryComponent>(ent, out var battery))
-            accent.OverrideChargeLevel = battery.CurrentCharge / battery.MaxCharge;
+            accent.OverrideChargeLevel = _battery.GetChargeLevel((ent.Owner, battery));
 
         if (TryComp<DamageableComponent>(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));
index e5db13ca844225aedc360945a76431168a6dd7be..e9bbfdf52809e1ccf6e6aebb2ec5ed73833023ff 100644 (file)
@@ -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!;
 
index b1bae0127c45455eed4fa7c9a4535c274824c65c..90d2dc758880d0c8ad75c8afac21d9e1e7d39094 100644 (file)
@@ -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<StunbatonComponent, ExaminedEvent>(OnExamined);
             SubscribeLocalEvent<StunbatonComponent, SolutionContainerChangedEvent>(OnSolutionChange);
             SubscribeLocalEvent<StunbatonComponent, StaminaDamageOnHitAttemptEvent>(OnStaminaHitAttempt);
-            SubscribeLocalEvent<StunbatonComponent, PredictedBatteryChargeChangedEvent>(OnChargeChanged);
+            SubscribeLocalEvent<StunbatonComponent, ChargeChangedEvent>(OnChargeChanged);
         }
 
         private void OnStaminaHitAttempt(Entity<StunbatonComponent> entity, ref StaminaDamageOnHitAttemptEvent args)
         {
             if (!_itemToggle.IsActivated(entity.Owner) ||
-            !TryComp<PredictedBatteryComponent>(entity.Owner, out var battery) || !_battery.TryUseCharge((entity.Owner, battery), entity.Comp.EnergyPerUse))
+            !TryComp<BatteryComponent>(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<PredictedBatteryComponent>(entity.Owner, out var battery))
+            if (TryComp<BatteryComponent>(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<PredictedBatteryComponent>(entity, out var battery) || _battery.GetCharge((entity, battery)) < entity.Comp.EnergyPerUse)
+            if (!TryComp<BatteryComponent>(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<RiggableComponent>(entity, out var riggable) ||
-                !TryComp<PredictedBatteryComponent>(entity, out var battery))
+                !TryComp<BatteryComponent>(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<StunbatonComponent> entity, ref PredictedBatteryChargeChangedEvent args)
+        private void OnChargeChanged(Entity<StunbatonComponent> entity, ref ChargeChangedEvent args)
         {
-            if (TryComp<PredictedBatteryComponent>(entity.Owner, out var battery) &&
+            if (TryComp<BatteryComponent>(entity.Owner, out var battery) &&
                 _battery.GetCharge((entity.Owner, battery)) < entity.Comp.EnergyPerUse)
             {
                 _itemToggle.TryDeactivate(entity.Owner, predicted: false);
index 988ade86cee437a769d98a637af5199781e8fdac..d418ea5a18e2c2251029c1c616aa2946914eb167 100644 (file)
@@ -24,7 +24,7 @@ public sealed class TeslaCoilSystem : EntitySystem
     {
         if (TryComp<BatteryComponent>(coil, out var batteryComponent))
         {
-            _battery.SetCharge((coil, batteryComponent), batteryComponent.CurrentCharge + coil.Comp.ChargeFromLightning);
+            _battery.ChangeCharge((coil, batteryComponent), coil.Comp.ChargeFromLightning);
         }
     }
 }
index c3f4926a127ebee05020948314fd12fdf5351037..437e082581bdd6242193029f85230a76145c301e 100644 (file)
@@ -13,29 +13,20 @@ namespace Content.Server.Xenoarchaeology.Artifact.XAE;
 public sealed class XAEChargeBatterySystem : BaseXAESystem<XAEChargeBatteryComponent>
 {
     [Dependency] private readonly BatterySystem _battery = default!;
-    [Dependency] private readonly PredictedBatterySystem _predictedBattery = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
 
     /// <summary> Pre-allocated and re-used collection.</summary>
     private readonly HashSet<Entity<BatteryComponent>> _batteryEntities = new();
-    private readonly HashSet<Entity<PredictedBatteryComponent>> _pBatteryEntities = new();
 
     /// <inheritdoc />
     protected override void OnActivated(Entity<XAEChargeBatteryComponent> 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);
-        }
     }
 }
index d6a9c66071f89091e3cc129d0f108257a5d9b986..ff602f9dcbb12f5dea097c275c191bd7f64823f2 100644 (file)
@@ -1,40 +1,17 @@
 using Content.Shared.Power.Components;
+using Content.Shared.Power.EntitySystems;
 using Content.Shared.PowerCell.Components;
 
 namespace Content.Shared.Power;
 
 /// <summary>
-/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage).
-/// Only raised for entities with <see cref="BatteryComponent"/>.
+/// Raised when a battery's charge, charge rate or capacity was updated (capacity affects relative charge percentage).
+/// If a battery uses <see cref="BatteryComponent.ChargeRate"/> to (dis)charge this is NOT raised every single tick, but only when the charge rate is updated.
+/// For instantaneous charge changes using <see cref="SharedBatterySystem.SetCharge"/>, <see cref="SharedBatterySystem.ChangeCharge"/> or similar this DOES get raised, but
+/// you should avoid doing so in update loops if the component has net sync enabled.
 /// </summary>
 [ByRefEvent]
-public readonly record struct ChargeChangedEvent(float Charge, float Delta, float MaxCharge)
-{
-    /// <summary>
-    /// The new charge of the battery.
-    /// </summary>
-    public readonly float Charge = Charge;
-
-    /// <summary>
-    /// The amount the charge was changed by.
-    /// </summary>
-    public readonly float Delta = Delta;
-
-    /// <summary>
-    /// The maximum charge of the battery.
-    /// </summary>
-    public readonly float MaxCharge = MaxCharge;
-}
-
-/// <summary>
-/// Raised when a predicted battery's charge or capacity changes (capacity affects relative charge percentage).
-/// Unlike <see cref="ChargeChangedEvent"/> 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 <see cref="PredictedBatteryComponent"/>.
-/// </summary>
-[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)
 {
     /// <summary>
     /// The new charge of the battery.
@@ -60,15 +37,14 @@ public readonly record struct PredictedBatteryChargeChangedEvent(float CurrentCh
 
 /// <summary>
 /// Raised when a battery changes its state between full, empty, or neither.
-/// Used only for <see cref="PredictedBatteryComponent"/>.
+/// 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).
 /// </summary>
 [ByRefEvent]
-public record struct PredictedBatteryStateChangedEvent(BatteryState OldState, BatteryState NewState);
+public record struct BatteryStateChangedEvent(BatteryState OldState, BatteryState NewState);
 
 /// <summary>
 /// Raised to calculate a predicted battery's recharge rate.
 /// Subscribe to this to offset its current charge rate.
-/// Used only for <see cref="PredictedBatteryComponent"/>.
 /// </summary>
 [ByRefEvent]
 public record struct RefreshChargeRateEvent(float MaxCharge)
@@ -80,7 +56,7 @@ public record struct RefreshChargeRateEvent(float MaxCharge)
 /// <summary>
 /// Event that supports multiple battery types.
 /// Raised when it is necessary to get information about battery charges.
-/// Works with either <see cref="BatteryComponent"/>, <see cref="PredictedBatteryComponent"/>, or <see cref="PowerCellSlotComponent"/>.
+/// Works with either <see cref="BatteryComponent"/> or <see cref="PowerCellSlotComponent"/>.
 /// If there are multiple batteries then the results will be summed up.
 /// </summary>
 [ByRefEvent]
@@ -93,7 +69,7 @@ public record struct GetChargeEvent
 /// <summary>
 /// Method event that supports multiple battery types.
 /// Raised when it is necessary to change the current battery charge by some value.
-/// Works with either <see cref="BatteryComponent"/>, <see cref="PredictedBatteryComponent"/>, or <see cref="PowerCellSlotComponent"/>.
+/// Works with either <see cref="BatteryComponent"/> or <see cref="PowerCellSlotComponent"/>.
 /// If there are multiple batteries then they will be changed in order of subscription until the total value was reached.
 /// </summary>
 [ByRefEvent]
index 396896a6c5b9123f52868a8acbe3d5a21b1e6209..343333f6db5caa0c315d8a8dc413baf3fd4cd992 100644 (file)
@@ -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;
 
 /// <summary>
-/// Battery node on the pow3r network. Needs other components to connect to actual networks.
-/// Use this for batteries that cannot be predicted.
-/// Use <see cref="PredictedBatteryComponent"/> 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 <see cref="PowerCellComponent"/> or battery powered guns with intrinsic battery.
 /// </summary>
-[RegisterComponent]
-[Virtual]
+/// <remarks>
+/// IMPORTANT: If your battery has an update loop setting the charge every single tick you should set <see cref="Component.NetSyncEnabled"> 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 <see cref="SharedBatterySystem.RefreshChargeRate"/> and
+/// the current charge will automatically be inferred if you use <see cref="SharedBatterySystem.GetCharge"/>.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
 [Access(typeof(SharedBatterySystem))]
-public partial class BatteryComponent : Component
+public sealed partial class BatteryComponent : Component
 {
     /// <summary>
-    /// Maximum charge of the battery in joules (i.e. watt seconds)
+    /// Maximum charge of the battery in joules (ie. watt seconds)
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField, ViewVariables]
     [GuidebookData]
     public float MaxCharge;
 
     /// <summary>
-    /// Current charge of the battery in joules (ie. watt seconds)
+    /// The price per one joule. Default is 1 speso for 10kJ.
+    /// </summary>
+    [DataField]
+    public float PricePerJoule = 0.0001f;
+
+    /// <summary>
+    /// Time stamp of the last networked update.
     /// </summary>
-    [DataField("startingCharge")] // TODO: rename this datafield to currentCharge
-    public float CurrentCharge;
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField, ViewVariables]
+    public TimeSpan LastUpdate = TimeSpan.Zero;
 
     /// <summary>
-    /// The price per one joule. Default is 1 speso for 10kJ.
+    /// The intial charge to be set on map init.
     /// </summary>
     [DataField]
-    public float PricePerJoule = 0.0001f;
+    public float StartingCharge;
+
+    /// <summary>
+    /// The charge at the last update in joules (i.e. watt seconds).
+    /// </summary>
+    [DataField, AutoNetworkedField, ViewVariables]
+    public float LastCharge;
+
+    /// <summary>
+    /// The current charge rate in watt.
+    /// </summary>
+    /// <remarks>
+    /// Not a datafield as this is only cached and recalculated on component startup.
+    /// </remarks>
+    [ViewVariables, AutoNetworkedField]
+    public float ChargeRate;
+
+    /// <summary>
+    /// The current charge state of the battery.
+    /// Used to track state changes for raising <see cref="BatteryStateChangedEvent"/>.
+    /// </summary>
+    /// <remarks>
+    /// Not a datafield as this is only cached and recalculated in an update loop.
+    /// </remarks>
+    [ViewVariables, AutoNetworkedField]
+    public BatteryState State = BatteryState.Neither;
 }
+
+/// <summary>
+/// Charge level status of the battery.
+/// </summary>
+[Serializable, NetSerializable]
+public enum BatteryState : byte
+{
+    /// <summary>
+    /// Full charge.
+    /// </summary>
+    Full,
+    /// <summary>
+    /// No charge.
+    /// </summary>
+    Empty,
+    /// <summary>
+    /// Neither full nor empty.
+    /// </summary>
+    Neither,
+}
+
index 7a5665ae82d3a2cf11b01156185d096e97b2d990..e18ad9d3b0c2909d774c33f192fdd1daae7ede93 100644 (file)
@@ -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;
 /// <summary>
 /// Self-recharging battery.
 /// To be used in combination with <see cref="BatteryComponent"/>.
-/// For <see cref="PredictedBatteryComponent"/> use <see cref="PredictedBatterySelfRechargerComponent"/> instead.
 /// </summary>
-[RegisterComponent, AutoGenerateComponentPause]
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
 public sealed partial class BatterySelfRechargerComponent : Component
 {
-    /// <summary>
-    /// Is the component currently enabled?
-    /// </summary>
-    [DataField]
-    public bool AutoRecharge = true;
-
     /// <summary>
     /// At what rate does the entity automatically recharge? In watts.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField, ViewVariables]
     public float AutoRechargeRate;
 
     /// <summary>
-    /// How long should the entity stop automatically recharging if charge is used?
+    /// How long should the entity stop automatically recharging if charge is used?
     /// </summary>
-    [DataField]
-    public TimeSpan AutoRechargePauseTime = TimeSpan.FromSeconds(0);
+    [DataField, AutoNetworkedField]
+    public TimeSpan AutoRechargePauseTime = TimeSpan.Zero;
 
     /// <summary>
     /// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system.
     /// </summary>
     [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-    [AutoPausedField]
-    public TimeSpan NextAutoRecharge = TimeSpan.FromSeconds(0);
+    [AutoNetworkedField, AutoPausedField, ViewVariables]
+    public TimeSpan? NextAutoRecharge = TimeSpan.FromSeconds(0);
 }
similarity index 75%
rename from Content.Shared/Power/Components/PredictedBatteryVisualsComponent.cs
rename to Content.Shared/Power/Components/BatteryVisualsComponent.cs
index f7ea9338ab4c394833c7754dbc704802882907df..120dd8591f47eeefac98354ff7f3340af196ea97 100644 (file)
@@ -5,11 +5,11 @@ using Robust.Shared.Serialization;
 namespace Content.Shared.Power.Components;
 
 /// <summary>
-/// Marker component that makes an entity with <see cref="PredictedBatteryComponent"/> update its appearance data for use with visualizers.
+/// Marker component that makes an entity with <see cref="BatteryComponent"/> update its appearance data for use with visualizers.
 /// Also works with an entity with <see cref="PowerCellSlotComponent"/> and will relay the state of the inserted powercell.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
-public sealed partial class PredictedBatteryVisualsComponent : Component;
+public sealed partial class BatteryVisualsComponent : Component;
 
 /// <summary>
 /// Keys for the appearance data.
@@ -37,15 +37,15 @@ public enum BatteryVisuals : byte
 public enum BatteryChargingState : byte
 {
     /// <summary>
-    /// PredictedBatteryComponent.ChargeRate &gt; 0
+    /// BatteryComponent.ChargeRate &gt; 0
     /// </summary>
     Charging,
     /// <summary>
-    /// PredictedBatteryComponent.ChargeRate &lt; 0
+    /// BatteryComponent.ChargeRate &lt; 0
     /// </summary>
     Decharging,
     /// <summary>
-    /// PredictedBatteryComponent.ChargeRate == 0
+    /// BatteryComponent.ChargeRate == 0
     /// </summary>
     Constant,
 }
index 59d0b8792bcb5f6066127d3bc011843c7dbd9204..b49680db0d32dc2f510f20e1d8bcf751479b7ded 100644 (file)
@@ -4,7 +4,7 @@ namespace Content.Shared.Power.Components;
 
 /// <summary>
 /// Allows the charge of a battery to be seen by examination.
-/// Works with either  <see cref="BatteryComponent"/> or <see cref="PredictedBatteryComponent"/>.
+/// Requires <see cref="BatteryComponent"/>.
 /// </summary>
 [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 (file)
index 0db5324..0000000
+++ /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;
-
-/// <summary>
-/// Predicted equivalent to <see cref="BatteryComponent"/>.
-/// 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.
-/// </summary>
-/// <remarks>
-/// We cannot simply network <see cref="BatteryComponent"/> 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.
-/// </remarks>
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
-[Access(typeof(PredictedBatterySystem))]
-public sealed partial class PredictedBatteryComponent : Component
-{
-    /// <summary>
-    /// Maximum charge of the battery in joules (ie. watt seconds)
-    /// </summary>
-    [DataField, AutoNetworkedField, ViewVariables]
-    [GuidebookData]
-    public float MaxCharge;
-
-    /// <summary>
-    /// The price per one joule. Default is 1 speso for 10kJ.
-    /// </summary>
-    [DataField]
-    public float PricePerJoule = 0.0001f;
-
-    /// <summary>
-    /// Time stamp of the last networked update.
-    /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-    [AutoNetworkedField, AutoPausedField, ViewVariables]
-    public TimeSpan LastUpdate = TimeSpan.Zero;
-
-    /// <summary>
-    /// The intial charge to be set on map init.
-    /// </summary>
-    [DataField]
-    public float StartingCharge;
-
-    /// <summary>
-    /// The charge at the last update in joules (i.e. watt seconds).
-    /// </summary>
-    [DataField, AutoNetworkedField, ViewVariables]
-    public float LastCharge;
-
-    /// <summary>
-    /// The current charge rate in watt.
-    /// </summary>
-    /// <remarks>
-    /// Not a datafield as this is only cached and recalculated on component startup.
-    /// </remarks>
-    [ViewVariables, AutoNetworkedField]
-    public float ChargeRate;
-
-    /// <summary>
-    /// The current charge state of the battery.
-    /// Used to track state changes for raising <see cref="PredictedBatteryStateChangedEvent"/>.
-    /// </summary>
-    /// <remarks>
-    /// Not a datafield as this is only cached and recalculated in an update loop.
-    /// </remarks>
-    [ViewVariables, AutoNetworkedField]
-    public BatteryState State = BatteryState.Neither;
-}
-
-/// <summary>
-/// Charge level status of the battery.
-/// </summary>
-[Serializable, NetSerializable]
-public enum BatteryState : byte
-{
-    /// <summary>
-    /// Full charge.
-    /// </summary>
-    Full,
-    /// <summary>
-    /// No charge.
-    /// </summary>
-    Empty,
-    /// <summary>
-    /// Neither full nor empty.
-    /// </summary>
-    Neither,
-}
-
diff --git a/Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs b/Content.Shared/Power/Components/PredictedBatterySelfRechargerComponent.cs
deleted file mode 100644 (file)
index 449a4e1..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Shared.Power.Components;
-
-/// <summary>
-/// Self-recharging battery.
-/// To be used in combination with <see cref="PredictedBatteryComponent"/>.
-/// For <see cref="BatteryComponent"/> use <see cref="BatterySelfRechargerComponent"/> instead.
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentState, AutoGenerateComponentPause]
-public sealed partial class PredictedBatterySelfRechargerComponent : Component
-{
-    /// <summary>
-    /// At what rate does the entity automatically recharge? In watts.
-    /// </summary>
-    [DataField, AutoNetworkedField, ViewVariables]
-    public float AutoRechargeRate;
-
-    /// <summary>
-    /// How long should the entity stop automatically recharging if a charge is used?
-    /// </summary>
-    [DataField, AutoNetworkedField]
-    public TimeSpan AutoRechargePauseTime = TimeSpan.Zero;
-
-    /// <summary>
-    /// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system.
-    /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-    [AutoNetworkedField, AutoPausedField, ViewVariables]
-    public TimeSpan? NextAutoRecharge = TimeSpan.FromSeconds(0);
-}
index b561eedcbbf0768744c1d78174e16feb044d13cc..00429067c40a658fe802cbbd29863f78f5d1e1d1 100644 (file)
@@ -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<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
         SubscribeLocalEvent<ChargerComponent, EmpDisabledRemovedEvent>(OnEmpRemoved);
         SubscribeLocalEvent<InsideChargerComponent, RefreshChargeRateEvent>(OnRefreshChargeRate);
-        SubscribeLocalEvent<InsideChargerComponent, PredictedBatteryStateChangedEvent>(OnStatusChanged);
+        SubscribeLocalEvent<InsideChargerComponent, BatteryStateChangedEvent>(OnStatusChanged);
     }
 
     private void OnStartup(Entity<ChargerComponent> ent, ref ComponentStartup args)
@@ -176,7 +176,7 @@ public sealed class ChargerSystem : EntitySystem
 
         args.NewChargeRate += chargerComp.ChargeRate;
     }
-    private void OnStatusChanged(Entity<InsideChargerComponent> ent, ref PredictedBatteryStateChangedEvent args)
+    private void OnStatusChanged(Entity<InsideChargerComponent> 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 (file)
index 760d727..0000000
+++ /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;
-
-/// <summary>
-/// Responsible for <see cref="PredictedBatteryComponent"/>.
-/// Predicted equivalent of <see cref="Content.Server.Power.EntitySystems.BatterySystem"/>.
-/// If you make changes to this make sure to keep the two consistent.
-/// </summary>
-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<PredictedBatteryComponent, ComponentInit>(OnInit);
-        SubscribeLocalEvent<PredictedBatteryComponent, ComponentStartup>(OnStartup);
-        SubscribeLocalEvent<PredictedBatteryComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<PredictedBatteryComponent, EmpPulseEvent>(OnEmpPulse);
-        SubscribeLocalEvent<PredictedBatteryComponent, RejuvenateEvent>(OnRejuvenate);
-        SubscribeLocalEvent<PredictedBatteryComponent, ExaminedEvent>(OnExamine);
-        SubscribeLocalEvent<PredictedBatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
-        SubscribeLocalEvent<PredictedBatteryComponent, ChangeChargeEvent>(OnChangeCharge);
-        SubscribeLocalEvent<PredictedBatteryComponent, GetChargeEvent>(OnGetCharge);
-        SubscribeLocalEvent<PredictedBatterySelfRechargerComponent, RefreshChargeRateEvent>(OnRefreshChargeRate);
-        SubscribeLocalEvent<PredictedBatterySelfRechargerComponent, ComponentStartup>(OnRechargerStartup);
-        SubscribeLocalEvent<PredictedBatterySelfRechargerComponent, ComponentRemove>(OnRechargerRemove);
-        SubscribeLocalEvent<PredictedBatteryVisualsComponent, PredictedBatteryChargeChangedEvent>(OnVisualsChargeChanged);
-        SubscribeLocalEvent<PredictedBatteryVisualsComponent, PredictedBatteryStateChangedEvent>(OnVisualsStateChanged);
-    }
-
-    private void OnInit(Entity<PredictedBatteryComponent> ent, ref ComponentInit args)
-    {
-        DebugTools.Assert(!HasComp<BatteryComponent>(ent), $"{ent} has both BatteryComponent and PredictedBatteryComponent");
-    }
-
-    private void OnStartup(Entity<PredictedBatteryComponent> 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<PredictedBatteryComponent> ent, ref MapInitEvent args)
-    {
-        SetCharge(ent.AsNullable(), ent.Comp.StartingCharge);
-        RefreshChargeRate(ent.AsNullable());
-    }
-
-    private void OnRejuvenate(Entity<PredictedBatteryComponent> ent, ref RejuvenateEvent args)
-    {
-        SetCharge(ent.AsNullable(), ent.Comp.MaxCharge);
-    }
-
-    private void OnEmpPulse(Entity<PredictedBatteryComponent> ent, ref EmpPulseEvent args)
-    {
-        args.Affected = true;
-        UseCharge(ent.AsNullable(), args.EnergyConsumption);
-    }
-
-    private void OnExamine(Entity<PredictedBatteryComponent> ent, ref ExaminedEvent args)
-    {
-        if (!args.IsInDetailsRange)
-            return;
-
-        if (!HasComp<ExaminableBatteryComponent>(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")
-            )
-        );
-    }
-
-    /// <summary>
-    /// Gets the price for the power contained in an entity's battery.
-    /// </summary>
-    private void CalculateBatteryPrice(Entity<PredictedBatteryComponent> ent, ref PriceCalculationEvent args)
-    {
-        args.Price += GetCharge(ent.AsNullable()) * ent.Comp.PricePerJoule;
-    }
-
-    private void OnChangeCharge(Entity<PredictedBatteryComponent> ent, ref ChangeChargeEvent args)
-    {
-        if (args.ResidualValue == 0)
-            return;
-
-        args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue);
-    }
-
-    private void OnGetCharge(Entity<PredictedBatteryComponent> ent, ref GetChargeEvent args)
-    {
-        args.CurrentCharge += GetCharge(ent.AsNullable());
-        args.MaxCharge += ent.Comp.MaxCharge;
-    }
-
-    private void OnRefreshChargeRate(Entity<PredictedBatterySelfRechargerComponent> 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<PredictedBatterySelfRechargerComponent, PredictedBatteryComponent>();
-        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<PredictedBatteryComponent>();
-        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<PredictedBatterySelfRechargerComponent> ent, ref ComponentStartup args)
-    {
-        // In case this component is added after the battery component.
-        RefreshChargeRate(ent.Owner);
-    }
-
-    private void OnRechargerRemove(Entity<PredictedBatterySelfRechargerComponent> 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<PredictedBatteryVisualsComponent> 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<PredictedBatteryVisualsComponent> 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);
-    }
-}
similarity index 79%
rename from Content.Shared/Power/EntitySystems/PredictedBatterySystem.API.cs
rename to Content.Shared/Power/EntitySystems/SharedBatterySystem.API.cs
index e1d685daeb8f402e39d691385bd47dc7414433d7..d51023f3eb19481f1e52c9be78fe859c625ca8fd 100644 (file)
@@ -4,11 +4,11 @@ using JetBrains.Annotations;
 namespace Content.Shared.Power.EntitySystems;
 
 /// <summary>
-/// Responsible for <see cref="PredictedBatteryComponent"/>.
+/// Responsible for <see cref="BatteryComponent"/>.
 /// Predicted equivalent of <see cref="Content.Server.Power.EntitySystems.BatterySystem"/>.
 /// If you make changes to this make sure to keep the two consistent.
 /// </summary>
-public sealed partial class PredictedBatterySystem
+public abstract partial class SharedBatterySystem
 {
     /// <summary>
     /// Changes the battery's charge by the given amount
@@ -17,7 +17,7 @@ public sealed partial class PredictedBatterySystem
     /// </summary>
     /// <returns>The actually changed amount.</returns>
     [PublicAPI]
-    public float ChangeCharge(Entity<PredictedBatteryComponent?> ent, float amount)
+    public float ChangeCharge(Entity<BatteryComponent?> 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
     /// </summary>
     /// <returns>The actually changed amount.</returns>
     [PublicAPI]
-    public float UseCharge(Entity<PredictedBatteryComponent?> ent, float amount)
+    public float UseCharge(Entity<BatteryComponent?> ent, float amount)
     {
         if (amount <= 0f)
             return 0f;
@@ -65,7 +65,7 @@ public sealed partial class PredictedBatterySystem
     /// </summary>
     /// <returns>If the full amount was able to be removed.</returns>
     [PublicAPI]
-    public bool TryUseCharge(Entity<PredictedBatteryComponent?> ent, float amount)
+    public bool TryUseCharge(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public void SetCharge(Entity<PredictedBatteryComponent?> ent, float value)
+    public void SetCharge(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public void SetMaxCharge(Entity<PredictedBatteryComponent?> ent, float value)
+    public void SetMaxCharge(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public void UpdateState(Entity<PredictedBatteryComponent?> ent)
+    public void UpdateState(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public float GetCharge(Entity<PredictedBatteryComponent?> ent)
+    public float GetCharge(Entity<BatteryComponent?> 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).
     /// </summary>
     [PublicAPI]
-    public float GetChargeLevel(Entity<PredictedBatteryComponent?> ent)
+    public float GetChargeLevel(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public int GetRemainingUses(Entity<PredictedBatteryComponent?> ent, float cost)
+    public int GetRemainingUses(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public int GetMaxUses(Entity<PredictedBatteryComponent?> ent, float cost)
+    public int GetMaxUses(Entity<BatteryComponent?> ent, float cost)
     {
         if (cost <= 0)
             return 0;
@@ -220,13 +220,13 @@ public sealed partial class PredictedBatterySystem
         return (int)(ent.Comp.MaxCharge / cost);
     }
 
-
     /// <summary>
     /// Refreshes the battery's current charge rate by raising a <see cref="RefreshChargeRateEvent"/>.
+    /// Subscribe to that event to add to or subtract from the total charge rate.
     /// </summary>
     /// <returns>The new charge rate.</returns>
     [PublicAPI]
-    public float RefreshChargeRate(Entity<PredictedBatteryComponent?> ent)
+    public float RefreshChargeRate(Entity<BatteryComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public void TrySetChargeCooldown(Entity<PredictedBatterySelfRechargerComponent?> ent)
+    public void TrySetChargeCooldown(Entity<BatterySelfRechargerComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public void SetChargeCooldown(Entity<PredictedBatterySelfRechargerComponent?> ent, TimeSpan cooldown)
+    public void SetChargeCooldown(Entity<BatterySelfRechargerComponent?> 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.
     /// </summary>
     [PublicAPI]
-    public bool IsFull(Entity<PredictedBatteryComponent?> ent)
+    public bool IsFull(Entity<BatteryComponent?> ent)
     {
         if (!Resolve(ent, ref ent.Comp))
             return false;
index 317dcb129ec5faa099c69fa07a18948bdb212bd5..e150fd60d8857b3249ec68ab386c89579e6baa70 100644 (file)
+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
+/// <summary>
+/// Responsible for <see cref="BatteryComponent"/>.
+/// </summary>
+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<BatteryComponent, ComponentStartup>(OnStartup);
+        SubscribeLocalEvent<BatteryComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<BatteryComponent, EmpPulseEvent>(OnEmpPulse);
+        SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnRejuvenate);
+        SubscribeLocalEvent<BatteryComponent, ExaminedEvent>(OnExamine);
+        SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
+        SubscribeLocalEvent<BatteryComponent, ChangeChargeEvent>(OnChangeCharge);
+        SubscribeLocalEvent<BatteryComponent, GetChargeEvent>(OnGetCharge);
+        SubscribeLocalEvent<BatterySelfRechargerComponent, RefreshChargeRateEvent>(OnRefreshChargeRate);
+        SubscribeLocalEvent<BatterySelfRechargerComponent, ComponentStartup>(OnRechargerStartup);
+        SubscribeLocalEvent<BatterySelfRechargerComponent, ComponentRemove>(OnRechargerRemove);
+        SubscribeLocalEvent<BatteryVisualsComponent, ChargeChangedEvent>(OnVisualsChargeChanged);
+        SubscribeLocalEvent<BatteryVisualsComponent, BatteryStateChangedEvent>(OnVisualsStateChanged);
+    }
+
+    protected virtual void OnStartup(Entity<BatteryComponent> 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<BatteryComponent> ent, ref MapInitEvent args)
+    {
+        SetCharge(ent.AsNullable(), ent.Comp.StartingCharge);
+        RefreshChargeRate(ent.AsNullable());
+    }
+
+    private void OnRejuvenate(Entity<BatteryComponent> ent, ref RejuvenateEvent args)
+    {
+        SetCharge(ent.AsNullable(), ent.Comp.MaxCharge);
     }
 
     private void OnEmpPulse(Entity<BatteryComponent> 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);
     }
 
-    /// <summary>
-    /// 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.
-    /// </summary>
-    /// <returns>The actually changed amount.</returns>
-    [PublicAPI]
-    public virtual float ChangeCharge(Entity<BatteryComponent?> ent, float amount)
+    private void OnExamine(Entity<BatteryComponent> ent, ref ExaminedEvent args)
     {
-        return 0f;
+        if (!args.IsInDetailsRange)
+            return;
+
+        if (!HasComp<ExaminableBatteryComponent>(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")
+            )
+        );
     }
 
     /// <summary>
-    /// 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.
     /// </summary>
-    /// <returns>The actually changed amount.</returns>
-    [PublicAPI]
-    public virtual float UseCharge(Entity<BatteryComponent?> ent, float amount)
+    private void CalculateBatteryPrice(Entity<BatteryComponent> ent, ref PriceCalculationEvent args)
     {
-        return 0f;
+        args.Price += GetCharge(ent.AsNullable()) * ent.Comp.PricePerJoule;
     }
 
-    /// <summary>
-    /// 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.
-    /// </summary>
-    /// <returns>If the full amount was able to be removed.</returns>
-    [PublicAPI]
-    public virtual bool TryUseCharge(Entity<BatteryComponent?> ent, float amount)
+    private void OnChangeCharge(Entity<BatteryComponent> ent, ref ChangeChargeEvent args)
     {
-        return false;
+        if (args.ResidualValue == 0)
+            return;
+
+        args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue);
     }
 
-    /// <summary>
-    /// Sets the battery's charge.
-    /// </summary>
-    [PublicAPI]
-    public virtual void SetCharge(Entity<BatteryComponent?> ent, float value) { }
+    private void OnGetCharge(Entity<BatteryComponent> ent, ref GetChargeEvent args)
+    {
+        args.CurrentCharge += GetCharge(ent.AsNullable());
+        args.MaxCharge += ent.Comp.MaxCharge;
+    }
 
-    /// <summary>
-    /// Sets the battery's maximum charge.
-    /// </summary>
-    [PublicAPI]
-    public virtual void SetMaxCharge(Entity<BatteryComponent?> ent, float value) { }
+    private void OnRefreshChargeRate(Entity<BatterySelfRechargerComponent> ent, ref RefreshChargeRateEvent args)
+    {
+        if (_timing.CurTime < ent.Comp.NextAutoRecharge)
+            return; // Still on cooldown
 
-    /// <summary>
-    /// Checks if the entity has a self recharge and puts it on cooldown if applicable.
-    /// Uses the cooldown time given in the component.
-    /// </summary>
-    [PublicAPI]
-    public virtual void TrySetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent) { }
+        args.NewChargeRate += ent.Comp.AutoRechargeRate;
+    }
 
-    /// <summary>
-    /// Puts the entity's self recharge on cooldown for the specified time.
-    /// </summary>
-    [PublicAPI]
-    public virtual void SetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent, TimeSpan cooldown) { }
+    public override void Update(float frameTime)
+    {
+        var curTime = _timing.CurTime;
+
+        // Update self-recharging cooldowns.
+        var rechargerQuery = EntityQueryEnumerator<BatterySelfRechargerComponent, BatteryComponent>();
+        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<BatteryComponent>();
+        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<BatterySelfRechargerComponent> 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<BatterySelfRechargerComponent> 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<BatteryVisualsComponent> 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<BatteryVisualsComponent> 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);
+    }
 }
index fdada109588e9202e42e639e76978a437f15ae43..8db75f63ba5116b4ade884c60c30b480b80419f6 100644 (file)
@@ -5,7 +5,7 @@ namespace Content.Shared.PowerCell.Components;
 
 /// <summary>
 /// This component enables power-cell related interactions (e.g. EntityWhitelists, cell sizes, examine, rigging).
-/// The actual power functionality is provided by the <see cref="PredictedBatteryComponent"/>.
+/// The actual power functionality is provided by the <see cref="BatteryComponent"/>.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
 public sealed partial class PowerCellComponent : Component;
index 15376bda8951880f60bcc5b3cfb0f7d669cdf363..36590c798333742cd6e1995ed4cb031ce8dd626b 100644 (file)
@@ -31,7 +31,7 @@ public sealed partial class PowerCellSystem
     [PublicAPI]
     public bool TryGetBatteryFromSlot(
         Entity<PowerCellSlotComponent?> ent,
-        [NotNullWhen(true)] out Entity<PredictedBatteryComponent>? battery)
+        [NotNullWhen(true)] out Entity<BatteryComponent>? battery)
     {
         if (!Resolve(ent, ref ent.Comp, false))
         {
@@ -45,7 +45,7 @@ public sealed partial class PowerCellSystem
             return false;
         }
 
-        if (!TryComp<PredictedBatteryComponent>(slot.Item, out var batteryComp))
+        if (!TryComp<BatteryComponent>(slot.Item, out var batteryComp))
         {
             battery = null;
             return false;
@@ -57,15 +57,15 @@ public sealed partial class PowerCellSystem
 
     /// <summary>
     /// 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 <see cref="PredictedBatteryComponent"/>.
+    /// If that fails check if the entity itself is a battery with <see cref="BatteryComponent"/>.
     /// </summary>
     [PublicAPI]
-    public bool TryGetBatteryFromSlotOrEntity(Entity<PowerCellSlotComponent?> ent, [NotNullWhen(true)] out Entity<PredictedBatteryComponent>? battery)
+    public bool TryGetBatteryFromSlotOrEntity(Entity<PowerCellSlotComponent?> ent, [NotNullWhen(true)] out Entity<BatteryComponent>? battery)
     {
         if (TryGetBatteryFromSlot(ent, out battery))
             return true;
 
-        if (TryComp<PredictedBatteryComponent>(ent, out var batteryComp))
+        if (TryComp<BatteryComponent>(ent, out var batteryComp))
         {
             battery = (ent.Owner, batteryComp);
             return true;
@@ -76,13 +76,13 @@ public sealed partial class PowerCellSystem
     }
 
     /// <summary>
-    /// First checks if the entity itself is a battery with <see cref="PredictedBatteryComponent"/>.
+    /// First checks if the entity itself is a battery with <see cref="BatteryComponent"/>.
     /// If that fails it will try to get a battery from the entity's power cell slot instead.
     /// </summary>
     [PublicAPI]
-    public bool TryGetBatteryFromEntityOrSlot(Entity<PowerCellSlotComponent?> ent, [NotNullWhen(true)] out Entity<PredictedBatteryComponent>? battery)
+    public bool TryGetBatteryFromEntityOrSlot(Entity<PowerCellSlotComponent?> ent, [NotNullWhen(true)] out Entity<BatteryComponent>? battery)
     {
-        if (TryComp<PredictedBatteryComponent>(ent, out var batteryComp))
+        if (TryComp<BatteryComponent>(ent, out var batteryComp))
         {
             battery = (ent.Owner, batteryComp);
             return true;
index ca4bc2a6619f9d285084361dbf65f2127ec38696..75e436becc5010ec124c9c8cdd841a43429e40dc 100644 (file)
@@ -16,8 +16,8 @@ public sealed partial class PowerCellSystem
         SubscribeLocalEvent<PowerCellSlotComponent, ChangeChargeEvent>(RelayToCell);
 
         SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(RelayToCellSlot); // Prevent the ninja from EMPing its own battery
-        SubscribeLocalEvent<PowerCellComponent, PredictedBatteryChargeChangedEvent>(RelayToCellSlot);
-        SubscribeLocalEvent<PowerCellComponent, PredictedBatteryStateChangedEvent>(RelayToCellSlot); // For shutting down devices if the battery is empty
+        SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(RelayToCellSlot);
+        SubscribeLocalEvent<PowerCellComponent, BatteryStateChangedEvent>(RelayToCellSlot); // For shutting down devices if the battery is empty
         SubscribeLocalEvent<PowerCellComponent, RefreshChargeRateEvent>(RelayToCellSlot); // Allow devices to charge/drain inserted batteries
     }
 
index 41d6c094ca16ef633bbbe723485686a1a5940f1b..8f4950c5d21f642f8bc2420ee2e75b3f0b9b3c60 100644 (file)
@@ -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<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellSlotInserted);
         SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellSlotRemoved);
         SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
-        SubscribeLocalEvent<PowerCellSlotComponent, PredictedBatteryStateChangedEvent>(OnCellSlotStateChanged);
+        SubscribeLocalEvent<PowerCellSlotComponent, BatteryStateChangedEvent>(OnCellSlotStateChanged);
 
         SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(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<PredictedBatteryVisualsComponent>(ent))
+        if (!HasComp<BatteryVisualsComponent>(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<PredictedBatteryVisualsComponent>(ent))
+        if (!HasComp<BatteryVisualsComponent>(ent))
             return;
 
         // Set the appearance to empty.
@@ -103,7 +103,7 @@ public sealed partial class PowerCellSystem : EntitySystem
     }
 
 
-    private void OnCellSlotStateChanged(Entity<PowerCellSlotComponent> ent, ref PredictedBatteryStateChangedEvent args)
+    private void OnCellSlotStateChanged(Entity<PowerCellSlotComponent> ent, ref BatteryStateChangedEvent args)
     {
         if (args.NewState != BatteryState.Empty)
             return;
@@ -123,11 +123,11 @@ public sealed partial class PowerCellSystem : EntitySystem
 
     private void OnCellExamined(Entity<PowerCellComponent> ent, ref ExaminedEvent args)
     {
-        if (TryComp<PredictedBatteryComponent>(ent, out var battery))
+        if (TryComp<BatteryComponent>(ent, out var battery))
             OnBatteryExamined((ent.Owner, battery), ref args);
     }
 
-    private void OnBatteryExamined(Entity<PredictedBatteryComponent> ent, ref ExaminedEvent args)
+    private void OnBatteryExamined(Entity<BatteryComponent> ent, ref ExaminedEvent args)
     {
         var chargePercent = _battery.GetChargeLevel(ent.AsNullable()) * 100;
         args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{chargePercent:F0}")));
index f4b866044cc0661d6f4cd8b10ded69dd84ce90d5..0c80ff802d5df282d0acd9d3fefaf21d40acbd1b 100644 (file)
@@ -16,7 +16,7 @@ public sealed partial class ActivatableUISystem
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ItemToggledEvent>(OnToggled);
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIOpenedEvent>(OnBatteryOpened);
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIClosedEvent>(OnBatteryClosed);
-        SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, PredictedBatteryStateChangedEvent>(OnBatteryStateChanged);
+        SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BatteryStateChangedEvent>(OnBatteryStateChanged);
         SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ActivatableUIOpenAttemptEvent>(OnBatteryOpenAttempt);
     }
 
@@ -57,7 +57,7 @@ public sealed partial class ActivatableUISystem
             _toggle.TryDeactivate(uid);
     }
 
-    private void OnBatteryStateChanged(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref PredictedBatteryStateChangedEvent args)
+    private void OnBatteryStateChanged(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref BatteryStateChangedEvent args)
     {
         // Deactivate when empty.
         if (args.NewState != BatteryState.Empty)
index 3378d82b44741c657d40e05b59bd40372c5e3984..63b680066f9f1db872598d139553ffcd2b46287c 100644 (file)
@@ -6,7 +6,7 @@ namespace Content.Shared.Weapons.Ranged.Components;
 
 /// <summary>
 /// Ammo provider that uses electric charge from a battery to provide ammunition to a weapon.
-/// This works with both <see cref="BatteryComponent"/> and <see cref="PredictedBatteryComponent"/>
+/// Works in combination with <see cref="BatteryComponent"/>.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
 [AutoGenerateComponentState(raiseAfterAutoHandleState: true), AutoGenerateComponentPause]
index 24cfe350518865a8dfeda60ff1f90d91f7be4d6b..f92cea07d3a3b31273d1e99c4d464ee33e3b53a5 100644 (file)
@@ -23,7 +23,6 @@ public abstract partial class SharedGunSystem
         SubscribeLocalEvent<BatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
         SubscribeLocalEvent<BatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
         SubscribeLocalEvent<BatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged);
-        SubscribeLocalEvent<BatteryAmmoProviderComponent, PredictedBatteryChargeChangedEvent>(OnPredictedChargeChanged);
         SubscribeLocalEvent<BatteryAmmoProviderComponent, ChargeChangedEvent>(OnChargeChanged);
     }
 
@@ -86,10 +85,10 @@ public abstract partial class SharedGunSystem
     /// </summary>
     public void TakeCharge(Entity<BatteryAmmoProviderComponent> 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<BatteryAmmoProviderComponent> ent, ref PredictedBatteryChargeChangedEvent args)
+    private void OnChargeChanged(Entity<BatteryAmmoProviderComponent> 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<BatteryAmmoProviderComponent> 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<BatteryAmmoProviderComponent> 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<BatteryAmmoProviderComponent> 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);
     }
 
     /// <summary>
     /// 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.
     /// </summary>
     public (int, int) GetShots(Entity<BatteryAmmoProviderComponent> ent)
     {
@@ -197,8 +190,7 @@ public abstract partial class SharedGunSystem
     }
 
     /// <summary>
-    /// 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.
     /// </summary>
     private void UpdateBattery(float frameTime)
     {
index 00f53f443cdd5dcfcb2b62cdd55a4787338c9d8b..137a494674a8fbcd9212b788ad7b1e2edca8d9f8 100644 (file)
     whitelist:
       components:
       - Battery
-      - PredictedBattery
 
 - type: cargoBounty
   id: BountyLaserGun
index 5dfc94a1f771c4ffc16e4af6a74fe392e5c6ae75..05dda23f0a2785c3ab3647c01908d8d155ac4817 100644 (file)
     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
index 2d320c868c415d2efc95bf166412f5cb43e34f68..3b85ce94c9ee2b0a3a626d4ca4486cb5a30d6856 100644 (file)
         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
index 1c23d18a97d46ed895fe53fff554c721065df57f..341404af2b2d78e39f5647b0afb325d57e8db1e5 100644 (file)
         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
index 6a8c8a5d0250f66c787cb23de6a24bef6c451385..557a36646fcc7166fc48ed932a8c8bf0fd9470cd 100644 (file)
@@ -18,7 +18,7 @@
   - type: BatteryAmmoProvider
     proto: DebugLaser
     fireCost: 1
-  - type: PredictedBatterySelfRecharger
+  - type: BatterySelfRecharger
     autoRechargeRate: 1000
 
 - type: entity
index 5c575da396b376e3c21d6464ac7f5a919af71c63..c0220cee1338e991e584ebfafc385c2e5640aaec 100644 (file)
     - 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
index 4a205c9cb026b1d937b3a9f83b15d1877f59ccee..1f3faf2155428dcedc032578ce968941ed3d7fc4 100644 (file)
@@ -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
index d080f8c45f5e6e1f728463d3737cfc026454c074..fc7058b68ebb1ac4c7a548b7176744457b8471d7 100644 (file)
   - 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
index ac4f476a3568cc322939af18a2ddd8a380344be6..d6727c7d88b33ff14a4897bd3cd03023c36ddbe4 100644 (file)
@@ -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
index 9547a0e38b4d3cf72bac0910aa6ffadd946a4e91..898cee85244b51e144a254a28a1670fae6361c8b 100644 (file)
     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
index a896e3a6e2be664538147f46df0aec0a444a7559..0c318dd20d112fac0e5f1ea75a1a711f9c43d8ce 100644 (file)
@@ -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
 
   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
 
     - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
       state: o2
       shader: unshaded
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 720
     startingCharge: 720
 
         state: o2
         shader: unshaded
         visible: false
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 720
     startingCharge: 0
 
     - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
       state: o2
       shader: unshaded
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1080
     startingCharge: 1080
 
         state: o2
         shader: unshaded
         visible: false
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1080
     startingCharge: 0
 
     - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
       state: o2
       shader: unshaded
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1800
     startingCharge: 1800
 
         state: o2
         shader: unshaded
         visible: false
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1800
     startingCharge: 0
 
     - 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
       state: o2
       shader: unshaded
       visible: false
-  - type: PredictedBattery
+  - type: Battery
     startingCharge: 0
 
 - type: entity
       - 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)
     - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
       state: o2
       shader: unshaded
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1400
     startingCharge: 1400
 
     - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
       state: o2
       shader: unshaded
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 2700
     startingCharge: 2700
 
     - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
       state: o2
       shader: unshaded
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 6200
     startingCharge: 6200
 
       state: o2
       shader: unshaded
       visible: false
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1400
     startingCharge: 0
 
       state: o2
       shader: unshaded
       visible: false
-  - type: PredictedBattery
+  - type: Battery
     startingCharge: 0
 
 - type: entity
       state: o2
       shader: unshaded
       visible: false
-  - type: PredictedBattery
+  - type: Battery
     startingCharge: 0
index 089610883cef0a7707219b1a6a8c1592be2cfe11..7e448e06f776b393bba790133ec2666a34b824b9 100644 (file)
           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
index 9fe4d377d7e27078b3c47158d90c693b5bca1778..59efee2e6990f1aca4e09048fdc8bdb1f98b75bf 100644 (file)
@@ -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
index 086901b145712929a9fdff7e0397fce840903709..e49c6b2c09c5790795efb1b41e27df8dc9ee6058 100644 (file)
@@ -19,7 +19,7 @@
     maxRange: 256
     followEntity: true
   - type: Appearance
-  - type: PredictedBatteryVisuals
+  - type: BatteryVisuals
   - type: GenericVisualizer
     visuals:
       enum.BatteryVisuals.State:
index 1ad4ef01c0d29615ea389465f2c507b16b702841..9e4a1a644db848df43c0983da9737b5b949f3fe5 100644 (file)
@@ -20,7 +20,7 @@
     - SemiAuto
     soundGunshot:
       path: /Audio/Weapons/Guns/Gunshots/laser.ogg
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1000
     startingCharge: 1000
   - type: StaticPrice
   - type: BatteryAmmoProvider
     proto: RedLaser
     fireCost: 62.5
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 500
     startingCharge: 500
   - type: Tag
   - type: BatteryAmmoProvider
     proto: Pulse
     fireCost: 200
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 2000
     startingCharge: 2000
   - type: Tag
   - type: BatteryAmmoProvider
     proto: Pulse
     fireCost: 200
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 5000
     startingCharge: 5000
 
   - type: BatteryAmmoProvider
     proto: Pulse
     fireCost: 100
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 40000
     startingCharge: 40000
 
     - type: BatteryAmmoProvider
       proto: AntiParticlesProjectile
       fireCost: 500
-    - type: PredictedBattery
+    - type: Battery
       maxCharge: 10000
       startingCharge: 10000
 
   - type: BatteryAmmoProvider
     proto: RedMediumLaser
     fireCost: 100
-  - type: PredictedBatterySelfRecharger
+  - type: BatterySelfRecharger
     autoRechargeRate: 40
   - type: MagazineVisuals
     magState: mag
   - type: BatteryAmmoProvider
     proto: RedMediumLaser
     fireCost: 100
-  - type: PredictedBatterySelfRecharger
+  - type: BatterySelfRecharger
     autoRechargeRate: 30
   - type: MagazineVisuals
     magState: mag
   - type: BatteryAmmoProvider
     proto: RedMediumLaser
     fireCost: 100
-  - type: PredictedBatterySelfRecharger
+  - type: BatterySelfRecharger
     autoRechargeRate: 40
   - type: StaticPrice
     price: 750
     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
 
     - proto: BulletDisabler
       fireCost: 62.5
       pacifismAllowedMode: true
-  - type: PredictedBatterySelfRecharger
+  - type: BatterySelfRecharger
     autoRechargeRate: 48
     autoRechargePauseTime: 10
 
       fireCost: 100
     - proto: BoltTempgunHot
       fireCost: 100
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 1000
     startingCharge: 1000
   - type: StaticPrice
index dc4be40c8248fc1b3cd6b1b5b5f5f52ae83b1274..ca56ae0c6cce2d29e0b3c78f79ec6a73444f1c6f 100644 (file)
     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
index 2a197e2685a304140b73f5a1bbf765845aafbab9..7b2d96854a65888c78b4cde64474192454cdc136 100644 (file)
   - type: BatteryAmmoProvider
     proto: FoodPieBananaCream
     fireCost: 30
-  - type: PredictedBattery
+  - type: Battery
     maxCharge: 90
     startingCharge: 90
-  - type: PredictedBatterySelfRecharger
+  - type: BatterySelfRecharger
     autoRechargeRate: 1
   - type: AmmoCounter
   - type: Item
index 27857e24a12e717d0e4d0b27dee58c90d6068c6e..295a7821907b4d8e23e0847a0c88782970fd6975 100644 (file)
@@ -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
index 217286a5c527f7911551c7d4503b1c66a80a1be6..2f6ac834ba46f3506d2eb02f5be337cc8559a4b9 100644 (file)
@@ -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
index e769d81fbbb62fab5ffd1ca7618b6985cc2a110e..e930e105e157abd4fc9193eb42d2a2adc42185dd 100644 (file)
   - 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
index 1def040b09887527e08fee47f69d6d310d352c6f..e7f6082734ed9fec55c1466faa1b13f734e7ef5c 100644 (file)
     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
index ec8b86e173ce476fc799990bc7ab6de50659a658..4efc5d0fe652e82d5b880fd503ed226c4bac72c2 100644 (file)
   - 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
index 5706b802a5f4230a0b96666233ece1e058d9b4ba..e4268a25c561ee7afc3c9cc7679cc30e367b42a6 100644 (file)
     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:
     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:
index 3572fe6a395fc2718458d005ed365f67d9077616..c29f7a972c3fcf61bca9f18701626cd00930a0d2 100644 (file)
     - 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
index f2d86a3900d98d1998d900ff8b903dd20a4eb130..a2b5a0a0066ebf3500a5bc565d02559b92892249 100644 (file)
@@ -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