Unify BatteryComponent and PredictedBatteryComponent (#41867)

* unify

* cleanup and merge conflicts

* floating points

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
slarticodefast 2025-12-19 19:18:12 +01:00 committed by BarryNorfolk
parent d039f6fdfd
commit 17ff77b61f
73 changed files with 551 additions and 963 deletions

View File

@ -23,7 +23,7 @@ public sealed partial class BorgMenu : FancyWindow
[Dependency] private readonly IEntityManager _entity = default!; [Dependency] private readonly IEntityManager _entity = default!;
private readonly NameModifierSystem _nameModifier; private readonly NameModifierSystem _nameModifier;
private readonly PowerCellSystem _powerCell; private readonly PowerCellSystem _powerCell;
private readonly PredictedBatterySystem _battery; private readonly SharedBatterySystem _battery;
public Action? BrainButtonPressed; public Action? BrainButtonPressed;
public Action? IdChipButtonPressed; // DeltaV public Action? IdChipButtonPressed; // DeltaV
@ -47,7 +47,7 @@ public sealed partial class BorgMenu : FancyWindow
_nameModifier = _entity.System<NameModifierSystem>(); _nameModifier = _entity.System<NameModifierSystem>();
_powerCell = _entity.System<PowerCellSystem>(); _powerCell = _entity.System<PowerCellSystem>();
_battery = _entity.System<PredictedBatterySystem>(); _battery = _entity.System<SharedBatterySystem>();
_maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength); _maxNameLength = _cfgManager.GetCVar(CCVars.MaxNameLength);

View File

@ -18,7 +18,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
[Dependency] private readonly SpriteSystem _sprite = default!; [Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!;
[Dependency] private readonly PowerCellSystem _powerCell = 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 AlertsSystem _alerts = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IPlayerManager _player = default!;

View File

@ -54,6 +54,7 @@ namespace Content.IntegrationTests.Tests.Power
nodeGroupID: HVPower nodeGroupID: HVPower
- type: PowerNetworkBattery - type: PowerNetworkBattery
- type: Battery - type: Battery
netsync: false
- type: BatteryCharger - type: BatteryCharger
- type: entity - type: entity
@ -68,6 +69,7 @@ namespace Content.IntegrationTests.Tests.Power
nodeGroupID: HVPower nodeGroupID: HVPower
- type: PowerNetworkBattery - type: PowerNetworkBattery
- type: Battery - type: Battery
netsync: false
- type: BatteryDischarger - type: BatteryDischarger
- type: entity - type: entity
@ -85,6 +87,7 @@ namespace Content.IntegrationTests.Tests.Power
nodeGroupID: HVPower nodeGroupID: HVPower
- type: PowerNetworkBattery - type: PowerNetworkBattery
- type: Battery - type: Battery
netsync: false
- type: BatteryDischarger - type: BatteryDischarger
node: output node: output
- type: BatteryCharger - type: BatteryCharger
@ -110,6 +113,7 @@ namespace Content.IntegrationTests.Tests.Power
maxSupply: 1000 maxSupply: 1000
supplyRampTolerance: 1000 supplyRampTolerance: 1000
- type: Battery - type: Battery
netsync: false
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: Transform - type: Transform
@ -119,6 +123,7 @@ namespace Content.IntegrationTests.Tests.Power
id: ApcDummy id: ApcDummy
components: components:
- type: Battery - type: Battery
netsync: false
maxCharge: 10000 maxCharge: 10000
startingCharge: 10000 startingCharge: 10000
- type: PowerNetworkBattery - type: PowerNetworkBattery
@ -380,6 +385,8 @@ namespace Content.IntegrationTests.Tests.Power
const float startingCharge = 100_000; const float startingCharge = 100_000;
PowerNetworkBatteryComponent netBattery = default!; PowerNetworkBatteryComponent netBattery = default!;
EntityUid generatorEnt = default!;
EntityUid consumerEnt = default!;
BatteryComponent battery = default!; BatteryComponent battery = default!;
PowerConsumerComponent consumer = default!; PowerConsumerComponent consumer = default!;
@ -395,8 +402,8 @@ namespace Content.IntegrationTests.Tests.Power
entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
} }
var generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates()); generatorEnt = entityManager.SpawnEntity("DischargingBatteryDummy", grid.Owner.ToCoordinates());
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2)); consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 2));
netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(generatorEnt); netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(generatorEnt);
battery = entityManager.GetComponent<BatteryComponent>(generatorEnt); battery = entityManager.GetComponent<BatteryComponent>(generatorEnt);
@ -441,7 +448,8 @@ namespace Content.IntegrationTests.Tests.Power
// Trivial integral to calculate expected power spent. // Trivial integral to calculate expected power spent.
const double spentExpected = (200 + 100) / 2.0 * 0.25; 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. // Trivial integral to calculate expected power spent.
const double spentExpected = (400 + 100) / 2.0 * 0.75 + 400 * 0.25; 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 entityManager = server.ResolveDependency<IEntityManager>();
var batterySys = entityManager.System<BatterySystem>(); var batterySys = entityManager.System<BatterySystem>();
var mapSys = entityManager.System<SharedMapSystem>(); var mapSys = entityManager.System<SharedMapSystem>();
EntityUid generatorEnt = default!;
EntityUid batteryEnt = default!;
PowerSupplierComponent supplier = default!; PowerSupplierComponent supplier = default!;
BatteryComponent battery = default!; BatteryComponent battery = default!;
@ -591,8 +602,8 @@ namespace Content.IntegrationTests.Tests.Power
entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i)); entityManager.SpawnEntity("CableHV", grid.Owner.ToCoordinates(0, i));
} }
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates()); generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates());
var batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.Owner.ToCoordinates(0, 2)); batteryEnt = entityManager.SpawnEntity("ChargingBatteryDummy", grid.Owner.ToCoordinates(0, 2));
supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt); supplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
var netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt); var netBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(batteryEnt);
@ -615,7 +626,8 @@ namespace Content.IntegrationTests.Tests.Power
{ {
// half a second @ 500 W = 250 // half a second @ 500 W = 250
// 50% efficiency, so 125 J stored total. // 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)); 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 gameTiming = server.ResolveDependency<IGameTiming>();
var batterySys = entityManager.System<BatterySystem>(); var batterySys = entityManager.System<BatterySystem>();
var mapSys = entityManager.System<SharedMapSystem>(); var mapSys = entityManager.System<SharedMapSystem>();
EntityUid batteryEnt = default!;
EntityUid supplyEnt = default!;
EntityUid consumerEnt = default!;
PowerConsumerComponent consumer = default!; PowerConsumerComponent consumer = default!;
PowerSupplierComponent supplier = default!; PowerSupplierComponent supplier = default!;
PowerNetworkBatteryComponent netBattery = default!; PowerNetworkBatteryComponent netBattery = default!;
@ -653,9 +668,9 @@ namespace Content.IntegrationTests.Tests.Power
var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180); entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt); consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt); supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
@ -694,7 +709,8 @@ namespace Content.IntegrationTests.Tests.Power
Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(200).Within(0.1)); Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(200).Within(0.1));
const int expectedSpent = 200; 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 gameTiming = server.ResolveDependency<IGameTiming>();
var batterySys = entityManager.System<BatterySystem>(); var batterySys = entityManager.System<BatterySystem>();
var mapSys = entityManager.System<SharedMapSystem>(); var mapSys = entityManager.System<SharedMapSystem>();
EntityUid batteryEnt = default!;
EntityUid supplyEnt = default!;
EntityUid consumerEnt = default!;
PowerConsumerComponent consumer = default!; PowerConsumerComponent consumer = default!;
PowerSupplierComponent supplier = default!; PowerSupplierComponent supplier = default!;
PowerNetworkBatteryComponent netBattery = default!; PowerNetworkBatteryComponent netBattery = default!;
@ -731,9 +750,9 @@ namespace Content.IntegrationTests.Tests.Power
var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1)); var terminal = entityManager.SpawnEntity("CableTerminal", grid.Owner.ToCoordinates(0, 1));
entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180); entityManager.GetComponent<TransformComponent>(terminal).LocalRotation = Angle.FromDegrees(180);
var batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2)); batteryEnt = entityManager.SpawnEntity("FullBatteryDummy", grid.Owner.ToCoordinates(0, 2));
var supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); supplyEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
var consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3)); consumerEnt = entityManager.SpawnEntity("ConsumerDummy", grid.Owner.ToCoordinates(0, 3));
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt); consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt); supplier = entityManager.GetComponent<PowerSupplierComponent>(supplyEnt);
@ -772,7 +791,8 @@ namespace Content.IntegrationTests.Tests.Power
Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(400).Within(0.1)); Assert.That(netBattery.SupplyRampPosition, Is.EqualTo(400).Within(0.1));
const int expectedSpent = 400; 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 entityManager = server.ResolveDependency<IEntityManager>();
var batterySys = entityManager.System<BatterySystem>(); var batterySys = entityManager.System<BatterySystem>();
var mapSys = entityManager.System<SharedMapSystem>(); var mapSys = entityManager.System<SharedMapSystem>();
EntityUid generatorEnt = default!;
EntityUid substationEnt = default!;
EntityUid apcEnt = default!;
PowerNetworkBatteryComponent substationNetBattery = default!; PowerNetworkBatteryComponent substationNetBattery = default!;
BatteryComponent apcBattery = 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, 1));
entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 2)); entityManager.SpawnEntity("CableMV", grid.Owner.ToCoordinates(0, 2));
var generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0)); generatorEnt = entityManager.SpawnEntity("GeneratorDummy", grid.Owner.ToCoordinates(0, 0));
var substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.Owner.ToCoordinates(0, 1)); substationEnt = entityManager.SpawnEntity("SubstationDummy", grid.Owner.ToCoordinates(0, 1));
var apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 2)); apcEnt = entityManager.SpawnEntity("ApcDummy", grid.Owner.ToCoordinates(0, 2));
var generatorSupplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt); var generatorSupplier = entityManager.GetComponent<PowerSupplierComponent>(generatorEnt);
substationNetBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(substationEnt); substationNetBattery = entityManager.GetComponent<PowerNetworkBatteryComponent>(substationEnt);
@ -1262,8 +1285,9 @@ namespace Content.IntegrationTests.Tests.Power
{ {
Assert.Multiple(() => Assert.Multiple(() =>
{ {
var currentCharge = batterySys.GetCharge((apcEnt, apcBattery));
Assert.That(substationNetBattery.CurrentSupply, Is.GreaterThan(0)); //substation should be providing power 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
}); });
}); });

View File

@ -3,6 +3,7 @@ using System.Linq;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.Maps; using Content.Server.Maps;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Power.NodeGroups; using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r; using Content.Server.Power.Pow3r;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
@ -47,6 +48,7 @@ public sealed class StationPowerTests
var entMan = server.EntMan; var entMan = server.EntMan;
var protoMan = server.ProtoMan; var protoMan = server.ProtoMan;
var ticker = entMan.System<GameTicker>(); var ticker = entMan.System<GameTicker>();
var batterySys = entMan.System<BatterySystem>();
// Load the map // Load the map
await server.WaitAssertion(() => await server.WaitAssertion(() =>
@ -70,7 +72,8 @@ public sealed class StationPowerTests
if (node.NodeGroup is not IBasePowerNet group) if (node.NodeGroup is not IBasePowerNet group)
continue; continue;
networks.TryGetValue(group.NetworkNode, out var charge); 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; var totalStartingCharge = networks.MaxBy(n => n.Value).Value;

View File

@ -6,7 +6,6 @@ using Content.Server.Cargo.Components;
using Content.Server.Doors.Systems; using Content.Server.Doors.Systems;
using Content.Server.Hands.Systems; using Content.Server.Hands.Systems;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Revenant.Components; // Imp using Content.Server.Revenant.Components; // Imp
using Content.Server.Revenant.EntitySystems; // Imp using Content.Server.Revenant.EntitySystems; // Imp
using Content.Server.Stack; using Content.Server.Stack;
@ -55,8 +54,7 @@ public sealed partial class AdminVerbSystem
[Dependency] private readonly AdminTestArenaSystem _adminTestArenaSystem = default!; [Dependency] private readonly AdminTestArenaSystem _adminTestArenaSystem = default!;
[Dependency] private readonly StationJobsSystem _stationJobsSystem = default!; [Dependency] private readonly StationJobsSystem _stationJobsSystem = default!;
[Dependency] private readonly JointSystem _jointSystem = default!; [Dependency] private readonly JointSystem _jointSystem = default!;
[Dependency] private readonly BatterySystem _batterySystem = default!; [Dependency] private readonly SharedBatterySystem _batterySystem = default!;
[Dependency] private readonly PredictedBatterySystem _predictedBatterySystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly GunSystem _gun = default!; [Dependency] private readonly GunSystem _gun = default!;
[Dependency] private readonly RevenantAnimatedSystem _revenantAnimate = default!; // Imp [Dependency] private readonly RevenantAnimatedSystem _revenantAnimate = default!; // Imp
@ -166,57 +164,6 @@ public sealed partial class AdminVerbSystem
args.Verbs.Add(makeVulnerable); 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)) if (TryComp<BatteryComponent>(args.Target, out var battery))
{ {
Verb refillBattery = new() Verb refillBattery = new()
@ -259,6 +206,8 @@ public sealed partial class AdminVerbSystem
var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target); var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target);
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill. recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
recharger.AutoRechargePauseTime = TimeSpan.Zero; // No delay. recharger.AutoRechargePauseTime = TimeSpan.Zero; // No delay.
Dirty(args.Target, recharger);
_batterySystem.RefreshChargeRate((args.Target, battery));
}, },
Impact = LogImpact.Medium, Impact = LogImpact.Medium,
Message = Loc.GetString("admin-trick-infinite-battery-object-description"), Message = Loc.GetString("admin-trick-infinite-battery-object-description"),

View File

@ -48,7 +48,7 @@ public sealed partial class BuildMech : IGraphAction
var cell = container.ContainedEntities[0]; 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."); Logger.Warning($"Mech construct entity {uid} had an invalid entity in container \"{Container}\"! Aborting build mech action.");
return; return;

View File

@ -153,7 +153,7 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
} }
else 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 (_battery.IsFull((entity.Owner, battery)))
{ {
if (TryComp<ApcPowerReceiverComponent>(entity.Owner, out var receiver)) if (TryComp<ApcPowerReceiverComponent>(entity.Owner, out var receiver))

View File

@ -24,7 +24,7 @@ namespace Content.Server.Light.EntitySystems
[Dependency] private readonly ActionContainerSystem _actionContainer = default!; [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PowerCellSystem _powerCell = 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 SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPointLightSystem _lights = 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. // 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) public override void Update(float frameTime)
{ {
var toRemove = new RemQueue<Entity<HandheldLightComponent>>(); var toRemove = new RemQueue<Entity<HandheldLightComponent>>();

View File

@ -33,7 +33,7 @@ public sealed partial class MechSystem : SharedMechSystem
{ {
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly AtmosphereSystem _atmosphere = 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 ContainerSystem _container = default!;
[Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = 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) if (TryComp<WiresPanelComponent>(uid, out var panel) && !panel.Open)
return; 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); InsertBattery(uid, args.Used, component, battery);
_actionBlocker.UpdateCanMove(uid); _actionBlocker.UpdateCanMove(uid);
@ -109,7 +109,7 @@ public sealed partial class MechSystem : SharedMechSystem
private void OnInsertBattery(EntityUid uid, MechComponent component, EntInsertedIntoContainerMessage args) 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; return;
component.Energy = _battery.GetCharge((args.Entity, battery)); component.Energy = _battery.GetCharge((args.Entity, battery));
@ -337,7 +337,7 @@ public sealed partial class MechSystem : SharedMechSystem
if (battery == null) if (battery == null)
return false; return false;
if (!TryComp<PredictedBatteryComponent>(battery, out var batteryComp)) if (!TryComp<BatteryComponent>(battery, out var batteryComp))
return false; return false;
_battery.SetCharge((battery.Value, batteryComp), _battery.GetCharge((battery.Value, batteryComp)) + delta.Float()); _battery.SetCharge((battery.Value, batteryComp), _battery.GetCharge((battery.Value, batteryComp)) + delta.Float());
@ -353,7 +353,7 @@ public sealed partial class MechSystem : SharedMechSystem
return true; 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)) if (!Resolve(uid, ref component, false))
return; return;

View File

@ -1,6 +1,5 @@
using Content.Server.Ninja.Events; using Content.Server.Ninja.Events;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Components;
@ -17,8 +16,7 @@ namespace Content.Server.Ninja.Systems;
/// </summary> /// </summary>
public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
{ {
[Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly SharedBatterySystem _battery = default!;
[Dependency] private readonly PredictedBatterySystem _predictedBattery = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popup = 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) protected override bool TryDrainPower(Entity<BatteryDrainerComponent> ent, EntityUid target)
{ {
var (uid, comp) = ent; 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; return false;
if (!TryComp<BatteryComponent>(target, out var targetBattery) || !TryComp<PowerNetworkBatteryComponent>(target, out var pnb)) if (!TryComp<BatteryComponent>(target, out var targetBattery) || !TryComp<PowerNetworkBatteryComponent>(target, out var pnb))
return false; 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); _popup.PopupEntity(Loc.GetString("battery-drainer-empty", ("battery", target)), uid, uid, PopupType.Medium);
return false; return false;
} }
var available = targetBattery.CurrentCharge; var required = battery.MaxCharge - _battery.GetCharge((comp.BatteryUid.Value, battery));
var required = battery.MaxCharge - _predictedBattery.GetCharge((comp.BatteryUid.Value, battery));
// higher tier storages can charge more // higher tier storages can charge more
var maxDrained = pnb.MaxSupply * comp.DrainTime; var maxDrained = pnb.MaxSupply * comp.DrainTime;
var input = Math.Min(Math.Min(available, required / comp.DrainEfficiency), maxDrained); var input = Math.Min(Math.Min(available, required / comp.DrainEfficiency), maxDrained);
@ -101,15 +99,13 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
return false; return false;
var output = input * comp.DrainEfficiency; var output = input * comp.DrainEfficiency;
// PowerCells use PredictedBatteryComponent _battery.ChangeCharge((comp.BatteryUid.Value, battery), output);
// SMES, substations and APCs use BatteryComponent
_predictedBattery.ChangeCharge((comp.BatteryUid.Value, battery), output);
// TODO: create effect message or something // TODO: create effect message or something
Spawn("EffectSparks", Transform(target).Coordinates); Spawn("EffectSparks", Transform(target).Coordinates);
_audio.PlayPvs(comp.SparkSound, target); _audio.PlayPvs(comp.SparkSound, target);
_popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid); _popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);
// repeat the doafter until battery is full // repeat the doafter until battery is full
return !_predictedBattery.IsFull((comp.BatteryUid.Value, battery)); return !_battery.IsFull((comp.BatteryUid.Value, battery));
} }
} }

View File

@ -9,7 +9,7 @@ namespace Content.Server.Ninja.Systems;
public sealed class ItemCreatorSystem : SharedItemCreatorSystem 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 SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedPopupSystem _popup = default!;

View File

@ -62,7 +62,7 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery)) if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery))
return; return;
if (!TryComp<PredictedBatteryComponent>(args.EntityUid, out var inserting)) if (!TryComp<BatteryComponent>(args.EntityUid, out var inserting))
{ {
args.Cancel(); args.Cancel();
return; 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. // 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, // 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. // 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 + selfcomp.AutoRechargeRate * AutoRechargeValue;
return battcomp.MaxCharge; return battcomp.MaxCharge;
} }

View File

@ -24,7 +24,7 @@ namespace Content.Server.Ninja.Systems;
public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
{ {
[Dependency] private readonly AlertsSystem _alerts = default!; [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 CodeConditionSystem _codeCondition = default!;
[Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!;
[Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedMindSystem _mind = default!;
@ -91,7 +91,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
/// <summary> /// <summary>
/// Get the battery component in a ninja's suit, if it's worn. /// Get the battery component in a ninja's suit, if it's worn.
/// </summary> /// </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) if (TryComp<SpaceNinjaComponent>(user, out var ninja)
&& ninja.Suit != null && ninja.Suit != null

View File

@ -17,7 +17,7 @@ namespace Content.Server.Ninja.Systems;
/// </summary> /// </summary>
public sealed class StunProviderSystem : SharedStunProviderSystem 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 DamageableSystem _damageable = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;

View File

@ -3,6 +3,7 @@ using Content.Server.Power.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
namespace Content.Server.Power.EntitySystems; namespace Content.Server.Power.EntitySystems;
@ -24,6 +25,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem
{ {
[Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = null!; [Dependency] private readonly UserInterfaceSystem _uiSystem = null!;
[Dependency] private readonly SharedBatterySystem _battery = null!;
public override void Initialize() public override void Initialize()
{ {
@ -48,7 +50,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem
var netBattery = Comp<PowerNetworkBatteryComponent>(ent); var netBattery = Comp<PowerNetworkBatteryComponent>(ent);
netBattery.CanCharge = args.On; 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) private void HandleSetOutputBreaker(Entity<BatteryInterfaceComponent> ent, ref BatterySetOutputBreakerMessage args)
@ -56,7 +58,7 @@ public sealed class BatteryInterfaceSystem : EntitySystem
var netBattery = Comp<PowerNetworkBatteryComponent>(ent); var netBattery = Comp<PowerNetworkBatteryComponent>(ent);
netBattery.CanDischarge = args.On; 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) private void HandleSetChargeRate(Entity<BatteryInterfaceComponent> ent, ref BatterySetChargeRateMessage args)
@ -90,13 +92,14 @@ public sealed class BatteryInterfaceSystem : EntitySystem
if (!_uiSystem.IsUiOpen(uid, BatteryUiKey.Key)) if (!_uiSystem.IsUiOpen(uid, BatteryUiKey.Key))
return; return;
var currentCharge = _battery.GetCharge((uid, battery));
_uiSystem.SetUiState( _uiSystem.SetUiState(
uid, uid,
BatteryUiKey.Key, BatteryUiKey.Key,
new BatteryBuiState new BatteryBuiState
{ {
Capacity = battery.MaxCharge, Capacity = battery.MaxCharge,
Charge = battery.CurrentCharge, Charge = currentCharge,
CanCharge = netBattery.CanCharge, CanCharge = netBattery.CanCharge,
CanDischarge = netBattery.CanDischarge, CanDischarge = netBattery.CanDischarge,
CurrentReceiving = netBattery.CurrentReceiving, CurrentReceiving = netBattery.CurrentReceiving,

View File

@ -1,158 +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;
}
}

View File

@ -1,85 +1,58 @@
using Content.Server.Power.Components; 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.Components;
using Content.Shared.Power.EntitySystems; using Content.Shared.Power.EntitySystems;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using JetBrains.Annotations;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.Timing;
namespace Content.Server.Power.EntitySystems; namespace Content.Server.Power.EntitySystems;
/// <summary> public sealed class BatterySystem : SharedBatterySystem
/// 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
{ {
[Dependency] private readonly IGameTiming _timing = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<BatteryComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<BatteryComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnBatteryRejuvenate);
SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate); SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate);
SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
SubscribeLocalEvent<BatteryComponent, ChangeChargeEvent>(OnChangeCharge);
SubscribeLocalEvent<BatteryComponent, GetChargeEvent>(OnGetCharge);
SubscribeLocalEvent<NetworkBatteryPreSync>(PreSync); SubscribeLocalEvent<NetworkBatteryPreSync>(PreSync);
SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync); SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync);
} }
private void OnInit(Entity<BatteryComponent> ent, ref ComponentInit args) protected override void OnStartup(Entity<BatteryComponent> ent, ref ComponentStartup args)
{ {
DebugTools.Assert(!HasComp<PredictedBatteryComponent>(ent), $"{ent} has both BatteryComponent and PredictedBatteryComponent"); // 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;
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!");
} }
private void OnNetBatteryRejuvenate(Entity<PowerNetworkBatteryComponent> ent, ref RejuvenateEvent args) private void OnNetBatteryRejuvenate(Entity<PowerNetworkBatteryComponent> ent, ref RejuvenateEvent args)
{ {
ent.Comp.NetworkBattery.CurrentStorage = ent.Comp.NetworkBattery.Capacity; 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)
{
if (!args.IsInDetailsRange)
return;
if (!HasComp<ExaminableBatteryComponent>(ent))
return;
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 PreSync(NetworkBatteryPreSync ev) private void PreSync(NetworkBatteryPreSync ev)
{ {
// Ignoring entity pausing. If the entity was paused, neither component's data should have been changed. // Ignoring entity pausing. If the entity was paused, neither component's data should have been changed.
var enumerator = AllEntityQuery<PowerNetworkBatteryComponent, BatteryComponent>(); 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.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); 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);
}
}
} }

View File

@ -8,6 +8,7 @@ using Content.Shared.Pinpointer;
using Content.Shared.Station.Components; using Content.Shared.Station.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
@ -22,6 +23,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
{ {
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedMapSystem _sharedMapSystem = default!; [Dependency] private readonly SharedMapSystem _sharedMapSystem = default!;
[Dependency] private readonly SharedBatterySystem _battery = default!;
// Note: this data does not need to be saved // Note: this data does not need to be saved
private Dictionary<EntityUid, Dictionary<Vector2i, PowerCableChunk>> _gridPowerCableChunks = new(); private Dictionary<EntityUid, Dictionary<Vector2i, PowerCableChunk>> _gridPowerCableChunks = new();
@ -510,7 +512,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
if (effectiveMax == 0) if (effectiveMax == 0)
effectiveMax = 1; effectiveMax = 1;
return battery.CurrentCharge / effectiveMax; return _battery.GetCharge((uid, battery)) / effectiveMax;
} }
private void GetSourcesForNode(EntityUid uid, Node node, out List<PowerMonitoringConsoleEntry> sources) private void GetSourcesForNode(EntityUid uid, Node node, out List<PowerMonitoringConsoleEntry> sources)

View File

@ -358,17 +358,18 @@ namespace Content.Server.Power.EntitySystems
if (requireBattery) if (requireBattery)
{ {
_battery.SetCharge((uid, battery), battery.CurrentCharge - apcBattery.IdleLoad * frameTime); _battery.ChangeCharge((uid, battery), -apcBattery.IdleLoad * frameTime);
} }
// Otherwise try to charge the battery // Otherwise try to charge the battery
else if (powered && !_battery.IsFull((uid, battery))) else if (powered && !_battery.IsFull((uid, battery)))
{ {
apcReceiver.Load += apcBattery.BatteryRechargeRate * apcBattery.BatteryRechargeEfficiency; 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 // 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) if (apcBattery.Enabled != enableBattery)
{ {

View File

@ -18,7 +18,7 @@ public sealed class RiggableSystem : EntitySystem
{ {
[Dependency] private readonly ExplosionSystem _explosionSystem = default!; [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly PredictedBatterySystem _predictedBattery = default!; [Dependency] private readonly SharedBatterySystem _battery = default!;
public override void Initialize() public override void Initialize()
{ {
@ -27,7 +27,6 @@ public sealed class RiggableSystem : EntitySystem
SubscribeLocalEvent<RiggableComponent, BeingMicrowavedEvent>(OnMicrowaved); SubscribeLocalEvent<RiggableComponent, BeingMicrowavedEvent>(OnMicrowaved);
SubscribeLocalEvent<RiggableComponent, SolutionContainerChangedEvent>(OnSolutionChanged); SubscribeLocalEvent<RiggableComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<RiggableComponent, ChargeChangedEvent>(OnChargeChanged); SubscribeLocalEvent<RiggableComponent, ChargeChangedEvent>(OnChargeChanged);
SubscribeLocalEvent<RiggableComponent, PredictedBatteryChargeChangedEvent>(OnChargeChanged);
} }
private void OnRejuvenate(Entity<RiggableComponent> entity, ref RejuvenateEvent args) 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 (TryComp<BatteryComponent>(entity, out var batteryComponent))
{ {
if (batteryComponent.CurrentCharge == 0f) var charge = _battery.GetCharge((entity, batteryComponent));
return;
Explode(entity, batteryComponent.CurrentCharge);
args.Handled = true;
}
if (TryComp<PredictedBatteryComponent>(entity, out var predictedBatteryComponent))
{
var charge = _predictedBattery.GetCharge((entity, predictedBatteryComponent));
if (charge == 0f) if (charge == 0f)
return; return;
@ -80,20 +70,7 @@ public sealed class RiggableSystem : EntitySystem
QueueDel(uid); QueueDel(uid);
} }
// non-predicted batteries
private void OnChargeChanged(Entity<RiggableComponent> ent, ref ChargeChangedEvent args) 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) if (!ent.Comp.IsRigged)
return; return;

View File

@ -2,6 +2,7 @@ using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.Rounding; using Content.Shared.Rounding;
using Content.Shared.SMES; using Content.Shared.SMES;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -14,6 +15,7 @@ internal sealed class SmesSystem : EntitySystem
{ {
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedBatterySystem _battery = default!;
public override void Initialize() public override void Initialize()
{ {
@ -61,7 +63,8 @@ internal sealed class SmesSystem : EntitySystem
if (!Resolve(uid, ref battery, false)) if (!Resolve(uid, ref battery, false))
return 0; 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) private ChargeState CalcChargeState(EntityUid uid, PowerNetworkBatteryComponent? netBattery = null)

View File

@ -1,5 +1,4 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems; using Content.Shared.Power.EntitySystems;
@ -10,8 +9,7 @@ namespace Content.Server.Power
[AdminCommand(AdminFlags.Debug)] [AdminCommand(AdminFlags.Debug)]
public sealed class SetBatteryPercentCommand : LocalizedEntityCommands public sealed class SetBatteryPercentCommand : LocalizedEntityCommands
{ {
[Dependency] private readonly BatterySystem _batterySystem = default!; [Dependency] private readonly SharedBatterySystem _batterySystem = default!;
[Dependency] private readonly PredictedBatterySystem _predictedBatterySystem = default!;
public override string Command => "setbatterypercent"; public override string Command => "setbatterypercent";
@ -39,8 +37,6 @@ namespace Content.Server.Power
if (EntityManager.TryGetComponent<BatteryComponent>(id, out var battery)) if (EntityManager.TryGetComponent<BatteryComponent>(id, out var battery))
_batterySystem.SetCharge((id.Value, battery), battery.MaxCharge * percent / 100); _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 else
{ {
shell.WriteLine(Loc.GetString($"cmd-setbatterypercent-battery-not-found", ("id", id))); shell.WriteLine(Loc.GetString($"cmd-setbatterypercent-battery-not-found", ("id", id)));

View File

@ -68,7 +68,7 @@ namespace Content.Server.PowerSink
_battery.ChangeCharge((entity, battery), networkLoad.NetworkLoad.ReceivingPower * frameTime); _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 // Check for warning message threshold
if (!component.SentImminentExplosionWarningMessage && if (!component.SentImminentExplosionWarningMessage &&
@ -90,7 +90,7 @@ namespace Content.Server.PowerSink
} }
// Check for explosion // Check for explosion
if (battery.CurrentCharge < battery.MaxCharge) if (!_battery.IsFull((entity, battery)))
continue; continue;
if (component.ExplosionTime == null) if (component.ExplosionTime == null)

View File

@ -11,7 +11,7 @@ namespace Content.Server.Radio.EntitySystems;
public sealed class JammerSystem : SharedJammerSystem public sealed class JammerSystem : SharedJammerSystem
{ {
[Dependency] private readonly PowerCellSystem _powerCell = default!; [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 SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedDeviceNetworkJammerSystem _jammer = 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 // TODO: Very important: Make this charge rate based instead of updating every single tick
// See PredictedBatteryComponent // See BatteryComponent
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
var query = EntityQueryEnumerator<ActiveRadioJammerComponent, RadioJammerComponent>(); var query = EntityQueryEnumerator<ActiveRadioJammerComponent, RadioJammerComponent>();

View File

@ -3,6 +3,7 @@ using Content.Server.Power.Components;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Events; using Content.Shared.DeviceNetwork.Events;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
namespace Content.Server.SensorMonitoring; namespace Content.Server.SensorMonitoring;
@ -11,6 +12,7 @@ public sealed class BatterySensorSystem : EntitySystem
public const string DeviceNetworkCommandSyncData = "bat_sync_data"; public const string DeviceNetworkCommandSyncData = "bat_sync_data";
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
[Dependency] private readonly SharedBatterySystem _battery = default!;
public override void Initialize() public override void Initialize()
{ {
@ -26,13 +28,14 @@ public sealed class BatterySensorSystem : EntitySystem
{ {
case DeviceNetworkCommandSyncData: case DeviceNetworkCommandSyncData:
var battery = Comp<BatteryComponent>(uid); var battery = Comp<BatteryComponent>(uid);
var currentCharge = _battery.GetCharge((uid, battery));
var netBattery = Comp<PowerNetworkBatteryComponent>(uid); var netBattery = Comp<PowerNetworkBatteryComponent>(uid);
var payload = new NetworkPayload var payload = new NetworkPayload
{ {
[DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData, [DeviceNetworkConstants.Command] = DeviceNetworkCommandSyncData,
[DeviceNetworkCommandSyncData] = new BatterySensorData( [DeviceNetworkCommandSyncData] = new BatterySensorData(
battery.CurrentCharge, currentCharge,
battery.MaxCharge, battery.MaxCharge,
netBattery.CurrentReceiving, netBattery.CurrentReceiving,
netBattery.MaxChargeRate, netBattery.MaxChargeRate,

View File

@ -25,7 +25,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
[Dependency] private readonly TriggerSystem _trigger = default!; [Dependency] private readonly TriggerSystem _trigger = default!;
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedContainerSystem _container = 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 EmagSystem _emag = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!; [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;

View File

@ -6,7 +6,6 @@ using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind; using Content.Server.Mind;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.Spawners.Components; using Content.Server.Spawners.Components;
using Content.Server.Spawners.EntitySystems; using Content.Server.Spawners.EntitySystems;
@ -23,6 +22,7 @@ using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Power.EntitySystems;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Content.Shared.Roles; using Content.Shared.Roles;
@ -52,7 +52,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
[Dependency] private readonly ToggleableGhostRoleSystem _ghostrole = default!; [Dependency] private readonly ToggleableGhostRoleSystem _ghostrole = default!;
[Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly AlertsSystem _alerts = default!;
[Dependency] private readonly DestructibleSystem _destructible = 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 DamageableSystem _damageable = default!;
[Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly SharedPopupSystem _popups = default!;
[Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSystem _station = default!;
@ -239,6 +239,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
UpdateDamagedAccent(entity); 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) private void UpdateDamagedAccent(Entity<StationAiCoreComponent> ent)
{ {
if (!TryGetHeld((ent.Owner, ent.Comp), out var held)) if (!TryGetHeld((ent.Owner, ent.Comp), out var held))
@ -248,7 +249,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
return; return;
if (TryComp<BatteryComponent>(ent, out var battery)) 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)) if (TryComp<DamageableComponent>(ent, out var damageable))
accent.OverrideTotalDamage = damageable.TotalDamage; accent.OverrideTotalDamage = damageable.TotalDamage;
@ -270,7 +271,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
if (!_proto.TryIndex(_batteryAlert, out var proto)) if (!_proto.TryIndex(_batteryAlert, out var proto))
return; return;
var chargePercent = battery.CurrentCharge / battery.MaxCharge; var chargePercent = _battery.GetChargeLevel((ent.Owner, battery));
var chargeLevel = Math.Round(chargePercent * proto.MaxSeverity); var chargeLevel = Math.Round(chargePercent * proto.MaxSeverity);
_alerts.ShowAlert(held.Value, _batteryAlert, (short)Math.Clamp(chargeLevel, 0, proto.MaxSeverity)); _alerts.ShowAlert(held.Value, _batteryAlert, (short)Math.Clamp(chargeLevel, 0, proto.MaxSeverity));

View File

@ -13,7 +13,7 @@ namespace Content.Server.Speech.EntitySystems;
public sealed class DamagedSiliconAccentSystem : EntitySystem public sealed class DamagedSiliconAccentSystem : EntitySystem
{ {
[Dependency] private readonly IRobustRandom _random = default!; [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 PowerCellSystem _powerCell = default!;
[Dependency] private readonly DestructibleSystem _destructibleSystem = default!; [Dependency] private readonly DestructibleSystem _destructibleSystem = default!;

View File

@ -18,7 +18,7 @@ namespace Content.Server.Stunnable.Systems
{ {
[Dependency] private readonly RiggableSystem _riggableSystem = default!; [Dependency] private readonly RiggableSystem _riggableSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = 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!; [Dependency] private readonly ItemToggleSystem _itemToggle = default!;
public override void Initialize() public override void Initialize()
@ -28,13 +28,13 @@ namespace Content.Server.Stunnable.Systems
SubscribeLocalEvent<StunbatonComponent, ExaminedEvent>(OnExamined); SubscribeLocalEvent<StunbatonComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<StunbatonComponent, SolutionContainerChangedEvent>(OnSolutionChange); SubscribeLocalEvent<StunbatonComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<StunbatonComponent, StaminaDamageOnHitAttemptEvent>(OnStaminaHitAttempt); SubscribeLocalEvent<StunbatonComponent, StaminaDamageOnHitAttemptEvent>(OnStaminaHitAttempt);
SubscribeLocalEvent<StunbatonComponent, PredictedBatteryChargeChangedEvent>(OnChargeChanged); SubscribeLocalEvent<StunbatonComponent, ChargeChangedEvent>(OnChargeChanged);
} }
private void OnStaminaHitAttempt(Entity<StunbatonComponent> entity, ref StaminaDamageOnHitAttemptEvent args) private void OnStaminaHitAttempt(Entity<StunbatonComponent> entity, ref StaminaDamageOnHitAttemptEvent args)
{ {
if (!_itemToggle.IsActivated(entity.Owner) || 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; args.Cancelled = true;
} }
@ -47,7 +47,7 @@ namespace Content.Server.Stunnable.Systems
: Loc.GetString("comp-stunbaton-examined-off"); : Loc.GetString("comp-stunbaton-examined-off");
args.PushMarkup(onMsg); 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); var count = _battery.GetRemainingUses((entity.Owner, battery), entity.Comp.EnergyPerUse);
args.PushMarkup(Loc.GetString("melee-battery-examine", ("color", "yellow"), ("count", count))); 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); 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; args.Cancelled = true;
if (args.User != null) if (args.User != null)
@ -79,7 +79,7 @@ namespace Content.Server.Stunnable.Systems
{ {
// Explode if baton is activated and rigged. // Explode if baton is activated and rigged.
if (!TryComp<RiggableComponent>(entity, out var riggable) || if (!TryComp<RiggableComponent>(entity, out var riggable) ||
!TryComp<PredictedBatteryComponent>(entity, out var battery)) !TryComp<BatteryComponent>(entity, out var battery))
return; return;
if (_itemToggle.IsActivated(entity.Owner) && riggable.IsRigged) 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) _battery.GetCharge((entity.Owner, battery)) < entity.Comp.EnergyPerUse)
{ {
_itemToggle.TryDeactivate(entity.Owner, predicted: false); _itemToggle.TryDeactivate(entity.Owner, predicted: false);

View File

@ -24,7 +24,7 @@ public sealed class TeslaCoilSystem : EntitySystem
{ {
if (TryComp<BatteryComponent>(coil, out var batteryComponent)) if (TryComp<BatteryComponent>(coil, out var batteryComponent))
{ {
_battery.SetCharge((coil, batteryComponent), batteryComponent.CurrentCharge + coil.Comp.ChargeFromLightning); _battery.ChangeCharge((coil, batteryComponent), coil.Comp.ChargeFromLightning);
} }
} }
} }

View File

@ -13,29 +13,20 @@ namespace Content.Server.Xenoarchaeology.Artifact.XAE;
public sealed class XAEChargeBatterySystem : BaseXAESystem<XAEChargeBatteryComponent> public sealed class XAEChargeBatterySystem : BaseXAESystem<XAEChargeBatteryComponent>
{ {
[Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly BatterySystem _battery = default!;
[Dependency] private readonly PredictedBatterySystem _predictedBattery = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!;
/// <summary> Pre-allocated and re-used collection.</summary> /// <summary> Pre-allocated and re-used collection.</summary>
private readonly HashSet<Entity<BatteryComponent>> _batteryEntities = new(); private readonly HashSet<Entity<BatteryComponent>> _batteryEntities = new();
private readonly HashSet<Entity<PredictedBatteryComponent>> _pBatteryEntities = new();
/// <inheritdoc /> /// <inheritdoc />
protected override void OnActivated(Entity<XAEChargeBatteryComponent> ent, ref XenoArtifactNodeActivatedEvent args) protected override void OnActivated(Entity<XAEChargeBatteryComponent> ent, ref XenoArtifactNodeActivatedEvent args)
{ {
_batteryEntities.Clear(); _batteryEntities.Clear();
_pBatteryEntities.Clear();
_lookup.GetEntitiesInRange(args.Coordinates, ent.Comp.Radius, _batteryEntities); _lookup.GetEntitiesInRange(args.Coordinates, ent.Comp.Radius, _batteryEntities);
foreach (var battery in _batteryEntities) foreach (var battery in _batteryEntities)
{ {
_battery.SetCharge(battery.AsNullable(), battery.Comp.MaxCharge); _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);
}
} }
} }

View File

@ -1,40 +1,17 @@
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using Content.Shared.Power.EntitySystems;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
namespace Content.Shared.Power; namespace Content.Shared.Power;
/// <summary> /// <summary>
/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage). /// Raised when a battery's charge, charge rate or capacity was updated (capacity affects relative charge percentage).
/// Only raised for entities with <see cref="BatteryComponent"/>. /// 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> /// </summary>
[ByRefEvent] [ByRefEvent]
public readonly record struct ChargeChangedEvent(float Charge, float Delta, float MaxCharge) public readonly record struct ChargeChangedEvent(float CurrentCharge, float Delta, float CurrentChargeRate, 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)
{ {
/// <summary> /// <summary>
/// The new charge of the battery. /// The new charge of the battery.
@ -60,15 +37,14 @@ public readonly record struct PredictedBatteryChargeChangedEvent(float CurrentCh
/// <summary> /// <summary>
/// Raised when a battery changes its state between full, empty, or neither. /// 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> /// </summary>
[ByRefEvent] [ByRefEvent]
public record struct PredictedBatteryStateChangedEvent(BatteryState OldState, BatteryState NewState); public record struct BatteryStateChangedEvent(BatteryState OldState, BatteryState NewState);
/// <summary> /// <summary>
/// Raised to calculate a predicted battery's recharge rate. /// Raised to calculate a predicted battery's recharge rate.
/// Subscribe to this to offset its current charge rate. /// Subscribe to this to offset its current charge rate.
/// Used only for <see cref="PredictedBatteryComponent"/>.
/// </summary> /// </summary>
[ByRefEvent] [ByRefEvent]
public record struct RefreshChargeRateEvent(float MaxCharge) public record struct RefreshChargeRateEvent(float MaxCharge)
@ -80,7 +56,7 @@ public record struct RefreshChargeRateEvent(float MaxCharge)
/// <summary> /// <summary>
/// Event that supports multiple battery types. /// Event that supports multiple battery types.
/// Raised when it is necessary to get information about battery charges. /// 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. /// If there are multiple batteries then the results will be summed up.
/// </summary> /// </summary>
[ByRefEvent] [ByRefEvent]
@ -93,7 +69,7 @@ public record struct GetChargeEvent
/// <summary> /// <summary>
/// Method event that supports multiple battery types. /// Method event that supports multiple battery types.
/// Raised when it is necessary to change the current battery charge by some value. /// 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. /// If there are multiple batteries then they will be changed in order of subscription until the total value was reached.
/// </summary> /// </summary>
[ByRefEvent] [ByRefEvent]

View File

@ -1,34 +1,98 @@
using Content.Shared.Power.EntitySystems; using Content.Shared.Power.EntitySystems;
using Content.Shared.PowerCell.Components;
using Content.Shared.Guidebook; using Content.Shared.Guidebook;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Power.Components; namespace Content.Shared.Power.Components;
/// <summary> /// <summary>
/// Battery node on the pow3r network. Needs other components to connect to actual networks. /// Used for any sort of battery that stores electical power.
/// Use this for batteries that cannot be predicted. /// Can be used as a battery node on the pow3r network. Needs other components to connect to actual networks, see PowerNetworkBatteryComponent.
/// Use <see cref="PredictedBatteryComponent"/> otherwise. /// Also used for power cells using <see cref="PowerCellComponent"/> or battery powered guns with intrinsic battery.
/// </summary> /// </summary>
[RegisterComponent] /// <remarks>
[Virtual] /// 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))] [Access(typeof(SharedBatterySystem))]
public partial class BatteryComponent : Component public sealed partial class BatteryComponent : Component
{ {
/// <summary> /// <summary>
/// Maximum charge of the battery in joules (i.e. watt seconds) /// Maximum charge of the battery in joules (ie. watt seconds)
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField, ViewVariables]
[GuidebookData] [GuidebookData]
public float MaxCharge; public float MaxCharge;
/// <summary>
/// Current charge of the battery in joules (ie. watt seconds)
/// </summary>
[DataField("startingCharge")] // TODO: rename this datafield to currentCharge
public float CurrentCharge;
/// <summary> /// <summary>
/// The price per one joule. Default is 1 speso for 10kJ. /// The price per one joule. Default is 1 speso for 10kJ.
/// </summary> /// </summary>
[DataField] [DataField]
public float PricePerJoule = 0.0001f; 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="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,
}

View File

@ -1,3 +1,4 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Power.Components; namespace Content.Shared.Power.Components;
@ -5,33 +6,27 @@ namespace Content.Shared.Power.Components;
/// <summary> /// <summary>
/// Self-recharging battery. /// Self-recharging battery.
/// To be used in combination with <see cref="BatteryComponent"/>. /// To be used in combination with <see cref="BatteryComponent"/>.
/// For <see cref="PredictedBatteryComponent"/> use <see cref="PredictedBatterySelfRechargerComponent"/> instead.
/// </summary> /// </summary>
[RegisterComponent, AutoGenerateComponentPause] [RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState, AutoGenerateComponentPause]
public sealed partial class BatterySelfRechargerComponent : Component public sealed partial class BatterySelfRechargerComponent : Component
{ {
/// <summary>
/// Is the component currently enabled?
/// </summary>
[DataField]
public bool AutoRecharge = true;
/// <summary> /// <summary>
/// At what rate does the entity automatically recharge? In watts. /// At what rate does the entity automatically recharge? In watts.
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField, ViewVariables]
public float AutoRechargeRate; public float AutoRechargeRate;
/// <summary> /// <summary>
/// How long should the entity stop automatically recharging if charge is used? /// How long should the entity stop automatically recharging if a charge is used?
/// </summary> /// </summary>
[DataField] [DataField, AutoNetworkedField]
public TimeSpan AutoRechargePauseTime = TimeSpan.FromSeconds(0); public TimeSpan AutoRechargePauseTime = TimeSpan.Zero;
/// <summary> /// <summary>
/// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system. /// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system.
/// </summary> /// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
[AutoPausedField] [AutoNetworkedField, AutoPausedField, ViewVariables]
public TimeSpan NextAutoRecharge = TimeSpan.FromSeconds(0); public TimeSpan? NextAutoRecharge = TimeSpan.FromSeconds(0);
} }

View File

@ -5,11 +5,11 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Power.Components; namespace Content.Shared.Power.Components;
/// <summary> /// <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. /// Also works with an entity with <see cref="PowerCellSlotComponent"/> and will relay the state of the inserted powercell.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class PredictedBatteryVisualsComponent : Component; public sealed partial class BatteryVisualsComponent : Component;
/// <summary> /// <summary>
/// Keys for the appearance data. /// Keys for the appearance data.
@ -37,15 +37,15 @@ public enum BatteryVisuals : byte
public enum BatteryChargingState : byte public enum BatteryChargingState : byte
{ {
/// <summary> /// <summary>
/// PredictedBatteryComponent.ChargeRate &gt; 0 /// BatteryComponent.ChargeRate &gt; 0
/// </summary> /// </summary>
Charging, Charging,
/// <summary> /// <summary>
/// PredictedBatteryComponent.ChargeRate &lt; 0 /// BatteryComponent.ChargeRate &lt; 0
/// </summary> /// </summary>
Decharging, Decharging,
/// <summary> /// <summary>
/// PredictedBatteryComponent.ChargeRate == 0 /// BatteryComponent.ChargeRate == 0
/// </summary> /// </summary>
Constant, Constant,
} }

View File

@ -4,7 +4,7 @@ namespace Content.Shared.Power.Components;
/// <summary> /// <summary>
/// Allows the charge of a battery to be seen by examination. /// 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> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class ExaminableBatteryComponent : Component; public sealed partial class ExaminableBatteryComponent : Component;

View File

@ -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,
}

View File

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

View File

@ -13,7 +13,7 @@ namespace Content.Shared.Power.EntitySystems;
public sealed class ChargerSystem : EntitySystem 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 SharedPowerReceiverSystem _receiver = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
@ -35,7 +35,7 @@ public sealed class ChargerSystem : EntitySystem
SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse); SubscribeLocalEvent<ChargerComponent, EmpPulseEvent>(OnEmpPulse);
SubscribeLocalEvent<ChargerComponent, EmpDisabledRemovedEvent>(OnEmpRemoved); SubscribeLocalEvent<ChargerComponent, EmpDisabledRemovedEvent>(OnEmpRemoved);
SubscribeLocalEvent<InsideChargerComponent, RefreshChargeRateEvent>(OnRefreshChargeRate); SubscribeLocalEvent<InsideChargerComponent, RefreshChargeRateEvent>(OnRefreshChargeRate);
SubscribeLocalEvent<InsideChargerComponent, PredictedBatteryStateChangedEvent>(OnStatusChanged); SubscribeLocalEvent<InsideChargerComponent, BatteryStateChangedEvent>(OnStatusChanged);
} }
private void OnStartup(Entity<ChargerComponent> ent, ref ComponentStartup args) private void OnStartup(Entity<ChargerComponent> ent, ref ComponentStartup args)
@ -176,7 +176,7 @@ public sealed class ChargerSystem : EntitySystem
args.NewChargeRate += chargerComp.ChargeRate; 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. // If the battery is full update the visuals and power draw of the charger.

View File

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

View File

@ -4,11 +4,11 @@ using JetBrains.Annotations;
namespace Content.Shared.Power.EntitySystems; namespace Content.Shared.Power.EntitySystems;
/// <summary> /// <summary>
/// Responsible for <see cref="PredictedBatteryComponent"/>. /// Responsible for <see cref="BatteryComponent"/>.
/// Predicted equivalent of <see cref="Content.Server.Power.EntitySystems.BatterySystem"/>. /// Predicted equivalent of <see cref="Content.Server.Power.EntitySystems.BatterySystem"/>.
/// If you make changes to this make sure to keep the two consistent. /// If you make changes to this make sure to keep the two consistent.
/// </summary> /// </summary>
public sealed partial class PredictedBatterySystem public abstract partial class SharedBatterySystem
{ {
/// <summary> /// <summary>
/// Changes the battery's charge by the given amount /// Changes the battery's charge by the given amount
@ -17,7 +17,7 @@ public sealed partial class PredictedBatterySystem
/// </summary> /// </summary>
/// <returns>The actually changed amount.</returns> /// <returns>The actually changed amount.</returns>
[PublicAPI] [PublicAPI]
public float ChangeCharge(Entity<PredictedBatteryComponent?> ent, float amount) public float ChangeCharge(Entity<BatteryComponent?> ent, float amount)
{ {
if (!Resolve(ent, ref ent.Comp)) if (!Resolve(ent, ref ent.Comp))
return 0; return 0;
@ -36,7 +36,7 @@ public sealed partial class PredictedBatterySystem
TrySetChargeCooldown(ent.Owner); 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); RaiseLocalEvent(ent, ref changedEv);
// Raise events if the battery status changed between full, empty, or neither. // Raise events if the battery status changed between full, empty, or neither.
@ -50,7 +50,7 @@ public sealed partial class PredictedBatterySystem
/// </summary> /// </summary>
/// <returns>The actually changed amount.</returns> /// <returns>The actually changed amount.</returns>
[PublicAPI] [PublicAPI]
public float UseCharge(Entity<PredictedBatteryComponent?> ent, float amount) public float UseCharge(Entity<BatteryComponent?> ent, float amount)
{ {
if (amount <= 0f) if (amount <= 0f)
return 0f; return 0f;
@ -65,7 +65,7 @@ public sealed partial class PredictedBatterySystem
/// </summary> /// </summary>
/// <returns>If the full amount was able to be removed.</returns> /// <returns>If the full amount was able to be removed.</returns>
[PublicAPI] [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)) if (!Resolve(ent, ref ent.Comp, false) || amount > GetCharge(ent))
return false; return false;
@ -78,7 +78,7 @@ public sealed partial class PredictedBatterySystem
/// Sets the battery's charge. /// Sets the battery's charge.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public void SetCharge(Entity<PredictedBatteryComponent?> ent, float value) public void SetCharge(Entity<BatteryComponent?> ent, float value)
{ {
if (!Resolve(ent, ref ent.Comp)) if (!Resolve(ent, ref ent.Comp))
return; return;
@ -95,7 +95,7 @@ public sealed partial class PredictedBatterySystem
ent.Comp.LastUpdate = curTime; ent.Comp.LastUpdate = curTime;
Dirty(ent); 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); RaiseLocalEvent(ent, ref ev);
// Raise events if the battery status changed between full, empty, or neither. // 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. /// Sets the battery's maximum charge.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public void SetMaxCharge(Entity<PredictedBatteryComponent?> ent, float value) public void SetMaxCharge(Entity<BatteryComponent?> ent, float value)
{ {
if (!Resolve(ent, ref ent.Comp)) if (!Resolve(ent, ref ent.Comp))
return; return;
@ -122,7 +122,7 @@ public sealed partial class PredictedBatterySystem
ent.Comp.LastUpdate = curTime; ent.Comp.LastUpdate = curTime;
Dirty(ent); 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); RaiseLocalEvent(ent, ref ev);
// Raise events if the battery status changed between full, empty, or neither. // 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. /// Updates the battery's charge state and sends an event if it changed.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public void UpdateState(Entity<PredictedBatteryComponent?> ent) public void UpdateState(Entity<BatteryComponent?> ent)
{ {
if (!Resolve(ent, ref ent.Comp)) if (!Resolve(ent, ref ent.Comp))
return; return;
@ -144,7 +144,7 @@ public sealed partial class PredictedBatterySystem
var charge = GetCharge(ent); var charge = GetCharge(ent);
if (charge == ent.Comp.MaxCharge) if (charge >= ent.Comp.MaxCharge)
newState = BatteryState.Full; newState = BatteryState.Full;
else if (charge == 0f) else if (charge == 0f)
newState = BatteryState.Empty; newState = BatteryState.Empty;
@ -155,7 +155,7 @@ public sealed partial class PredictedBatterySystem
ent.Comp.State = newState; ent.Comp.State = newState;
Dirty(ent); Dirty(ent);
var changedEv = new PredictedBatteryStateChangedEvent(oldState, newState); var changedEv = new BatteryStateChangedEvent(oldState, newState);
RaiseLocalEvent(ent, ref changedEv); RaiseLocalEvent(ent, ref changedEv);
} }
@ -163,7 +163,7 @@ public sealed partial class PredictedBatterySystem
/// Gets the battery's current charge. /// Gets the battery's current charge.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public float GetCharge(Entity<PredictedBatteryComponent?> ent) public float GetCharge(Entity<BatteryComponent?> ent)
{ {
if (!Resolve(ent, ref ent.Comp, false)) if (!Resolve(ent, ref ent.Comp, false))
return 0f; return 0f;
@ -179,7 +179,7 @@ public sealed partial class PredictedBatterySystem
/// Gets the fraction of charge remaining (01). /// Gets the fraction of charge remaining (01).
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public float GetChargeLevel(Entity<PredictedBatteryComponent?> ent) public float GetChargeLevel(Entity<BatteryComponent?> ent)
{ {
if (!Resolve(ent, ref ent.Comp, false)) if (!Resolve(ent, ref ent.Comp, false))
return 0f; return 0f;
@ -194,7 +194,7 @@ public sealed partial class PredictedBatterySystem
/// Gets number of remaining uses for the given charge cost. /// Gets number of remaining uses for the given charge cost.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public int GetRemainingUses(Entity<PredictedBatteryComponent?> ent, float cost) public int GetRemainingUses(Entity<BatteryComponent?> ent, float cost)
{ {
if (cost <= 0) if (cost <= 0)
return 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. /// Gets number of maximum uses at full charge for the given charge cost.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public int GetMaxUses(Entity<PredictedBatteryComponent?> ent, float cost) public int GetMaxUses(Entity<BatteryComponent?> ent, float cost)
{ {
if (cost <= 0) if (cost <= 0)
return 0; return 0;
@ -220,13 +220,13 @@ public sealed partial class PredictedBatterySystem
return (int)(ent.Comp.MaxCharge / cost); return (int)(ent.Comp.MaxCharge / cost);
} }
/// <summary> /// <summary>
/// Refreshes the battery's current charge rate by raising a <see cref="RefreshChargeRateEvent"/>. /// 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> /// </summary>
/// <returns>The new charge rate.</returns> /// <returns>The new charge rate.</returns>
[PublicAPI] [PublicAPI]
public float RefreshChargeRate(Entity<PredictedBatteryComponent?> ent) public float RefreshChargeRate(Entity<BatteryComponent?> ent)
{ {
if (!Resolve(ent, ref ent.Comp, false)) if (!Resolve(ent, ref ent.Comp, false))
return 0f; return 0f;
@ -241,7 +241,7 @@ public sealed partial class PredictedBatterySystem
Dirty(ent); Dirty(ent);
// Inform other systems about the new rate; // 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); RaiseLocalEvent(ent, ref changedEv);
return refreshEv.NewChargeRate; return refreshEv.NewChargeRate;
@ -252,7 +252,7 @@ public sealed partial class PredictedBatterySystem
/// Uses the cooldown time given in the component. /// Uses the cooldown time given in the component.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public void TrySetChargeCooldown(Entity<PredictedBatterySelfRechargerComponent?> ent) public void TrySetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent)
{ {
if (!Resolve(ent, ref ent.Comp, false)) if (!Resolve(ent, ref ent.Comp, false))
return; return;
@ -270,7 +270,7 @@ public sealed partial class PredictedBatterySystem
/// Puts the entity's self recharge on cooldown for the specified time. /// Puts the entity's self recharge on cooldown for the specified time.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public void SetChargeCooldown(Entity<PredictedBatterySelfRechargerComponent?> ent, TimeSpan cooldown) public void SetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent, TimeSpan cooldown)
{ {
if (!Resolve(ent, ref ent.Comp)) if (!Resolve(ent, ref ent.Comp))
return; return;
@ -284,7 +284,7 @@ public sealed partial class PredictedBatterySystem
/// Returns whether the battery is full. /// Returns whether the battery is full.
/// </summary> /// </summary>
[PublicAPI] [PublicAPI]
public bool IsFull(Entity<PredictedBatteryComponent?> ent) public bool IsFull(Entity<BatteryComponent?> ent)
{ {
if (!Resolve(ent, ref ent.Comp)) if (!Resolve(ent, ref ent.Comp))
return false; return false;

View File

@ -1,83 +1,177 @@
using Content.Shared.Cargo;
using Content.Shared.Emp; using Content.Shared.Emp;
using Content.Shared.Examine;
using Content.Shared.Power.Components; using Content.Shared.Power.Components;
using JetBrains.Annotations; using Content.Shared.Rejuvenate;
using Robust.Shared.Timing;
namespace Content.Shared.Power.EntitySystems; 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() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<BatteryComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<BatteryComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BatteryComponent, EmpPulseEvent>(OnEmpPulse); 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) private void OnEmpPulse(Entity<BatteryComponent> ent, ref EmpPulseEvent args)
{ {
args.Affected = true; args.Affected = true;
UseCharge(ent.AsNullable(), args.EnergyConsumption); 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> private void OnExamine(Entity<BatteryComponent> ent, ref ExaminedEvent args)
/// 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)
{ {
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> /// <summary>
/// Removes the given amount of charge from the battery /// Gets the price for the power contained in an entity's battery.
/// and resets the self-recharge cooldown if it exists.
/// </summary> /// </summary>
/// <returns>The actually changed amount.</returns> private void CalculateBatteryPrice(Entity<BatteryComponent> ent, ref PriceCalculationEvent args)
[PublicAPI]
public virtual float UseCharge(Entity<BatteryComponent?> ent, float amount)
{ {
return 0f; args.Price += GetCharge(ent.AsNullable()) * ent.Comp.PricePerJoule;
} }
/// <summary> private void OnChangeCharge(Entity<BatteryComponent> ent, ref ChangeChargeEvent args)
/// 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)
{ {
return false; if (args.ResidualValue == 0)
return;
args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue);
} }
/// <summary> private void OnGetCharge(Entity<BatteryComponent> ent, ref GetChargeEvent args)
/// Sets the battery's charge. {
/// </summary> args.CurrentCharge += GetCharge(ent.AsNullable());
[PublicAPI] args.MaxCharge += ent.Comp.MaxCharge;
public virtual void SetCharge(Entity<BatteryComponent?> ent, float value) { } }
/// <summary> private void OnRefreshChargeRate(Entity<BatterySelfRechargerComponent> ent, ref RefreshChargeRateEvent args)
/// Sets the battery's maximum charge. {
/// </summary> if (_timing.CurTime < ent.Comp.NextAutoRecharge)
[PublicAPI] return; // Still on cooldown
public virtual void SetMaxCharge(Entity<BatteryComponent?> ent, float value) { }
/// <summary> args.NewChargeRate += ent.Comp.AutoRechargeRate;
/// 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) { }
/// <summary> public override void Update(float frameTime)
/// Puts the entity's self recharge on cooldown for the specified time. {
/// </summary> var curTime = _timing.CurTime;
[PublicAPI]
public virtual void SetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent, TimeSpan cooldown) { } // 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);
}
} }

View File

@ -5,7 +5,7 @@ namespace Content.Shared.PowerCell.Components;
/// <summary> /// <summary>
/// This component enables power-cell related interactions (e.g. EntityWhitelists, cell sizes, examine, rigging). /// 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> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class PowerCellComponent : Component; public sealed partial class PowerCellComponent : Component;

View File

@ -31,7 +31,7 @@ public sealed partial class PowerCellSystem
[PublicAPI] [PublicAPI]
public bool TryGetBatteryFromSlot( public bool TryGetBatteryFromSlot(
Entity<PowerCellSlotComponent?> ent, Entity<PowerCellSlotComponent?> ent,
[NotNullWhen(true)] out Entity<PredictedBatteryComponent>? battery) [NotNullWhen(true)] out Entity<BatteryComponent>? battery)
{ {
if (!Resolve(ent, ref ent.Comp, false)) if (!Resolve(ent, ref ent.Comp, false))
{ {
@ -45,7 +45,7 @@ public sealed partial class PowerCellSystem
return false; return false;
} }
if (!TryComp<PredictedBatteryComponent>(slot.Item, out var batteryComp)) if (!TryComp<BatteryComponent>(slot.Item, out var batteryComp))
{ {
battery = null; battery = null;
return false; return false;
@ -57,15 +57,15 @@ public sealed partial class PowerCellSystem
/// <summary> /// <summary>
/// First tries to get a battery from the entity's power cell slot. /// 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> /// </summary>
[PublicAPI] [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)) if (TryGetBatteryFromSlot(ent, out battery))
return true; return true;
if (TryComp<PredictedBatteryComponent>(ent, out var batteryComp)) if (TryComp<BatteryComponent>(ent, out var batteryComp))
{ {
battery = (ent.Owner, batteryComp); battery = (ent.Owner, batteryComp);
return true; return true;
@ -86,13 +86,13 @@ public sealed partial class PowerCellSystem
} }
/// <summary> /// <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. /// If that fails it will try to get a battery from the entity's power cell slot instead.
/// </summary> /// </summary>
[PublicAPI] [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); battery = (ent.Owner, batteryComp);
return true; return true;

View File

@ -16,8 +16,8 @@ public sealed partial class PowerCellSystem
SubscribeLocalEvent<PowerCellSlotComponent, ChangeChargeEvent>(RelayToCell); SubscribeLocalEvent<PowerCellSlotComponent, ChangeChargeEvent>(RelayToCell);
SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(RelayToCellSlot); // Prevent the ninja from EMPing its own battery SubscribeLocalEvent<PowerCellComponent, EmpAttemptEvent>(RelayToCellSlot); // Prevent the ninja from EMPing its own battery
SubscribeLocalEvent<PowerCellComponent, PredictedBatteryChargeChangedEvent>(RelayToCellSlot); SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(RelayToCellSlot);
SubscribeLocalEvent<PowerCellComponent, PredictedBatteryStateChangedEvent>(RelayToCellSlot); // For shutting down devices if the battery is empty SubscribeLocalEvent<PowerCellComponent, BatteryStateChangedEvent>(RelayToCellSlot); // For shutting down devices if the battery is empty
SubscribeLocalEvent<PowerCellComponent, RefreshChargeRateEvent>(RelayToCellSlot); // Allow devices to charge/drain inserted batteries SubscribeLocalEvent<PowerCellComponent, RefreshChargeRateEvent>(RelayToCellSlot); // Allow devices to charge/drain inserted batteries
} }

View File

@ -15,7 +15,7 @@ public sealed partial class PowerCellSystem : EntitySystem
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = 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!; [Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize() public override void Initialize()
@ -27,7 +27,7 @@ public sealed partial class PowerCellSystem : EntitySystem
SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellSlotInserted); SubscribeLocalEvent<PowerCellSlotComponent, EntInsertedIntoContainerMessage>(OnCellSlotInserted);
SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellSlotRemoved); SubscribeLocalEvent<PowerCellSlotComponent, EntRemovedFromContainerMessage>(OnCellSlotRemoved);
SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined); SubscribeLocalEvent<PowerCellSlotComponent, ExaminedEvent>(OnCellSlotExamined);
SubscribeLocalEvent<PowerCellSlotComponent, PredictedBatteryStateChangedEvent>(OnCellSlotStateChanged); SubscribeLocalEvent<PowerCellSlotComponent, BatteryStateChangedEvent>(OnCellSlotStateChanged);
SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined); SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined);
@ -65,7 +65,7 @@ public sealed partial class PowerCellSystem : EntitySystem
_battery.RefreshChargeRate(args.Entity); _battery.RefreshChargeRate(args.Entity);
// Only update the visuals if we actually use them. // Only update the visuals if we actually use them.
if (!HasComp<PredictedBatteryVisualsComponent>(ent)) if (!HasComp<BatteryVisualsComponent>(ent))
return; return;
// Set the data to that of the power cell // Set the data to that of the power cell
@ -94,7 +94,7 @@ public sealed partial class PowerCellSystem : EntitySystem
_battery.RefreshChargeRate(args.Entity); _battery.RefreshChargeRate(args.Entity);
// Only update the visuals if we actually use them. // Only update the visuals if we actually use them.
if (!HasComp<PredictedBatteryVisualsComponent>(ent)) if (!HasComp<BatteryVisualsComponent>(ent))
return; return;
// Set the appearance to empty. // 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) if (args.NewState != BatteryState.Empty)
return; return;
@ -123,11 +123,11 @@ public sealed partial class PowerCellSystem : EntitySystem
private void OnCellExamined(Entity<PowerCellComponent> ent, ref ExaminedEvent args) 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); 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; var chargePercent = _battery.GetChargeLevel(ent.AsNullable()) * 100;
args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{chargePercent:F0}"))); args.PushMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{chargePercent:F0}")));

View File

@ -16,7 +16,7 @@ public sealed partial class ActivatableUISystem
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ItemToggledEvent>(OnToggled); SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ItemToggledEvent>(OnToggled);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIOpenedEvent>(OnBatteryOpened); SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIOpenedEvent>(OnBatteryOpened);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIClosedEvent>(OnBatteryClosed); SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BoundUIClosedEvent>(OnBatteryClosed);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, PredictedBatteryStateChangedEvent>(OnBatteryStateChanged); SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, BatteryStateChangedEvent>(OnBatteryStateChanged);
SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ActivatableUIOpenAttemptEvent>(OnBatteryOpenAttempt); SubscribeLocalEvent<ActivatableUIRequiresPowerCellComponent, ActivatableUIOpenAttemptEvent>(OnBatteryOpenAttempt);
} }
@ -57,7 +57,7 @@ public sealed partial class ActivatableUISystem
_toggle.TryDeactivate(uid); _toggle.TryDeactivate(uid);
} }
private void OnBatteryStateChanged(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref PredictedBatteryStateChangedEvent args) private void OnBatteryStateChanged(Entity<ActivatableUIRequiresPowerCellComponent> ent, ref BatteryStateChangedEvent args)
{ {
// Deactivate when empty. // Deactivate when empty.
if (args.NewState != BatteryState.Empty) if (args.NewState != BatteryState.Empty)

View File

@ -6,7 +6,7 @@ namespace Content.Shared.Weapons.Ranged.Components;
/// <summary> /// <summary>
/// Ammo provider that uses electric charge from a battery to provide ammunition to a weapon. /// 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> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState(raiseAfterAutoHandleState: true), AutoGenerateComponentPause] [AutoGenerateComponentState(raiseAfterAutoHandleState: true), AutoGenerateComponentPause]

View File

@ -23,7 +23,6 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<BatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine); SubscribeLocalEvent<BatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
SubscribeLocalEvent<BatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine); SubscribeLocalEvent<BatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
SubscribeLocalEvent<BatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged); SubscribeLocalEvent<BatteryAmmoProviderComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<BatteryAmmoProviderComponent, PredictedBatteryChargeChangedEvent>(OnPredictedChargeChanged);
SubscribeLocalEvent<BatteryAmmoProviderComponent, ChargeChangedEvent>(OnChargeChanged); SubscribeLocalEvent<BatteryAmmoProviderComponent, ChargeChangedEvent>(OnChargeChanged);
} }
@ -86,10 +85,10 @@ public abstract partial class SharedGunSystem
/// </summary> /// </summary>
public void TakeCharge(Entity<BatteryAmmoProviderComponent> ent, int shots = 1) 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); var ev = new ChangeChargeEvent(-ent.Comp.FireCost * shots);
RaiseLocalEvent(ent, ref ev); 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) private (EntityUid? Entity, IShootable) GetShootable(BatteryAmmoProviderComponent component, EntityCoordinates coordinates)
@ -140,9 +139,8 @@ public abstract partial class SharedGunSystem
UpdateShots(ent); 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. // 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. // Update the visuals and charge counter UI.
UpdateShots(ent); UpdateShots(ent);
@ -150,14 +148,6 @@ public abstract partial class SharedGunSystem
UpdateNextUpdate(ent, args.CurrentCharge, args.MaxCharge, args.CurrentChargeRate); 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) private void UpdateNextUpdate(Entity<BatteryAmmoProviderComponent> ent, float currentCharge, float maxCharge, float currentChargeRate)
{ {
// Don't queue any updates if charge is constant. // 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. // 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) 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); UpdateShots(ent);
} }
/// <summary> /// <summary>
/// Gets the current and maximum amount of shots from this entity's battery. /// 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> /// </summary>
public (int, int) GetShots(Entity<BatteryAmmoProviderComponent> ent) public (int, int) GetShots(Entity<BatteryAmmoProviderComponent> ent)
{ {
@ -197,8 +190,7 @@ public abstract partial class SharedGunSystem
} }
/// <summary> /// <summary>
/// Update loop for refreshing the ammo counter for charging/draining predicted batteries. /// Update loop for refreshing the ammo counter for charging/draining batteries.
/// This is not needed for unpredicted batteries since those already raise ChargeChangedEvent periodically.
/// </summary> /// </summary>
private void UpdateBattery(float frameTime) private void UpdateBattery(float frameTime)
{ {

View File

@ -633,7 +633,7 @@
damage: 35 damage: 35
sound: /Audio/Weapons/egloves.ogg sound: /Audio/Weapons/egloves.ogg
- type: LandAtCursor # it deals stamina damage when thrown - type: LandAtCursor # it deals stamina damage when thrown
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: GuideHelp - type: GuideHelp

View File

@ -241,10 +241,10 @@
startValue: 0.1 startValue: 0.1
endValue: 2.0 endValue: 2.0
isLooped: true 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. maxCharge: 600 #lights drain 3/s but recharge of 2 makes this 1/s. Therefore 600 is 10 minutes of light.
startingCharge: 600 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. 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 - type: entity

View File

@ -306,10 +306,10 @@
startValue: 0.1 startValue: 0.1
endValue: 2.0 endValue: 2.0
isLooped: true isLooped: true
- type: PredictedBattery - type: Battery
maxCharge: 600 maxCharge: 600
startingCharge: 600 startingCharge: 600
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 2 autoRechargeRate: 2
- type: Item - type: Item
size: Normal size: Normal

View File

@ -18,7 +18,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: DebugLaser proto: DebugLaser
fireCost: 1 fireCost: 1
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 1000 autoRechargeRate: 1000
- type: entity - type: entity

View File

@ -22,10 +22,10 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedLaser proto: RedLaser
fireCost: 62.5 fireCost: 62.5
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 40 autoRechargeRate: 40
- type: Gun - type: Gun
fireRate: 0.75 fireRate: 0.75

View File

@ -53,9 +53,9 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: WatcherBolt proto: WatcherBolt
fireCost: 50 fireCost: 50
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 50 autoRechargeRate: 50
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: Gun - type: Gun

View File

@ -171,10 +171,10 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedLaser proto: RedLaser
fireCost: 140 fireCost: 140
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 50 autoRechargeRate: 50
- type: Gun - type: Gun
fireRate: 0.3 fireRate: 0.3

View File

@ -52,9 +52,9 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedLightLaser proto: RedLightLaser
fireCost: 50 fireCost: 50
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 50 autoRechargeRate: 50
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: Gun - type: Gun

View File

@ -254,6 +254,11 @@
powerLoad: 500 powerLoad: 500
- type: ExtensionCableReceiver - type: ExtensionCableReceiver
- type: Battery - 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 maxCharge: 300000
startingCharge: 300000 startingCharge: 300000
- type: ApcPowerReceiverBattery - type: ApcPowerReceiverBattery

View File

@ -5,7 +5,7 @@
components: components:
- type: Item - type: Item
storedRotation: -90 storedRotation: -90
- type: PredictedBattery - type: Battery
pricePerJoule: 0.15 pricePerJoule: 0.15
- type: PowerCell - type: PowerCell
- type: Explosive - type: Explosive
@ -32,7 +32,7 @@
- PowerCell - PowerCell
- type: Riggable - type: Riggable
- type: Appearance - type: Appearance
- type: PredictedBatteryVisuals - type: BatteryVisuals
- type: GenericVisualizer - type: GenericVisualizer
visuals: visuals:
enum.BatteryVisuals.State: enum.BatteryVisuals.State:
@ -50,7 +50,7 @@
- type: Sprite - type: Sprite
layers: layers:
- state: potato - state: potato
- type: PredictedBattery - type: Battery
maxCharge: 70 maxCharge: 70
startingCharge: 70 startingCharge: 70
- type: Tag - type: Tag
@ -74,7 +74,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 360 maxCharge: 360
startingCharge: 360 startingCharge: 360
- type: Tag - type: Tag
@ -94,7 +94,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
maxCharge: 360 maxCharge: 360
startingCharge: 0 startingCharge: 0
@ -104,7 +104,7 @@
name: small-capacity nuclear power cell name: small-capacity nuclear power cell
description: A self rechargeable power cell, designed for fast recharge rate at the expense of capacity. description: A self rechargeable power cell, designed for fast recharge rate at the expense of capacity.
components: components:
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 36 # 10 seconds to recharge autoRechargeRate: 36 # 10 seconds to recharge
autoRechargePauseTime: 30 autoRechargePauseTime: 30
@ -122,7 +122,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 720 maxCharge: 720
startingCharge: 720 startingCharge: 720
@ -139,7 +139,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
maxCharge: 720 maxCharge: 720
startingCharge: 0 startingCharge: 0
@ -157,7 +157,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 1080 maxCharge: 1080
startingCharge: 1080 startingCharge: 1080
- type: ReverseEngineering # DeltaV: Upgrade line of high -> hyper -> microreactor - type: ReverseEngineering # DeltaV: Upgrade line of high -> hyper -> microreactor
@ -178,7 +178,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
maxCharge: 1080 maxCharge: 1080
startingCharge: 0 startingCharge: 0
@ -196,7 +196,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 1800 maxCharge: 1800
startingCharge: 1800 startingCharge: 1800
- type: ReverseEngineering # DeltaV - type: ReverseEngineering # DeltaV
@ -217,7 +217,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
maxCharge: 1800 maxCharge: 1800
startingCharge: 0 startingCharge: 0
@ -235,10 +235,10 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 720 maxCharge: 720
startingCharge: 720 startingCharge: 720
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 12 # takes 1 minute to charge itself back to full autoRechargeRate: 12 # takes 1 minute to charge itself back to full
- type: entity - type: entity
@ -254,7 +254,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
startingCharge: 0 startingCharge: 0
- type: entity - type: entity
@ -270,10 +270,10 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 1200 maxCharge: 1200
startingCharge: 1200 startingCharge: 1200
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 40 autoRechargeRate: 40
- type: ReverseEngineering # DeltaV - type: ReverseEngineering # DeltaV
difficulty: 4 difficulty: 4
@ -321,7 +321,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 1400 maxCharge: 1400
startingCharge: 1400 startingCharge: 1400
@ -339,7 +339,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 2700 maxCharge: 2700
startingCharge: 2700 startingCharge: 2700
@ -357,7 +357,7 @@
- map: [ "enum.PowerCellVisualLayers.Unshaded" ] - map: [ "enum.PowerCellVisualLayers.Unshaded" ]
state: o2 state: o2
shader: unshaded shader: unshaded
- type: PredictedBattery - type: Battery
maxCharge: 6200 maxCharge: 6200
startingCharge: 6200 startingCharge: 6200
@ -375,7 +375,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
maxCharge: 1400 maxCharge: 1400
startingCharge: 0 startingCharge: 0
@ -393,7 +393,7 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
startingCharge: 0 startingCharge: 0
- type: entity - type: entity
@ -410,5 +410,5 @@
state: o2 state: o2
shader: unshaded shader: unshaded
visible: false visible: false
- type: PredictedBattery - type: Battery
startingCharge: 0 startingCharge: 0

View File

@ -40,6 +40,11 @@
acts: [ "Destruction" ] acts: [ "Destruction" ]
- type: PowerSink - type: PowerSink
- type: Battery - 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 maxCharge: 250000000
pricePerJoule: 0.000009 pricePerJoule: 0.000009
- type: ExaminableBattery - type: ExaminableBattery

View File

@ -49,7 +49,7 @@
parent: [ HandheldHealthAnalyzerUnpowered, PowerCellSlotSmallItem] parent: [ HandheldHealthAnalyzerUnpowered, PowerCellSlotSmallItem]
suffix: "" suffix: ""
components: components:
- type: PredictedBatteryVisuals - type: BatteryVisuals
- type: PowerCellDraw - type: PowerCellDraw
drawRate: 1.2 #Calculated for 5 minutes on a small cell drawRate: 1.2 #Calculated for 5 minutes on a small cell
- type: ToggleCellDraw - type: ToggleCellDraw

View File

@ -19,7 +19,7 @@
maxRange: 256 maxRange: 256
followEntity: true followEntity: true
- type: Appearance - type: Appearance
- type: PredictedBatteryVisuals - type: BatteryVisuals
- type: GenericVisualizer - type: GenericVisualizer
visuals: visuals:
enum.BatteryVisuals.State: enum.BatteryVisuals.State:

View File

@ -20,7 +20,7 @@
- SemiAuto - SemiAuto
soundGunshot: soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/laser.ogg path: /Audio/Weapons/Guns/Gunshots/laser.ogg
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: StaticPrice - type: StaticPrice
@ -232,7 +232,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedLaser proto: RedLaser
fireCost: 62.5 fireCost: 62.5
- type: PredictedBattery - type: Battery
maxCharge: 500 maxCharge: 500
startingCharge: 500 startingCharge: 500
- type: Tag - type: Tag
@ -362,7 +362,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: Pulse proto: Pulse
fireCost: 200 fireCost: 200
- type: PredictedBattery - type: Battery
maxCharge: 2000 maxCharge: 2000
startingCharge: 2000 startingCharge: 2000
- type: Tag - type: Tag
@ -404,7 +404,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: Pulse proto: Pulse
fireCost: 200 fireCost: 200
- type: PredictedBattery - type: Battery
maxCharge: 5000 maxCharge: 5000
startingCharge: 5000 startingCharge: 5000
@ -440,7 +440,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: Pulse proto: Pulse
fireCost: 100 fireCost: 100
- type: PredictedBattery - type: Battery
maxCharge: 40000 maxCharge: 40000
startingCharge: 40000 startingCharge: 40000
@ -507,7 +507,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: AntiParticlesProjectile proto: AntiParticlesProjectile
fireCost: 500 fireCost: 500
- type: PredictedBattery - type: Battery
maxCharge: 10000 maxCharge: 10000
startingCharge: 10000 startingCharge: 10000
@ -740,7 +740,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedMediumLaser proto: RedMediumLaser
fireCost: 100 fireCost: 100
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 40 autoRechargeRate: 40
- type: MagazineVisuals - type: MagazineVisuals
magState: mag magState: mag
@ -789,7 +789,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedMediumLaser proto: RedMediumLaser
fireCost: 100 fireCost: 100
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 30 autoRechargeRate: 30
- type: MagazineVisuals - type: MagazineVisuals
magState: mag magState: mag
@ -888,7 +888,7 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: RedMediumLaser proto: RedMediumLaser
fireCost: 100 fireCost: 100
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 40 autoRechargeRate: 40
- type: StaticPrice - type: StaticPrice
price: 750 price: 750
@ -937,7 +937,7 @@
sprite: Objects/Weapons/Guns/Battery/inhands_64x.rsi sprite: Objects/Weapons/Guns/Battery/inhands_64x.rsi
heldPrefix: energy heldPrefix: energy
- type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: GunRequiresWield #remove when inaccuracy on spreads is fixed
- type: PredictedBattery - type: Battery
maxCharge: 720 # Delta V - Was 480 maxCharge: 720 # Delta V - Was 480
startingCharge: 720 startingCharge: 720
- type: BatterySelfRecharger #Delta V - Self recharging not removed - type: BatterySelfRecharger #Delta V - Self recharging not removed
@ -988,7 +988,7 @@
- proto: BulletDisabler - proto: BulletDisabler
fireCost: 62.5 fireCost: 62.5
pacifismAllowedMode: true pacifismAllowedMode: true
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 48 autoRechargeRate: 48
autoRechargePauseTime: 10 autoRechargePauseTime: 10
@ -1030,7 +1030,7 @@
fireCost: 100 fireCost: 100
- proto: BoltTempgunHot - proto: BoltTempgunHot
fireCost: 100 fireCost: 100
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: StaticPrice - type: StaticPrice

View File

@ -124,6 +124,11 @@
proto: BulletEnergyTurretLaser proto: BulletEnergyTurretLaser
fireCost: 100 fireCost: 100
- type: Battery - 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 maxCharge: 2000
startingCharge: 2000 startingCharge: 2000
- type: ApcPowerReceiverBattery - type: ApcPowerReceiverBattery

View File

@ -131,10 +131,10 @@
- type: BatteryAmmoProvider - type: BatteryAmmoProvider
proto: FoodPieBananaCream proto: FoodPieBananaCream
fireCost: 30 fireCost: 30
- type: PredictedBattery - type: Battery
maxCharge: 90 maxCharge: 90
startingCharge: 90 startingCharge: 90
- type: PredictedBatterySelfRecharger - type: BatterySelfRecharger
autoRechargeRate: 1 autoRechargeRate: 1
- type: AmmoCounter - type: AmmoCounter
- type: Item - type: Item

View File

@ -42,7 +42,7 @@
damage: 35 damage: 35
sound: /Audio/Weapons/egloves.ogg sound: /Audio/Weapons/egloves.ogg
- type: LandAtCursor # it deals stamina damage when thrown - type: LandAtCursor # it deals stamina damage when thrown
- type: PredictedBattery - type: Battery
maxCharge: 360 maxCharge: 360
startingCharge: 360 startingCharge: 360
- type: UseDelay - type: UseDelay

View File

@ -47,7 +47,7 @@
damage: 35 damage: 35
sound: /Audio/Weapons/egloves.ogg sound: /Audio/Weapons/egloves.ogg
- type: LandAtCursor # it deals stamina damage when thrown - type: LandAtCursor # it deals stamina damage when thrown
- type: PredictedBattery - type: Battery
maxCharge: 1000 maxCharge: 1000
startingCharge: 1000 startingCharge: 1000
- type: Item - type: Item

View File

@ -458,6 +458,11 @@
- type: ApcPowerReceiver - type: ApcPowerReceiver
- type: ExtensionCableReceiver - type: ExtensionCableReceiver
- type: Battery - 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 maxCharge: 30000
startingCharge: 0 startingCharge: 0
- type: EmergencyLight - type: EmergencyLight

View File

@ -56,6 +56,11 @@
sprite: Structures/Power/Generation/Tesla/coil.rsi sprite: Structures/Power/Generation/Tesla/coil.rsi
state: coil state: coil
- type: Battery - 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 maxCharge: 5000000
startingCharge: 0 startingCharge: 0
- type: BatteryDischarger - type: BatteryDischarger

View File

@ -56,6 +56,11 @@
- type: Appearance - type: Appearance
- type: ApcVisuals - type: ApcVisuals
- type: Battery - 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 maxCharge: 50000
startingCharge: 0 startingCharge: 0
- type: ExaminableBattery - type: ExaminableBattery

View File

@ -73,6 +73,11 @@
sprite: Structures/Power/power.rsi sprite: Structures/Power/power.rsi
state: provider state: provider
- type: Battery - 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 - type: NodeContainer
nodes: nodes:
input: input:
@ -104,6 +109,11 @@
sprite: Structures/Power/power.rsi sprite: Structures/Power/power.rsi
state: provider state: provider
- type: Battery - 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 - type: NodeContainer
nodes: nodes:
input: input:

View File

@ -33,6 +33,11 @@
- type: Smes - type: Smes
- type: Appearance - type: Appearance
- type: Battery - 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 startingCharge: 0
maxCharge: 8000000 maxCharge: 8000000
- type: ExaminableBattery - type: ExaminableBattery

View File

@ -5,6 +5,11 @@
components: components:
# Core power behavior # Core power behavior
- type: Battery - 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: ExaminableBattery
- type: NodeContainer - type: NodeContainer
examinable: true examinable: true