Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Globalization;
5 
7 {
9  {
10  //[power/min]
11  private float capacity;
12  private float adjustedCapacity;
13 
14  private float charge, prevCharge;
15 
16  //how fast the battery can be recharged
17  private float maxRechargeSpeed;
18 
19  //how fast it's currently being recharged (can be changed, so that
20  //charging can be slowed down or disabled if there's a shortage of power)
21  private float rechargeSpeed;
22  private float lastSentCharge;
23 
24  //charge indicator description
25  protected Vector2 indicatorPosition, indicatorSize;
26 
27  protected bool isHorizontal;
28 
29  protected override PowerPriority Priority { get { return PowerPriority.Battery; } }
30 
31  private float currPowerOutput;
32  public float CurrPowerOutput
33  {
34  get { return currPowerOutput; }
35  private set
36  {
37  System.Diagnostics.Debug.Assert(value >= 0.0f, $"Tried to set PowerContainer's output to a negative value ({value})");
38  currPowerOutput = Math.Max(0, value);
39  }
40  }
41 
42  [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.")]
43  public Vector2 IndicatorPosition
44  {
45  get { return indicatorPosition; }
46  set { indicatorPosition = value; }
47  }
48 
49  [Serialize("0,0", IsPropertySaveable.Yes, description: "The size of the progress bar indicating the charge of the item (in pixels).")]
50  public Vector2 IndicatorSize
51  {
52  get { return indicatorSize; }
53  set { indicatorSize = value; }
54  }
55 
56  [Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge of the item fill up horizontally or vertically.")]
57  public bool IsHorizontal
58  {
59  get { return isHorizontal; }
60  set { isHorizontal = value; }
61  }
62 
63  [Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "Maximum output of the device when fully charged (kW).")]
64  public float MaxOutPut { set; get; }
65 
66  [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.")]
67  public float Capacity
68  {
69  get => capacity;
70  set
71  {
72  capacity = Math.Max(value, 1.0f);
73  adjustedCapacity = GetCapacity();
74  }
75  }
76 
77  [Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current charge of the device.")]
78  public float Charge
79  {
80  get { return charge; }
81  set
82  {
83  if (!MathUtils.IsValid(value)) return;
84  charge = MathHelper.Clamp(value, 0.0f, adjustedCapacity);
85 
86  //send a network event if the charge has changed by more than 5%
87  if (Math.Abs(charge - lastSentCharge) / adjustedCapacity > 0.05f)
88  {
89 #if SERVER
90  if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); }
91 #endif
92  lastSentCharge = charge;
93  }
94  }
95  }
96 
97  public float ChargePercentage => MathUtils.Percentage(Charge, adjustedCapacity);
98 
99  [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.")]
100  public float MaxRechargeSpeed
101  {
102  get { return maxRechargeSpeed; }
103  set { maxRechargeSpeed = Math.Max(value, 1.0f); }
104  }
105 
106  [Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current recharge speed of the device.")]
107  public float RechargeSpeed
108  {
109  get { return rechargeSpeed; }
110  set
111  {
112  if (!MathUtils.IsValid(value)) return;
113  rechargeSpeed = MathHelper.Clamp(value, 0.0f, maxRechargeSpeed);
114  rechargeSpeed = MathUtils.RoundTowardsClosest(rechargeSpeed, Math.Max(maxRechargeSpeed * 0.1f, 1.0f));
115  if (isRunning)
116  {
117  HasBeenTuned = true;
118  }
119  }
120  }
121 
122  [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.")]
123  public bool ExponentialRechargeSpeed { get; set; }
124 
125  private float efficiency;
126  [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.")]
127  public float Efficiency
128  {
129  get { return efficiency; }
130  set { efficiency = MathHelper.Clamp(value, 0.0f, 1.0f); }
131  }
132 
133  private bool flipIndicator;
134  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge be flipped to fill from the other side.")]
135  public bool FlipIndicator
136  {
137  get { return flipIndicator; }
138  set { flipIndicator = value; }
139  }
140 
141  public bool OutputDisabled { get; private set; }
142 
144 
145  public const float aiRechargeTargetRatio = 0.5f;
146  private bool isRunning;
147 
148  public bool HasBeenTuned { get; private set; }
149 
151  : base(item, element)
152  {
153  IsActive = true;
154  InitProjSpecific();
155  prevCharge = Charge;
156  }
157 
158  partial void InitProjSpecific();
159 
160  public override bool Pick(Character picker)
161  {
162  return picker != null;
163  }
164 
165  public override void Update(float deltaTime, Camera cam)
166  {
167  if (item.Connections == null)
168  {
169  IsActive = false;
170  return;
171  }
172 
173  adjustedCapacity = GetCapacity();
174  isRunning = true;
175  float chargeRatio = charge / adjustedCapacity;
176 
177  if (chargeRatio > 0.0f)
178  {
179  ApplyStatusEffects(ActionType.OnActive, deltaTime);
180  }
181 
182  float loadReading = 0;
183  if (powerOut != null && powerOut.Grid != null)
184  {
185  loadReading = powerOut.Grid.Load;
186  }
187 
188  item.SendSignal(((int)Math.Round(CurrPowerOutput)).ToString(), "power_value_out");
189  item.SendSignal(((int)Math.Round(loadReading)).ToString(), "load_value_out");
190  item.SendSignal(((int)Math.Round(Charge)).ToString(), "charge");
191  item.SendSignal(((int)Math.Round(Charge / adjustedCapacity * 100)).ToString(), "charge_%");
192  item.SendSignal(((int)Math.Round(RechargeSpeed / maxRechargeSpeed * 100)).ToString(), "charge_rate");
193  }
194 
199  public override float GetCurrentPowerConsumption(Connection connection = null)
200  {
201  if (connection == powerIn)
202  {
203  //Don't draw power if fully charged
204  if (charge >= adjustedCapacity)
205  {
206  charge = adjustedCapacity;
207  return 0;
208  }
209  else
210  {
211  if (item.Condition <= 0.0f) { return 0.0f; }
212 
213  float missingCharge = adjustedCapacity - charge;
214  float targetRechargeSpeed = rechargeSpeed;
215 
217  {
218  targetRechargeSpeed = MathF.Pow(rechargeSpeed / maxRechargeSpeed, 2) * maxRechargeSpeed;
219  }
220  //For the last kwMin scale the recharge rate linearly to prevent overcharging and to have a smooth cutoff
221  if (missingCharge < 1.0f)
222  {
223  targetRechargeSpeed *= missingCharge;
224  }
225 
226  return MathHelper.Clamp(targetRechargeSpeed, 0, MaxRechargeSpeed);
227  }
228  }
229  else
230  {
231  CurrPowerOutput = 0;
232  return charge > 0 ? -1 : 0;
233  }
234  }
235 
245  public override PowerRange MinMaxPowerOut(Connection connection, float load = 0)
246  {
247  if (OutputDisabled) { return PowerRange.Zero; }
248  if (connection == powerOut)
249  {
250  float maxOutput;
251  float chargeRatio = prevCharge / adjustedCapacity;
252  if (chargeRatio < 0.1f)
253  {
254  maxOutput = Math.Max(chargeRatio * 10.0f, 0.0f) * MaxOutPut;
255  }
256  else
257  {
258  maxOutput = MaxOutPut;
259  }
260 
261  //Limit max power out to not exceed the charge of the container
262  maxOutput = Math.Min(maxOutput, prevCharge * 60 / UpdateInterval);
263  return new PowerRange(0.0f, maxOutput);
264  }
265 
266  return PowerRange.Zero;
267  }
268 
279  public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
280  {
281  if (OutputDisabled) { return 0; }
282  //Only power out connection can provide power and Max poweroutput can't be negative
283  if (connection == powerOut && minMaxPower.Max > 0)
284  {
285  //Set power output based on the relative max power output capabilities and load demand
286  CurrPowerOutput = MathHelper.Clamp((load - power) / minMaxPower.Max, 0, 1) * MinMaxPowerOut(connection, load).Max;
287  return CurrPowerOutput;
288  }
289  return 0.0f;
290  }
291 
295  public override void GridResolved(Connection conn)
296  {
297  if (conn == powerIn)
298  {
299  //Increase charge based on how much power came in from the grid
300  Charge += (CurrPowerConsumption * Voltage) / 60 * UpdateInterval * efficiency;
301  }
302  else
303  {
304  //Decrease charge based on how much power is leaving the device
305  Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, adjustedCapacity);
306  prevCharge = Charge;
307  }
308  }
309 
310  public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
311  {
312  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
313 
314  if (objective.Override)
315  {
316  HasBeenTuned = false;
317  }
318  if (HasBeenTuned) { return true; }
319 
320  float targetRatio = objective.Option.IsEmpty || objective.Option == "charge" ? aiRechargeTargetRatio : -1;
321  if (targetRatio > 0 || float.TryParse(objective.Option.Value, out targetRatio))
322  {
323  if (Math.Abs(rechargeSpeed - maxRechargeSpeed * targetRatio) > 0.05f)
324  {
325 #if SERVER
326  item.CreateServerEvent(this);
327 #endif
328  RechargeSpeed = maxRechargeSpeed * targetRatio;
329 #if CLIENT
330  if (rechargeSpeedSlider != null)
331  {
332  rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
333  }
334 #endif
335  if (character.IsOnPlayerTeam)
336  {
337  character.Speak(TextManager.GetWithVariables("DialogChargeBatteries",
338  ("[itemname]", item.Name, FormatCapitals.Yes),
339  ("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
340  null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
341  }
342  }
343  }
344  else
345  {
346  if (rechargeSpeed > 0.0f)
347  {
348 #if SERVER
349  item.CreateServerEvent(this);
350 #endif
351  RechargeSpeed = 0.0f;
352 #if CLIENT
353  if (rechargeSpeedSlider != null)
354  {
355  rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f);
356  }
357 #endif
358  if (character.IsOnPlayerTeam)
359  {
360  character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries",
361  ("[itemname]", item.Name, FormatCapitals.Yes),
362  ("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
363  null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
364  }
365  }
366  }
367 
368  return true;
369  }
370 
371  public override void ReceiveSignal(Signal signal, Connection connection)
372  {
373  if (connection.IsPower) { return; }
374  switch (connection.Name)
375  {
376  case "disable_output":
377  OutputDisabled = signal.value != "0";
378  break;
379  case "set_rate":
380  if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
381  {
382  if (!MathUtils.IsValid(tempSpeed)) { return; }
383 
384  float rechargeRate = MathHelper.Clamp(tempSpeed / 100.0f, 0.0f, 1.0f);
385  RechargeSpeed = rechargeRate * MaxRechargeSpeed;
386 #if CLIENT
387  if (rechargeSpeedSlider != null)
388  {
389  rechargeSpeedSlider.BarScroll = rechargeRate;
390  }
391 #endif
392  }
393  break;
394 
395  }
396  }
397 
398  public float GetCapacity() => item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.BatteryCapacity, Capacity);
399  }
400 }
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
Submarine Submarine
Definition: Entity.cs:53
static NetworkMember NetworkMember
Definition: GameMain.cs:190
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
void SendSignal(string signal, string connectionName)
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
Finalized power out from the container for the connection, provided the given grid information Output...
override float GetCurrentPowerConsumption(Connection connection=null)
Returns the power consumption if checking the powerIn connection, or a negative value if the output c...
override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
true if the operation was completed
override void GridResolved(Connection conn)
When the corresponding grid connection is resolved, adjust the container's charge.
override PowerRange MinMaxPowerOut(Connection connection, float load=0)
Minimum and maximum output for the queried connection. Powerin min max equals CurrPowerConsumption as...
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
PowerPriority
Order in which power sources will provide to a grid, lower number is higher priority
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19