ECS BatteryComponent (#15337)

This commit is contained in:
Leon Friedrich
2023-04-19 22:10:08 +12:00
committed by GitHub
parent e95cd6e412
commit 9513ea66e4
6 changed files with 128 additions and 112 deletions

View File

@@ -1,3 +1,5 @@
using Content.Server.Power.EntitySystems;
namespace Content.Server.Power.Components namespace Content.Server.Power.Components
{ {
/// <summary> /// <summary>
@@ -12,17 +14,32 @@ namespace Content.Server.Power.Components
/// <summary> /// <summary>
/// Maximum charge of the battery in joules (ie. watt seconds) /// Maximum charge of the battery in joules (ie. watt seconds)
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] public float MaxCharge { get => _maxCharge; set => SetMaxCharge(value); } [ViewVariables(VVAccess.ReadWrite)]
public float MaxCharge
{
get => _maxCharge;
[Obsolete("Use system method")]
set => _entMan.System<BatterySystem>().SetMaxCharge(Owner, value, this);
}
[DataField("maxCharge")] [DataField("maxCharge")]
private float _maxCharge; [Access(typeof(BatterySystem))]
public float _maxCharge;
/// <summary> /// <summary>
/// Current charge of the battery in joules (ie. watt seconds) /// Current charge of the battery in joules (ie. watt seconds)
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float CurrentCharge { get => _currentCharge; set => SetCurrentCharge(value); } public float CurrentCharge
{
get => Charge;
[Obsolete("Use system method")]
set => _entMan.System<BatterySystem>().SetCharge(Owner, value, this);
}
[DataField("startingCharge")] [DataField("startingCharge")]
private float _currentCharge; [Access(typeof(BatterySystem))]
public float Charge;
/// <summary> /// <summary>
/// True if the battery is fully charged. /// True if the battery is fully charged.
@@ -36,61 +53,14 @@ namespace Content.Server.Power.Components
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float PricePerJoule = 0.0001f; public float PricePerJoule = 0.0001f;
/// <summary> [Obsolete("Use system method")]
/// If sufficient charge is avaiable on the battery, use it. Otherwise, don't. public bool TryUseCharge(float value)
/// </summary> => _entMan.System<BatterySystem>().TryUseCharge(Owner, value, this);
public virtual bool TryUseCharge(float chargeToUse)
{
if (chargeToUse > CurrentCharge)
{
return false;
}
else
{
CurrentCharge -= chargeToUse;
return true;
}
}
public virtual float UseCharge(float toDeduct)
{
var chargeChangedBy = Math.Min(CurrentCharge, toDeduct);
CurrentCharge -= chargeChangedBy;
return chargeChangedBy;
}
public void FillFrom(BatteryComponent battery)
{
var powerDeficit = MaxCharge - CurrentCharge;
if (battery.TryUseCharge(powerDeficit))
{
CurrentCharge += powerDeficit;
}
else
{
CurrentCharge += battery.CurrentCharge;
battery.CurrentCharge = 0;
}
}
protected virtual void OnChargeChanged()
{
_entMan.EventBus.RaiseLocalEvent(Owner, new ChargeChangedEvent(), false);
}
private void SetMaxCharge(float newMax)
{
_maxCharge = Math.Max(newMax, 0);
_currentCharge = Math.Min(_currentCharge, MaxCharge);
OnChargeChanged();
}
private void SetCurrentCharge(float newChargeAmount)
{
_currentCharge = MathHelper.Clamp(newChargeAmount, 0, MaxCharge);
OnChargeChanged();
}
} }
public struct ChargeChangedEvent {} /// <summary>
/// Raised when a battery's charge or capacity changes (capacity affects relative charge percentage).
/// </summary>
[ByRefEvent]
public record struct ChargeChangedEvent(float Charge, float MaxCharge);
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Emp; using Content.Server.Emp;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.Pow3r;
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.APC; using Content.Shared.APC;
@@ -54,7 +55,7 @@ namespace Content.Server.Power.EntitySystems
} }
// Change the APC's state only when the battery state changes, or when it's first created. // Change the APC's state only when the battery state changes, or when it's first created.
private void OnBatteryChargeChanged(EntityUid uid, ApcComponent component, ChargeChangedEvent args) private void OnBatteryChargeChanged(EntityUid uid, ApcComponent component, ref ChargeChangedEvent args)
{ {
UpdateApcState(uid, component); UpdateApcState(uid, component);
} }
@@ -116,7 +117,7 @@ namespace Content.Server.Power.EntitySystems
public void UpdateApcState(EntityUid uid, public void UpdateApcState(EntityUid uid,
ApcComponent? apc=null, ApcComponent? apc=null,
BatteryComponent? battery=null) PowerNetworkBatteryComponent? battery = null)
{ {
if (!Resolve(uid, ref apc, ref battery)) if (!Resolve(uid, ref apc, ref battery))
return; return;
@@ -126,7 +127,7 @@ namespace Content.Server.Power.EntitySystems
UpdatePanelAppearance(uid, appearance, apc); UpdatePanelAppearance(uid, appearance, apc);
} }
var newState = CalcChargeState(uid, apc, battery); var newState = CalcChargeState(uid, battery.NetworkBattery);
if (newState != apc.LastChargeState && apc.LastChargeStateTime + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime) if (newState != apc.LastChargeState && apc.LastChargeStateTime + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime)
{ {
apc.LastChargeState = newState; apc.LastChargeState = newState;
@@ -138,7 +139,7 @@ namespace Content.Server.Power.EntitySystems
} }
} }
var extPowerState = CalcExtPowerState(uid, apc, battery); var extPowerState = CalcExtPowerState(uid, battery.NetworkBattery);
if (extPowerState != apc.LastExternalState if (extPowerState != apc.LastExternalState
|| apc.LastUiUpdate + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime) || apc.LastUiUpdate + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime)
{ {
@@ -150,58 +151,43 @@ namespace Content.Server.Power.EntitySystems
public void UpdateUIState(EntityUid uid, public void UpdateUIState(EntityUid uid,
ApcComponent? apc = null, ApcComponent? apc = null,
BatteryComponent? battery = null, PowerNetworkBatteryComponent? netBat = null,
ServerUserInterfaceComponent? ui = null) ServerUserInterfaceComponent? ui = null)
{ {
if (!Resolve(uid, ref apc, ref battery, ref ui)) if (!Resolve(uid, ref apc, ref netBat, ref ui))
return; return;
var netBattery = Comp<PowerNetworkBatteryComponent>(uid); var battery = netBat.NetworkBattery;
float power = netBattery is not null ? netBattery.CurrentSupply : 0f;
if (_userInterfaceSystem.GetUiOrNull(uid, ApcUiKey.Key, ui) is { } bui) var state = new ApcBoundInterfaceState(apc.MainBreakerEnabled, apc.HasAccess,
{ (int) MathF.Ceiling(battery.CurrentSupply), apc.LastExternalState,
bui.SetState(new ApcBoundInterfaceState(apc.MainBreakerEnabled, apc.HasAccess, (int)MathF.Ceiling(power), apc.LastExternalState, battery.CurrentCharge / battery.MaxCharge)); battery.AvailableSupply / battery.Capacity);
}
_userInterfaceSystem.TrySetUiState(uid, ApcUiKey.Key, state, ui: ui);
} }
public ApcChargeState CalcChargeState(EntityUid uid, private ApcChargeState CalcChargeState(EntityUid uid, PowerState.Battery battery)
ApcComponent? apc=null,
BatteryComponent? battery=null)
{ {
if (apc != null && HasComp<EmaggedComponent>(uid)) if (HasComp<EmaggedComponent>(uid))
return ApcChargeState.Emag; return ApcChargeState.Emag;
if (!Resolve(uid, ref apc, ref battery)) if (battery.CurrentStorage / battery.Capacity > ApcComponent.HighPowerThreshold)
return ApcChargeState.Lack;
var chargeFraction = battery.CurrentCharge / battery.MaxCharge;
if (chargeFraction > ApcComponent.HighPowerThreshold)
{ {
return ApcChargeState.Full; return ApcChargeState.Full;
} }
var netBattery = Comp<PowerNetworkBatteryComponent>(uid); var delta = battery.CurrentSupply - battery.CurrentReceiving;
var delta = netBattery.CurrentSupply - netBattery.CurrentReceiving;
return delta < 0 ? ApcChargeState.Charging : ApcChargeState.Lack; return delta < 0 ? ApcChargeState.Charging : ApcChargeState.Lack;
} }
public ApcExternalPowerState CalcExtPowerState(EntityUid uid, private ApcExternalPowerState CalcExtPowerState(EntityUid uid, PowerState.Battery battery)
ApcComponent? apc=null,
BatteryComponent? battery=null)
{ {
if (!Resolve(uid, ref apc, ref battery)) if (battery.CurrentReceiving == 0 && !MathHelper.CloseTo(battery.CurrentStorage / battery.Capacity, 1))
return ApcExternalPowerState.None;
var netBat = Comp<PowerNetworkBatteryComponent>(uid);
if (netBat.CurrentReceiving == 0 && !MathHelper.CloseTo(battery.CurrentCharge / battery.MaxCharge, 1))
{ {
return ApcExternalPowerState.None; return ApcExternalPowerState.None;
} }
var delta = netBat.CurrentReceiving - netBat.CurrentSupply; var delta = battery.CurrentSupply - battery.CurrentReceiving;
if (!MathHelper.CloseToPercent(delta, 0, 0.1f) && delta < 0) if (!MathHelper.CloseToPercent(delta, 0, 0.1f) && delta < 0)
{ {
return ApcExternalPowerState.Low; return ApcExternalPowerState.Low;

View File

@@ -4,6 +4,7 @@ using Content.Server.Power.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Utility;
namespace Content.Server.Power.EntitySystems namespace Content.Server.Power.EntitySystems
{ {
@@ -61,8 +62,9 @@ namespace Content.Server.Power.EntitySystems
var enumerator = AllEntityQuery<PowerNetworkBatteryComponent, BatteryComponent>(); var enumerator = AllEntityQuery<PowerNetworkBatteryComponent, BatteryComponent>();
while (enumerator.MoveNext(out var netBat, out var bat)) while (enumerator.MoveNext(out var netBat, out var bat))
{ {
DebugTools.Assert(bat.Charge <= bat.MaxCharge && bat.Charge >= 0);
netBat.NetworkBattery.Capacity = bat.MaxCharge; netBat.NetworkBattery.Capacity = bat.MaxCharge;
netBat.NetworkBattery.CurrentStorage = bat.CurrentCharge; netBat.NetworkBattery.CurrentStorage = bat.Charge;
} }
} }
@@ -73,10 +75,17 @@ namespace Content.Server.Power.EntitySystems
while (enumerator.MoveNext(out var uid, out var netBat, out var bat)) while (enumerator.MoveNext(out var uid, out var netBat, out var bat))
{ {
var netCharge = netBat.NetworkBattery.CurrentStorage; var netCharge = netBat.NetworkBattery.CurrentStorage;
bat.Charge = netCharge;
DebugTools.Assert(bat.Charge <= bat.MaxCharge && bat.Charge >= 0);
// TODO maybe decrease tolerance & track the charge at the time the event was most recently raised.
// Ensures that events aren't skipped when there are many tiny power changes.
if (MathHelper.CloseTo(bat.CurrentCharge, netCharge)) if (MathHelper.CloseTo(bat.CurrentCharge, netCharge))
continue; continue;
bat.CurrentCharge = netCharge; var changeEv = new ChargeChangedEvent(netCharge, bat.MaxCharge);
RaiseLocalEvent(uid, ref changeEv);
} }
} }
@@ -101,7 +110,61 @@ namespace Content.Server.Power.EntitySystems
private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args) private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args)
{ {
args.Affected = true; args.Affected = true;
component.UseCharge(args.EnergyConsumption); UseCharge(uid, args.EnergyConsumption, component);
}
public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0)
return 0;
var newValue = Math.Clamp(0, battery.CurrentCharge - value, battery._maxCharge);
var delta = newValue - battery.Charge;
battery.Charge = newValue;
var ev = new ChargeChangedEvent(battery.CurrentCharge, battery._maxCharge);
RaiseLocalEvent(uid, ref ev);
return delta;
}
public void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
if (!Resolve(uid, ref battery))
return;
var old = battery._maxCharge;
battery._maxCharge = Math.Max(value, 0);
battery.Charge = Math.Min(battery.Charge, battery._maxCharge);
if (MathHelper.CloseTo(battery._maxCharge, old))
return;
var ev = new ChargeChangedEvent(battery.CurrentCharge, battery._maxCharge);
RaiseLocalEvent(uid, ref ev);
}
public void SetCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
if (!Resolve(uid, ref battery))
return;
var old = battery.Charge;
battery.Charge = MathHelper.Clamp(value, 0, battery._maxCharge);
if (MathHelper.CloseTo(battery.Charge, old))
return;
var ev = new ChargeChangedEvent(battery.CurrentCharge, battery._maxCharge);
RaiseLocalEvent(uid, ref ev);
}
/// <summary>
/// If sufficient charge is available on the battery, use it. Otherwise, don't.
/// </summary>
public bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
{
if (!Resolve(uid, ref battery, false) || value > battery.Charge)
return false;
UseCharge(uid, value, battery);
return true;
} }
} }
} }

View File

@@ -66,7 +66,7 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
Explode(uid, component, args.User); Explode(uid, component, args.User);
} }
private void OnChargeChanged(EntityUid uid, PowerCellComponent component, ChargeChangedEvent args) private void OnChargeChanged(EntityUid uid, PowerCellComponent component, ref ChargeChangedEvent args)
{ {
if (component.IsRigged) if (component.IsRigged)
{ {
@@ -74,13 +74,10 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
return; return;
} }
if (!TryComp(uid, out BatteryComponent? battery))
return;
if (!TryComp(uid, out AppearanceComponent? appearance)) if (!TryComp(uid, out AppearanceComponent? appearance))
return; return;
var frac = battery.CurrentCharge / battery.MaxCharge; var frac = args.Charge / args.MaxCharge;
var level = (byte) ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels); var level = (byte) ContentHelpers.RoundToNearestLevels(frac, 1, PowerCellComponent.PowerCellVisualsLevels);
_sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level, appearance); _sharedAppearanceSystem.SetData(uid, PowerCellVisuals.ChargeLevel, level, appearance);

View File

@@ -32,9 +32,9 @@ public sealed partial class GunSystem
UpdateShots(uid, component); UpdateShots(uid, component);
} }
private void OnBatteryChargeChange(EntityUid uid, BatteryAmmoProviderComponent component, ChargeChangedEvent args) private void OnBatteryChargeChange(EntityUid uid, BatteryAmmoProviderComponent component, ref ChargeChangedEvent args)
{ {
UpdateShots(uid, component); UpdateShots(uid, component, args.Charge, args.MaxCharge);
} }
private void UpdateShots(EntityUid uid, BatteryAmmoProviderComponent component) private void UpdateShots(EntityUid uid, BatteryAmmoProviderComponent component)
@@ -42,13 +42,13 @@ public sealed partial class GunSystem
if (!TryComp<BatteryComponent>(uid, out var battery)) if (!TryComp<BatteryComponent>(uid, out var battery))
return; return;
UpdateShots(uid, component, battery); UpdateShots(uid, component, battery.Charge, battery.MaxCharge);
} }
private void UpdateShots(EntityUid uid, BatteryAmmoProviderComponent component, BatteryComponent battery) private void UpdateShots(EntityUid uid, BatteryAmmoProviderComponent component, float charge, float maxCharge)
{ {
var shots = (int) (battery.CurrentCharge / component.FireCost); var shots = (int) (charge / component.FireCost);
var maxShots = (int) (battery.MaxCharge / component.FireCost); var maxShots = (int) (maxCharge / component.FireCost);
if (component.Shots != shots || component.Capacity != maxShots) if (component.Shots != shots || component.Capacity != maxShots)
{ {
@@ -128,10 +128,7 @@ public sealed partial class GunSystem
protected override void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) protected override void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component)
{ {
if (!TryComp<BatteryComponent>(uid, out var battery)) // Will raise ChargeChangedEvent
return; _battery.UseCharge(uid, component.FireCost);
battery.CurrentCharge -= component.FireCost;
UpdateShots(uid, component, battery);
} }
} }

View File

@@ -3,6 +3,7 @@ using Content.Server.Administration.Logs;
using Content.Server.Cargo.Systems; using Content.Server.Cargo.Systems;
using Content.Server.Examine; using Content.Server.Examine;
using Content.Server.Interaction; using Content.Server.Interaction;
using Content.Server.Power.EntitySystems;
using Content.Server.Stunnable; using Content.Server.Stunnable;
using Content.Server.Weapons.Ranged.Components; using Content.Server.Weapons.Ranged.Components;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -37,6 +38,8 @@ public sealed partial class GunSystem : SharedGunSystem
[Dependency] private readonly StaminaSystem _stamina = default!; [Dependency] private readonly StaminaSystem _stamina = default!;
[Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly BatterySystem _battery = default!;
public const float DamagePitchVariation = SharedMeleeWeaponSystem.DamagePitchVariation; public const float DamagePitchVariation = SharedMeleeWeaponSystem.DamagePitchVariation;
public const float GunClumsyChance = 0.5f; public const float GunClumsyChance = 0.5f;