Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs
1 using FarseerPhysics;
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 
9 {
10  partial class StatusHUD : ItemComponent
11  {
12  private static readonly LocalizedString[] BleedingTexts =
13  {
14  TextManager.Get("MinorBleeding"),
15  TextManager.Get("Bleeding"),
16  TextManager.Get("HeavyBleeding"),
17  TextManager.Get("CatastrophicBleeding")
18  };
19 
20  private static readonly LocalizedString[] OxygenTexts =
21  {
22  TextManager.Get("OxygenNormal"),
23  TextManager.Get("OxygenReduced"),
24  TextManager.Get("OxygenLow"),
25  TextManager.Get("NotBreathing")
26  };
27 
28  [Serialize(500.0f, IsPropertySaveable.No, description: "How close to a target the user must be to see their health data (in pixels).")]
29  public float Range
30  {
31  get;
32  private set;
33  }
34 
35  [Serialize(50.0f, IsPropertySaveable.No, description: "The range within which the health info texts fades out.")]
36  public float FadeOutRange
37  {
38  get;
39  private set;
40  }
41 
42  [Serialize(false, IsPropertySaveable.No)]
43  public bool ThermalGoggles
44  {
45  get;
46  private set;
47  }
48 
49  [Serialize(false, IsPropertySaveable.No)]
50  public bool DebugWiring
51  {
52  get;
53  private set;
54  }
55 
56  [Serialize(true, IsPropertySaveable.No)]
57  public bool ShowDeadCharacters
58  {
59  get;
60  private set;
61  }
62 
63  [Serialize(true, IsPropertySaveable.No)]
64  public bool ShowTexts
65  {
66  get;
67  private set;
68  }
69 
70  [Serialize("72,119,72,120", IsPropertySaveable.No)]
71  public Color OverlayColor
72  {
73  get;
74  private set;
75  }
76 
77  private readonly List<Character> visibleCharacters = new List<Character>();
78 
79  private const float UpdateInterval = 0.5f;
80  private float updateTimer;
81 
82  private Character equipper;
83 
84  private bool isEquippable;
85 
86  private float thermalEffectState;
87 
88  public IEnumerable<Character> VisibleCharacters
89  {
90  get
91  {
92  if (equipper == null || equipper.Removed) { return Enumerable.Empty<Character>(); }
93  return visibleCharacters;
94  }
95  }
96 
97  public override void OnItemLoaded()
98  {
99  isEquippable = item.GetComponent<Pickable>() != null;
100  if (!isEquippable) { IsActive = true; }
101  }
102 
103  public override void Update(float deltaTime, Camera cam)
104  {
105  base.Update(deltaTime, cam);
106 
107  Entity refEntity = equipper;
108  if (isEquippable)
109  {
110  if (equipper == null || equipper.Removed)
111  {
112  IsActive = false;
113  return;
114  }
115  }
116  else
117  {
118  refEntity = item;
119  }
120 
121  if (equipper != null && equipper == Character.Controlled && DebugWiring)
122  {
123  ConnectionPanel.DebugWiringEnabledUntil = Timing.TotalTimeUnpaused + 0.5;
124  }
125 
126  thermalEffectState += deltaTime;
127  thermalEffectState %= 10000.0f;
128 
129  if (updateTimer > 0.0f)
130  {
131  updateTimer -= deltaTime;
132  return;
133  }
134 
135  visibleCharacters.Clear();
136  foreach (Character c in Character.CharacterList)
137  {
138  if (c == equipper || !c.Enabled || c.Removed) { continue; }
139  if (!ShowDeadCharacters && c.IsDead) { continue; }
140  if (c.InDetectable) { continue; }
141 
142  float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
143  if (dist < Range * Range)
144  {
145  Vector2 diff = c.WorldPosition - refEntity.WorldPosition;
146  if (Submarine.CheckVisibility(refEntity.SimPosition, refEntity.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
147  {
148  visibleCharacters.Add(c);
149  }
150  }
151  }
152 
153  updateTimer = UpdateInterval;
154  }
155 
156  public override void Equip(Character character)
157  {
158  updateTimer = 0.0f;
159  equipper = character;
160  IsActive = true;
161  }
162 
163  public override void Unequip(Character character)
164  {
165  equipper = null;
166  IsActive = false;
167  }
168 
169  public override void Drop(Character dropper, bool setTransform = true)
170  {
171  Unequip(dropper);
172  }
173 
174  public override void DrawHUD(SpriteBatch spriteBatch, Character character)
175  {
176  if (character == null) { return; }
177 
178  if (OverlayColor.A > 0)
179  {
180  GUIStyle.UIGlow.Draw(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
181  OverlayColor);
182  }
183 
184  if (ShowTexts)
185  {
186  Character closestCharacter = null;
187  float closestDist = float.PositiveInfinity;
188  foreach (Character c in visibleCharacters)
189  {
190  if (c == character || !c.Enabled || c.Removed) { continue; }
191 
192  float dist = Vector2.DistanceSquared(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), c.WorldPosition);
193  if (dist < closestDist)
194  {
195  closestCharacter = c;
196  closestDist = dist;
197  }
198  }
199 
200  if (closestCharacter != null)
201  {
202  float dist = Vector2.Distance(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), closestCharacter.WorldPosition);
203  DrawCharacterInfo(spriteBatch, closestCharacter, 1.0f - MathHelper.Max((dist - (Range - FadeOutRange)) / FadeOutRange, 0.0f));
204  }
205  }
206 
207  if (ThermalGoggles)
208  {
209  spriteBatch.End();
210  GameMain.LightManager.SolidColorEffect.Parameters["color"].SetValue(Color.Red.ToVector4() * (0.3f + MathF.Sin(thermalEffectState) * 0.05f));
211  GameMain.LightManager.SolidColorEffect.CurrentTechnique = GameMain.LightManager.SolidColorEffect.Techniques["SolidColorBlur"];
212  GameMain.LightManager.SolidColorEffect.Parameters["blurDistance"].SetValue(0.01f + MathF.Sin(thermalEffectState) * 0.005f);
213  GameMain.LightManager.SolidColorEffect.CurrentTechnique.Passes[0].Apply();
214  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: Screen.Selected.Cam.Transform, effect: GameMain.LightManager.SolidColorEffect);
215 
216  Entity refEntity = equipper;
217  if (!isEquippable || refEntity == null)
218  {
219  refEntity = item;
220  }
221 
222  foreach (Character c in Character.CharacterList)
223  {
224  if (c == character || !c.Enabled || c.Removed || c.Params.HideInThermalGoggles) { continue; }
225  if (!ShowDeadCharacters && c.IsDead) { continue; }
226 
227  float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
228  if (dist > Range * Range) { continue; }
229 
230  Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
231  foreach (Limb limb in c.AnimController.Limbs)
232  {
233  if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { continue; }
234  float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
235  float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
236  Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
237  Vector2 drawPos = new Vector2(limb.body.DrawPosition.X + (noise1 - 0.5f) * 100, -limb.body.DrawPosition.Y + (noise2 - 0.5f) * 100);
238  pingCircle.Draw(spriteBatch, drawPos, 0.0f, scale: Math.Max(spriteScale.X, spriteScale.Y));
239  }
240  }
241 
242  spriteBatch.End();
243  spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
244  }
245  }
246 
247  private void DrawCharacterInfo(SpriteBatch spriteBatch, Character target, float alpha = 1.0f)
248  {
249  Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.DrawPosition);
250  hudPos += Vector2.UnitX * 50.0f;
251 
252  List<LocalizedString> texts = new List<LocalizedString>();
253  List<Color> textColors = new List<Color>();
254  texts.Add(target.Info == null ? target.DisplayName : target.Info.DisplayName);
255  Color nameColor = GUIStyle.TextColorNormal;
256  if (Character.Controlled != null && target.TeamID != Character.Controlled.TeamID)
257  {
258  nameColor = target.TeamID == CharacterTeamType.FriendlyNPC ? Color.SkyBlue : GUIStyle.Red;
259  }
260  textColors.Add(nameColor);
261 
262  if (target.IsDead)
263  {
264  texts.Add(TextManager.Get("Deceased"));
265  textColors.Add(GUIStyle.Red);
266  if (target.CauseOfDeath != null)
267  {
268  texts.Add(
270  TextManager.AddPunctuation(':', TextManager.Get("CauseOfDeath"), TextManager.Get("CauseOfDeath." + target.CauseOfDeath.Type.ToString())));
271  textColors.Add(GUIStyle.Red);
272  }
273  }
274  else
275  {
276  if (!target.CustomInteractHUDText.IsNullOrEmpty() && target.AllowCustomInteract)
277  {
278  texts.Add(target.CustomInteractHUDText);
279  textColors.Add(GUIStyle.Green);
280  }
281  if (!target.IsIncapacitated && target.IsPet)
282  {
283  texts.Add(CharacterHUD.GetCachedHudText("PlayHint", InputType.Use));
284  textColors.Add(GUIStyle.Green);
285  }
286  if (equipper?.FocusedCharacter == target && target.CanBeHealedBy(equipper, checkFriendlyTeam: false))
287  {
288  texts.Add(CharacterHUD.GetCachedHudText("HealHint", InputType.Health));
289  textColors.Add(GUIStyle.Green);
290  }
291  if (target.CanBeDraggedBy(Character.Controlled))
292  {
293  texts.Add(CharacterHUD.GetCachedHudText("GrabHint", InputType.Grab));
294  textColors.Add(GUIStyle.Green);
295  }
296 
297  if (target.IsUnconscious)
298  {
299  texts.Add(TextManager.Get("Unconscious"));
300  textColors.Add(GUIStyle.Orange);
301  }
302  if (target.Stun > 0.01f)
303  {
304  texts.Add(TextManager.Get("Stunned"));
305  textColors.Add(GUIStyle.Orange);
306  }
307 
308  int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 100.0f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1);
309  texts.Add(OxygenTexts[oxygenTextIndex]);
310  textColors.Add(Color.Lerp(GUIStyle.Red, GUIStyle.Green, target.Oxygen / 100.0f));
311 
312  if (target.Bleeding > 0.0f)
313  {
314  int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 100.0f * BleedingTexts.Length), 0, BleedingTexts.Length - 1);
315  texts.Add(BleedingTexts[bleedingTextIndex]);
316  textColors.Add(Color.Lerp(GUIStyle.Orange, GUIStyle.Red, target.Bleeding / 100.0f));
317  }
318 
319  var allAfflictions = target.CharacterHealth.GetAllAfflictions();
320  Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
321  foreach (Affliction affliction in allAfflictions)
322  {
323  if (affliction.Strength <= 0f) { continue; }
324  if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold)
325  {
326  if (target.IsHuman || target.IsOnPlayerTeam || (affliction.Prefab.AfflictionType != AfflictionPrefab.PoisonType && affliction.Prefab.AfflictionType != AfflictionPrefab.ParalysisType))
327  {
328  // Always show the poisons on monsters, because poisoning bigger monsters require multiple doses.
329  // The solution is hacky, but didn't want to introduce an extra property for this.
330  // We also want to have a relatively high thershold for showing the poisons on the scanner on humans, so that it's not instantly clear that a target is poisoned and especially not which poison was used.
331  // Paralysis is treated like a poison but isn't technically a poison, so that we can have multiple afflictions that still are treated the same.
332  continue;
333  }
334  }
335  if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
336  {
337  combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
338  }
339  else
340  {
341  combinedAfflictionStrengths[affliction.Prefab] = affliction.Strength;
342  }
343  }
344 
345  foreach (AfflictionPrefab affliction in combinedAfflictionStrengths.Keys)
346  {
347  texts.Add(TextManager.AddPunctuation(':', affliction.Name, Math.Max((int)combinedAfflictionStrengths[affliction], 1).ToString() + " %"));
348  textColors.Add(Color.Lerp(GUIStyle.Orange, GUIStyle.Red, combinedAfflictionStrengths[affliction] / affliction.MaxStrength));
349  }
350  }
351 
352  GUI.DrawString(spriteBatch, hudPos, texts[0].Value, textColors[0] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
353  hudPos.X += 5.0f * GUI.Scale;
354  hudPos.Y += GUIStyle.SubHeadingFont.MeasureString(texts[0].Value).Y;
355 
356  hudPos.X = (int)hudPos.X;
357  hudPos.Y = (int)hudPos.Y;
358 
359  for (int i = 1; i < texts.Count; i++)
360  {
361  GUI.DrawString(spriteBatch, hudPos, texts[i], textColors[i] * alpha, Color.Black * 0.7f * alpha, 2, GUIStyle.SmallFont);
362  hudPos.Y += (int)(GUIStyle.SubHeadingFont.MeasureString(texts[i].Value).Y);
363  }
364  }
365  }
366 }
readonly LocalizedString CauseOfDeathDescription
Vector2 WorldToScreen(Vector2 coords)
Definition: Camera.cs:416
Matrix Transform
Definition: Camera.cs:136
Vector2 ScreenToWorld(Vector2 coords)
Definition: Camera.cs:410
readonly AfflictionPrefab Affliction
Definition: CauseOfDeath.cs:13
readonly CauseOfDeathType Type
Definition: CauseOfDeath.cs:12
bool CanBeHealedBy(Character character, bool checkFriendlyTeam=true)
virtual Vector2 WorldPosition
Definition: Entity.cs:49
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
bool InDetectable
Indetectable characters can't be spotted by AIs and aren't visible on the sonar or health scanner.
Definition: Entity.cs:61
static int GraphicsWidth
Definition: GameMain.cs:162
static int GraphicsHeight
Definition: GameMain.cs:168
static Lights.LightManager LightManager
Definition: GameMain.cs:78
static GameScreen GameScreen
Definition: GameMain.cs:52
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override void DrawHUD(SpriteBatch spriteBatch, Character character)
override void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
static Body CheckVisibility(Vector2 rayStart, Vector2 rayEnd, bool ignoreLevel=false, bool ignoreSubs=false, bool ignoreSensors=true, bool ignoreDisabledWalls=true, bool ignoreBranches=true, Predicate< Fixture > blocksVisibilityPredicate=null)
Check visibility between two points (in sim units).