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 { /// /// Component that requires power to function /// [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(); AvailableProviders = componentMgr.GetAllComponents().Where(x => x.CanServiceDevice(this)).ToList(); ConnectToBestProvider(); } } protected virtual bool SaveLoad => true; /// /// The method of draw we will try to use to place our load set via component parameter, defaults to using power providers /// [ViewVariables] public DrawTypes DrawType { get => _drawType ?? DefaultDrawType; protected set => _drawType = value; } private DrawTypes? _drawType; protected virtual DrawTypes DefaultDrawType => DrawTypes.Provider; /// /// The power draw method we are currently connected to and using /// [ViewVariables] public DrawTypes Connected { get; protected set; } = DrawTypes.None; [ViewVariables] public bool Powered { get; private set; } = false; /// /// Is an external power source currently available? /// [ViewVariables] public bool ExternalPowered { get => _externalPowered; set { _externalPowered = value; UpdatePowered(); } } private bool _externalPowered = false; /// /// Is an internal power source currently available? /// [ViewVariables] protected bool InternalPowered { get => _internalPowered; set { _internalPowered = value; UpdatePowered(); } } private bool _internalPowered = false; /// /// Priority for powernet draw, lower will draw first, defined in powernet.cs /// [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 /// /// Power load from this entity. /// In Watts. /// [ViewVariables] public float Load { get => _load; set => UpdateLoad(value); } /// /// All the power providers that we are within range of /// public List AvailableProviders = new List(); private PowerProviderComponent _provider; private bool _isPowerCut; /// /// A power provider that will handle our load, if we are linked to any /// [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 OnPowerStateChanged; public override void OnAdd() { base.OnAdd(); if (DrawType == DrawTypes.Node || DrawType == DrawTypes.Both) { if (!Owner.TryGetComponent(out PowerNodeComponent node)) { Owner.AddComponent(); node = Owner.GetComponent(); } node.OnPowernetConnect += PowernetConnect; node.OnPowernetDisconnect += PowernetDisconnect; node.OnPowernetRegenerate += PowernetRegenerate; } } /// 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) { var loc = IoCManager.Resolve(); if (!Powered) { 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(); node.Parent.UpdateDevice(this, oldLoad); } else if (Connected == DrawTypes.Provider) { Provider.UpdateDevice(this, oldLoad); } } /// /// Updates the state of whether or not this device is powered, /// and fires off events if said state has changed. /// 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); } } } /// /// Register a new power provider as a possible connection to this device /// /// public void AddProvider(PowerProviderComponent provider) { AvailableProviders.Add(provider); if (Connected != DrawTypes.Node) { ConnectToBestProvider(); } } /// /// Find the nearest registered power provider and connect to it /// 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().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().WorldPosition - position).LengthSquared; foreach (var availprovider in AvailableProviders) { //Find distance to new provider var distance = (availprovider.Owner.GetComponent().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; } /// /// Remove a power provider from being a possible connection to this device /// /// 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(); } } /// /// Node has become anchored to a powernet /// /// /// 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; } /// /// Powernet wire was remove so we need to regenerate the powernet /// /// /// protected virtual void PowernetRegenerate(object sender, PowernetEventArgs eventarg) { eventarg.Powernet.AddDevice(this); } /// /// Node has become unanchored from a powernet /// /// /// protected virtual void PowernetDisconnect(object sender, PowernetEventArgs eventarg) { eventarg.Powernet.RemoveDevice(this); Connected = DrawTypes.None; ConnectToBestProvider(); } /// /// Process mechanism to keep track of internal battery and power status. /// /// Time since the last process frame. internal virtual void ProcessInternalPower(float frametime) { if (Owner.TryGetComponent(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; } } } /// /// The different methods that a can use to connect to a power network. /// public enum DrawTypes { /// /// This device cannot be connected to a power network. /// None = 0, /// /// This device can connect to a /// Node = 1, Provider = 2, Both = 3, } public class PowerStateEventArgs : EventArgs { public readonly bool Powered; public PowerStateEventArgs(bool powered) { Powered = powered; } } }