Power Rework (#863)

Co-authored-by: py01 <pyronetics01@gmail.com>
This commit is contained in:
py01
2020-06-28 09:23:26 -06:00
committed by GitHub
parent ffe25de723
commit 23cc6b1d4e
154 changed files with 11253 additions and 3913 deletions

View File

@@ -0,0 +1,9 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
public abstract class BasePowerNetComponent : BaseNetConnectorComponent<IPowerNet>
{
protected override IPowerNet NullNet => PowerNetNodeGroup.NullNet;
}
}

View File

@@ -0,0 +1,73 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
/// <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]
private BatteryComponent _battery;
[ViewVariables]
private PowerSupplierComponent _supplier;
[ViewVariables(VVAccess.ReadWrite)]
public int ActiveSupplyRate { get => _activeSupplyRate; set => SetActiveSupplyRate(value); }
private int _activeSupplyRate;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _activeSupplyRate, "activeSupplyRate", 50);
}
public override void Initialize()
{
base.Initialize();
_battery = Owner.GetComponent<BatteryComponent>();
_supplier = Owner.GetComponent<PowerSupplierComponent>();
UpdateSupplyRate();
}
public void Update(float frameTime)
{
//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.BatteryState == BatteryState.Empty)
{
SetSupplierSupplyRate(0);
}
else
{
SetSupplierSupplyRate(ActiveSupplyRate);
}
}
private void SetSupplierSupplyRate(int newSupplierSupplyRate)
{
if (_supplier.SupplyRate != newSupplierSupplyRate)
{
_supplier.SupplyRate = newSupplierSupplyRate;
}
}
private void SetActiveSupplyRate(int newEnabledSupplyRate)
{
_activeSupplyRate = newEnabledSupplyRate;
UpdateSupplyRate();
}
}
}

View File

@@ -0,0 +1,72 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
/// <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); }
private int _activeDrawRate;
[ViewVariables]
private BatteryComponent _battery;
[ViewVariables]
public PowerConsumerComponent Consumer { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _activeDrawRate, "activeDrawRate", 100);
}
public override void Initialize()
{
base.Initialize();
_battery = Owner.GetComponent<BatteryComponent>();
Consumer = Owner.GetComponent<PowerConsumerComponent>();
UpdateDrawRate();
}
public void Update(float frameTime)
{
//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.BatteryState == BatteryState.Full)
{
SetConsumerDraw(0);
}
else
{
SetConsumerDraw(ActiveDrawRate);
}
}
private void SetConsumerDraw(int newConsumerDrawRate)
{
if (Consumer.DrawRate != newConsumerDrawRate)
{
Consumer.DrawRate = newConsumerDrawRate;
}
}
private void SetActiveDrawRate(int newEnabledDrawRate)
{
_activeDrawRate = newEnabledDrawRate;
UpdateDrawRate();
}
}
}

View File

@@ -0,0 +1,78 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Diagnostics;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
[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); }
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); }
private Priority _priority;
/// <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 override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _drawRate, "drawRate", 0);
serializer.DataField(ref _priority, "priority", Priority.First);
}
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);
_receivedPower = newReceivedPower;
}
private void SetPriority(Priority newPriority)
{
Net.UpdateConsumerPriority(this, Priority, newPriority);
_priority = newPriority;
}
}
public enum Priority
{
First,
Last,
}
}

View File

@@ -0,0 +1,39 @@
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power.PowerNetComponents
{
[RegisterComponent]
public class PowerSupplierComponent : BasePowerNetComponent
{
public override string Name => "PowerSupplier";
[ViewVariables(VVAccess.ReadWrite)]
public int SupplyRate { get => _supplyRate; set => SetSupplyRate(value); }
private int _supplyRate;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _supplyRate, "supplyRate", 0);
}
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;
}
}
}

View File

@@ -0,0 +1,89 @@
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using System;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Handles the "user-facing" side of the actual SMES object.
/// This is operations that are specific to the SMES, like UI and visuals.
/// Code interfacing with the powernet is handled in <see cref="BatteryStorageComponent"/> and <see cref="BatteryDischargerComponent"/>.
/// </summary>
[RegisterComponent]
public class SmesComponent : Component
{
public override string Name => "Smes";
private BatteryComponent _battery;
private AppearanceComponent _appearance;
private int _lastChargeLevel = 0;
private TimeSpan _lastChargeLevelChange;
private ChargeState _lastChargeState;
private TimeSpan _lastChargeStateChange;
private const int VisualsChangeDelay = 1;
#pragma warning disable 649
[Dependency] private readonly IGameTiming _gameTiming;
#pragma warning restore 649
public override void Initialize()
{
base.Initialize();
_battery = Owner.GetComponent<BatteryComponent>();
_appearance = Owner.GetComponent<AppearanceComponent>();
}
public void OnUpdate()
{
var newLevel = GetNewChargeLevel();
if (newLevel != _lastChargeLevel && _lastChargeLevelChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastChargeLevel = newLevel;
_lastChargeLevelChange = _gameTiming.CurTime;
_appearance.SetData(SmesVisuals.LastChargeLevel, newLevel);
}
var newChargeState = GetNewChargeState();
if (newChargeState != _lastChargeState && _lastChargeStateChange + TimeSpan.FromSeconds(VisualsChangeDelay) < _gameTiming.CurTime)
{
_lastChargeState = newChargeState;
_lastChargeStateChange = _gameTiming.CurTime;
_appearance.SetData(SmesVisuals.LastChargeState, newChargeState);
}
}
private int GetNewChargeLevel()
{
return ContentHelpers.RoundToLevels(_battery.CurrentCharge, _battery.MaxCharge, 6);
}
private ChargeState GetNewChargeState()
{
var supplier = Owner.GetComponent<PowerSupplierComponent>();
var consumer = Owner.GetComponent<PowerConsumerComponent>();
if (supplier.SupplyRate > 0 && consumer.DrawRate != consumer.ReceivedPower)
{
return ChargeState.Discharging;
}
else if (supplier.SupplyRate == 0 && consumer.DrawRate > 0)
{
return ChargeState.Charging;
}
else
{
return ChargeState.Still;
}
}
}
}

View File

@@ -0,0 +1,77 @@
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.Components.Power
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class SolarControlConsoleComponent : SharedSolarControlConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
private BoundUserInterface _userInterface;
private PowerReceiverComponent _powerReceiver;
private PowerSolarSystem _powerSolarSystem;
private bool Powered => _powerReceiver.Powered;
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(SolarControlConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerSolarSystem = _entitySystemManager.GetEntitySystem<PowerSolarSystem>();
}
public void UpdateUIState()
{
_userInterface.SetState(new SolarControlConsoleBoundInterfaceState(_powerSolarSystem.TargetPanelRotation, _powerSolarSystem.TargetPanelVelocity, _powerSolarSystem.TotalPanelPower, _powerSolarSystem.TowardsSun));
}
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
switch (obj.Message)
{
case SolarControlConsoleAdjustMessage msg:
if (double.IsFinite(msg.Rotation))
{
_powerSolarSystem.TargetPanelRotation = msg.Rotation.Reduced();
}
if (double.IsFinite(msg.AngularVelocity))
{
_powerSolarSystem.TargetPanelVelocity = msg.AngularVelocity.Reduced();
}
break;
}
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
{
return;
}
if (!Powered)
{
return;
}
// always update the UI immediately before opening, just in case
UpdateUIState();
_userInterface.Open(actor.playerSession);
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using Content.Server.GameObjects.Components.Damage;
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// This is a solar panel.
/// It generates power from the sun based on coverage.
/// </summary>
[RegisterComponent]
public class SolarPanelComponent : Component, IBreakAct
{
public override string Name => "SolarPanel";
private PowerSupplierComponent _powerSupplier;
/// <summary>
/// Maximum supply output by this panel (coverage = 1)
/// </summary>
private int _maxSupply = 1500;
[ViewVariables(VVAccess.ReadWrite)]
public int MaxSupply
{
get => _maxSupply;
set {
_maxSupply = value;
UpdateSupply();
}
}
/// <summary>
/// Current coverage of this panel (from 0 to 1).
/// This is updated by <see cref='PowerSolarSystem'/>.
/// </summary>
private float _coverage = 0;
[ViewVariables]
public float Coverage
{
get => _coverage;
set {
// This gets updated once-per-tick, so avoid updating it if truly unnecessary
if (_coverage != value) {
_coverage = value;
UpdateSupply();
}
}
}
/// <summary>
/// The game time (<see cref='IGameTiming'/>) of the next coverage update.
/// This may have a random offset applied.
/// This is used to reduce solar panel updates and stagger them to prevent lagspikes.
/// This should only be updated by the PowerSolarSystem but is viewable for debugging.
/// </summary>
[ViewVariables]
public TimeSpan TimeOfNextCoverageUpdate = TimeSpan.MinValue;
private void UpdateSupply()
{
if (_powerSupplier != null)
_powerSupplier.SupplyRate = (int) (_maxSupply * _coverage);
}
public override void Initialize()
{
base.Initialize();
_powerSupplier = Owner.GetComponent<PowerSupplierComponent>();
UpdateSupply();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxSupply, "maxsupply", 1500);
}
public void OnBreak(BreakageEventArgs args)
{
var sprite = Owner.GetComponent<SpriteComponent>();
sprite.LayerSetState(0, "broken");
MaxSupply = 0;
}
}
}