using Content.Server.Cargo.Systems; using Content.Server.Emp; using Content.Server.Power.Components; using Content.Shared.Examine; using Content.Shared.Rejuvenate; using Content.Shared.Timing; using JetBrains.Annotations; using Robust.Shared.Utility; using Robust.Shared.Timing; namespace Content.Server.Power.EntitySystems { [UsedImplicitly] public sealed class BatterySystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnNetBatteryRejuvenate); SubscribeLocalEvent(OnBatteryRejuvenate); SubscribeLocalEvent(CalculateBatteryPrice); SubscribeLocalEvent(OnEmpPulse); SubscribeLocalEvent(OnChangeCharge); SubscribeLocalEvent(OnGetCharge); SubscribeLocalEvent(PreSync); SubscribeLocalEvent(PostSync); } private void OnNetBatteryRejuvenate(EntityUid uid, PowerNetworkBatteryComponent component, RejuvenateEvent args) { component.NetworkBattery.CurrentStorage = component.NetworkBattery.Capacity; } private void OnBatteryRejuvenate(EntityUid uid, BatteryComponent component, RejuvenateEvent args) { SetCharge(uid, component.MaxCharge, component); } private void OnExamine(EntityUid uid, ExaminableBatteryComponent component, ExaminedEvent args) { if (!TryComp(uid, out var batteryComponent)) return; if (args.IsInDetailsRange) { var effectiveMax = batteryComponent.MaxCharge; if (effectiveMax == 0) effectiveMax = 1; var chargeFraction = batteryComponent.CurrentCharge / effectiveMax; var chargePercentRounded = (int) (chargeFraction * 100); args.PushMarkup( Loc.GetString( "examinable-battery-component-examine-detail", ("percent", chargePercentRounded), ("markupPercentColor", "green") ) ); } } private void PreSync(NetworkBatteryPreSync ev) { // Ignoring entity pausing. If the entity was paused, neither component's data should have been changed. var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var netBat, out var bat)) { DebugTools.Assert(bat.CurrentCharge <= bat.MaxCharge && bat.CurrentCharge >= 0); netBat.NetworkBattery.Capacity = bat.MaxCharge; netBat.NetworkBattery.CurrentStorage = bat.CurrentCharge; } } private void PostSync(NetworkBatteryPostSync ev) { // Ignoring entity pausing. If the entity was paused, neither component's data should have been changed. var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var netBat, out var bat)) { SetCharge(uid, netBat.NetworkBattery.CurrentStorage, bat); } } public override void Update(float frameTime) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp, out var batt)) { if (!comp.AutoRecharge || IsFull(uid, batt)) continue; if (comp.AutoRechargePause) { if (comp.NextAutoRecharge > _timing.CurTime) continue; } SetCharge(uid, batt.CurrentCharge + comp.AutoRechargeRate * frameTime, batt); } } /// /// Gets the price for the power contained in an entity's battery. /// private void CalculateBatteryPrice(EntityUid uid, BatteryComponent component, ref PriceCalculationEvent args) { args.Price += component.CurrentCharge * component.PricePerJoule; } private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseEvent args) { args.Affected = true; UseCharge(uid, args.EnergyConsumption, component); // Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP. TrySetChargeCooldown(uid); } private void OnChangeCharge(Entity entity, ref ChangeChargeEvent args) { if (args.ResidualValue == 0) return; args.ResidualValue -= ChangeCharge(entity, args.ResidualValue); } private void OnGetCharge(Entity entity, ref GetChargeEvent args) { args.CurrentCharge += entity.Comp.CurrentCharge; args.MaxCharge += entity.Comp.MaxCharge; } public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0) return 0; return ChangeCharge(uid, -value, battery); } 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.CurrentCharge = Math.Min(battery.CurrentCharge, 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.CurrentCharge; battery.CurrentCharge = MathHelper.Clamp(value, 0, battery.MaxCharge); if (MathHelper.CloseTo(battery.CurrentCharge, old) && !(old != battery.CurrentCharge && battery.CurrentCharge == battery.MaxCharge)) { return; } var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge); RaiseLocalEvent(uid, ref ev); } /// /// Changes the current battery charge by some value /// public float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (!Resolve(uid, ref battery)) return 0; var newValue = Math.Clamp(0, battery.CurrentCharge + value, battery.MaxCharge); var delta = newValue - battery.CurrentCharge; battery.CurrentCharge = newValue; TrySetChargeCooldown(uid); var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge); RaiseLocalEvent(uid, ref ev); return delta; } /// /// Checks if the entity has a self recharge and puts it on cooldown if applicable. /// public void TrySetChargeCooldown(EntityUid uid, float value = -1) { if (!TryComp(uid, out var batteryself)) return; if (!batteryself.AutoRechargePause) return; // If no answer or a negative is given for value, use the default from AutoRechargePauseTime. if (value < 0) value = batteryself.AutoRechargePauseTime; if (_timing.CurTime + TimeSpan.FromSeconds(value) <= batteryself.NextAutoRecharge) return; SetChargeCooldown(uid, batteryself.AutoRechargePauseTime, batteryself); } /// /// Puts the entity's self recharge on cooldown for the specified time. /// public void SetChargeCooldown(EntityUid uid, float value, BatterySelfRechargerComponent? batteryself = null) { if (!Resolve(uid, ref batteryself)) return; if (value >= 0) batteryself.NextAutoRecharge = _timing.CurTime + TimeSpan.FromSeconds(value); else batteryself.NextAutoRecharge = _timing.CurTime; } /// /// If sufficient charge is available on the battery, use it. Otherwise, don't. /// public bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null) { if (!Resolve(uid, ref battery, false) || value > battery.CurrentCharge) return false; UseCharge(uid, value, battery); return true; } /// /// Returns whether the battery is full. /// public bool IsFull(EntityUid uid, BatteryComponent? battery = null) { if (!Resolve(uid, ref battery)) return false; return battery.CurrentCharge >= battery.MaxCharge; } } }