using System; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.EntitySystems; 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.Log; using Robust.Shared.Maths; 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, EntitySystems.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 virtual DrawTypes DrawType { get => _drawType; protected set => _drawType = value; } private DrawTypes _drawType = 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] public 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; 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; /// /// 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); serializer.DataField(ref _drawType, "drawtype", DrawTypes.Provider); 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.AddText(loc.GetString("The device is ")); message.PushColor(Color.Orange); message.AddText(loc.GetString("not powered")); message.Pop(); message.AddText("."); } } 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 = ExternalPowered || InternalPowered; if (oldPowered != Powered) { if (Powered) { OnPowerStateChanged?.Invoke(this, new PowerStateEventArgs(true)); } else { OnPowerStateChanged?.Invoke(this, new PowerStateEventArgs(false)); } } } /// /// 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; } } }