Files
tbd-station-14/Content.Server/GameObjects/Components/Power/PowerDevice.cs
Remie Richards 2e38c194f7 IExamine can now limit certain details behind a 'details' range check. (#1039)
* IExamine can now limit certain details behind a 'details' range check.

* Comic's Review fixes.
- colour -> color. My ancestors are saddened by this.
- Can see wire panel opened/closed at any distance again.
2020-05-31 20:29:06 +02:00

430 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Power
{
/// <summary>
/// Component that requires power to function
/// </summary>
[RegisterComponent]
public class PowerDeviceComponent : Component, IExamine
{
public override string Name => "PowerDevice";
protected override void Startup()
{
base.Startup();
if (DrawType != DrawTypes.Node)
{
var componentMgr = IoCManager.Resolve<IComponentManager>();
AvailableProviders = componentMgr.GetAllComponents<PowerProviderComponent>().Where(x => x.CanServiceDevice(this)).ToList();
ConnectToBestProvider();
}
}
protected virtual bool SaveLoad => true;
/// <summary>
/// The method of draw we will try to use to place our load set via component parameter, defaults to using power providers
/// </summary>
[ViewVariables]
public DrawTypes DrawType
{
get => _drawType ?? DefaultDrawType;
protected set => _drawType = value;
}
private DrawTypes? _drawType;
protected virtual DrawTypes DefaultDrawType => DrawTypes.Provider;
/// <summary>
/// The power draw method we are currently connected to and using
/// </summary>
[ViewVariables]
public DrawTypes Connected { get; protected set; } = DrawTypes.None;
[ViewVariables]
public bool Powered { get; private set; } = false;
/// <summary>
/// Is an external power source currently available?
/// </summary>
[ViewVariables]
public bool ExternalPowered
{
get => _externalPowered;
set
{
_externalPowered = value;
UpdatePowered();
}
}
private bool _externalPowered = false;
/// <summary>
/// Is an internal power source currently available?
/// </summary>
[ViewVariables]
protected bool InternalPowered
{
get => _internalPowered;
set
{
_internalPowered = value;
UpdatePowered();
}
}
private bool _internalPowered = false;
/// <summary>
/// Priority for powernet draw, lower will draw first, defined in powernet.cs
/// </summary>
[ViewVariables]
public virtual Powernet.Priority Priority
{
get => _priority;
protected set => _priority = value;
}
private Powernet.Priority _priority = Powernet.Priority.Medium;
[ViewVariables(VVAccess.ReadWrite)]
public bool IsPowerCut
{
get => _isPowerCut;
set
{
_isPowerCut = value;
UpdatePowered();
}
}
private float _load = 100; //arbitrary magic number to start
/// <summary>
/// Power load from this entity.
/// In Watts.
/// </summary>
[ViewVariables]
public float Load
{
get => _load;
set => UpdateLoad(value);
}
/// <summary>
/// All the power providers that we are within range of
/// </summary>
public List<PowerProviderComponent> AvailableProviders = new List<PowerProviderComponent>();
private PowerProviderComponent _provider;
private bool _isPowerCut;
/// <summary>
/// A power provider that will handle our load, if we are linked to any
/// </summary>
[ViewVariables]
public PowerProviderComponent Provider
{
get => _provider;
set
{
Connected = DrawTypes.Provider;
if (_provider != null)
{
_provider.RemoveDevice(this);
}
_provider = value;
if (value != null)
{
_provider.AddDevice(this);
}
else
{
Connected = DrawTypes.None;
}
}
}
public event EventHandler<PowerStateEventArgs> OnPowerStateChanged;
public override void OnAdd()
{
base.OnAdd();
if (DrawType == DrawTypes.Node || DrawType == DrawTypes.Both)
{
if (!Owner.TryGetComponent(out PowerNodeComponent node))
{
Owner.AddComponent<PowerNodeComponent>();
node = Owner.GetComponent<PowerNodeComponent>();
}
node.OnPowernetConnect += PowernetConnect;
node.OnPowernetDisconnect += PowernetDisconnect;
node.OnPowernetRegenerate += PowernetRegenerate;
}
}
/// <inheritdoc />
protected override void Shutdown()
{
if (Owner.TryGetComponent(out PowerNodeComponent node))
{
if (node.Parent != null && node.Parent.HasDevice(this))
{
node.Parent.RemoveDevice(this);
}
node.OnPowernetConnect -= PowernetConnect;
node.OnPowernetDisconnect -= PowernetDisconnect;
node.OnPowernetRegenerate -= PowernetRegenerate;
}
Connected = DrawTypes.None;
if (Provider != null)
{
Provider = null;
}
base.Shutdown();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
var drawType = DrawType;
serializer.DataField(ref drawType, "drawtype", DefaultDrawType);
DrawType = drawType;
serializer.DataField(ref _priority, "priority", Powernet.Priority.Medium);
if (SaveLoad)
{
serializer.DataField(ref _load, "load", 100);
}
}
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
if (!Powered && inDetailsRange)
{
message.AddMarkup(loc.GetString("The device is [color=orange]not powered[/color]."));
}
}
private void UpdateLoad(float value)
{
var oldLoad = _load;
_load = value;
if (Connected == DrawTypes.Node)
{
var node = Owner.GetComponent<PowerNodeComponent>();
node.Parent.UpdateDevice(this, oldLoad);
}
else if (Connected == DrawTypes.Provider)
{
Provider.UpdateDevice(this, oldLoad);
}
}
/// <summary>
/// Updates the state of whether or not this device is powered,
/// and fires off events if said state has changed.
/// </summary>
private void UpdatePowered()
{
var oldPowered = Powered;
Powered = !IsPowerCut && (ExternalPowered || InternalPowered);
if (oldPowered != Powered)
{
OnPowerStateChanged?.Invoke(this, new PowerStateEventArgs(Powered));
if (Owner.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(PowerDeviceVisuals.Powered, Powered);
}
}
}
/// <summary>
/// Register a new power provider as a possible connection to this device
/// </summary>
/// <param name="provider"></param>
public void AddProvider(PowerProviderComponent provider)
{
AvailableProviders.Add(provider);
if (Connected != DrawTypes.Node)
{
ConnectToBestProvider();
}
}
/// <summary>
/// Find the nearest registered power provider and connect to it
/// </summary>
private void ConnectToBestProvider()
{
//Any values we can connect to or are we already connected to a node, cancel!
if (!AvailableProviders.Any() || Connected == DrawTypes.Node || Deleted)
return;
//Get the starting value for our loop
var position = Owner.GetComponent<ITransformComponent>().WorldPosition;
var bestprovider = AvailableProviders[0];
//If we are already connected to a power provider we need to do a loop to find the nearest one, otherwise skip it and use first entry
if (Connected == DrawTypes.Provider)
{
var bestdistance = (bestprovider.Owner.GetComponent<ITransformComponent>().WorldPosition - position).LengthSquared;
foreach (var availprovider in AvailableProviders)
{
//Find distance to new provider
var distance = (availprovider.Owner.GetComponent<ITransformComponent>().WorldPosition - position).LengthSquared;
//If new provider distance is shorter it becomes new best possible provider
if (distance < bestdistance)
{
bestdistance = distance;
bestprovider = availprovider;
}
}
}
if (Provider != bestprovider)
Provider = bestprovider;
}
/// <summary>
/// Remove a power provider from being a possible connection to this device
/// </summary>
/// <param name="provider"></param>
public void RemoveProvider(PowerProviderComponent provider)
{
if (!AvailableProviders.Contains(provider))
return;
AvailableProviders.Remove(provider);
if (provider == Provider)
{
Provider = null;
ExternalPowered = false;
}
if (Connected != DrawTypes.Node)
{
ConnectToBestProvider();
}
}
/// <summary>
/// Node has become anchored to a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
protected virtual void PowernetConnect(object sender, PowernetEventArgs eventarg)
{
//This sets connected = none so it must be first
Provider = null;
eventarg.Powernet.AddDevice(this);
Connected = DrawTypes.Node;
}
/// <summary>
/// Powernet wire was remove so we need to regenerate the powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
protected virtual void PowernetRegenerate(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.AddDevice(this);
}
/// <summary>
/// Node has become unanchored from a powernet
/// </summary>
/// <param name="sender"></param>
/// <param name="eventarg"></param>
protected virtual void PowernetDisconnect(object sender, PowernetEventArgs eventarg)
{
eventarg.Powernet.RemoveDevice(this);
Connected = DrawTypes.None;
ConnectToBestProvider();
}
/// <summary>
/// Process mechanism to keep track of internal battery and power status.
/// </summary>
/// <param name="frametime">Time since the last process frame.</param>
internal virtual void ProcessInternalPower(float frametime)
{
if (Owner.TryGetComponent<PowerStorageComponent>(out var storage) && storage.CanDeductCharge(Load))
{
// We still keep InternalPowered correct if connected externally,
// but don't use it.
if (!ExternalPowered)
{
storage.DeductCharge(Load);
}
InternalPowered = true;
}
else
{
InternalPowered = false;
}
}
}
/// <summary>
/// The different methods that a <see cref="PowerDeviceComponent"/> can use to connect to a power network.
/// </summary>
public enum DrawTypes
{
/// <summary>
/// This device cannot be connected to a power network.
/// </summary>
None = 0,
/// <summary>
/// This device can connect to a <see cref=""/>
/// </summary>
Node = 1,
Provider = 2,
Both = 3,
}
public class PowerStateEventArgs : EventArgs
{
public readonly bool Powered;
public PowerStateEventArgs(bool powered)
{
Powered = powered;
}
}
}