Pow3r: stage 1 (#4208)

Co-authored-by: 20kdc <asdd2808@gmail.com>
This commit is contained in:
Pieter-Jan Briers
2021-07-04 18:11:52 +02:00
committed by GitHub
parent ea60a81fdf
commit 103bc19508
212 changed files with 8584 additions and 4426 deletions

View File

@@ -0,0 +1,193 @@
#nullable enable
using System;
using Content.Server.Access.Components;
using Content.Server.Power.NodeGroups;
using Content.Server.UserInterface;
using Content.Shared.APC;
using Content.Shared.Interaction;
using Content.Shared.Notification.Managers;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class ApcComponent : BaseApcNetComponent, IActivate
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override string Name => "Apc";
public bool MainBreakerEnabled { get; private set; } = true;
private ApcChargeState _lastChargeState;
private TimeSpan _lastChargeStateChange;
private ApcExternalPowerState _lastExternalPowerState;
private TimeSpan _lastExternalPowerStateChange;
private float _lastCharge;
private TimeSpan _lastChargeChange;
private bool _uiDirty = true;
private const float HighPowerThreshold = 0.9f;
private const int VisualsChangeDelay = 1;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ApcUiKey.Key);
public BatteryComponent? Battery => Owner.TryGetComponent(out BatteryComponent? batteryComponent) ? batteryComponent : null;
[ComponentDependency] private AccessReader? _accessReader = null;
protected override void Initialize()
{
base.Initialize();
Owner.EnsureComponentWarn<ServerUserInterfaceComponent>();
Owner.EnsureComponentWarn<AccessReader>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
}
Update();
}
protected override void AddSelfToNet(IApcNet apcNet)
{
apcNet.AddApc(this);
}
protected override void RemoveSelfFromNet(IApcNet apcNet)
{
apcNet.RemoveApc(this);
}
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
if (serverMsg.Message is ApcToggleMainBreakerMessage)
{
var user = serverMsg.Session.AttachedEntity;
if(user == null) return;
if (_accessReader == null || _accessReader.IsAllowed(user))
{
MainBreakerEnabled = !MainBreakerEnabled;
Owner.GetComponent<PowerNetworkBatteryComponent>().CanDischarge = MainBreakerEnabled;
_uiDirty = true;
SoundSystem.Play(Filter.Pvs(Owner), "/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
}
else
{
user.PopupMessageCursor(Loc.GetString("apc-component-insufficient-access"));
}
}
}
public void Update()
{
var newState = CalcChargeState();
if (newState != _lastChargeState && _lastChargeStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastChargeState = newState;
_lastChargeStateChange = _gameTiming.CurTime;
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(ApcVisuals.ChargeState, newState);
}
}
Owner.TryGetComponent(out BatteryComponent? battery);
var newCharge = battery?.CurrentCharge;
if (newCharge != null && newCharge != _lastCharge && _lastChargeChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastCharge = newCharge.Value;
_lastChargeChange = _gameTiming.CurTime;
_uiDirty = true;
}
var extPowerState = CalcExtPowerState();
if (extPowerState != _lastExternalPowerState && _lastExternalPowerStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastExternalPowerState = extPowerState;
_lastExternalPowerStateChange = _gameTiming.CurTime;
_uiDirty = true;
}
if (_uiDirty && battery != null && newCharge != null)
{
UserInterface?.SetState(new ApcBoundInterfaceState(MainBreakerEnabled, extPowerState, newCharge.Value / battery.MaxCharge));
_uiDirty = false;
}
}
private ApcChargeState CalcChargeState()
{
if (!Owner.TryGetComponent(out BatteryComponent? battery))
{
return ApcChargeState.Lack;
}
var chargeFraction = battery.CurrentCharge / battery.MaxCharge;
if (chargeFraction > HighPowerThreshold)
{
return ApcChargeState.Full;
}
var netBattery = Owner.GetComponent<PowerNetworkBatteryComponent>();
var delta = netBattery.CurrentSupply - netBattery.CurrentReceiving;
return delta < 0 ? ApcChargeState.Charging : ApcChargeState.Lack;
}
private ApcExternalPowerState CalcExtPowerState()
{
var bat = Battery;
if (bat == null)
return ApcExternalPowerState.None;
var netBat = Owner.GetComponent<PowerNetworkBatteryComponent>();
if (netBat.CurrentReceiving == 0 && netBat.LoadingNetworkDemand != 0)
{
return ApcExternalPowerState.None;
}
var delta = netBat.CurrentReceiving - netBat.LoadingNetworkDemand;
if (!MathHelper.CloseTo(delta, 0, 0.1f) && delta < 0)
{
return ApcExternalPowerState.Low;
}
return ApcExternalPowerState.Good;
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out ActorComponent? actor))
{
return;
}
UserInterface?.Open(actor.PlayerSession);
}
}
}

View File

@@ -0,0 +1,121 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.Power.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
[RegisterComponent]
public class ApcPowerProviderComponent : BaseApcNetComponent
{
public override string Name => "PowerProvider";
public IEntity ProviderOwner => Owner;
/// <summary>
/// The max distance this can transmit power to <see cref="ApcPowerReceiverComponent"/>s from.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int PowerTransferRange { get => _powerTransferRange; set => SetPowerTransferRange(value); }
[DataField("powerTransferRange")]
private int _powerTransferRange = 3;
[ViewVariables] public List<ApcPowerReceiverComponent> LinkedReceivers { get; } = new();
/// <summary>
/// If <see cref="ApcPowerReceiverComponent"/>s should consider connecting to this.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool Connectable { get; private set; } = true;
public void AddReceiver(ApcPowerReceiverComponent receiver)
{
LinkedReceivers.Add(receiver);
receiver.NetworkLoad.LinkedNetwork = default;
Net?.QueueNetworkReconnect();
}
public void RemoveReceiver(ApcPowerReceiverComponent receiver)
{
LinkedReceivers.Remove(receiver);
receiver.NetworkLoad.LinkedNetwork = default;
Net?.QueueNetworkReconnect();
}
protected override void Startup()
{
base.Startup();
foreach (var receiver in FindAvailableReceivers())
{
receiver.Provider = this;
}
}
protected override void OnRemove()
{
Connectable = false;
var receivers = LinkedReceivers.ToArray();
foreach (var receiver in receivers)
{
receiver.Provider = null;
}
foreach (var receiver in receivers)
{
receiver.TryFindAndSetProvider();
}
base.OnRemove();
}
private IEnumerable<ApcPowerReceiverComponent> FindAvailableReceivers()
{
var nearbyEntities = IoCManager.Resolve<IEntityLookup>()
.GetEntitiesInRange(Owner, PowerTransferRange);
foreach (var entity in nearbyEntities)
{
if (entity.TryGetComponent<ApcPowerReceiverComponent>(out var receiver) &&
receiver.Connectable &&
receiver.NeedsProvider &&
receiver.Owner.Transform.Coordinates.TryDistance(Owner.EntityManager, Owner.Transform.Coordinates, out var distance) &&
distance < Math.Min(PowerTransferRange, receiver.PowerReceptionRange))
{
yield return receiver;
}
}
}
protected override void AddSelfToNet(IApcNet apcNet)
{
apcNet.AddPowerProvider(this);
}
protected override void RemoveSelfFromNet(IApcNet apcNet)
{
apcNet.RemovePowerProvider(this);
}
private void SetPowerTransferRange(int newPowerTransferRange)
{
var receivers = LinkedReceivers.ToArray();
foreach (var receiver in receivers)
{
receiver.Provider = null;
}
_powerTransferRange = newPowerTransferRange;
foreach (var receiver in receivers)
{
receiver.TryFindAndSetProvider();
}
}
}
}

View File

@@ -1,12 +1,15 @@
#nullable enable
using System;
using Content.Server.APC;
using System.Diagnostics.CodeAnalysis;
using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r;
using Content.Shared.Examine;
using Content.Shared.Power;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
@@ -15,26 +18,21 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Attempts to link with a nearby <see cref="IPowerProvider"/>s so that it can receive power from a <see cref="IApcNet"/>.
/// Attempts to link with a nearby <see cref="ApcPowerProviderComponent"/>s
/// so that it can receive power from a <see cref="IApcNet"/>.
/// </summary>
[RegisterComponent]
public class PowerReceiverComponent : Component, IExamine
public class ApcPowerReceiverComponent : Component, IExamine
{
[ViewVariables] [ComponentDependency] private readonly IPhysBody? _physicsComponent = null;
public override string Name => "PowerReceiver";
public override string Name => "ApcPowerReceiver";
[ViewVariables]
public bool Powered => (HasApcPower || !NeedsPower) && !PowerDisabled;
public bool Powered => (MathHelper.CloseTo(NetworkLoad.ReceivingPower, Load) || !NeedsPower) && !PowerDisabled;
/// <summary>
/// If this is being powered by an Apc.
/// </summary>
[ViewVariables]
public bool HasApcPower { get; private set; }
/// <summary>
/// The max distance from a <see cref="PowerProviderComponent"/> that this can receive power from.
/// The max distance from a <see cref="ApcPowerProviderComponent"/> that this can receive power from.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int PowerReceptionRange { get => _powerReceptionRange; set => SetPowerReceptionRange(value); }
@@ -42,32 +40,53 @@ namespace Content.Server.Power.Components
private int _powerReceptionRange = 3;
[ViewVariables]
public IPowerProvider Provider { get => _provider; set => SetProvider(value); }
private IPowerProvider _provider = PowerProviderComponent.NullProvider;
public ApcPowerProviderComponent? Provider
{
get => _provider;
set
{
// Will get updated before power networks process.
NetworkLoad.LinkedNetwork = default;
_provider?.RemoveReceiver(this);
_provider = value;
value?.AddReceiver(this);
ApcPowerChanged();
}
}
private ApcPowerProviderComponent? _provider;
/// <summary>
/// If this should be considered for connection by <see cref="PowerProviderComponent"/>s.
/// If this should be considered for connection by <see cref="ApcPowerProviderComponent"/>s.
/// </summary>
public bool Connectable => Anchored;
private bool Anchored => _physicsComponent == null || _physicsComponent.BodyType == BodyType.Static;
[ViewVariables]
public bool NeedsProvider { get; private set; } = true;
[ViewVariables] public bool NeedsProvider => Provider == null;
/// <summary>
/// Amount of charge this needs from an APC per second to function.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int Load { get => _load; set => SetLoad(value); }
[DataField("powerLoad")]
private int _load = 5;
public float Load { get => NetworkLoad.DesiredPower; set => NetworkLoad.DesiredPower = value; }
/// <summary>
/// When false, causes this to appear powered even if not receiving power from an Apc.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool NeedsPower { get => _needsPower; set => SetNeedsPower(value); }
public bool NeedsPower
{
get => _needsPower;
set
{
_needsPower = value;
// Reset this so next tick will do a power update.
LastPowerReceived = float.NaN;
}
}
[DataField("needsPower")]
private bool _needsPower = true;
@@ -75,9 +94,16 @@ namespace Content.Server.Power.Components
/// When true, causes this to never appear powered.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool PowerDisabled { get => _powerDisabled; set => SetPowerDisabled(value); }
[DataField("powerDisabled")]
private bool _powerDisabled;
public bool PowerDisabled { get => !NetworkLoad.Enabled; set => NetworkLoad.Enabled = !value; }
public float LastPowerReceived = float.NaN;
[ViewVariables]
public PowerState.Load NetworkLoad { get; } = new PowerState.Load
{
DesiredPower = 5
};
protected override void Startup()
{
@@ -94,7 +120,8 @@ namespace Content.Server.Power.Components
protected override void OnRemove()
{
_provider.RemoveReceiver(this);
_provider?.RemoveReceiver(this);
base.OnRemove();
}
@@ -108,20 +135,17 @@ namespace Content.Server.Power.Components
public void ApcPowerChanged()
{
var oldPowered = Powered;
HasApcPower = Provider.HasApcPower;
if (Powered != oldPowered)
OnNewPowerState();
OnNewPowerState();
}
private bool TryFindAvailableProvider(out IPowerProvider foundProvider)
private bool TryFindAvailableProvider([NotNullWhen(true)] out ApcPowerProviderComponent? foundProvider)
{
var nearbyEntities = IoCManager.Resolve<IEntityLookup>()
.GetEntitiesInRange(Owner, PowerReceptionRange);
foreach (var entity in nearbyEntities)
{
if (entity.TryGetComponent<PowerProviderComponent>(out var provider))
if (entity.TryGetComponent<ApcPowerProviderComponent>(out var provider))
{
if (provider.Connectable)
{
@@ -136,60 +160,18 @@ namespace Content.Server.Power.Components
}
}
}
foundProvider = default!;
foundProvider = default;
return false;
}
public void ClearProvider()
{
_provider.RemoveReceiver(this);
_provider = PowerProviderComponent.NullProvider;
NeedsProvider = true;
ApcPowerChanged();
}
private void SetProvider(IPowerProvider newProvider)
{
_provider.RemoveReceiver(this);
_provider = newProvider;
newProvider.AddReceiver(this);
NeedsProvider = false;
ApcPowerChanged();
}
private void SetPowerReceptionRange(int newPowerReceptionRange)
{
ClearProvider();
Provider = null;
_powerReceptionRange = newPowerReceptionRange;
TryFindAndSetProvider();
}
private void SetLoad(int newLoad)
{
Provider.UpdateReceiverLoad(Load, newLoad);
_load = newLoad;
}
private void SetNeedsPower(bool newNeedsPower)
{
var oldPowered = Powered;
_needsPower = newNeedsPower;
if (oldPowered != Powered)
{
OnNewPowerState();
}
}
private void SetPowerDisabled(bool newPowerDisabled)
{
var oldPowered = Powered;
_powerDisabled = newPowerDisabled;
if (oldPowered != Powered)
{
OnNewPowerState();
}
}
private void OnNewPowerState()
{
SendMessage(new PowerChangedMessage(Powered));
@@ -211,7 +193,7 @@ namespace Content.Server.Power.Components
}
else
{
ClearProvider();
Provider = null;
}
}

View File

@@ -0,0 +1,9 @@
#nullable enable
using Content.Server.Power.NodeGroups;
namespace Content.Server.Power.Components
{
public abstract class BaseApcNetComponent : BaseNetConnectorComponent<IApcNet>
{
}
}

View File

@@ -1,7 +1,6 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Content.Server.Battery.Components;
using Content.Server.Hands.Components;
using Content.Server.Items;
using Content.Server.Weapon.Ranged.Barrels.Components;
@@ -45,7 +44,7 @@ namespace Content.Server.Power.Components
{
base.Initialize();
Owner.EnsureComponent<PowerReceiverComponent>();
Owner.EnsureComponent<ApcPowerReceiverComponent>();
_container = ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-powerCellContainer");
// Default state in the visualizer is OFF, so when this gets powered on during initialization it will generally show empty
}
@@ -191,7 +190,7 @@ namespace Content.Server.Power.Components
private CellChargerStatus GetStatus()
{
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
if (Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) &&
!receiver.Powered)
{
return CellChargerStatus.Off;
@@ -234,7 +233,7 @@ namespace Content.Server.Power.Components
// Not called UpdateAppearance just because it messes with the load
var status = GetStatus();
if (_status == status ||
!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
!Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver))
{
return;
}
@@ -279,7 +278,7 @@ namespace Content.Server.Power.Components
private void TransferPower(float frameTime)
{
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
if (Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) &&
!receiver.Powered)
{
return;

View File

@@ -17,23 +17,18 @@ namespace Content.Server.Power.Components
private Voltage _voltage = Voltage.High;
[ViewVariables]
public TNetType Net { get => _net; set => SetNet(value); }
private TNetType _net = default!; //set in OnAdd()
protected abstract TNetType NullNet { get; }
public TNetType? Net { get => _net; set => SetNet(value); }
private TNetType? _net;
[ViewVariables]
private bool _needsNet = true;
private bool _needsNet => _net != null;
protected override void OnAdd()
{
base.OnAdd();
_net = NullNet;
}
[DataField("node")] [ViewVariables] public string? NodeId;
protected override void Initialize()
{
base.Initialize();
if (_needsNet)
{
TryFindAndSetNet();
@@ -56,9 +51,8 @@ namespace Content.Server.Power.Components
public void ClearNet()
{
RemoveSelfFromNet(_net);
_net = NullNet;
_needsNet = true;
if (_net != null)
RemoveSelfFromNet(_net);
}
protected abstract void AddSelfToNet(TNetType net);
@@ -70,7 +64,7 @@ namespace Content.Server.Power.Components
if (Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
var compatibleNet = container.Nodes.Values
.Where(node => node.NodeGroupID == (NodeGroupID) Voltage)
.Where(node => (NodeId == null || NodeId == node.Name) && node.NodeGroupID == (NodeGroupID) Voltage)
.Select(node => node.NodeGroup)
.OfType<TNetType>()
.FirstOrDefault();
@@ -85,12 +79,15 @@ namespace Content.Server.Power.Components
return false;
}
private void SetNet(TNetType newNet)
private void SetNet(TNetType? newNet)
{
RemoveSelfFromNet(_net);
AddSelfToNet(newNet);
if (_net != null)
RemoveSelfFromNet(_net);
if (newNet != null)
AddSelfToNet(newNet);
_net = newNet;
_needsNet = false;
}
private void SetVoltage(Voltage newVoltage)

View File

@@ -1,10 +1,10 @@
#nullable enable
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Power.NodeGroups;
namespace Content.Server.Power.Components
{
public abstract class BasePowerNetComponent : BaseNetConnectorComponent<IPowerNet>
{
protected override IPowerNet NullNet => PowerNetNodeGroup.NullNet;
}
}

View File

@@ -0,0 +1,24 @@
using Content.Server.Power.NodeGroups;
using Robust.Shared.GameObjects;
namespace Content.Server.Power.Components
{
/// <summary>
/// Connects the loading side of a <see cref="BatteryComponent"/> to a non-APC power network.
/// </summary>
[RegisterComponent]
public class BatteryChargerComponent : BasePowerNetComponent
{
public override string Name => "BatteryCharger";
protected override void AddSelfToNet(IPowerNet net)
{
net.AddCharger(this);
}
protected override void RemoveSelfFromNet(IPowerNet net)
{
net.RemoveCharger(this);
}
}
}

View File

@@ -0,0 +1,101 @@
#nullable enable
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Battery node on the pow3r network. Needs other components to connect to actual networks.
/// </summary>
[RegisterComponent]
public class BatteryComponent : Component
{
public override string Name => "Battery";
/// <summary>
/// Maximum charge of the battery in joules (ie. watt seconds)
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public float MaxCharge { get => _maxCharge; set => SetMaxCharge(value); }
[DataField("maxCharge")]
private float _maxCharge;
/// <summary>
/// Current charge of the battery in joules (ie. watt seconds)
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float CurrentCharge { get => _currentCharge; set => SetCurrentCharge(value); }
[DataField("startingCharge")]
private float _currentCharge;
/// <summary>
/// True if the battery is fully charged.
/// </summary>
[ViewVariables] public bool IsFullyCharged => MathHelper.CloseTo(CurrentCharge, MaxCharge);
[ViewVariables(VVAccess.ReadWrite)] [DataField("autoRecharge")] public bool AutoRecharge { get; set; }
[ViewVariables(VVAccess.ReadWrite)] [DataField("autoRechargeRate")] public float AutoRechargeRate { get; set; }
/// <summary>
/// If sufficient charge is avaiable on the battery, use it. Otherwise, don't.
/// </summary>
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() { }
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 void OnUpdate(float frameTime)
{
if (!AutoRecharge) return;
if (IsFullyCharged) return;
CurrentCharge += AutoRechargeRate * frameTime;
}
}
}

View File

@@ -1,79 +1,21 @@
#nullable enable
using Content.Server.Battery.Components;
using Content.Server.Power.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Uses charge from a <see cref="BatteryComponent"/> to supply power via a <see cref="PowerSupplierComponent"/>.
/// </summary>
[RegisterComponent]
public class BatteryDischargerComponent : Component
public class BatteryDischargerComponent : BasePowerNetComponent
{
public override string Name => "BatteryDischarger";
[ViewVariables]
[ComponentDependency] private BatteryComponent? _battery = default!;
[ViewVariables]
[ComponentDependency] private PowerSupplierComponent? _supplier = default!;
[ViewVariables(VVAccess.ReadWrite)]
public int ActiveSupplyRate { get => _activeSupplyRate; set => SetActiveSupplyRate(value); }
[DataField("activeSupplyRate")]
private int _activeSupplyRate = 50;
protected override void Initialize()
protected override void AddSelfToNet(IPowerNet net)
{
base.Initialize();
Owner.EnsureComponentWarn<PowerSupplierComponent>();
UpdateSupplyRate();
net.AddDischarger(this);
}
public void Update(float frameTime)
protected override void RemoveSelfFromNet(IPowerNet net)
{
if (_battery == null)
return;
//Simplified implementation - if the battery is empty, and charge is being added to the battery
//at a lower rate that this is using it, the charge is used without creating power supply.
_battery.CurrentCharge -= ActiveSupplyRate * frameTime;
UpdateSupplyRate();
}
private void UpdateSupplyRate()
{
if (_battery == null)
return;
if (_battery.BatteryState == BatteryState.Empty)
{
SetSupplierSupplyRate(0);
}
else
{
SetSupplierSupplyRate(ActiveSupplyRate);
}
}
private void SetSupplierSupplyRate(int newSupplierSupplyRate)
{
if (_supplier == null)
return;
if (_supplier.SupplyRate != newSupplierSupplyRate)
{
_supplier.SupplyRate = newSupplierSupplyRate;
}
}
private void SetActiveSupplyRate(int newEnabledSupplyRate)
{
_activeSupplyRate = newEnabledSupplyRate;
UpdateSupplyRate();
net.RemoveDischarger(this);
}
}
}

View File

@@ -1,79 +0,0 @@
#nullable enable
using Content.Server.Battery.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Takes power via a <see cref="PowerConsumerComponent"/> to charge a <see cref="BatteryComponent"/>.
/// </summary>
[RegisterComponent]
public class BatteryStorageComponent : Component
{
public override string Name => "BatteryStorage";
[ViewVariables(VVAccess.ReadWrite)]
public int ActiveDrawRate { get => _activeDrawRate; set => SetActiveDrawRate(value); }
[DataField("activeDrawRate")]
private int _activeDrawRate = 100;
[ViewVariables]
[ComponentDependency] private BatteryComponent? _battery = default!;
[ViewVariables]
public PowerConsumerComponent? Consumer => _consumer;
[ComponentDependency] private PowerConsumerComponent? _consumer = default!;
protected override void Initialize()
{
base.Initialize();
Owner.EnsureComponentWarn<PowerConsumerComponent>();
UpdateDrawRate();
}
public void Update(float frameTime)
{
if (_consumer == null || _battery == null)
return;
//Simplified implementation - If a frame adds more power to a partially full battery than it can hold, the power is lost.
_battery.CurrentCharge += _consumer.ReceivedPower * frameTime;
UpdateDrawRate();
}
private void UpdateDrawRate()
{
if (_battery == null)
return;
if (_battery.BatteryState == BatteryState.Full)
{
SetConsumerDraw(0);
}
else
{
SetConsumerDraw(ActiveDrawRate);
}
}
private void SetConsumerDraw(int newConsumerDrawRate)
{
if (_consumer == null)
return;
if (_consumer.DrawRate != newConsumerDrawRate)
{
_consumer.DrawRate = newConsumerDrawRate;
}
}
private void SetActiveDrawRate(int newEnabledDrawRate)
{
_activeDrawRate = newEnabledDrawRate;
UpdateDrawRate();
}
}
}

View File

@@ -0,0 +1,59 @@
#nullable enable
using System.Threading.Tasks;
using Content.Server.Stack;
using Content.Server.Tools.Components;
using Content.Shared.Interaction;
using Content.Shared.Tool;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Allows the attached entity to be destroyed by a cutting tool, dropping a piece of cable.
/// </summary>
[RegisterComponent]
public class CableComponent : Component, IInteractUsing
{
public override string Name => "Cable";
[ViewVariables]
[DataField("cableDroppedOnCutPrototype")]
private string? _cableDroppedOnCutPrototype = "CableHVStack1";
/// <summary>
/// Checked by <see cref="CablePlacerComponent"/> to determine if there is
/// already a cable of a type on a tile.
/// </summary>
[ViewVariables]
public CableType CableType => _cableType;
[DataField("cableType")]
private CableType _cableType = CableType.HighVoltage;
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (_cableDroppedOnCutPrototype == null)
return false;
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool)) return false;
if (!await tool.UseTool(eventArgs.User, Owner, 0.25f, ToolQuality.Cutting)) return false;
Owner.Delete();
var droppedEnt = Owner.EntityManager.SpawnEntity(_cableDroppedOnCutPrototype, eventArgs.ClickLocation);
// TODO: Literally just use a prototype that has a single thing in the stack, it's not that complicated...
if (droppedEnt.TryGetComponent<StackComponent>(out var stack))
EntitySystem.Get<StackSystem>().SetCount(droppedEnt.Uid, stack, 1);
return true;
}
}
public enum CableType
{
HighVoltage,
MediumVoltage,
Apc,
}
}

View File

@@ -0,0 +1,58 @@
#nullable enable
using System.Threading.Tasks;
using Content.Server.Stack;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
[RegisterComponent]
internal class CablePlacerComponent : Component, IAfterInteract
{
[Dependency] private readonly IMapManager _mapManager = default!;
/// <inheritdoc />
public override string Name => "CablePlacer";
[ViewVariables]
[DataField("cablePrototypeID")]
private string? _cablePrototypeID = "CableHV";
[ViewVariables]
[DataField("blockingWireType")]
private CableType _blockingCableType = CableType.HighVoltage;
/// <inheritdoc />
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (_cablePrototypeID == null)
return true;
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
return true;
if(!_mapManager.TryGetGrid(eventArgs.ClickLocation.GetGridId(Owner.EntityManager), out var grid))
return true;
var snapPos = grid.TileIndicesFor(eventArgs.ClickLocation);
if(grid.GetTileRef(snapPos).Tile.IsEmpty)
return true;
foreach (var anchored in grid.GetAnchoredEntities(snapPos))
{
if (Owner.EntityManager.ComponentManager.TryGetComponent<CableComponent>(anchored, out var wire) && wire.CableType == _blockingCableType)
{
return true;
}
}
if (Owner.TryGetComponent<StackComponent>(out var stack)
&& !EntitySystem.Get<StackSystem>().Use(Owner.Uid, stack, 1))
return true;
Owner.EntityManager.SpawnEntity(_cablePrototypeID, grid.GridTileToLocal(snapPos));
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
[RegisterComponent]
public sealed class CableVisComponent : Component
{
public override string Name => "CableVis";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("node")]
public string? Node;
}
}

View File

@@ -0,0 +1,39 @@
#nullable enable
using Content.Shared.Examine;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
[RegisterComponent]
public class ExaminableBatteryComponent : Component, IExamine
{
public override string Name => "ExaminableBattery";
[ViewVariables]
[ComponentDependency] private BatteryComponent? _battery = default!;
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
if (_battery == null)
return;
if (inDetailsRange)
{
var effectiveMax = _battery.MaxCharge;
if (effectiveMax == 0)
effectiveMax = 1;
var chargeFraction = _battery.CurrentCharge / effectiveMax;
var chargePercentRounded = (int) (chargeFraction * 100);
message.AddMarkup(
Loc.GetString(
"examinable-battery-component-examine-detail",
("percent", chargePercentRounded),
("markupPercentColor", "green")
)
);
}
}
}
}

View File

@@ -1,39 +0,0 @@
#nullable enable
using System.Collections.Generic;
using Content.Server.NodeContainer.NodeGroups;
namespace Content.Server.Power.Components
{
/// <summary>
/// Maintains a set of <see cref="IPowerNet"/>s that need to be updated with <see cref="IPowerNet.UpdateConsumerReceivedPower"/>.
/// Defers updating to reduce recalculations when a group is altered multiple times in a frame.
/// </summary>
public interface IPowerNetManager
{
/// <summary>
/// Queue up an <see cref="IPowerNet"/> to be updated.
/// </summary>
void AddDirtyPowerNet(IPowerNet powerNet);
void Update(float frameTime);
}
public class PowerNetManager : IPowerNetManager
{
private readonly HashSet<IPowerNet> _dirtyPowerNets = new();
public void AddDirtyPowerNet(IPowerNet powerNet)
{
_dirtyPowerNets.Add(powerNet);
}
public void Update(float frameTime)
{
foreach (var powerNet in _dirtyPowerNets)
{
powerNet.UpdateConsumerReceivedPower();
}
_dirtyPowerNets.Clear();
}
}
}

View File

@@ -1,13 +1,15 @@
#nullable enable
using System;
using System.Diagnostics;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Draws power directly from an MV or HV wire it is on top of.
/// </summary>
[RegisterComponent]
public class PowerConsumerComponent : BasePowerNetComponent
{
@@ -16,28 +18,19 @@ namespace Content.Server.Power.Components
/// <summary>
/// How much power this needs to be fully powered.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int DrawRate { get => _drawRate; set => SetDrawRate(value); }
[DataField("drawRate")]
private int _drawRate;
/// <summary>
/// Determines which <see cref="PowerConsumerComponent"/>s receive power when there is not enough
/// power for each.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Priority Priority { get => _priority; set => SetPriority(value); }
[DataField("priority")]
private Priority _priority = Priority.First;
public float DrawRate { get => NetworkLoad.DesiredPower; set => NetworkLoad.DesiredPower = value; }
/// <summary>
/// How much power this is currently receiving from <see cref="PowerSupplierComponent"/>s.
/// </summary>
[ViewVariables]
public int ReceivedPower { get => _receivedPower; set => SetReceivedPower(value); }
private int _receivedPower;
public float ReceivedPower => NetworkLoad.ReceivingPower;
public event EventHandler<ReceivedPowerChangedEventArgs>? OnReceivedPowerChanged;
public float LastReceived = float.NaN;
public PowerState.Load NetworkLoad { get; } = new();
protected override void AddSelfToNet(IPowerNet powerNet)
{
@@ -48,44 +41,5 @@ namespace Content.Server.Power.Components
{
powerNet.RemoveConsumer(this);
}
private void SetDrawRate(int newDrawRate)
{
var oldDrawRate = DrawRate;
_drawRate = newDrawRate; //must be set before updating powernet, as it checks the DrawRate of every consumer
Net.UpdateConsumerDraw(this, oldDrawRate, newDrawRate);
}
private void SetReceivedPower(int newReceivedPower)
{
Debug.Assert(newReceivedPower >= 0 && newReceivedPower <= DrawRate);
if(_receivedPower == newReceivedPower) return;
_receivedPower = newReceivedPower;
OnReceivedPowerChanged?.Invoke(this, new ReceivedPowerChangedEventArgs(_drawRate, _receivedPower));
}
private void SetPriority(Priority newPriority)
{
Net.UpdateConsumerPriority(this, Priority, newPriority);
_priority = newPriority;
}
}
public enum Priority
{
First,
Last,
}
public class ReceivedPowerChangedEventArgs : EventArgs
{
public readonly int DrawRate;
public readonly int ReceivedPower;
public ReceivedPowerChangedEventArgs(int drawRate, int receivedPower)
{
DrawRate = drawRate;
ReceivedPower = receivedPower;
}
}
}

View File

@@ -0,0 +1,119 @@
using Content.Server.Power.Pow3r;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Glue component that manages the pow3r network node for batteries that are connected to the power network.
/// </summary>
/// <remarks>
/// This needs components like <see cref="BatteryChargerComponent"/> to work correctly,
/// and battery storage should be handed off to components like <see cref="BatteryComponent"/>.
/// </remarks>
[RegisterComponent]
public sealed class PowerNetworkBatteryComponent : Component
{
public override string Name => "PowerNetworkBattery";
[DataField("maxChargeRate")]
[ViewVariables(VVAccess.ReadWrite)]
public float MaxChargeRate
{
get => NetworkBattery.MaxChargeRate;
set => NetworkBattery.MaxChargeRate = value;
}
[DataField("maxSupply")]
[ViewVariables(VVAccess.ReadWrite)]
public float MaxSupply
{
get => NetworkBattery.MaxSupply;
set => NetworkBattery.MaxSupply = value;
}
[DataField("supplyRampTolerance")]
[ViewVariables(VVAccess.ReadWrite)]
public float SupplyRampTolerance
{
get => NetworkBattery.SupplyRampTolerance;
set => NetworkBattery.SupplyRampTolerance = value;
}
[DataField("supplyRampRate")]
[ViewVariables(VVAccess.ReadWrite)]
public float SupplyRampRate
{
get => NetworkBattery.SupplyRampRate;
set => NetworkBattery.SupplyRampRate = value;
}
[DataField("supplyRampPosition")]
[ViewVariables(VVAccess.ReadWrite)]
public float SupplyRampPosition
{
get => NetworkBattery.SupplyRampPosition;
set => NetworkBattery.SupplyRampPosition = value;
}
[DataField("currentSupply")]
[ViewVariables(VVAccess.ReadWrite)]
public float CurrentSupply
{
get => NetworkBattery.CurrentSupply;
set => NetworkBattery.CurrentSupply = value;
}
[DataField("currentReceiving")]
[ViewVariables(VVAccess.ReadWrite)]
public float CurrentReceiving
{
get => NetworkBattery.CurrentReceiving;
set => NetworkBattery.CurrentReceiving = value;
}
[DataField("loadingNetworkDemand")]
[ViewVariables(VVAccess.ReadWrite)]
public float LoadingNetworkDemand
{
get => NetworkBattery.LoadingNetworkDemand;
set => NetworkBattery.LoadingNetworkDemand = value;
}
[DataField("enabled")]
[ViewVariables(VVAccess.ReadWrite)]
public bool Enabled
{
get => NetworkBattery.Enabled;
set => NetworkBattery.Enabled = value;
}
[DataField("canCharge")]
[ViewVariables(VVAccess.ReadWrite)]
public bool CanCharge
{
get => NetworkBattery.CanCharge;
set => NetworkBattery.CanCharge = value;
}
[DataField("canDisharge")]
[ViewVariables(VVAccess.ReadWrite)]
public bool CanDischarge
{
get => NetworkBattery.CanDischarge;
set => NetworkBattery.CanDischarge = value;
}
[DataField("efficiency")]
[ViewVariables(VVAccess.ReadWrite)]
public float Efficiency
{
get => NetworkBattery.Efficiency;
set => NetworkBattery.Efficiency = value;
}
[ViewVariables]
public PowerState.Battery NetworkBattery { get; } = new();
}
}

View File

@@ -1,174 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.APC;
using Content.Server.APC.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Power.Components
{
/// <summary>
/// Relays <see cref="PowerReceiverComponent"/>s in an area to a <see cref="IApcNet"/> so they can receive power.
/// </summary>
public interface IPowerProvider
{
void AddReceiver(PowerReceiverComponent receiver);
void RemoveReceiver(PowerReceiverComponent receiver);
void UpdateReceiverLoad(int oldLoad, int newLoad);
public IEntity? ProviderOwner { get; }
public bool HasApcPower { get; }
}
[RegisterComponent]
public class PowerProviderComponent : BaseApcNetComponent, IPowerProvider
{
public override string Name => "PowerProvider";
public IEntity ProviderOwner => Owner;
[ViewVariables]
public bool HasApcPower => Net.Powered;
/// <summary>
/// The max distance this can transmit power to <see cref="PowerReceiverComponent"/>s from.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int PowerTransferRange { get => _powerTransferRange; set => SetPowerTransferRange(value); }
[DataField("powerTransferRange")]
private int _powerTransferRange = 3;
[ViewVariables]
public IReadOnlyList<PowerReceiverComponent> LinkedReceivers => _linkedReceivers;
private List<PowerReceiverComponent> _linkedReceivers = new();
/// <summary>
/// If <see cref="PowerReceiverComponent"/>s should consider connecting to this.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool Connectable { get; private set; } = true;
public static readonly IPowerProvider NullProvider = new NullPowerProvider();
public void AddReceiver(PowerReceiverComponent receiver)
{
var oldLoad = GetTotalLoad();
_linkedReceivers.Add(receiver);
var newLoad = oldLoad + receiver.Load;
Net.UpdatePowerProviderReceivers(this, oldLoad, newLoad);
}
public void RemoveReceiver(PowerReceiverComponent receiver)
{
var oldLoad = GetTotalLoad();
_linkedReceivers.Remove(receiver);
var newLoad = oldLoad - receiver.Load;
Net.UpdatePowerProviderReceivers(this, oldLoad, newLoad);
}
public void UpdateReceiverLoad(int oldLoad, int newLoad)
{
Net.UpdatePowerProviderReceivers(this, oldLoad, newLoad);
}
protected override void Startup()
{
base.Startup();
foreach (var receiver in FindAvailableReceivers())
{
receiver.Provider = this;
}
}
protected override void OnRemove()
{
Connectable = false;
var receivers = _linkedReceivers.ToArray();
foreach (var receiver in receivers)
{
receiver.ClearProvider();
}
foreach (var receiver in receivers)
{
receiver.TryFindAndSetProvider();
}
base.OnRemove();
}
private List<PowerReceiverComponent> FindAvailableReceivers()
{
var nearbyEntities = IoCManager.Resolve<IEntityLookup>()
.GetEntitiesInRange(Owner, PowerTransferRange);
var receivers = new List<PowerReceiverComponent>();
foreach (var entity in nearbyEntities)
{
if (entity.TryGetComponent<PowerReceiverComponent>(out var receiver) &&
receiver.Connectable &&
receiver.NeedsProvider &&
receiver.Owner.Transform.Coordinates.TryDistance(Owner.EntityManager, Owner.Transform.Coordinates, out var distance) &&
distance < Math.Min(PowerTransferRange, receiver.PowerReceptionRange))
{
receivers.Add(receiver);
}
}
return receivers;
}
protected override void AddSelfToNet(IApcNet apcNet)
{
apcNet.AddPowerProvider(this);
}
protected override void RemoveSelfFromNet(IApcNet apcNet)
{
apcNet.RemovePowerProvider(this);
}
private void SetPowerTransferRange(int newPowerTransferRange)
{
var receivers = _linkedReceivers.ToArray();
foreach (var receiver in receivers)
{
receiver.ClearProvider();
}
_powerTransferRange = newPowerTransferRange;
foreach (var receiver in receivers)
{
receiver.TryFindAndSetProvider();
}
}
private int GetTotalLoad()
{
var load = 0;
foreach (var receiver in _linkedReceivers)
{
load += receiver.Load;
}
return load;
}
private class NullPowerProvider : IPowerProvider
{
/// <summary>
/// It is important that this returns false, so <see cref="PowerReceiverComponent"/>s with a <see cref="NullPowerProvider"/> have no power.
/// </summary>
public bool HasApcPower => false;
public void AddReceiver(PowerReceiverComponent receiver) { }
public void RemoveReceiver(PowerReceiverComponent receiver) { }
public void UpdateReceiverLoad(int oldLoad, int newLoad) { }
public IEntity? ProviderOwner => default;
}
}
}

View File

@@ -1,5 +1,6 @@
#nullable enable
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Power.NodeGroups;
using Content.Server.Power.Pow3r;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -12,9 +13,45 @@ namespace Content.Server.Power.Components
public override string Name => "PowerSupplier";
[ViewVariables(VVAccess.ReadWrite)]
public int SupplyRate { get => _supplyRate; set => SetSupplyRate(value); }
[DataField("supplyRate")]
private int _supplyRate;
public float MaxSupply { get => NetworkSupply.MaxSupply; set => NetworkSupply.MaxSupply = value; }
[ViewVariables(VVAccess.ReadWrite)]
[DataField("supplyRampTolerance")]
public float SupplyRampTolerance
{
get => NetworkSupply.SupplyRampTolerance;
set => NetworkSupply.SupplyRampTolerance = value;
}
[ViewVariables(VVAccess.ReadWrite)]
[DataField("supplyRampRate")]
public float SupplyRampRate
{
get => NetworkSupply.SupplyRampRate;
set => NetworkSupply.SupplyRampRate = value;
}
[ViewVariables(VVAccess.ReadWrite)]
[DataField("supplyRampPosition")]
public float SupplyRampPosition
{
get => NetworkSupply.SupplyRampPosition;
set => NetworkSupply.SupplyRampPosition = value;
}
[ViewVariables(VVAccess.ReadWrite)]
[DataField("enabled")]
public bool Enabled
{
get => NetworkSupply.Enabled;
set => NetworkSupply.Enabled = value;
}
[ViewVariables] public float CurrentSupply => NetworkSupply.CurrentSupply;
[ViewVariables]
public PowerState.Supply NetworkSupply { get; } = new();
protected override void AddSelfToNet(IPowerNet powerNet)
{
@@ -25,11 +62,5 @@ namespace Content.Server.Power.Components
{
powerNet.RemoveSupplier(this);
}
private void SetSupplyRate(int newSupplyRate)
{
Net.UpdateSupplierSupply(this, SupplyRate, newSupplyRate);
_supplyRate = newSupplyRate;
}
}
}