| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- using Content.Server.Administration.Logs;
- using Content.Server.Audio;
- using Content.Server.Power.Components;
- using Content.Shared.Database;
- using Content.Shared.Power;
- using Content.Shared.UserInterface;
- using Robust.Server.GameObjects;
- using Robust.Shared.Player;
- namespace Content.Server.Power.EntitySystems;
- public sealed class PowerChargeSystem : EntitySystem
- {
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly AmbientSoundSystem _ambientSoundSystem = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<PowerChargeComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<PowerChargeComponent, ComponentShutdown>(OnComponentShutdown);
- SubscribeLocalEvent<PowerChargeComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
- SubscribeLocalEvent<PowerChargeComponent, AfterActivatableUIOpenEvent>(OnAfterUiOpened);
- SubscribeLocalEvent<PowerChargeComponent, AnchorStateChangedEvent>(OnAnchorStateChange);
- // This needs to be ui key agnostic
- SubscribeLocalEvent<PowerChargeComponent, SwitchChargingMachineMessage>(OnSwitchGenerator);
- }
- private void OnAnchorStateChange(EntityUid uid, PowerChargeComponent component, AnchorStateChangedEvent args)
- {
- if (args.Anchored || !TryComp<ApcPowerReceiverComponent>(uid, out var powerReceiverComponent))
- return;
- component.Active = false;
- component.Charge = 0;
- UpdateState(new Entity<PowerChargeComponent, ApcPowerReceiverComponent>(uid, component, powerReceiverComponent));
- }
- private void OnAfterUiOpened(EntityUid uid, PowerChargeComponent component, AfterActivatableUIOpenEvent args)
- {
- if (!TryComp<ApcPowerReceiverComponent>(uid, out var apcPowerReceiver))
- return;
- UpdateUI((uid, component, apcPowerReceiver), component.ChargeRate);
- }
- private void OnSwitchGenerator(EntityUid uid, PowerChargeComponent component, SwitchChargingMachineMessage args)
- {
- SetSwitchedOn(uid, component, args.On, user: args.Actor);
- }
- private void OnUIOpenAttempt(EntityUid uid, PowerChargeComponent component, ActivatableUIOpenAttemptEvent args)
- {
- if (!component.Intact)
- args.Cancel();
- }
- private void OnComponentShutdown(EntityUid uid, PowerChargeComponent component, ComponentShutdown args)
- {
- if (!component.Active)
- return;
- component.Active = false;
- var eventArgs = new ChargedMachineDeactivatedEvent();
- RaiseLocalEvent(uid, ref eventArgs);
- }
- private void OnMapInit(Entity<PowerChargeComponent> ent, ref MapInitEvent args)
- {
- ApcPowerReceiverComponent? powerReceiver = null;
- if (!Resolve(ent, ref powerReceiver, false))
- return;
- UpdatePowerState(ent, powerReceiver);
- UpdateState((ent, ent.Comp, powerReceiver));
- }
- private void SetSwitchedOn(EntityUid uid, PowerChargeComponent component, bool on,
- ApcPowerReceiverComponent? powerReceiver = null, EntityUid? user = null)
- {
- if (!Resolve(uid, ref powerReceiver))
- return;
- if (user is { } )
- _adminLogger.Add(LogType.Action, on ? LogImpact.Medium : LogImpact.High, $"{ToPrettyString(user):player} set ${ToPrettyString(uid):target} to {(on ? "on" : "off")}");
- component.SwitchedOn = on;
- UpdatePowerState(component, powerReceiver);
- component.NeedUIUpdate = true;
- }
- private static void UpdatePowerState(PowerChargeComponent component, ApcPowerReceiverComponent powerReceiver)
- {
- powerReceiver.Load = component.SwitchedOn ? component.ActivePowerUse : component.IdlePowerUse;
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var query = EntityQueryEnumerator<PowerChargeComponent, ApcPowerReceiverComponent>();
- while (query.MoveNext(out var uid, out var chargingMachine, out var powerReceiver))
- {
- var ent = (uid, gravGen: chargingMachine, powerReceiver);
- if (!chargingMachine.Intact)
- continue;
- // Calculate charge rate based on power state and such.
- // Negative charge rate means discharging.
- float chargeRate;
- if (chargingMachine.SwitchedOn)
- {
- if (powerReceiver.Powered)
- {
- chargeRate = chargingMachine.ChargeRate;
- }
- else
- {
- // Scale discharge rate such that if we're at 25% active power we discharge at 75% rate.
- var receiving = powerReceiver.PowerReceived;
- var mainSystemPower = Math.Max(0, receiving - chargingMachine.IdlePowerUse);
- var ratio = 1 - mainSystemPower / (chargingMachine.ActivePowerUse - chargingMachine.IdlePowerUse);
- chargeRate = -(ratio * chargingMachine.ChargeRate);
- }
- }
- else
- {
- chargeRate = -chargingMachine.ChargeRate;
- }
- var active = chargingMachine.Active;
- var lastCharge = chargingMachine.Charge;
- chargingMachine.Charge = Math.Clamp(chargingMachine.Charge + frameTime * chargeRate, 0, chargingMachine.MaxCharge);
- if (chargeRate > 0)
- {
- // Charging.
- if (MathHelper.CloseTo(chargingMachine.Charge, chargingMachine.MaxCharge) && !chargingMachine.Active)
- {
- chargingMachine.Active = true;
- }
- }
- else
- {
- // Discharging
- if (MathHelper.CloseTo(chargingMachine.Charge, 0) && chargingMachine.Active)
- {
- chargingMachine.Active = false;
- }
- }
- var updateUI = chargingMachine.NeedUIUpdate;
- if (!MathHelper.CloseTo(lastCharge, chargingMachine.Charge))
- {
- UpdateState(ent);
- updateUI = true;
- }
- if (updateUI)
- UpdateUI(ent, chargeRate);
- if (active == chargingMachine.Active)
- continue;
- if (chargingMachine.Active)
- {
- var eventArgs = new ChargedMachineActivatedEvent();
- RaiseLocalEvent(uid, ref eventArgs);
- }
- else
- {
- var eventArgs = new ChargedMachineDeactivatedEvent();
- RaiseLocalEvent(uid, ref eventArgs);
- }
- }
- }
- private void UpdateUI(Entity<PowerChargeComponent, ApcPowerReceiverComponent> ent, float chargeRate)
- {
- var (_, component, powerReceiver) = ent;
- if (!_uiSystem.IsUiOpen(ent.Owner, component.UiKey))
- return;
- var chargeTarget = chargeRate < 0 ? 0 : component.MaxCharge;
- short chargeEta;
- var atTarget = false;
- if (MathHelper.CloseTo(component.Charge, chargeTarget))
- {
- chargeEta = short.MinValue; // N/A
- atTarget = true;
- }
- else
- {
- var diff = chargeTarget - component.Charge;
- chargeEta = (short) Math.Abs(diff / chargeRate);
- }
- var status = chargeRate switch
- {
- > 0 when atTarget => PowerChargePowerStatus.FullyCharged,
- < 0 when atTarget => PowerChargePowerStatus.Off,
- > 0 => PowerChargePowerStatus.Charging,
- < 0 => PowerChargePowerStatus.Discharging,
- _ => throw new ArgumentOutOfRangeException()
- };
- var state = new PowerChargeState(
- component.SwitchedOn,
- (byte) (component.Charge * 255),
- status,
- (short) Math.Round(powerReceiver.PowerReceived),
- (short) Math.Round(powerReceiver.Load),
- chargeEta
- );
- _uiSystem.SetUiState(
- ent.Owner,
- component.UiKey,
- state);
- component.NeedUIUpdate = false;
- }
- private void UpdateState(Entity<PowerChargeComponent, ApcPowerReceiverComponent> ent)
- {
- var (uid, machine, powerReceiver) = ent;
- var appearance = EntityManager.GetComponentOrNull<AppearanceComponent>(uid);
- _appearance.SetData(uid, PowerChargeVisuals.Charge, machine.Charge, appearance);
- _appearance.SetData(uid, PowerChargeVisuals.Active, machine.Active);
- if (!machine.Intact)
- {
- MakeBroken((uid, machine), appearance);
- }
- else if (powerReceiver.PowerReceived < machine.IdlePowerUse)
- {
- MakeUnpowered((uid, machine), appearance);
- }
- else if (!machine.SwitchedOn)
- {
- MakeOff((uid, machine), appearance);
- }
- else
- {
- MakeOn((uid, machine), appearance);
- }
- }
- private void MakeBroken(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
- {
- _ambientSoundSystem.SetAmbience(ent, false);
- _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.Broken, appearance);
- }
- private void MakeUnpowered(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
- {
- _ambientSoundSystem.SetAmbience(ent, false);
- _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.Unpowered, appearance);
- }
- private void MakeOff(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
- {
- _ambientSoundSystem.SetAmbience(ent, false);
- _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.Off, appearance);
- }
- private void MakeOn(Entity<PowerChargeComponent> ent, AppearanceComponent? appearance)
- {
- _ambientSoundSystem.SetAmbience(ent, true);
- _appearance.SetData(ent, PowerChargeVisuals.State, PowerChargeStatus.On, appearance);
- }
- }
- [ByRefEvent] public record struct ChargedMachineActivatedEvent;
- [ByRefEvent] public record struct ChargedMachineDeactivatedEvent;
|