Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs
1 using Microsoft.Xna.Framework;
2 using System;
3 using System.Globalization;
4 using System.Xml.Linq;
6 
8 {
10  {
11  private float force;
12 
19  private float? lastReceivedTargetForce;
20 
25  private float targetForce;
26 
32  private const float ForceToPowerExponent = 3f / 2f;
33  private const float PowerToForceExponent = 1.0f / ForceToPowerExponent;
34 
35  private float maxForce;
36 
37  private readonly Attack propellerDamage;
38 
39  private float damageTimer;
40 
41  private bool hasPower;
42 
43  private float prevVoltage;
44 
45  private float controlLockTimer;
46 
47  public Character User;
48 
49  [Editable(0.0f, 10000000.0f),
50  Serialize(500.0f, IsPropertySaveable.Yes, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
51  public float MaxForce
52  {
53  get => maxForce;
54  set => maxForce = Math.Max(0.0f, value);
55  }
56 
57  [Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes,
58  description: "The position of the propeller as an offset from the item's center (in pixels)."+
59  " Determines where the particles spawn and the position that causes characters to take damage from the engine if the PropellerDamage is defined.")]
60  public Vector2 PropellerPos
61  {
62  get;
63  set;
64  }
65 
68  {
69  get;
70  set;
71  }
72 
73  public float Force
74  {
75  get { return force;}
76  set { force = MathHelper.Clamp(value, -100.0f, 100.0f); }
77  }
78 
79  public float CurrentVolume => CurrentStress;
80 
81  public float CurrentBrokenVolume
82  {
83  get
84  {
85  if (item.ConditionPercentage > 10.0f) { return 0.0f; }
86  return Math.Abs(targetForce / 100.0f) * (1.0f - item.ConditionPercentage / 10.0f);
87  }
88  }
89 
90  public float CurrentStress => Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage, 1.0f)));
91 
92  private const float TinkeringForceIncrease = 1.5f;
93 
94  public Engine(Item item, ContentXElement element)
95  : base(item, element)
96  {
97  IsActive = true;
98 
99  foreach (var subElement in element.Elements())
100  {
101  switch (subElement.Name.ToString().ToLowerInvariant())
102  {
103  case "propellerdamage":
104  propellerDamage = new Attack(subElement, item.Name + ", Engine");
105  break;
106  }
107  }
108 
109  InitProjSpecific(element);
110  }
111 
112  partial void InitProjSpecific(ContentXElement element);
113 
114  public override void Update(float deltaTime, Camera cam)
115  {
116  UpdateOnActiveEffects(deltaTime);
117 
118  UpdateAnimation(deltaTime);
119 
120  controlLockTimer -= deltaTime;
121 
122  if (powerConsumption == 0.0f)
123  {
124  prevVoltage = 1;
125  hasPower = true;
126  }
127  else
128  {
129  hasPower = Voltage > MinVoltage;
130  }
131 
132  if (lastReceivedTargetForce.HasValue)
133  {
134  targetForce = lastReceivedTargetForce.Value;
135  }
136  Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, deltaTime * 10.0f);
137  if (Math.Abs(Force) > 1.0f)
138  {
139  float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, MaxOverVoltageFactor);
140  float currForce = force * MathF.Pow(voltageFactor, PowerToForceExponent);
141  float condition = item.MaxCondition <= 0.0f ? 0.0f : item.Condition / item.MaxCondition;
142  // Broken engine makes more noise.
143  float noise = Math.Abs(currForce) * MathHelper.Lerp(1.5f, 1f, condition);
144  UpdateAITargets(noise);
145  //arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines
146  float forceMultiplier = 0.1f;
147  if (User != null)
148  {
149  forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100));
150  }
151  currForce *= item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.EngineMaxSpeed, MaxForce) * forceMultiplier;
152  if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
153  {
154  currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease;
155  }
156 
157  currForce = item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.EngineSpeed, currForce);
158 
159  //less effective when in a bad condition
160  currForce *= MathHelper.Lerp(0.5f, 2.0f, condition);
161  if (item.Submarine.FlippedX) { currForce *= -1; }
162  Vector2 forceVector = new Vector2(currForce, 0);
163  item.Submarine.ApplyForce(forceVector * deltaTime * Timing.FixedUpdateRate);
164  UpdatePropellerDamage(deltaTime);
165 #if CLIENT
166  float particleInterval = 1.0f / particlesPerSec;
167  particleTimer += deltaTime;
168  while (particleTimer > particleInterval)
169  {
170  Vector2 particleVel = -forceVector.ClampLength(5000.0f) / 5.0f;
171  GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos * item.Scale,
172  particleVel * Rand.Range(0.8f, 1.1f),
173  0.0f, item.CurrentHull);
174  particleTimer -= particleInterval;
175  }
176 #endif
177  }
178  }
179 
183  public override float GetCurrentPowerConsumption(Connection connection = null)
184  {
185  if (connection != this.powerIn || !IsActive)
186  {
187  return 0;
188  }
189 
190  currPowerConsumption = MathF.Pow(Math.Abs(targetForce) / 100.0f, ForceToPowerExponent) * powerConsumption;
191  //engines consume more power when in a bad condition
192  item.GetComponent<Repairable>()?.AdjustPowerConsumption(ref currPowerConsumption);
193  return currPowerConsumption;
194  }
195 
199  public override void GridResolved(Connection connection)
200  {
201  if (connection == powerIn)
202  {
203  prevVoltage = Voltage;
204  }
205  }
206 
207  private void UpdateAITargets(float noise)
208  {
209  if (item.AiTarget != null)
210  {
211  item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, noise / 100);
212  if (item.CurrentHull != null && item.CurrentHull.AiTarget != null)
213  {
214  // It's possible that some other item increases the hull's soundrange more than the engine.
216  }
217  }
218  }
219 
220  private void UpdatePropellerDamage(float deltaTime)
221  {
222  if (DisablePropellerDamage) { return; }
223 
224  damageTimer += deltaTime;
225  if (damageTimer < 0.5f) { return; }
226  damageTimer = 0.1f;
227 
228  if (propellerDamage == null) { return; }
229 
230  float scaledDamageRange = propellerDamage.DamageRange * item.Scale;
231 
232  Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale;
233  float broadRange = Math.Max(scaledDamageRange * 2, 500);
234  foreach (Character character in Character.CharacterList)
235  {
236  if (!character.Enabled || character.Removed) { continue; }
237  if (Math.Abs(character.WorldPosition.X - propellerWorldPos.X) > broadRange) { continue; }
238  if (Math.Abs(character.WorldPosition.Y - propellerWorldPos.Y) > broadRange) { continue; }
239 
240  foreach (Limb limb in character.AnimController.Limbs)
241  {
242  if (limb.IsSevered || !limb.body.Enabled) { continue; }
243  float distSqr = Vector2.DistanceSquared(limb.WorldPosition, propellerWorldPos);
244  if (distSqr > scaledDamageRange * scaledDamageRange) { continue; }
245  character.LastDamageSource = item;
246  propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
247  break;
248  }
249  }
250  }
251 
252  partial void UpdateAnimation(float deltaTime);
253 
254  public override void UpdateBroken(float deltaTime, Camera cam)
255  {
256  base.UpdateBroken(deltaTime, cam);
257  force = MathHelper.Lerp(force, 0.0f, 0.1f);
258  }
259 
260  public override void FlipX(bool relativeToSub)
261  {
262  PropellerPos = new Vector2(-PropellerPos.X, PropellerPos.Y);
263  }
264 
265  public override void FlipY(bool relativeToSub)
266  {
267  PropellerPos = new Vector2(PropellerPos.X, -PropellerPos.Y);
268  }
269 
270  public override void ReceiveSignal(Signal signal, Connection connection)
271  {
272  base.ReceiveSignal(signal, connection);
273 
274  if (connection.Name == "set_force")
275  {
276  if (float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float tempForce))
277  {
278  controlLockTimer = 0.1f;
279  lastReceivedTargetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
280  User = signal.sender;
281  }
282  }
283  }
284 
285  public override XElement Save(XElement parentElement)
286  {
287  Vector2 prevPropellerPos = PropellerPos;
288  //undo flipping before saving
289  if (item.FlippedX) { PropellerPos = new Vector2(-PropellerPos.X, PropellerPos.Y); }
290  if (item.FlippedY) { PropellerPos = new Vector2(PropellerPos.X, -PropellerPos.Y); }
291  XElement element = base.Save(parentElement);
292  PropellerPos = prevPropellerPos;
293  return element;
294  }
295  }
296 }
Attacks are used to deal damage to characters, structures and items. They can be defined in the weapo...
float GetSkillLevel(string skillIdentifier)
AITarget AiTarget
Definition: Entity.cs:55
Submarine Submarine
Definition: Entity.cs:53
static ParticleManager ParticleManager
Definition: GameMain.cs:101
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
override float GetCurrentPowerConsumption(Connection connection=null)
Power consumption of the engine. Only consume power when active and adjust consumption based on condi...
override void GridResolved(Connection connection)
When grid is resolved update the previous voltage
const float MaxOverVoltageFactor
Maximum voltage factor when the device is being overvolted. I.e. how many times more effectively the ...
float powerConsumption
The maximum amount of power the item can draw from connected items
float currPowerConsumption
The amount of power currently consumed by the item. Negative values mean that the item is providing p...
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients