using System; using System.Xml.Linq; using Microsoft.Xna.Framework; using System.Collections.Generic; #if CLIENT using Barotrauma.Sounds; #endif namespace Barotrauma.Items.Components { partial class Powered : ItemComponent { private static float updateTimer; protected static float UpdateInterval = 0.2f; /// /// List of all powered ItemComponents /// private static readonly List poweredList = new List(); public static IEnumerable PoweredList { get { return poweredList; } } /// /// Items that have already received the "probe signal" that's used to distribute power and load across the grid /// protected static HashSet lastPowerProbeRecipients = new HashSet(); /// /// The amount of power currently consumed by the item. Negative values mean that the item is providing power to connected items /// protected float currPowerConsumption; /// /// Current voltage of the item (load / power) /// private float voltage; /// /// The minimum voltage required for the item to work /// private float minVoltage; /// /// The maximum amount of power the item can draw from connected items /// protected float powerConsumption; protected Connection powerIn, powerOut; [Editable, Serialize(0.5f, true, description: "The minimum voltage required for the device to function. " + "The voltage is calculated as power / powerconsumption, meaning that a device " + "with a power consumption of 1000 kW would need at least 500 kW of power to work if the minimum voltage is set to 0.5.")] public float MinVoltage { get { return powerConsumption <= 0.0f ? 0.0f : minVoltage; } set { minVoltage = value; } } [Editable, Serialize(0.0f, true, description: "How much power the device draws (or attempts to draw) from the electrical grid when active.")] public float PowerConsumption { get { return powerConsumption; } set { powerConsumption = value; } } [Serialize(false, true, description: "Is the device currently active. Inactive devices don't consume power.")] public override bool IsActive { get { return base.IsActive; } set { base.IsActive = value; if (!value) { currPowerConsumption = 0.0f; } } } [Serialize(0.0f, true, description: "The current power consumption of the device. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")] public float CurrPowerConsumption { get {return currPowerConsumption; } set { currPowerConsumption = value; } } [Serialize(0.0f, true, description: "The current voltage of the item (calculated as power consumption / available power). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")] public float Voltage { get { return voltage; } set { voltage = Math.Max(0.0f, value); } } [Editable, Serialize(true, true, description: "Can the item be damaged by electomagnetic pulses.")] public bool VulnerableToEMP { get; set; } public Powered(Item item, XElement element) : base(item, element) { poweredList.Add(this); InitProjectSpecific(element); } partial void InitProjectSpecific(XElement element); protected void UpdateOnActiveEffects(float deltaTime) { if (currPowerConsumption <= 0.0f) { //if the item consumes no power, ignore the voltage requirement and //apply OnActive statuseffects as long as this component is active if (powerConsumption <= 0.0f) { ApplyStatusEffects(ActionType.OnActive, deltaTime, null); } return; } if (voltage > minVoltage) { ApplyStatusEffects(ActionType.OnActive, deltaTime, null); } #if CLIENT if (voltage > minVoltage) { if (!powerOnSoundPlayed && powerOnSound != null) { SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, hullGuess: item.CurrentHull, ignoreMuffling: powerOnSound.IgnoreMuffling); powerOnSoundPlayed = true; } } else if (voltage < 0.1f) { powerOnSoundPlayed = false; } #endif } public override void Update(float deltaTime, Camera cam) { currPowerConsumption = powerConsumption; UpdateOnActiveEffects(deltaTime); } public override void OnItemLoaded() { if (item.Connections == null) { return; } foreach (Connection c in item.Connections) { if (!c.IsPower) { continue; } if (this is PowerTransfer pt) { if (c.Name == "power_in") { powerIn = c; } else if (c.Name == "power_out") { powerOut = c; } else if (c.Name == "power") { powerIn = powerOut = c; } } else { if (c.IsOutput) { if (c.Name == "power_in") { #if DEBUG DebugConsole.ThrowError($"Item \"{item.Name}\" has a power output connection called power_in. If the item is supposed to receive power through the connection, change it to an input connection."); #else DebugConsole.NewMessage($"Item \"{item.Name}\" has a power output connection called power_in. If the item is supposed to receive power through the connection, change it to an input connection.", Color.Orange); #endif } powerOut = c; } else { if (c.Name == "power_out") { #if DEBUG DebugConsole.ThrowError($"Item \"{item.Name}\" has a power input connection called power_out. If the item is supposed to output power through the connection, change it to an output connection."); #else DebugConsole.NewMessage($"Item \"{item.Name}\" has a power input connection called power_out. If the item is supposed to output power through the connection, change it to an output connection.", Color.Orange); #endif } powerIn = c; } } } } public virtual void ReceivePowerProbeSignal(Connection connection, Item source, float power) { } public static void UpdatePower(float deltaTime) { if (updateTimer > 0.0f) { updateTimer -= deltaTime; return; } updateTimer = UpdateInterval; //reset power first foreach (Powered powered in poweredList) { if (powered is PowerTransfer pt) { powered.CurrPowerConsumption = 0.0f; pt.PowerLoad = 0.0f; if (pt is RelayComponent relay) { relay.DisplayLoad = 0.0f; } } //only reset voltage if the item has a power connector //(other items, such as handheld devices, get power through other means and shouldn't be updated here) if (powered.powerIn != null || powered.powerOut != null) { powered.voltage = 0.0f; } } //go through all the devices that are consuming/providing power //and send out a "probe signal" which the PowerTransfer components use to add up the grid power/load foreach (Powered powered in poweredList) { if (powered is PowerTransfer pt) { if (pt.ExtraLoad > 0.0f) { lastPowerProbeRecipients.Clear(); powered.powerIn?.SendPowerProbeSignal(powered.item, -pt.ExtraLoad); } continue; } else if (powered.currPowerConsumption > 0.0f) { //consuming power lastPowerProbeRecipients.Clear(); powered.powerIn?.SendPowerProbeSignal(powered.item, -powered.currPowerConsumption); } } foreach (Powered powered in poweredList) { if (powered is PowerTransfer) { continue; } else if (powered.currPowerConsumption < 0.0f) { //providing power lastPowerProbeRecipients.Clear(); powered.powerOut?.SendPowerProbeSignal(powered.item, -powered.currPowerConsumption); } if (powered is PowerContainer pc) { if (pc.CurrPowerOutput <= 0.0f || pc.item.Condition <= 0.0f) { continue; } //providing power lastPowerProbeRecipients.Clear(); powered.powerOut?.SendPowerProbeSignal(powered.item, pc.CurrPowerOutput); } } //go through powered items and calculate their current voltage foreach (Powered powered in poweredList) { if (powered is PowerTransfer pt1 || (pt1 = powered.Item.GetComponent()) != null) { powered.voltage = -pt1.CurrPowerConsumption / Math.Max(pt1.PowerLoad, 1.0f); continue; } if ((powered.powerConsumption <= 0.0f || (powered.Item.GetComponent() is Repairable repairable && repairable.IsTinkering && repairable.TinkeringPowersDevices)) && !(powered is PowerContainer)) { powered.voltage = 1.0f; continue; } if (powered.powerIn == null) { continue; } foreach (Connection powerSource in powered.powerIn.Recipients) { if (!powerSource.IsPower || !powerSource.IsOutput) { continue; } var pt = powerSource.Item.GetComponent(); if (pt != null) { float voltage = -pt.CurrPowerConsumption / Math.Max(pt.PowerLoad, 1.0f); powered.voltage = Math.Max(powered.voltage, voltage); continue; } var pc = powerSource.Item.GetComponent(); if (pc != null && pc.item.Condition > 0.0f) { float voltage = pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f); powered.voltage += voltage; } } } } /// /// Returns the amount of power that can be supplied by batteries directly connected to the item /// protected float GetAvailableInstantaneousBatteryPower() { if (item.Connections == null) { return 0.0f; } float availablePower = 0.0f; foreach (Connection c in item.Connections) { var recipients = c.Recipients; foreach (Connection recipient in recipients) { if (!recipient.IsPower || !recipient.IsOutput) { continue; } var battery = recipient.Item?.GetComponent(); if (battery == null) { continue; } float maxOutputPerFrame = battery.MaxOutPut / 60.0f; float framesPerMinute = 3600.0f; availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame); } } return availablePower; } protected override void RemoveComponentSpecific() { base.RemoveComponentSpecific(); poweredList.Remove(this); } } }