Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs
2023-11-30 13:53:00 +02:00

401 lines
16 KiB
C#

using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
namespace Barotrauma.Items.Components
{
partial class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
{
//[power/min]
private float capacity;
private float adjustedCapacity;
private float charge, prevCharge;
//how fast the battery can be recharged
private float maxRechargeSpeed;
//how fast it's currently being recharged (can be changed, so that
//charging can be slowed down or disabled if there's a shortage of power)
private float rechargeSpeed;
private float lastSentCharge;
//charge indicator description
protected Vector2 indicatorPosition, indicatorSize;
protected bool isHorizontal;
protected override PowerPriority Priority { get { return PowerPriority.Battery; } }
private float currPowerOutput;
public float CurrPowerOutput
{
get { return currPowerOutput; }
private set
{
System.Diagnostics.Debug.Assert(value >= 0.0f, $"Tried to set PowerContainer's output to a negative value ({value})");
currPowerOutput = Math.Max(0, value);
}
}
[Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the progress bar indicating the charge of the item. In pixels as an offset from the upper left corner of the sprite.")]
public Vector2 IndicatorPosition
{
get { return indicatorPosition; }
set { indicatorPosition = value; }
}
[Serialize("0,0", IsPropertySaveable.Yes, description: "The size of the progress bar indicating the charge of the item (in pixels).")]
public Vector2 IndicatorSize
{
get { return indicatorSize; }
set { indicatorSize = value; }
}
[Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge of the item fill up horizontally or vertically.")]
public bool IsHorizontal
{
get { return isHorizontal; }
set { isHorizontal = value; }
}
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "Maximum output of the device when fully charged (kW).")]
public float MaxOutPut { set; get; }
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "The maximum capacity of the device (kW * min). For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
public float Capacity
{
get => capacity;
set
{
capacity = Math.Max(value, 1.0f);
adjustedCapacity = GetCapacity();
}
}
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current charge of the device.")]
public float Charge
{
get { return charge; }
set
{
if (!MathUtils.IsValid(value)) return;
charge = MathHelper.Clamp(value, 0.0f, adjustedCapacity);
//send a network event if the charge has changed by more than 5%
if (Math.Abs(charge - lastSentCharge) / adjustedCapacity > 0.05f)
{
#if SERVER
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); }
#endif
lastSentCharge = charge;
}
}
}
public float ChargePercentage => MathUtils.Percentage(Charge, adjustedCapacity);
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
public float MaxRechargeSpeed
{
get { return maxRechargeSpeed; }
set { maxRechargeSpeed = Math.Max(value, 1.0f); }
}
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current recharge speed of the device.")]
public float RechargeSpeed
{
get { return rechargeSpeed; }
set
{
if (!MathUtils.IsValid(value)) return;
rechargeSpeed = MathHelper.Clamp(value, 0.0f, maxRechargeSpeed);
rechargeSpeed = MathUtils.RoundTowardsClosest(rechargeSpeed, Math.Max(maxRechargeSpeed * 0.1f, 1.0f));
if (isRunning)
{
HasBeenTuned = true;
}
}
}
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
public bool ExponentialRechargeSpeed { get; set; }
private float efficiency;
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, IsPropertySaveable.Yes, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
public float Efficiency
{
get { return efficiency; }
set { efficiency = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private bool flipIndicator;
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge be flipped to fill from the other side.")]
public bool FlipIndicator
{
get { return flipIndicator; }
set { flipIndicator = value; }
}
public bool OutputDisabled { get; private set; }
public float RechargeRatio => RechargeSpeed / MaxRechargeSpeed;
public const float aiRechargeTargetRatio = 0.5f;
private bool isRunning;
public bool HasBeenTuned { get; private set; }
public PowerContainer(Item item, ContentXElement element)
: base(item, element)
{
IsActive = true;
InitProjSpecific();
prevCharge = Charge;
}
partial void InitProjSpecific();
public override bool Pick(Character picker)
{
return picker != null;
}
public override void Update(float deltaTime, Camera cam)
{
if (item.Connections == null)
{
IsActive = false;
return;
}
adjustedCapacity = GetCapacity();
isRunning = true;
float chargeRatio = charge / adjustedCapacity;
if (chargeRatio > 0.0f)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime);
}
float loadReading = 0;
if (powerOut != null && powerOut.Grid != null)
{
loadReading = powerOut.Grid.Load;
}
item.SendSignal(((int)Math.Round(CurrPowerOutput)).ToString(), "power_value_out");
item.SendSignal(((int)Math.Round(loadReading)).ToString(), "load_value_out");
item.SendSignal(((int)Math.Round(Charge)).ToString(), "charge");
item.SendSignal(((int)Math.Round(Charge / adjustedCapacity * 100)).ToString(), "charge_%");
item.SendSignal(((int)Math.Round(RechargeSpeed / maxRechargeSpeed * 100)).ToString(), "charge_rate");
}
/// <summary>
/// Returns the power consumption if checking the powerIn connection, or a negative value if the output can provide power when checking powerOut.
/// Power consumption is proportional to set recharge speed and if there is less than max charge.
/// </summary>
public override float GetCurrentPowerConsumption(Connection connection = null)
{
if (connection == powerIn)
{
//Don't draw power if fully charged
if (charge >= adjustedCapacity)
{
charge = adjustedCapacity;
return 0;
}
else
{
if (item.Condition <= 0.0f) { return 0.0f; }
float missingCharge = adjustedCapacity - charge;
float targetRechargeSpeed = rechargeSpeed;
if (ExponentialRechargeSpeed)
{
targetRechargeSpeed = MathF.Pow(rechargeSpeed / maxRechargeSpeed, 2) * maxRechargeSpeed;
}
//For the last kwMin scale the recharge rate linearly to prevent overcharging and to have a smooth cutoff
if (missingCharge < 1.0f)
{
targetRechargeSpeed *= missingCharge;
}
return MathHelper.Clamp(targetRechargeSpeed, 0, MaxRechargeSpeed);
}
}
else
{
CurrPowerOutput = 0;
return charge > 0 ? -1 : 0;
}
}
/// <summary>
/// Minimum and maximum output for the queried connection.
/// Powerin min max equals CurrPowerConsumption as its abnormal for there to be power out.
/// PowerOut min power out is zero and max is the maxout unless below 10% charge where
/// the output is scaled relative to the 10% charge.
/// </summary>
/// <param name="connection">Connection being queried</param>
/// <param name="load">Current grid load</param>
/// <returns>Minimum and maximum power output for the connection</returns>
public override PowerRange MinMaxPowerOut(Connection connection, float load = 0)
{
if (OutputDisabled) { return PowerRange.Zero; }
if (connection == powerOut)
{
float maxOutput;
float chargeRatio = prevCharge / adjustedCapacity;
if (chargeRatio < 0.1f)
{
maxOutput = Math.Max(chargeRatio * 10.0f, 0.0f) * MaxOutPut;
}
else
{
maxOutput = MaxOutPut;
}
//Limit max power out to not exceed the charge of the container
maxOutput = Math.Min(maxOutput, prevCharge * 60 / UpdateInterval);
return new PowerRange(0.0f, maxOutput);
}
return PowerRange.Zero;
}
/// <summary>
/// Finalized power out from the container for the connection, provided the given grid information
/// Output power based on the maxpower all batteries can output. So all batteries can
/// equally share powerout based on their output capabilities.
/// </summary>
/// <param name="connection"></param>
/// <param name="power"></param>
/// <param name="minMaxPower"></param>
/// <param name="load"></param>
/// <returns></returns>
public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
{
if (OutputDisabled) { return 0; }
//Only power out connection can provide power and Max poweroutput can't be negative
if (connection == powerOut && minMaxPower.Max > 0)
{
//Set power output based on the relative max power output capabilities and load demand
CurrPowerOutput = MathHelper.Clamp((load - power) / minMaxPower.Max, 0, 1) * MinMaxPowerOut(connection, load).Max;
return CurrPowerOutput;
}
return 0.0f;
}
/// <summary>
/// When the corresponding grid connection is resolved, adjust the container's charge.
/// </summary>
public override void GridResolved(Connection conn)
{
if (conn == powerIn)
{
//Increase charge based on how much power came in from the grid
Charge += (CurrPowerConsumption * Voltage) / 60 * UpdateInterval * efficiency;
}
else
{
//Decrease charge based on how much power is leaving the device
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, adjustedCapacity);
prevCharge = Charge;
}
}
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
if (objective.Override)
{
HasBeenTuned = false;
}
if (HasBeenTuned) { return true; }
float targetRatio = objective.Option.IsEmpty || objective.Option == "charge" ? aiRechargeTargetRatio : -1;
if (targetRatio > 0 || float.TryParse(objective.Option.Value, out targetRatio))
{
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * targetRatio) > 0.05f)
{
#if SERVER
item.CreateServerEvent(this);
#endif
RechargeSpeed = maxRechargeSpeed * targetRatio;
#if CLIENT
if (rechargeSpeedSlider != null)
{
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
}
#endif
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariables("DialogChargeBatteries",
("[itemname]", item.Name, FormatCapitals.Yes),
("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
}
}
}
else
{
if (rechargeSpeed > 0.0f)
{
#if SERVER
item.CreateServerEvent(this);
#endif
RechargeSpeed = 0.0f;
#if CLIENT
if (rechargeSpeedSlider != null)
{
rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
}
#endif
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries",
("[itemname]", item.Name, FormatCapitals.Yes),
("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
}
}
}
return true;
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (connection.IsPower) { return; }
switch (connection.Name)
{
case "disable_output":
OutputDisabled = signal.value != "0";
break;
case "set_rate":
if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
{
if (!MathUtils.IsValid(tempSpeed)) { return; }
float rechargeRate = MathHelper.Clamp(tempSpeed / 100.0f, 0.0f, 1.0f);
RechargeSpeed = rechargeRate * MaxRechargeSpeed;
#if CLIENT
if (rechargeSpeedSlider != null)
{
rechargeSpeedSlider.BarScroll = rechargeRate;
}
#endif
}
break;
}
}
public float GetCapacity() => item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.BatteryCapacity, Capacity);
}
}