Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
3 using FarseerPhysics;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Collections.Generic;
8 using System.Linq;
9 
10 namespace Barotrauma
11 {
12  partial class CharacterHUD
13  {
14  const float BossHealthBarDuration = 120.0f;
15 
16  abstract class BossProgressBar
17  {
18  public float FadeTimer;
19 
20  public readonly GUIComponent TopContainer;
21  public readonly GUIComponent SideContainer;
22 
23  public readonly GUIProgressBar TopHealthBar;
24  public readonly GUIProgressBar SideHealthBar;
25 
26  public abstract bool Completed { get; }
27 
28  public abstract bool Interrupted { get; }
29 
30  public abstract float State { get; }
31 
32  public abstract string NumberToDisplay { get; }
33 
34  public abstract Color Color { get; }
35 
36  public BossProgressBar(LocalizedString label)
37  {
38  FadeTimer = BossHealthBarDuration;
39 
40  TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter)
41  {
42  MinSize = new Point(100, 50),
43  RelativeOffset = new Vector2(0.0f, 0.01f)
44  }, isHorizontal: false, childAnchor: Anchor.TopCenter);
45  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), label, textAlignment: Alignment.Center, textColor: GUIStyle.Red);
46  TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
47  {
48  MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
49  }, barSize: 0.0f, style: "CharacterHealthBarCentered")
50  {
51  Color = GUIStyle.Red
52  };
53  CreateNumberText(TopHealthBar);
54 
55  SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
56  {
57  MinSize = new Point(80, 60)
58  }, isHorizontal: false, childAnchor: Anchor.TopRight);
59  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), label, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red);
60  SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
61  {
62  Color = GUIStyle.Red
63  };
64  CreateNumberText(SideHealthBar);
65 
66  TopContainer.Visible = SideContainer.Visible = false;
67  TopContainer.CanBeFocused = false;
68  TopContainer.Children.ForEach(c => c.CanBeFocused = false);
69  SideContainer.CanBeFocused = false;
70  SideContainer.Children.ForEach(c => c.CanBeFocused = false);
71 
72  void CreateNumberText(GUIComponent parent)
73  {
74  new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform)
75  { AbsoluteOffset = new Point(2) },
76  string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDark)
77  {
78  TextGetter = () => NumberToDisplay
79  };
80  new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform),
81  string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorBright)
82  {
83  TextGetter = () => NumberToDisplay
84  };
85  }
86  }
87 
88  public abstract bool IsDuplicate(object targetObject);
89  }
90 
91  class BossHealthBar : BossProgressBar
92  {
93  public readonly Character Character;
94 
95  public override float State => Character.Vitality / Character.MaxVitality;
96 
97  public override bool Completed => Character.IsDead;
98 
99  public override bool Interrupted => Character.Removed || !Character.Enabled;
100 
101  public override Color Color =>
103  GUIStyle.HealthBarColorPoisoned : GUIStyle.Red;
104 
105  public override string NumberToDisplay => string.Empty;
106 
107  public BossHealthBar(Character character) : base(character.DisplayName)
108  {
109  Character = character;
110  }
111 
112  public override bool IsDuplicate(object targetObject)
113  {
114  return targetObject is Character character && Character == character;
115  }
116  }
117 
118  class MissionProgressBar : BossProgressBar
119  {
120  public readonly Mission Mission;
121 
122  public override float State => Mission.State / (float)Mission.Prefab.MaxProgressState;
123 
124  public override bool Completed => Mission.State >= Mission.Prefab.MaxProgressState;
125 
126  public override bool Interrupted => Mission.Failed || GameMain.GameSession?.Missions == null || !GameMain.GameSession.Missions.Contains(Mission);
127 
128  public override Color Color => GUIStyle.Red;
129 
130  public override string NumberToDisplay => Mission.Prefab.ShowProgressInNumbers ?
131  $"{Mission.State}/{Mission.Prefab.MaxProgressState}" :
132  string.Empty;
133 
134  public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel)
135  {
136  Mission = mission;
137  }
138 
139  public override bool IsDuplicate(object targetObject)
140  {
141  return targetObject is Mission mission && Mission == mission;
142  }
143  }
144 
145  private static readonly Dictionary<ISpatialEntity, int> orderIndicatorCount = new Dictionary<ISpatialEntity, int>();
146  const float ItemOverlayDelay = 1.0f;
147  private static Item focusedItem;
148  private static float focusedItemOverlayTimer;
149 
150  private static readonly List<Item> brokenItems = new List<Item>();
151  private static float brokenItemsCheckTimer;
152 
153  private static readonly List<BossProgressBar> bossProgressBars = new List<BossProgressBar>();
154 
155  private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
156  private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;
157 
158  private static GUILayoutGroup bossHealthContainer;
159 
160  private static GUIFrame hudFrame;
161  public static GUIFrame HUDFrame
162  {
163 
164  get
165  {
166  if (hudFrame == null)
167  {
168  hudFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null)
169  {
170  CanBeFocused = false
171  };
172  bossHealthContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.5f), hudFrame.RectTransform, Anchor.CenterRight)
173  {
174  RelativeOffset = new Vector2(0.005f, 0.0f)
175  })
176  {
177  AbsoluteSpacing = GUI.IntScale(10)
178  };
179  }
180  return hudFrame;
181  }
182  }
183 
184  public static bool RecreateHudTexts { get; set; } = true;
185  private static bool lastHudTextsContextual;
186  private static float timeHealthWindowClosed;
187 
188  public static bool IsCampaignInterfaceOpen =>
189  GameMain.GameSession?.Campaign != null &&
191 
192  public static bool ShouldDrawInventory(Character character)
193  {
194  var controller = character.SelectedItem?.GetComponent<Controller>();
195 
196  return
197  character?.Inventory != null &&
198  !character.Removed && !character.IsKnockedDown &&
199  (controller?.User != character || !controller.HideHUD || Screen.Selected.IsEditor) &&
202  }
203 
204  public static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
205  {
206  if (cachedHudTextLanguage != GameSettings.CurrentConfig.Language)
207  {
208  cachedHudTexts.Clear();
209  }
210  Identifier key = (textTag + keyBind + GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).ToIdentifier();
211  if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
212  text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
213  cachedHudTexts.Add(key, text);
214  cachedHudTextLanguage = GameSettings.CurrentConfig.Language;
215  return text;
216  }
217 
218  public static void AddToGUIUpdateList(Character character)
219  {
220  if (GUI.DisableHUD) { return; }
221 
222  if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
223  {
224  if (character.Inventory != null)
225  {
226  for (int i = 0; i < character.Inventory.Capacity; i++)
227  {
228  var item = character.Inventory.GetItemAt(i);
229  if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
230 
231  foreach (ItemComponent ic in item.Components)
232  {
233  if (ic.DrawHudWhenEquipped) { ic.AddToGUIUpdateList(); }
234  }
235  }
236  }
237 
238  if (character.Params.CanInteract && character.SelectedCharacter != null)
239  {
241  }
242  }
243 
245  }
246 
247  public static void Update(float deltaTime, Character character, Camera cam)
248  {
249 
250  UpdateBossProgressBars(deltaTime);
251 
252  if (GUI.DisableHUD)
253  {
254  if (character.Inventory != null && !LockInventory(character))
255  {
256  character.Inventory.UpdateSlotInput();
257  }
258  return;
259  }
260 
261  if (character.ShowInteractionLabels && character.ViewTarget == null)
262  {
263  InteractionLabelManager.Update(character, cam);
264  }
265 
266  if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
267  {
268  if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
269  {
270  bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
271  bool healthWindowOpen = CharacterHealth.OpenHealthWindow != null || timeHealthWindowClosed < 0.2f;
272  if (mouseOnPortrait && !healthWindowOpen && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
273  {
275  }
276  }
277 
278  if (character.Inventory != null)
279  {
280  if (!LockInventory(character))
281  {
282  character.Inventory.Update(deltaTime, cam);
283  }
284  else
285  {
286  character.Inventory.ClearSubInventories();
287  }
288  }
289 
290  if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
291  {
292  if (character.SelectedCharacter.IsInventoryAccessibleTo(character))
293  {
294  character.SelectedCharacter.Inventory.Update(deltaTime, cam);
295  }
296  character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime);
297  }
298 
300  }
301 
302  if (focusedItem != null)
303  {
304  if (character.FocusedItem != null)
305  {
306  focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f);
307  }
308  else
309  {
310  focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f);
311  if (focusedItemOverlayTimer <= 0.0f)
312  {
313  focusedItem = null;
314  RecreateHudTexts = true;
315  }
316  }
317  }
318 
319  if (brokenItemsCheckTimer > 0.0f)
320  {
321  brokenItemsCheckTimer -= deltaTime;
322  }
323  else
324  {
325  brokenItems.Clear();
326  brokenItemsCheckTimer = 1.0f;
327  foreach (Item item in Item.ItemList)
328  {
329  if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; }
330  if (!item.Repairables.Any(r => r.IsBelowRepairIconThreshold)) { continue; }
331  if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
332 
333  Vector2 diff = item.WorldPosition - character.WorldPosition;
334  if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
335  {
336  brokenItems.Add(item);
337  }
338  }
339  }
340 
341  if (CharacterHealth.OpenHealthWindow != null)
342  {
343  timeHealthWindowClosed = 0.0f;
344  }
345  else
346  {
347  timeHealthWindowClosed += deltaTime;
348  }
349  }
350 
351  public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
352  {
353  if (GUI.DisableHUD) { return; }
354 
355  character.CharacterHealth.Alignment = Alignment.Right;
356 
357  if (GameMain.GameSession?.CrewManager != null)
358  {
359  orderIndicatorCount.Clear();
361  {
362  if (!DrawIcon(activeOrder.Order)) { continue; }
363 
364  if (activeOrder.FadeOutTime.HasValue)
365  {
366  DrawOrderIndicator(spriteBatch, cam, character, activeOrder.Order, iconAlpha: MathHelper.Clamp(activeOrder.FadeOutTime.Value / 10.0f, 0.2f, 1.0f));
367  }
368  else
369  {
370  float iconAlpha = GetDistanceBasedIconAlpha(activeOrder.Order.TargetSpatialEntity, maxDistance: 450.0f);
371  if (iconAlpha <= 0.0f) { continue; }
372  DrawOrderIndicator(spriteBatch, cam, character, activeOrder.Order,
373  iconAlpha: iconAlpha, createOffset: false, scaleMultiplier: 0.5f, overrideAlpha: true);
374  }
375  }
376 
377  if (character.GetCurrentOrderWithTopPriority() is Order currentOrder && DrawIcon(currentOrder))
378  {
379  DrawOrderIndicator(spriteBatch, cam, character, currentOrder, 1.0f);
380  }
381 
382  static bool DrawIcon(Order o) =>
383  o != null &&
384  (o.TargetEntity is not Item i ||
386  i.GetRootInventoryOwner() == i);
387  }
388 
389  if (GameMain.GameSession != null)
390  {
391  foreach (var mission in GameMain.GameSession.Missions)
392  {
393  if (!mission.DisplayTargetHudIcons) { continue; }
394  foreach (var target in mission.HudIconTargets)
395  {
396  if (target.Submarine != character.Submarine) { continue; }
397  float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance);
398  if (alpha <= 0.0f) { continue; }
399  GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha);
400  }
401  }
402  }
403 
404  foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities)
405  {
406  DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f);
407  }
408 
409  foreach (Item brokenItem in brokenItems)
410  {
411  if (!brokenItem.IsInteractable(character)) { continue; }
412  float alpha = GetDistanceBasedIconAlpha(brokenItem);
413  if (alpha <= 0.0f) { continue; }
414  GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite,
415  Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
416  }
417 
418  if (OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder))
419  {
420  foreach (Item deconstructItem in Item.DeconstructItems)
421  {
422  if (deconstructItem.ParentInventory != null) { continue; }
423  if (deconstructItem.OrderedToBeIgnored) { continue; }
424  if (deconstructItem.Submarine != character.Submarine || !deconstructItem.IsInteractable(character)) { continue; }
425  float alpha = GetDistanceBasedIconAlpha(deconstructItem, maxDistance: 450) * 0.7f;
426  if (alpha <= 0.0f) { continue; }
427  GUI.DrawIndicator(spriteBatch, deconstructItem.DrawPosition, cam, 100.0f, deconstructOrder.SymbolSprite,
428  GUIStyle.Red, scaleMultiplier: 0.5f, overrideAlpha: alpha);
429  }
430  }
431 
432  float GetDistanceBasedIconAlpha(ISpatialEntity target, float maxDistance = 1000.0f)
433  {
434  float dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
435  return Math.Min((maxDistance - dist) / maxDistance * 2.0f, 1.0f);
436  }
437 
438  if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen && (!character.IsKeyDown(InputType.Aim) || character.HeldItems.None(it => it?.GetComponent<Sprayer>() != null)))
439  {
440  if (!character.ShowInteractionLabels)
441  {
442  if (character.FocusedCharacter is { CanBeSelected: true })
443  {
444  DrawCharacterHoverTexts(spriteBatch, cam, character);
445  }
446 
447  if (character.FocusedItem != null)
448  {
449  if (focusedItem != character.FocusedItem)
450  {
451  focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
452  RecreateHudTexts = true;
453  }
454  focusedItem = character.FocusedItem;
455  }
456 
457  if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay)
458  {
459  Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition);
460  float circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f;
461  circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f);
462  if (circleSize > 0.0f)
463  {
464  Vector2 scale = new Vector2(circleSize / GUIStyle.FocusIndicator.FrameSize.X);
465  GUIStyle.FocusIndicator.Draw(spriteBatch,
466  (int)((focusedItemOverlayTimer - 1.0f) * GUIStyle.FocusIndicator.FrameCount * 3.0f),
467  circlePos,
468  Color.LightBlue * 0.3f,
469  origin: GUIStyle.FocusIndicator.FrameSize.ToVector2() / 2,
470  rotate: (float)Timing.TotalTime,
471  scale: scale);
472  }
473 
474  if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
475  {
476  bool hudTextsContextual = PlayerInput.KeyDown(InputType.ContextualCommand);
477  if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
478  {
479  RecreateHudTexts = true;
480  lastHudTextsContextual = hudTextsContextual;
481  }
482  var hudTexts = focusedItem.GetHUDTexts(character, RecreateHudTexts);
483  RecreateHudTexts = false;
484 
485  int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
486 
487  Vector2 textSize = GUIStyle.Font.MeasureString(hudTexts.First().Text);
488  Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(hudTexts.First().Text);
489 
490  Vector2 startPos = cam.WorldToScreen(focusedItem.DrawPosition);
491  startPos.Y -= (hudTexts.Count + 1) * textSize.Y;
492  if (focusedItem.Sprite != null)
493  {
494  startPos.X += (int)(circleSize * 0.4f * dir);
495  startPos.Y -= (int)(circleSize * 0.4f);
496  }
497 
498  Vector2 textPos = startPos;
499  if (dir == -1) { textPos.X -= largeTextSize.X; }
500 
501  float alpha = MathHelper.Clamp((focusedItemOverlayTimer - ItemOverlayDelay) * 2.0f, 0.0f, 1.0f);
502 
503  GUI.DrawString(spriteBatch, textPos, hudTexts.First().Text, hudTexts.First().Color * alpha, Color.Black * alpha * 0.7f, 2, font: GUIStyle.SubHeadingFont, ForceUpperCase.No);
504  startPos.X += dir * 10.0f * GUI.Scale;
505  textPos.X += dir * 10.0f * GUI.Scale;
506  textPos.Y += largeTextSize.Y;
507  foreach (ColoredText coloredText in hudTexts.Skip(1))
508  {
509  if (dir == -1)
510  {
511  textPos.X = (int)(startPos.X - GUIStyle.SmallFont.MeasureString(coloredText.Text).X);
512  }
513  GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color * alpha, Color.Black * alpha * 0.7f, 2, GUIStyle.SmallFont);
514  textPos.Y += textSize.Y;
515  }
516  }
517  }
518  }
519 
520  if (character.ShowInteractionLabels && character.ViewTarget == null)
521  {
522  InteractionLabelManager.DrawLabels(spriteBatch, cam, character);
523  }
524 
525  foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
526  {
527  progressBar.Draw(spriteBatch, cam);
528  }
529 
530  void DrawInteractionIcon(Entity entity, Identifier iconStyle)
531  {
532  if (entity == null || entity.Removed) { return; }
533 
534  Hull currentHull = entity switch
535  {
536  Character character => character.CurrentHull,
537  Item item => item.CurrentHull,
538  _ => null
539  };
540  Range<float> visibleRange = new Range<float>(currentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
541  LocalizedString label = null;
542  if (entity is Character characterEntity)
543  {
544  if (characterEntity.IsDead || characterEntity.IsIncapacitated) { return; }
545  if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine)
546  {
547  //TODO: we could probably do better than just hardcoding
548  //a check for InteractionType.Examine here.
549 
550  if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; }
551 
552  var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true);
553  if (body != null && body.UserData != entity) { return; }
554 
555  visibleRange = new Range<float>(-100f, 500f);
556  }
557  label = characterEntity?.Info?.Title;
558  }
559 
560  if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; }
561 
562  float dist = Vector2.Distance(character.WorldPosition, entity.WorldPosition);
563  float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist);
564  float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor);
565  GUI.DrawIndicator(
566  spriteBatch,
567  entity.WorldPosition,
568  cam,
569  visibleRange,
570  style.GetDefaultSprite(),
571  style.Color * alpha,
572  label: label);
573  }
574 
575  foreach (Character npc in Character.CharacterList)
576  {
577  if (npc.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; }
578  DrawInteractionIcon(npc, ("CampaignInteractionIcon." + npc.CampaignInteractionType).ToIdentifier());
579  }
580 
581  if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is not null)
582  {
583  foreach (var (entity, iconStyle) in tutorialMode.Tutorial.Icons)
584  {
585  DrawInteractionIcon(entity, iconStyle);
586  }
587  }
588 
589  foreach (Item item in Item.ItemList)
590  {
591  if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; }
592  if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; }
593  var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true);
594  if (body != null && body.UserData as Item != item) { continue; }
595  GUI.DrawIndicator(spriteBatch, item.WorldPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range<float>(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false);
596  }
597  }
598 
599  if (character.SelectedItem != null &&
600  (character.CanInteractWith(character.SelectedItem) || Screen.Selected == GameMain.SubEditorScreen))
601  {
602  character.SelectedItem.DrawHUD(spriteBatch, cam, character);
603  }
604  if (character.Inventory != null)
605  {
606  foreach (Item item in character.Inventory.AllItems)
607  {
608  if (character.HasEquippedItem(item))
609  {
610  item.DrawHUD(spriteBatch, cam, character);
611  }
612  }
613  }
614 
615  if (IsCampaignInterfaceOpen) { return; }
616 
617  if (character.Inventory != null)
618  {
619  for (int i = 0; i < character.Inventory.Capacity; i++)
620  {
621  var item = character.Inventory.GetItemAt(i);
622  if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
623  //if the item is also equipped in another slot we already went through, don't draw the hud again
624  bool duplicateFound = false;
625  for (int j = 0; j < i; j++)
626  {
627  if (character.Inventory.SlotTypes[j] != InvSlotType.Any && character.Inventory.GetItemAt(j) == item)
628  {
629  duplicateFound = true;
630  break;
631  }
632  }
633  if (duplicateFound) { continue; }
634  foreach (ItemComponent ic in item.Components)
635  {
636  if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
637  }
638  }
639  }
640 
641  bool mouseOnPortrait = false;
642  if (character.Stun <= 0.1f && !character.IsDead)
643  {
645  if (CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null && !wiringMode)
646  {
647  if (character.Info != null && !character.ShouldLockHud())
648  {
649  character.Info.DrawBackground(spriteBatch);
650  character.Info.DrawJobIcon(spriteBatch,
651  new Rectangle(
652  (int)(HUDLayoutSettings.BottomRightInfoArea.X + HUDLayoutSettings.BottomRightInfoArea.Width * 0.05f),
653  (int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f),
654  (int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2),
655  (int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)), character.Info.IsDisguisedAsAnother);
656  float yOffset = (GameMain.GameSession?.Campaign is MultiPlayerCampaign ? -10 : 4) * GUI.Scale;
657  character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, yOffset), targetWidth: HUDLayoutSettings.PortraitArea.Width, true, character.Info.IsDisguisedAsAnother);
658  character.Info.DrawForeground(spriteBatch);
659  }
660  mouseOnPortrait = MouseOnCharacterPortrait() && !character.ShouldLockHud();
661  if (mouseOnPortrait)
662  {
663  GUIStyle.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUIStyle.Green * 0.5f);
664  }
665  }
666  if (ShouldDrawInventory(character))
667  {
668  character.Inventory.Locked = character == Character.Controlled && LockInventory(character);
669  character.Inventory.DrawOwn(spriteBatch);
670  character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
671  CharacterInventory.Layout.Default :
673  }
674  }
675 
676  if (!character.IsIncapacitated && character.Stun <= 0.0f)
677  {
678  if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
679  {
680  if (character.SelectedCharacter.IsInventoryAccessibleTo(character))
681  {
682  character.SelectedCharacter.Inventory.Locked = false;
683  character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
684  character.SelectedCharacter.Inventory.DrawOwn(spriteBatch);
685  }
686  if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
687  {
688  character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
689  //character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
690  }
691  }
692  else if (character.Inventory != null)
693  {
694  //character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
695  }
696  }
697 
698  if (mouseOnPortrait)
699  {
701  spriteBatch,
702  character.Info?.Job == null ? character.DisplayName : character.DisplayName + " (" + character.Info.Job.Name + ")",
703  HUDLayoutSettings.PortraitArea);
704  }
705  }
706 
707 
708 
709  public static bool MouseOnCharacterPortrait()
710  {
711  if (Character.Controlled == null) { return false; }
712  if (CharacterHealth.OpenHealthWindow != null || Character.Controlled.SelectedCharacter != null) { return false; }
713  return HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition);
714  }
715 
716  private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character)
717  {
718  var allItems = character.Inventory?.AllItems;
719  if (allItems != null)
720  {
721  foreach (Item item in allItems)
722  {
723  var statusHUD = item?.GetComponent<StatusHUD>();
724  if (statusHUD != null && statusHUD.IsActive && statusHUD.VisibleCharacters.Contains(character.FocusedCharacter))
725  {
726  return;
727  }
728  }
729  }
730 
731  float dist = Vector2.Distance(character.FocusedCharacter.DrawPosition, character.DrawPosition);
732  Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) / dist * Math.Min(dist, Character.MaxDragDistance);
733  startPos = cam.WorldToScreen(startPos);
734 
735  string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName;
736  Vector2 textPos = startPos;
737  Vector2 textSize = GUIStyle.Font.MeasureString(focusName);
738  Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(focusName);
739 
740  textPos -= new Vector2(textSize.X / 2, textSize.Y);
741 
742  Color nameColor = character.FocusedCharacter.GetNameColor();
743  GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
744  textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y;
745 
746  if (character.FocusedCharacter.Info?.Title != null &&
747  !character.FocusedCharacter.Info.Title.IsNullOrEmpty() &&
748  character.FocusedCharacter.TeamID != CharacterTeamType.Team1)
749  {
750  GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
751  textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y;
752  }
753  textPos.X += 10.0f * GUI.Scale;
754 
755  if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet)
756  {
757  GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
758  GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
759  textPos.Y += largeTextSize.Y;
760  }
761 
762  if (character.FocusedCharacter.CanBeDraggedBy(character))
763  {
764  string text = character.CanEat ? "EatHint" : "GrabHint";
765  GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, InputType.Grab),
766  GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
767  textPos.Y += largeTextSize.Y;
768  }
769 
770  if (character.FocusedCharacter.CanBeHealedBy(character))
771  {
772  GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health),
773  GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
774  textPos.Y += textSize.Y;
775  }
776  if (!character.FocusedCharacter.CustomInteractHUDText.IsNullOrEmpty() && character.FocusedCharacter.AllowCustomInteract)
777  {
778  GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.CustomInteractHUDText, GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
779  textPos.Y += textSize.Y;
780  }
781  }
782 
783  public static void ShowBossHealthBar(Character character, float damage)
784  {
785  if (character == null || character.IsDead || character.Removed) { return; }
786  if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; }
787  AddBossProgressBar(new BossHealthBar(character));
788  }
789 
790  public static void ShowMissionProgressBar(Mission mission)
791  {
792  if (mission == null || mission.Completed || mission.Failed) { return; }
793  if (bossProgressBars.Any(b => b.IsDuplicate(mission))) { return; }
794  AddBossProgressBar(new MissionProgressBar(mission));
795  }
796 
797  public static void ClearBossProgressBars()
798  {
799  for (int i = bossProgressBars.Count - 1; i>= 0; i--)
800  {
801  RemoveBossProgressBar(bossProgressBars[i]);
802  }
803  bossProgressBars.Clear();
804  }
805 
806  private static void RemoveBossProgressBar(BossProgressBar progressBar)
807  {
808  progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer);
809  progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer);
810  bossProgressBars.Remove(progressBar);
811  }
812 
813  private static void AddBossProgressBar(BossProgressBar progressBar)
814  {
815  var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
816  if (healthBarMode == EnemyHealthBarMode.HideAll)
817  {
818  return;
819  }
820  if (bossProgressBars.Count > 5)
821  {
822  BossProgressBar oldestHealthBar = bossProgressBars.First();
823  foreach (var bar in bossProgressBars)
824  {
825  if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
826  {
827  oldestHealthBar = bar;
828  }
829  }
830  oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
831  }
832  bossProgressBars.Add(progressBar);
833  }
834 
835  public static void UpdateBossProgressBars(float deltaTime)
836  {
837  var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
838 
839  for (int i = 0; i < bossProgressBars.Count; i++)
840  {
841  var bossHealthBar = bossProgressBars[i];
842 
843  bool showTopBar = i == bossProgressBars.Count - 1;
844  if (showTopBar && !bossHealthBar.TopContainer.Visible)
845  {
846  bossHealthBar.SideContainer.SetAsLastChild();
847  SetColor(bossHealthBar, bossHealthBar.SideContainer, 0);
848  }
849 
850  bossHealthBar.TopContainer.Visible = showTopBar;
851  bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible;
852 
853  bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = bossHealthBar.State;
854  float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f);
855 
856  if (bossHealthBar.TopContainer.Visible)
857  {
858  SetColor(bossHealthBar, bossHealthBar.TopContainer, alpha);
859  }
860  if (bossHealthBar.SideContainer.Visible)
861  {
862  SetColor(bossHealthBar, bossHealthBar.SideContainer, alpha);
863  }
864 
865  static void SetColor(BossProgressBar bossHealthBar, GUIComponent container, float alpha)
866  {
867  foreach (var component in container.GetAllChildren())
868  {
869  component.Color = new Color(bossHealthBar.Color, (byte)(alpha * 255));
870  if (component is GUITextBlock textBlock)
871  {
872  textBlock.TextColor = new Color(bossHealthBar.Completed ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255));
873  }
874  }
875  }
876 
877  if (bossHealthBar.Interrupted)
878  {
879  bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 1.0f);
880  }
881  else if (bossHealthBar.Completed)
882  {
883  bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 5.0f);
884  }
885  bossHealthBar.FadeTimer -= deltaTime;
886  }
887  for (int i = bossProgressBars.Count - 1; i >= 0 ; i--)
888  {
889  var bossHealthBar = bossProgressBars[i];
890  if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
891  {
892  bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
893  bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
894  bossProgressBars.RemoveAt(i);
895  bossHealthContainer.Recalculate();
896  }
897  }
898  }
899 
900  private static bool LockInventory(Character character)
901  {
902  if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; }
903  return character.ShouldLockHud();
904  }
905 
907  private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order,
908  float iconAlpha = 1.0f, bool createOffset = true, float scaleMultiplier = 1.0f, bool overrideAlpha = false)
909  {
910  if (order?.SymbolSprite == null) { return; }
911  if (order.IsReport && order.OrderGiver != character && !order.HasAppropriateJob(character)) { return; }
912 
913  ISpatialEntity target = order.ConnectedController?.Item ?? order.TargetSpatialEntity;
914  if (target == null) { return; }
915 
916  //don't show the indicator if far away and not inside the same sub
917  //prevents exploiting the indicators in locating the sub
918  if (character.Submarine != target.Submarine &&
919  Vector2.DistanceSquared(character.WorldPosition, target.WorldPosition) > 1000.0f * 1000.0f)
920  {
921  return;
922  }
923 
924  if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); }
925 
926  Vector2 drawPos = target is Entity entity ? entity.DrawPosition :
927  target.Submarine == null ? target.Position : target.Position + target.Submarine.DrawPosition;
928  drawPos += Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target];
929  GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha,
930  createOffset: createOffset, scaleMultiplier: scaleMultiplier, overrideAlpha: overrideAlpha ? (float?)iconAlpha : null);
931 
932  orderIndicatorCount[target] = orderIndicatorCount[target] + 1;
933  }
934 
935  private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f)
936  {
937  if (objectiveEntity == null) return;
938 
939  Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
940  GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
941  }
942 
943  static partial void RecreateHudTextsIfControllingProjSpecific(Character character)
944  {
945  if (character == Character.Controlled)
946  {
947  RecreateHudTexts = true;
948  }
949  }
950 
951  static partial void RecreateHudTextsIfFocusedProjSpecific(params Item[] items)
952  {
953  foreach (var item in items)
954  {
955  if (item == Character.Controlled?.FocusedItem)
956  {
957  RecreateHudTexts = true;
958  break;
959  }
960  }
961  }
962  }
963 }
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
static readonly Identifier ParalysisType
static readonly Identifier PoisonType
Vector2 WorldToScreen(Vector2 coords)
Definition: Camera.cs:416
static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
static void ShowBossHealthBar(Character character, float damage)
static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
static void Update(float deltaTime, Character character, Camera cam)
float GetAfflictionStrengthByType(Identifier afflictionType, bool allowLimbAfflictions=true)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
bool CanBeHealedBy(Character character, bool checkFriendlyTeam=true)
bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel=CharacterInventory.AccessLevel.Limited)
Is the inventory accessible to the character? Doesn't check if the character can actually interact wi...
bool IsKnockedDown
Is the character knocked down regardless whether the technical state is dead, unconcious,...
override void Update(float deltaTime, Camera cam, bool isSubInventory=false)
Triggers a "conversation popup" with text and support for different branching options.
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
virtual Vector2 WorldPosition
Definition: Entity.cs:49
virtual Vector2 DrawPosition
Definition: Entity.cs:51
Submarine Submarine
Definition: Entity.cs:53
virtual Vector2 SimPosition
Definition: Entity.cs:45
virtual Vector2 Position
Definition: Entity.cs:47
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
Definition: GUIComponent.cs:49
void DrawToolTip(SpriteBatch spriteBatch)
Creates and draws a tooltip.
RectTransform RectTransform
static GameSession?? GameSession
Definition: GameMain.cs:88
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static NetworkMember NetworkMember
Definition: GameMain.cs:190
void Draw(SpriteBatch spriteBatch, Camera cam)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
List< ColoredText > GetHUDTexts(Character character, bool recreateHudTexts=true)
void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character)
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
static readonly List< Item > ItemList
static HashSet< Item > DeconstructItems
Items that have been marked for deconstruction
The base class for components holding the different functionalities of the item
virtual void DrawHUD(SpriteBatch spriteBatch, Character character)
readonly Entity TargetEntity
Definition: Order.cs:495
bool DrawIconWhenContained
Definition: Order.cs:560
static readonly PrefabCollection< OrderPrefab > Prefabs
Definition: Order.cs:41
static bool KeyDown(InputType inputType)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
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).
static IEnumerable< MapEntity > VisibleEntities
static readonly LanguageIdentifier None
Definition: TextPack.cs:12