Re-organize all projects (#4166)
This commit is contained in:
301
Content.Server/Power/Components/BaseCharger.cs
Normal file
301
Content.Server/Power/Components/BaseCharger.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
#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;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Notification;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
[ComponentReference(typeof(IInteractUsing))]
|
||||
public abstract class BaseCharger : Component, IActivate, IInteractUsing
|
||||
{
|
||||
[ViewVariables]
|
||||
private BatteryComponent? _heldBattery;
|
||||
|
||||
[ViewVariables]
|
||||
private ContainerSlot _container = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private CellChargerStatus _status;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("chargeRate")]
|
||||
private int _chargeRate = 100;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("transferEfficiency")]
|
||||
private float _transferEfficiency = 0.85f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponent<PowerReceiverComponent>();
|
||||
_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
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
switch (message)
|
||||
{
|
||||
case PowerChangedMessage powerChanged:
|
||||
PowerUpdate(powerChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
_heldBattery = null;
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
var result = TryInsertItem(eventArgs.Using);
|
||||
if (!result)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Owner, Loc.GetString("Unable to insert capacitor"));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
RemoveItem(eventArgs.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will remove the item directly into the user's hand / floor
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
private void RemoveItem(IEntity user)
|
||||
{
|
||||
var heldItem = _container.ContainedEntity;
|
||||
if (heldItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_container.Remove(heldItem);
|
||||
_heldBattery = null;
|
||||
if (user.TryGetComponent(out HandsComponent? handsComponent))
|
||||
{
|
||||
handsComponent.PutInHandOrDrop(heldItem.GetComponent<ItemComponent>());
|
||||
}
|
||||
|
||||
if (heldItem.TryGetComponent(out ServerBatteryBarrelComponent? batteryBarrelComponent))
|
||||
{
|
||||
batteryBarrelComponent.UpdateAppearance();
|
||||
}
|
||||
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void PowerUpdate(PowerChangedMessage eventArgs)
|
||||
{
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private sealed class InsertVerb : Verb<BaseCharger>
|
||||
{
|
||||
protected override void GetData(IEntity user, BaseCharger component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
if (!user.TryGetComponent(out HandsComponent? handsComponent))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
if (component._container.ContainedEntity != null || handsComponent.GetActiveHand == null)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
var heldItemName = Loc.GetString(handsComponent.GetActiveHand.Owner.Name);
|
||||
|
||||
data.Text = Loc.GetString("Insert {0}", heldItemName);
|
||||
data.IconTexture = "/Textures/Interface/VerbIcons/insert.svg.192dpi.png";
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, BaseCharger component)
|
||||
{
|
||||
if (!user.TryGetComponent(out HandsComponent? handsComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (handsComponent.GetActiveHand == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var userItem = handsComponent.GetActiveHand.Owner;
|
||||
handsComponent.Drop(userItem);
|
||||
component.TryInsertItem(userItem);
|
||||
}
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private sealed class EjectVerb : Verb<BaseCharger>
|
||||
{
|
||||
protected override void GetData(IEntity user, BaseCharger component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
if (component._container.ContainedEntity == null)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
var containerItemName = Loc.GetString(component._container.ContainedEntity.Name);
|
||||
|
||||
data.Text = Loc.GetString("Eject {0}", containerItemName);
|
||||
data.IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png";
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, BaseCharger component)
|
||||
{
|
||||
component.RemoveItem(user);
|
||||
}
|
||||
}
|
||||
|
||||
private CellChargerStatus GetStatus()
|
||||
{
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
|
||||
!receiver.Powered)
|
||||
{
|
||||
return CellChargerStatus.Off;
|
||||
}
|
||||
if (_container.ContainedEntity == null)
|
||||
{
|
||||
return CellChargerStatus.Empty;
|
||||
}
|
||||
if (_heldBattery != null && Math.Abs(_heldBattery.MaxCharge - _heldBattery.CurrentCharge) < 0.01)
|
||||
{
|
||||
return CellChargerStatus.Charged;
|
||||
}
|
||||
return CellChargerStatus.Charging;
|
||||
}
|
||||
|
||||
private bool TryInsertItem(IEntity entity)
|
||||
{
|
||||
if (!IsEntityCompatible(entity) || _container.ContainedEntity != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!_container.Insert(entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_heldBattery = GetBatteryFrom(entity);
|
||||
UpdateStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the supplied entity should fit into the charger.
|
||||
/// </summary>
|
||||
protected abstract bool IsEntityCompatible(IEntity entity);
|
||||
|
||||
protected abstract BatteryComponent? GetBatteryFrom(IEntity entity);
|
||||
|
||||
private void UpdateStatus()
|
||||
{
|
||||
// Not called UpdateAppearance just because it messes with the load
|
||||
var status = GetStatus();
|
||||
if (_status == status ||
|
||||
!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_status = status;
|
||||
Owner.TryGetComponent(out AppearanceComponent? appearance);
|
||||
|
||||
switch (_status)
|
||||
{
|
||||
// Update load just in case
|
||||
case CellChargerStatus.Off:
|
||||
receiver.Load = 0;
|
||||
appearance?.SetData(CellVisual.Light, CellChargerStatus.Off);
|
||||
break;
|
||||
case CellChargerStatus.Empty:
|
||||
receiver.Load = 0;
|
||||
appearance?.SetData(CellVisual.Light, CellChargerStatus.Empty);
|
||||
break;
|
||||
case CellChargerStatus.Charging:
|
||||
receiver.Load = (int) (_chargeRate / _transferEfficiency);
|
||||
appearance?.SetData(CellVisual.Light, CellChargerStatus.Charging);
|
||||
break;
|
||||
case CellChargerStatus.Charged:
|
||||
receiver.Load = 0;
|
||||
appearance?.SetData(CellVisual.Light, CellChargerStatus.Charged);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
appearance?.SetData(CellVisual.Occupied, _container.ContainedEntity != null);
|
||||
}
|
||||
|
||||
public void OnUpdate(float frameTime) //todo: make single system for this
|
||||
{
|
||||
if (_status == CellChargerStatus.Empty || _status == CellChargerStatus.Charged || _container.ContainedEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TransferPower(frameTime);
|
||||
}
|
||||
|
||||
private void TransferPower(float frameTime)
|
||||
{
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
|
||||
!receiver.Powered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_heldBattery == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_heldBattery.CurrentCharge += _chargeRate * frameTime;
|
||||
// Just so the sprite won't be set to 99.99999% visibility
|
||||
if (_heldBattery.MaxCharge - _heldBattery.CurrentCharge < 0.01)
|
||||
{
|
||||
_heldBattery.CurrentCharge = _heldBattery.MaxCharge;
|
||||
}
|
||||
UpdateStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Content.Server/Power/Components/BaseNetConnectorComponent.cs
Normal file
110
Content.Server/Power/Components/BaseNetConnectorComponent.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
public abstract class BaseNetConnectorComponent<TNetType> : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Voltage Voltage { get => _voltage; set => SetVoltage(value); }
|
||||
[DataField("voltage")]
|
||||
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; }
|
||||
|
||||
[ViewVariables]
|
||||
private bool _needsNet = true;
|
||||
|
||||
public override void OnAdd()
|
||||
{
|
||||
base.OnAdd();
|
||||
_net = NullNet;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
if (_needsNet)
|
||||
{
|
||||
TryFindAndSetNet();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
ClearNet();
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public void TryFindAndSetNet()
|
||||
{
|
||||
if (TryFindNet(out var net))
|
||||
{
|
||||
Net = net;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearNet()
|
||||
{
|
||||
RemoveSelfFromNet(_net);
|
||||
_net = NullNet;
|
||||
_needsNet = true;
|
||||
}
|
||||
|
||||
protected abstract void AddSelfToNet(TNetType net);
|
||||
|
||||
protected abstract void RemoveSelfFromNet(TNetType net);
|
||||
|
||||
private bool TryFindNet([NotNullWhen(true)] out TNetType? foundNet)
|
||||
{
|
||||
if (Owner.TryGetComponent<NodeContainerComponent>(out var container))
|
||||
{
|
||||
var compatibleNet = container.Nodes.Values
|
||||
.Where(node => node.NodeGroupID == (NodeGroupID) Voltage)
|
||||
.Select(node => node.NodeGroup)
|
||||
.OfType<TNetType>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (compatibleNet != null)
|
||||
{
|
||||
foundNet = compatibleNet;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
foundNet = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetNet(TNetType newNet)
|
||||
{
|
||||
RemoveSelfFromNet(_net);
|
||||
AddSelfToNet(newNet);
|
||||
_net = newNet;
|
||||
_needsNet = false;
|
||||
}
|
||||
|
||||
private void SetVoltage(Voltage newVoltage)
|
||||
{
|
||||
ClearNet();
|
||||
_voltage = newVoltage;
|
||||
TryFindAndSetNet();
|
||||
}
|
||||
}
|
||||
|
||||
public enum Voltage
|
||||
{
|
||||
High = NodeGroupID.HVPower,
|
||||
Medium = NodeGroupID.MVPower,
|
||||
Apc = NodeGroupID.Apc,
|
||||
}
|
||||
}
|
||||
10
Content.Server/Power/Components/BasePowerNetComponent.cs
Normal file
10
Content.Server/Power/Components/BasePowerNetComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
#nullable enable
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
public abstract class BasePowerNetComponent : BaseNetConnectorComponent<IPowerNet>
|
||||
{
|
||||
protected override IPowerNet NullNet => PowerNetNodeGroup.NullNet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#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>
|
||||
/// Uses charge from a <see cref="BatteryComponent"/> to supply power via a <see cref="PowerSupplierComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BatteryDischargerComponent : Component
|
||||
{
|
||||
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;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponentWarn<PowerSupplierComponent>();
|
||||
UpdateSupplyRate();
|
||||
}
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Content.Server/Power/Components/BatteryStorageComponent.cs
Normal file
79
Content.Server/Power/Components/BatteryStorageComponent.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
#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!;
|
||||
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Content.Server/Power/Components/IPowerNetManager.cs
Normal file
39
Content.Server/Power/Components/IPowerNetManager.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Content.Server/Power/Components/PowerConsumerComponent.cs
Normal file
91
Content.Server/Power/Components/PowerConsumerComponent.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class PowerConsumerComponent : BasePowerNetComponent
|
||||
{
|
||||
public override string Name => "PowerConsumer";
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <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 event EventHandler<ReceivedPowerChangedEventArgs>? OnReceivedPowerChanged;
|
||||
|
||||
protected override void AddSelfToNet(IPowerNet powerNet)
|
||||
{
|
||||
powerNet.AddConsumer(this);
|
||||
}
|
||||
|
||||
protected override void RemoveSelfFromNet(IPowerNet powerNet)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
174
Content.Server/Power/Components/PowerProviderComponent.cs
Normal file
174
Content.Server/Power/Components/PowerProviderComponent.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
236
Content.Server/Power/Components/PowerReceiverComponent.cs
Normal file
236
Content.Server/Power/Components/PowerReceiverComponent.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.APC;
|
||||
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.Physics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
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"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class PowerReceiverComponent : Component, IExamine
|
||||
{
|
||||
[ViewVariables] [ComponentDependency] private readonly IPhysBody? _physicsComponent = null;
|
||||
|
||||
public override string Name => "PowerReceiver";
|
||||
|
||||
[ViewVariables]
|
||||
public bool Powered => (HasApcPower || !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.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PowerReceptionRange { get => _powerReceptionRange; set => SetPowerReceptionRange(value); }
|
||||
[DataField("powerReceptionRange")]
|
||||
private int _powerReceptionRange = 3;
|
||||
|
||||
[ViewVariables]
|
||||
public IPowerProvider Provider { get => _provider; set => SetProvider(value); }
|
||||
private IPowerProvider _provider = PowerProviderComponent.NullProvider;
|
||||
|
||||
/// <summary>
|
||||
/// If this should be considered for connection by <see cref="PowerProviderComponent"/>s.
|
||||
/// </summary>
|
||||
public bool Connectable => Anchored;
|
||||
|
||||
private bool Anchored => _physicsComponent == null || _physicsComponent.BodyType == BodyType.Static;
|
||||
|
||||
[ViewVariables]
|
||||
public bool NeedsProvider { get; private set; } = true;
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <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); }
|
||||
[DataField("needsPower")]
|
||||
private bool _needsPower = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
if (NeedsProvider)
|
||||
{
|
||||
TryFindAndSetProvider();
|
||||
}
|
||||
if (_physicsComponent != null)
|
||||
{
|
||||
AnchorUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
_provider.RemoveReceiver(this);
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public void TryFindAndSetProvider()
|
||||
{
|
||||
if (TryFindAvailableProvider(out var provider))
|
||||
{
|
||||
Provider = provider;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApcPowerChanged()
|
||||
{
|
||||
var oldPowered = Powered;
|
||||
HasApcPower = Provider.HasApcPower;
|
||||
if (Powered != oldPowered)
|
||||
OnNewPowerState();
|
||||
}
|
||||
|
||||
private bool TryFindAvailableProvider(out IPowerProvider foundProvider)
|
||||
{
|
||||
var nearbyEntities = IoCManager.Resolve<IEntityLookup>()
|
||||
.GetEntitiesInRange(Owner, PowerReceptionRange);
|
||||
|
||||
foreach (var entity in nearbyEntities)
|
||||
{
|
||||
if (entity.TryGetComponent<PowerProviderComponent>(out var provider))
|
||||
{
|
||||
if (provider.Connectable)
|
||||
{
|
||||
if (provider.Owner.Transform.Coordinates.TryDistance(Owner.EntityManager, Owner.Transform.Coordinates, out var distance))
|
||||
{
|
||||
if (distance < Math.Min(PowerReceptionRange, provider.PowerTransferRange))
|
||||
{
|
||||
foundProvider = provider;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
_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));
|
||||
|
||||
if (Owner.TryGetComponent<AppearanceComponent>(out var appearance))
|
||||
{
|
||||
appearance.SetData(PowerDeviceVisuals.Powered, Powered);
|
||||
}
|
||||
}
|
||||
|
||||
public void AnchorUpdate()
|
||||
{
|
||||
if (Anchored)
|
||||
{
|
||||
if (NeedsProvider)
|
||||
{
|
||||
TryFindAndSetProvider();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearProvider();
|
||||
}
|
||||
}
|
||||
|
||||
///<summary>
|
||||
///Adds some markup to the examine text of whatever object is using this component to tell you if it's powered or not, even if it doesn't have an icon state to do this for you.
|
||||
///</summary>
|
||||
public void Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("It appears to be {0}.", Powered ? "[color=darkgreen]powered[/color]" : "[color=darkred]un-powered[/color]"));
|
||||
}
|
||||
}
|
||||
|
||||
public class PowerChangedMessage : ComponentMessage
|
||||
{
|
||||
public readonly bool Powered;
|
||||
|
||||
public PowerChangedMessage(bool powered)
|
||||
{
|
||||
Powered = powered;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Content.Server/Power/Components/PowerSupplierComponent.cs
Normal file
35
Content.Server/Power/Components/PowerSupplierComponent.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
#nullable enable
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class PowerSupplierComponent : BasePowerNetComponent
|
||||
{
|
||||
public override string Name => "PowerSupplier";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int SupplyRate { get => _supplyRate; set => SetSupplyRate(value); }
|
||||
[DataField("supplyRate")]
|
||||
private int _supplyRate;
|
||||
|
||||
protected override void AddSelfToNet(IPowerNet powerNet)
|
||||
{
|
||||
powerNet.AddSupplier(this);
|
||||
}
|
||||
|
||||
protected override void RemoveSelfFromNet(IPowerNet powerNet)
|
||||
{
|
||||
powerNet.RemoveSupplier(this);
|
||||
}
|
||||
|
||||
private void SetSupplyRate(int newSupplyRate)
|
||||
{
|
||||
Net.UpdateSupplierSupply(this, SupplyRate, newSupplyRate);
|
||||
_supplyRate = newSupplyRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user