Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs
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 CharacterHealth
13  {
14  private static bool toggledThisFrame;
15 
16  public class DamageOverlayPrefab : Prefab
17  {
19 
20  public readonly Sprite DamageOverlay;
21 
22  public DamageOverlayPrefab(ContentXElement element, AfflictionsFile file) : base(file, file.Path.Value.ToIdentifier())
23  {
24  DamageOverlay = new Sprite(element);
25  }
26 
27  public override void Dispose()
28  {
30  }
31  }
32 
33  public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
34 
35 
36  private Point screenResolution;
37 
38  private float uiScale, inventoryScale;
39 
40  private Alignment alignment = Alignment.Right;
42  {
43  get { return alignment; }
44  set
45  {
46  if (alignment == value) { return; }
47  alignment = value;
48  UpdateAlignment();
49  }
50  }
51 
52  public GUIButton SuicideButton { get; private set; }
53 
54  // healthbars
55  private GUIProgressBar healthBar;
56  private GUIProgressBar healthBarShadow;
57  private float healthShadowSize;
58  private float healthShadowDelay;
59  private float healthBarPulsateTimer;
60  private float healthBarPulsatePhase;
61 
62  private float bloodParticleTimer;
63 
64  private GUIFrame healthWindow;
65 
66  private GUITextBlock deadIndicator;
67 
68  //private GUIComponent lowSkillIndicator;
69 
70  private GUIButton cprButton;
71 
72  private GUIListBox afflictionTooltip;
73 
74  private static readonly Color oxygenLowGrainColor = new Color(0.1f, 0.1f, 0.1f, 1f);
75 
76  private SpriteSheet limbIndicatorOverlay;
77  private float limbIndicatorOverlayAnimState;
78 
79  private SpriteSheet medUIExtra;
80  private float medUIExtraAnimState;
81 
82  private int highlightedLimbIndex = -1;
83  private int selectedLimbIndex = -1;
84  private LimbHealth currentDisplayedLimb;
85 
89  private GUIComponent afflictionIconContainer;
90  private float afflictionIconRefreshTimer;
91  const float AfflictionIconRefreshInterval = 1.0f;
92 
93  private GUIButton showHiddenAfflictionsButton;
94 
98  private GUIComponent hiddenAfflictionIconContainer;
99 
100  private GUIProgressBar healthWindowHealthBar;
101  private GUIProgressBar healthWindowHealthBarShadow;
102 
103  private GUITextBlock characterName;
104  private GUIListBox afflictionIconList;
105  private GUILayoutGroup treatmentLayout;
106  private GUIListBox recommendedTreatmentContainer;
107 
108  private float distortTimer;
109 
110  // 0-1
111  private float damageIntensity;
112  private readonly float damageIntensityDropdownRate = 0.1f;
113 
114  public float DamageOverlayTimer { get; private set; }
115 
116  private float updateDisplayedAfflictionsTimer;
117  private const float UpdateDisplayedAfflictionsInterval = 0.5f;
118  private List<Affliction> currentDisplayedAfflictions = new List<Affliction>();
119 
120  public float DisplayedVitality, DisplayVitalityDelay;
121 
122  public bool MouseOnElement
123  {
124  get { return highlightedLimbIndex > -1; }
125  }
126 
127  private static CharacterHealth openHealthWindow;
129  {
130  get
131  {
132  return openHealthWindow;
133  }
134  set
135  {
136  if (openHealthWindow == value) { return; }
137  if (value != null)
138  {
139  if (!value.UseHealthWindow || value.Character.DisableHealthWindow) { return; }
140  }
141 
142  var prevOpenHealthWindow = openHealthWindow;
143 
144  if (prevOpenHealthWindow != null)
145  {
146  prevOpenHealthWindow.highlightedLimbIndex = -1;
147  }
148 
149  openHealthWindow = value;
150  toggledThisFrame = true;
151  if (Character.Controlled == null) { return; }
152 
153  if (value == null &&
155  Character.Controlled.SelectedCharacter.CharacterHealth == prevOpenHealthWindow)
156  {
158  }
159 
161  if (openHealthWindow != null)
162  {
163  if (value.Character.Info == null || value.Character == Character.Controlled || Character.Controlled.HasEquippedItem("healthscanner".ToIdentifier()))
164  {
165  openHealthWindow.characterName.Text = value.Character.Name;
166  }
167  else
168  {
169  openHealthWindow.characterName.Text = value.Character.Info.DisplayName;
170  value.Character.Info.CheckDisguiseStatus(false);
171  }
173  }
174 
175  HintManager.OnShowHealthInterface();
176  }
177  }
178 
180  {
181  get { return cprButton; }
182  }
183 
185  {
186  get;
187  private set;
188  }
189 
191  {
192  get { return healthBarPulsateTimer; }
193  set { healthBarPulsateTimer = MathHelper.Clamp(value, 0.0f, 10.0f); }
194  }
195 
196  private GUIFrame healthBarHolder;
197 
198  partial void InitProjSpecific(ContentXElement element, Character character)
199  {
201 
202  character.OnAttacked += OnAttacked;
203 
204  healthWindow = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.6f), GUI.Canvas, anchor: Anchor.Center, scaleBasis: ScaleBasis.Smallest), style: "GUIFrameListBox");
205 
206  var healthWindowVerticalLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), healthWindow.RectTransform, Anchor.Center))
207  {
208  Stretch = true
209  };
210 
211  var nameContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), healthWindowVerticalLayout.RectTransform) { MinSize = new Point(0, 20) }, isHorizontal: true)
212  {
213  Stretch = true
214  };
215 
216  new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform, Anchor.CenterLeft),
217  onDraw: (spriteBatch, component) =>
218  {
219  character.Info?.DrawPortrait(spriteBatch, new Vector2(component.Rect.X, component.Rect.Center.Y - component.Rect.Width / 2), Vector2.Zero, component.Rect.Width, false, character != Character.Controlled);
220  });
221  characterName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), nameContainer.RectTransform), "", textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont)
222  {
223  AutoScaleHorizontal = true
224  };
225  new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform),
226  onDraw: (spriteBatch, component) =>
227  {
228  character.Info?.DrawJobIcon(spriteBatch, component.Rect, character != Character.Controlled);
229  });
230 
231 
232  var healthBarContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.07f), healthWindowVerticalLayout.RectTransform), style: null);
233  var healthBarIcon = new GUIFrame(new RectTransform(new Vector2(0.095f, 1.0f), healthBarContainer.RectTransform), style: "GUIHealthBarIcon");
234  healthWindowHealthBarShadow = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight),
235  barSize: 1.0f, color: GUIStyle.Green, style: "GUIHealthBar")
236  {
237  IsHorizontal = true
238  };
239  healthWindowHealthBar = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight),
240  barSize: 1.0f, color: GUIStyle.Green, style: "GUIHealthBar")
241  {
242  IsHorizontal = true
243  };
244 
245  //spacing
246  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), healthWindowVerticalLayout.RectTransform), style: null);
247 
248  var characterIndicatorArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), healthWindowVerticalLayout.RectTransform), isHorizontal: true)
249  {
250  Stretch = true,
251  //RelativeSpacing = 0.05f
252  };
253 
254  InventorySlotContainer = new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1.0f), characterIndicatorArea.RectTransform, Anchor.TopLeft, Pivot.TopRight),
255  (spriteBatch, component) =>
256  {
257  for (int i = 0; i < character.Inventory.Capacity; i++)
258  {
259  if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
260  if (character.Inventory.HideSlot(i)) { continue; }
261 
262  //don't draw the item if it's being dragged out of the slot
263  bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();
264 
265  Inventory.DrawSlot(spriteBatch, Character.Inventory, Character.Inventory.visualSlots[i], Character.Inventory.GetItemAt(i), i, drawItem, Character.Inventory.SlotTypes[i]);
266 
267  if (medUIExtra != null)
268  {
269  float overlayScale = Math.Min(
270  Character.Inventory.visualSlots[i].Rect.Width / (float)medUIExtra.FrameSize.X,
271  Character.Inventory.visualSlots[i].Rect.Height / (float)medUIExtra.FrameSize.Y);
272 
273  int frame = (int)medUIExtraAnimState;
274 
275  medUIExtra.Draw(spriteBatch, frame, Character.Inventory.visualSlots[i].Rect.Center.ToVector2(), Color.Gray, origin: medUIExtra.FrameSize.ToVector2() / 2, rotate: 0.0f,
276  scale: Vector2.One * overlayScale);
277  }
278  }
279  },
280  (dt, component) =>
281  {
282  if (!GameMain.Instance.Paused)
283  {
284  medUIExtraAnimState = (medUIExtraAnimState + dt * 10.0f) % 16.0f;
285  }
286  });
287 
288 
289  cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
290  {
291  UserData = UIHighlightAction.ElementId.CPRButton,
292  OnClicked = (button, userData) =>
293  {
294  Character selectedCharacter = Character.Controlled?.SelectedCharacter;
295  if (selectedCharacter == null || (!selectedCharacter.IsUnconscious && selectedCharacter.Stun <= 0.0f))
296  {
297  return false;
298  }
299 
300  Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ?
301  AnimController.Animation.None : AnimController.Animation.CPR;
302 
303  selectedCharacter.AnimController.ResetPullJoints();
304 
305  if (GameMain.Client != null)
306  {
307  GameMain.Client.CreateEntityEvent(Character.Controlled, new Character.TreatmentEventData());
308  }
309 
310  return true;
311  },
312  ToolTip = TextManager.Get("doctor.cprobjective"),
313  IgnoreLayoutGroups = true,
314  Visible = false
315  };
316 
317  var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform),
318  (spriteBatch, component) =>
319  {
320  DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
321  },
322  (deltaTime, component) =>
323  {
324  UpdateLimbIndicators(deltaTime, component.RectTransform.Rect);
325  }
326  );
327  deadIndicator = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.1f), limbSelection.RectTransform, Anchor.Center),
328  text: TextManager.Get("Deceased"), font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: "GUIToolTip")
329  {
330  Visible = false,
331  CanBeFocused = false
332  };
333  if (deadIndicator.Text.Contains(' '))
334  {
335  deadIndicator.Wrap = true;
336  }
337  else
338  {
339  deadIndicator.AutoScaleHorizontal = true;
340  }
341 
342  afflictionIconList = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null);
343 
344  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform),
345  TextManager.Get("SuitableTreatments"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter);
346 
347  treatmentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), healthWindowVerticalLayout.RectTransform), true)
348  {
349  Stretch = false
350  };
351 
352  recommendedTreatmentContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), treatmentLayout.RectTransform, Anchor.Center, Pivot.Center), isHorizontal: true, style: null)
353  {
354  Spacing = GUI.IntScale(4),
355  KeepSpaceForScrollBar = false,
356  ScrollBarVisible = false,
357  AutoHideScrollBar = false
358  };
359  new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center)
360  {
361  CanBeFocused = false
362  };
363 
364  characterIndicatorArea.Recalculate();
365 
366  healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
367  {
368  HoverCursor = CursorState.Hand
369  };
370 
371  healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location;
372  healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
373  healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;
374 
375  healthBarShadow = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight),
376  barSize: 1.0f, color: Color.Green, style: "CharacterHealthBar", showFrame: false)
377  {
378  Visible = false
379  };
380  healthShadowSize = 1.0f;
381 
382  healthBar = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight),
383  barSize: 1.0f, color: GUIStyle.HealthBarColorHigh, style: "CharacterHealthBar")
384  {
385  HoverCursor = CursorState.Hand,
386  ToolTip = TextManager.GetWithVariable("hudbutton.healthinterface", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)),
387  Enabled = true
388  };
389 
390  afflictionIconContainer = new GUILayoutGroup(
391  HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
392  isHorizontal: true, childAnchor: Anchor.CenterRight)
393  {
394  AbsoluteSpacing = GUI.IntScale(5)
395  };
396 
397  showHiddenAfflictionsButton = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: "GUIButtonCircular")
398  {
399  Visible = false,
400  CanBeFocused = false
401  };
402 
403  hiddenAfflictionIconContainer = new GUILayoutGroup(
404  HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
405  isHorizontal: true, childAnchor: Anchor.CenterRight)
406  {
407  AbsoluteSpacing = GUI.IntScale(5)
408  };
409 
410  UpdateAlignment();
411 
412  SuicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.02f), GUI.Canvas, Anchor.TopCenter)
413  {
414  MinSize = new Point(150, 20), RelativeOffset = new Vector2(0.0f, 0.01f)
415  },
416  TextManager.Get("GiveInButton"), style: "GUIButtonLarge")
417  {
418  Visible = false,
419  ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"),
420  OnClicked = (button, userData) =>
421  {
422  GUI.ForceMouseOn(null);
423  if (Character.Controlled != null)
424  {
425  if (GameMain.Client != null)
426  {
427  GameMain.Client.CreateEntityEvent(Character.Controlled, new Character.CharacterStatusEventData());
428  }
429  else
430  {
431  var (type, affliction) = GetCauseOfDeath();
432  Character.Controlled.Kill(type, affliction);
433  Character.Controlled = null;
434  }
435  }
436  return true;
437  }
438  };
440 
441  if (element != null)
442  {
443  foreach (var subElement in element.Elements())
444  {
445  switch (subElement.Name.ToString().ToLowerInvariant())
446  {
447  case "sprite":
448  case "meduisilhouette":
449  limbIndicatorOverlay = new SpriteSheet(subElement);
450  break;
451  case "meduiextra":
452  medUIExtra = new SpriteSheet(subElement);
453  break;
454  }
455  }
456  }
457 
458  healthWindowVerticalLayout.Recalculate();
459  }
460 
461  private void OnAttacked(Character attacker, AttackResult attackResult)
462  {
463  if (Math.Abs(attackResult.Damage) < 0.01f) { return; }
464  DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
465  if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
466 
467  if (healthBarPulsateTimer <= 0.0f) { healthBarPulsatePhase = 0.0f; }
468  healthBarPulsateTimer = 1.0f;
469 
470  float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
471  damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
472 
473  DisplayVitalityDelay = 0.5f;
474  }
475 
476  private void UpdateAlignment()
477  {
478  screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
479  inventoryScale = Inventory.UIScale;
480  uiScale = GUI.Scale;
481 
482  showHiddenAfflictionsButton.RectTransform.NonScaledSize = new Point(afflictionIconContainer.Rect.Height);
483  //remove affliction icons so we recreate and resize them
484  for (int i = afflictionIconContainer.CountChildren - 1; i >= 0; i--)
485  {
486  var child = afflictionIconContainer.GetChild(i);
487  if (child.UserData is AfflictionPrefab)
488  {
489  afflictionIconContainer.RemoveChild(child);
490  }
491  }
492 
493  healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location;
494  healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
495  healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;
496 
497  switch (alignment)
498  {
499  case Alignment.Left:
500  healthWindow.RectTransform.SetPosition(Anchor.BottomLeft);
501  healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.InventoryAreaLower.X, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
502  break;
503  case Alignment.Right:
504  healthWindow.RectTransform.SetPosition(Anchor.BottomRight);
505  healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
506  break;
507  }
508 
509  healthWindow.RectTransform.RecalculateChildren(false);
510 
511  Character.Inventory?.RefreshSlotPositions();
512  }
513 
514  public void UpdateClientSpecific(float deltaTime)
515  {
516  if (GameMain.NetworkMember == null)
517  {
518  DisplayedVitality = Vitality;
519  }
520  else
521  {
522  DisplayVitalityDelay -= deltaTime;
523  if (DisplayVitalityDelay <= 0.0f)
524  {
525  DisplayedVitality = Vitality;
526  }
527  }
528 
529  if (damageIntensity > 0)
530  {
531  damageIntensity -= deltaTime * damageIntensityDropdownRate;
532  if (damageIntensity < 0)
533  {
534  damageIntensity = 0;
535  }
536  }
537  if (DamageOverlayTimer > 0.0f)
538  {
539  DamageOverlayTimer -= deltaTime;
540  }
541  }
542 
543  private float timeUntilNextHeartbeatSound = 0.0f;
544  private bool nextHeartbeatSoundIsSystole = true;
545  private const string diastoleSoundTag = "heartbeatdiastole", systoleSoundTag = "heartbeatsystole";
546 
547  partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime)
548  {
549  if (prevOxygen > 0.0f && OxygenAmount <= 0.0f && Character.Controlled == Character)
550  {
551  string soundName;
552  if (Character.Info != null)
553  {
554  soundName = Character.Info.ReplaceVars($"drown[{Character.Info.Prefab.MenuCategoryVar}]");
555  }
556  else
557  {
558  var charInfoPrefab = CharacterPrefab.HumanPrefab.CharacterInfoPrefab;
559  soundName = charInfoPrefab.ReplaceVars($"drown[{charInfoPrefab.MenuCategoryVar}]", charInfoPrefab.Heads.First());
560  }
561  SoundPlayer.PlaySound(soundName);
562  }
563 
564  if (Character == Character.Controlled && !IsUnconscious && !Character.IsDead && OxygenAmount < LowOxygenThreshold)
565  {
566  timeUntilNextHeartbeatSound -= deltaTime;
567  if (timeUntilNextHeartbeatSound < 0.0f)
568  {
569  if (nextHeartbeatSoundIsSystole)
570  {
571  SoundPlayer.PlaySound(systoleSoundTag, 1.0f - (OxygenAmount / LowOxygenThreshold));
572  timeUntilNextHeartbeatSound = MathHelper.Lerp(0.18f, 0.3f, Math.Clamp(OxygenAmount / InsufficientOxygenThreshold, 0.0f, 1.0f));
573  }
574  else
575  {
576  SoundPlayer.PlaySound(diastoleSoundTag, 1.0f - (OxygenAmount / LowOxygenThreshold));
577  timeUntilNextHeartbeatSound = MathHelper.Lerp(0.3f, 0.5f, Math.Clamp(OxygenAmount / InsufficientOxygenThreshold, 0.0f, 1.0f));
578  }
579  nextHeartbeatSoundIsSystole = !nextHeartbeatSoundIsSystole;
580  }
581  }
582  }
583 
584  partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime)
585  {
586  if (Character.InvisibleTimer > 0.0f) { return; }
587 
588  bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
589  if (bloodParticleTimer <= 0.0f)
590  {
591  Limb limb = targetLimb ?? Character.AnimController.MainLimb;
592 
593  bool inWater = Character.AnimController.InWater;
594  var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air;
595  var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab?.DrawTarget == drawTarget || e.Prefab.ParticlePrefab?.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
596  float particleMinScale = emitter?.Prefab.Properties.ScaleMin ?? 0.5f;
597  float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1;
598  float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
599  float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity);
600 
601  Vector2 velocity = Rand.Vector(affliction.Strength * 0.1f);
602  if (!inWater)
603  {
604  bloodParticleSize *= 2.0f;
605  velocity = limb.LinearVelocity * 100.0f;
606  }
607 
608  // TODO: use the blood emitter?
609  var blood = GameMain.ParticleManager.CreateParticle(
610  inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
611  limb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull);
612 
613  if (blood != null)
614  {
615  blood.Size *= bloodParticleSize;
616  if (!inWater && !string.IsNullOrEmpty(Character.BloodDecalName) && Rand.Range(0.0f, 1.0f) < 0.05f)
617  {
618  blood.OnCollision += (Vector2 pos, Hull hull) =>
619  {
620  var decal = hull?.AddDecal(Character.BloodDecalName, pos, Rand.Range(1.0f, 2.0f), isNetworkEvent: true);
621  if (decal != null)
622  {
623  decal.FadeTimer = decal.LifeTime - decal.FadeOutTime * 2;
624  }
625  };
626  }
627  }
628  bloodParticleTimer = MathHelper.Lerp(2, 0.5f, severity);
629  }
630  }
631 
632  public static bool IsMouseOnHealthBar()
633  {
634  if (Character.Controlled?.CharacterHealth == null) { return false; }
636  }
637 
638  public void UpdateHUD(float deltaTime)
639  {
640  if (GUI.DisableHUD) { return; }
641  if (openHealthWindow != null)
642  {
643  if (openHealthWindow != Character.Controlled?.CharacterHealth && openHealthWindow != Character.Controlled?.SelectedCharacter?.CharacterHealth)
644  {
645  openHealthWindow = null;
646  return;
647  }
648  }
649 
650  bool forceAfflictionContainerUpdate = false;
651  if (updateDisplayedAfflictionsTimer > 0.0f)
652  {
653  updateDisplayedAfflictionsTimer -= deltaTime;
654  }
655  else
656  {
657  forceAfflictionContainerUpdate = true;
658  currentDisplayedAfflictions = GetAllAfflictions(mergeSameAfflictions: true, predicate: a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null);
659  currentDisplayedAfflictions.Sort((a1, a2) =>
660  {
661  int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond);
662  if (dmgPerSecond != 0) { return dmgPerSecond; }
663  return Math.Sign(GetStr(a1) - GetStr(a2));
664  static float GetStr(Affliction affliction)
665  {
666  return affliction.Strength / affliction.Prefab.MaxStrength * (affliction.Prefab.IsBuff ? 1.0f : 10.0f);
667  }
668  });
669  HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions);
670  updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval;
671  }
672 
673  if (healthShadowDelay > 0.0f)
674  {
675  healthShadowDelay -= deltaTime;
676  }
677  else
678  {
679  healthShadowSize = healthBar.BarSize > healthShadowSize ?
680  Math.Min(healthShadowSize + deltaTime, healthBar.BarSize) :
681  Math.Max(healthShadowSize - deltaTime, healthBar.BarSize);
682  }
683 
684  float blurStrength = 0.0f;
685  float distortStrength = 0.0f;
686  float distortSpeed = 0.0f;
687  float radialDistortStrength = 0.0f;
688  float chromaticAberrationStrength = 0.0f;
689  float grainStrength = 0.0f;
690  Color grainColor = Color.Transparent;
691 
692  float oxygenLowStrength = 0.0f;
694  {
695  blurStrength = 1.0f;
696  distortSpeed = 1.0f;
697  }
698  else if (OxygenAmount < 100.0f)
699  {
700  oxygenLowStrength = Math.Min(1.0f - (OxygenAmount - LowOxygenThreshold) / LowOxygenThreshold, 1.0f);
701  blurStrength = MathHelper.Lerp(0.5f, 1.0f, 1.0f - Vitality / MaxVitality) * oxygenLowStrength;
702  distortStrength = blurStrength * oxygenLowStrength;
703  distortSpeed = blurStrength + 1.0f;
704  distortSpeed *= distortSpeed * distortSpeed * distortSpeed;
705 
706  grainStrength = MathHelper.Lerp(0.5f, 10.0f, oxygenLowStrength);
707  grainColor = oxygenLowGrainColor;
708  }
709 
710  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
711  {
712  var affliction = kvp.Key;
713  distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength());
714  blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
715  radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
716  chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
717 
718  float afflictionGrainStrength = affliction.GetScreenGrainStrength();
719  if (afflictionGrainStrength > 0.0f)
720  {
721  grainStrength = Math.Max(grainStrength, afflictionGrainStrength);
722  Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White;
723  grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
724  }
725  }
726 
727  Character.RadialDistortStrength = radialDistortStrength;
728  Character.ChromaticAberrationStrength = chromaticAberrationStrength;
729  Character.GrainStrength = grainStrength;
730  Character.GrainColor = grainColor;
731  if (blurStrength > 0.0f)
732  {
733  distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi;
734  Character.BlurStrength = (float)(Math.Sin(distortTimer) + 1.5f) * 0.25f * blurStrength;
735  Character.DistortStrength = (float)(Math.Sin(distortTimer) + 1.0f) * 0.05f * distortStrength;
736  }
737  else
738  {
739  Character.BlurStrength = 0.0f;
740  Character.DistortStrength = 0.0f;
741  distortTimer = 0.0f;
742  }
743 
744  UpdateStatusHUD(deltaTime);
745 
746  if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null &&
747  Character.Controlled.AllowInput && !toggledThisFrame)
748  {
749  if (openHealthWindow != null)
750  {
751  OpenHealthWindow = null;
752  }
753  else if (Character.Controlled == Character &&
755  {
756  OpenHealthWindow = this;
757  forceAfflictionContainerUpdate = true;
758  }
759  }
760  else if (openHealthWindow == this)
761  {
762  if (HUD.CloseHUD(healthWindow.Rect))
763  {
764  //emulate a Health input to get the character to deselect the item server-side
765  if (GameMain.Client != null)
766  {
768  }
769  OpenHealthWindow = null;
770  }
771 
772  foreach (GUIComponent afflictionIcon in afflictionIconList.Content.Children)
773  {
774  if (afflictionIcon.UserData is not Affliction affliction) { continue; }
775  if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
776  {
777  afflictionIcon.Flash(GUIStyle.Red);
778  }
779  else if (affliction.AppliedAsSuccessfulTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
780  {
781  afflictionIcon.Flash(GUIStyle.Green);
782  }
783  }
784 
785  if (GUI.MouseOn?.UserData is Affliction)
786  {
787  Affliction affliction = GUI.MouseOn?.UserData as Affliction;
788 
789  if (afflictionTooltip == null || afflictionTooltip.UserData != affliction)
790  {
791  afflictionTooltip = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.2f), GUI.Canvas, scaleBasis: ScaleBasis.Smallest))
792  {
793  UserData = affliction,
794  CanBeFocused = false
795  };
796 
797  CreateAfflictionInfoElements(afflictionTooltip.Content, affliction);
798 
799  int height = afflictionTooltip.Content.Children.Sum(c => c.Rect.Height) + 10;
800  afflictionTooltip.RectTransform.Resize(new Point(afflictionTooltip.Rect.Width, height), true);
801  if (Alignment == Alignment.Right)
802  {
803  afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.X, GUI.MouseOn.Rect.Y);
804  afflictionTooltip.RectTransform.Pivot = Pivot.TopRight;
805  }
806  else
807  {
808  afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.Right, GUI.MouseOn.Rect.Y);
809  afflictionTooltip.RectTransform.Anchor = Anchor.TopLeft;
810  }
811 
812  afflictionTooltip.ScrollBarVisible = false;
813 
814  var labelContainer = afflictionTooltip.Content.GetChildByUserData("label");
815 
816  labelContainer.RectTransform.Resize(new Point(labelContainer.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f)));
817  }
818  }
819  else
820  {
821  afflictionTooltip = null;
822  }
823  }
824  toggledThisFrame = false;
825 
826  if (OpenHealthWindow == this)
827  {
828  var highlightedLimb = highlightedLimbIndex < 0 ? null : limbHealths[highlightedLimbIndex];
829  if (highlightedLimbIndex < 0 && selectedLimbIndex < 0)
830  {
831  // If no limb is selected or highlighted, select the one with the most critical afflictions.
832  var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault();
833  if (affliction != null && (affliction.DamagePerSecond > 0 || affliction.Strength > 0))
834  {
835  var limbHealth = GetMatchingLimbHealth(affliction);
836  if (limbHealth != null)
837  {
838  selectedLimbIndex = limbHealths.IndexOf(limbHealth);
839  }
840  }
841  else
842  {
843  // If no affliction is critical, select the limb which has most damage.
844  var limbHealth = limbHealths.OrderByDescending(l => GetTotalDamage(l)).FirstOrDefault();
845  selectedLimbIndex = limbHealths.IndexOf(limbHealth);
846  }
847  }
848  LimbHealth selectedLimb = selectedLimbIndex < 0 ? highlightedLimb : limbHealths[selectedLimbIndex];
849  if (selectedLimb != currentDisplayedLimb || forceAfflictionContainerUpdate)
850  {
851  UpdateAfflictionContainer(selectedLimb);
852  currentDisplayedLimb = selectedLimb;
853  }
854 
855  UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction));
856 
857  foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children)
858  {
859  var treatmentButton = component.GetChild<GUIButton>();
860  if (treatmentButton?.UserData is not ItemPrefab itemPrefab) { continue; }
861  var matchingItem = AIObjectiveRescue.FindMedicalItem(Character.Controlled.Inventory, itemPrefab.Identifier);
862  treatmentButton.Enabled = matchingItem != null;
863  if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover)
864  {
865  //highlight the slot the treatment item is in
866  var rootContainer = matchingItem.RootContainer ?? matchingItem;
867  var index = Character.Controlled.Inventory.FindIndex(rootContainer);
868  if (Character.Controlled.Inventory.visualSlots != null && index > -1 && index < Character.Controlled.Inventory.visualSlots.Length &&
870  {
871  Character.Controlled.Inventory.visualSlots[index].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f);
872  }
873  }
874  if (matchingItem != null && !treatmentButton.ToolTip.IsNullOrEmpty()) { continue; }
875  treatmentButton.ToolTip = RichString.Rich($"‖color:255,255,255,255‖{itemPrefab.Name}‖color:end‖" + '\n' + itemPrefab.Description);
876  if (treatmentButton.Enabled)
877  {
878  treatmentButton.ToolTip =
880  $"‖color:gui.green‖[{PlayerInput.PrimaryMouseLabel}] "
881  + $"{TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n'
882  + treatmentButton.ToolTip.NestedStr);
883  }
884  foreach (GUIComponent child in treatmentButton.Children)
885  {
886  child.Enabled = treatmentButton.Enabled;
887  }
888  }
889  }
890 
891  if (Character.IsDead)
892  {
893  healthBar.Color = healthWindowHealthBar.Color = Color.Black;
894  healthBar.BarSize = healthWindowHealthBar.BarSize = 1.0f;
895  }
896  else
897  {
898  healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(DisplayedVitality / MaxVitality, GUIStyle.HealthBarColorLow, GUIStyle.HealthBarColorMedium, GUIStyle.HealthBarColorHigh);
899  healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f;
900  healthBar.BarSize = healthWindowHealthBar.BarSize =
901  (DisplayedVitality > 0.0f) ?
902  (MaxVitality > 0.0f ? DisplayedVitality / MaxVitality : 0.0f) :
903  (Math.Abs(MinVitality) > 0.0f ? 1.0f - DisplayedVitality / MinVitality : 0.0f);
904 
905  if (healthBarPulsateTimer > 0.0f)
906  {
907  //0-1
908  float pulsateAmount = (float)(Math.Sin(healthBarPulsatePhase) + 1.0f) / 2.0f;
909 
910  healthBar.RectTransform.LocalScale = healthBarShadow.RectTransform.LocalScale = new Vector2(1.0f, (1.0f + pulsateAmount * healthBarPulsateTimer * 0.5f));
911  healthBarPulsatePhase += deltaTime * 5.0f;
912  healthBarPulsateTimer -= deltaTime;
913  }
914  else
915  {
916  healthBar.RectTransform.LocalScale = Vector2.One;
917  }
918  }
919 
920  if (OpenHealthWindow == this)
921  {
923  {
924  openHealthWindow = null;
925  }
926 
927  if (Inventory.DraggingItems.Any())
928  {
929  if (highlightedLimbIndex > -1)
930  {
931  selectedLimbIndex = highlightedLimbIndex;
932  }
933  }
934  }
935  else
936  {
937  if (openHealthWindow != null && Character != Character.Controlled && Character != Character.Controlled?.SelectedCharacter)
938  {
939  openHealthWindow = null;
940  }
941  highlightedLimbIndex = -1;
942  }
943 
944  healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
945  if (Character.AllowInput && UseHealthWindow && !Character.DisableHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
946  (GUI.IsMouseOn(healthBar) || GUI.MouseOn?.UserData is AfflictionPrefab) && Inventory.SelectedSlot == null)
947  {
948  healthBar.State = GUIComponent.ComponentState.Hover;
950  {
951  OpenHealthWindow = openHealthWindow == this ? null : this;
952  }
953  }
954  else
955  {
956  healthBar.State = GUIComponent.ComponentState.None;
957  }
958 
959  SuicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated;
960 
961  if (GameMain.GameSession?.Campaign is { } campaign)
962  {
963  RectTransform endRoundButton = campaign?.EndRoundButton?.RectTransform;
964  RectTransform readyCheckButton = campaign?.ReadyCheckButton?.RectTransform;
965  if (endRoundButton != null)
966  {
967  if (SuicideButton.Visible)
968  {
969  Point offset = new Point(0, SuicideButton.Rect.Height);
970  endRoundButton.ScreenSpaceOffset = offset;
971  }
972  else if (endRoundButton.ScreenSpaceOffset != Point.Zero)
973  {
974  endRoundButton.ScreenSpaceOffset = Point.Zero;
975  }
976  if (readyCheckButton != null)
977  {
978  readyCheckButton.ScreenSpaceOffset = endRoundButton.ScreenSpaceOffset;
979  }
980  }
981  }
982 
983  cprButton.Visible =
985  && !Character.IsDead
987  && openHealthWindow == this;
988  cprButton.Selected =
989  Character.Controlled != null &&
992 
993  deadIndicator.Visible = Character.IsDead;
994  }
995 
996  public void AddToGUIUpdateList()
997  {
998  if (GUI.DisableHUD) { return; }
999  if (OpenHealthWindow == this)
1000  {
1001  healthWindow.AddToGUIUpdateList();
1002  afflictionTooltip?.AddToGUIUpdateList();
1003  }
1005  {
1006  healthBarHolder.AddToGUIUpdateList();
1007  afflictionIconContainer.AddToGUIUpdateList();
1008  if (hiddenAfflictionIconContainer.Visible)
1009  {
1010  hiddenAfflictionIconContainer.AddToGUIUpdateList();
1011  }
1012  }
1013  if (SuicideButton.Visible && Character == Character.Controlled)
1014  {
1015  SuicideButton.AddToGUIUpdateList();
1016  }
1017  if (cprButton != null && cprButton.Visible)
1018  {
1019  cprButton.AddToGUIUpdateList();
1020  }
1021  }
1022 
1023  public void DrawHUD(SpriteBatch spriteBatch)
1024  {
1025  if (GUI.DisableHUD || Character.Removed) { return; }
1026  if (GameMain.GraphicsWidth != screenResolution.X ||
1027  GameMain.GraphicsHeight != screenResolution.Y ||
1028  Math.Abs(inventoryScale - Inventory.UIScale) > 0.01f ||
1029  Math.Abs(uiScale - GUI.Scale) > 0.01f)
1030  {
1031  UpdateAlignment();
1032  }
1033 
1034  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
1035  {
1036  var affliction = kvp.Key;
1037  affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
1038  new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
1039  }
1040 
1041  float damageOverlayAlpha = DamageOverlayTimer;
1042  if (Vitality < MaxVitality * 0.1f)
1043  {
1044  damageOverlayAlpha = Math.Max(1.0f - (Vitality / UnmodifiedMaxVitality * 10.0f), damageOverlayAlpha);
1045  }
1046  else
1047  {
1048  float pulsateAmount = (float)(Math.Sin(healthBarPulsatePhase) + 1.0f) / 2.0f;
1049  damageOverlayAlpha = pulsateAmount * healthBarPulsateTimer * damageIntensity;
1050  }
1051 
1052  if (damageOverlayAlpha > 0.0f)
1053  {
1054  DamageOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * damageOverlayAlpha, Vector2.Zero, 0.0f,
1055  new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
1056  }
1057 
1058  if (Character.Inventory != null)
1059  {
1060  healthBar.RectTransform.ScreenSpaceOffset = healthBarShadow.RectTransform.ScreenSpaceOffset = Point.Zero;
1061  }
1062 
1063  if (healthBarHolder != null)
1064  {
1065  // If manning a turret the portrait doesn't get rendered so we push the health bar to remove the empty gap
1066  healthBarHolder.RectTransform.ScreenSpaceOffset = Character.ShouldLockHud() ? new Point(0, HUDLayoutSettings.PortraitArea.Height) : Point.Zero;
1067  }
1068  }
1069 
1070  //private (Affliction Affliction, LocalizedString NameToolTip)? highlightedAfflictionIcon = null;
1071 
1072  private readonly List<Affliction> statusIcons = new List<Affliction>();
1073  private readonly Dictionary<AfflictionPrefab, float> statusIconVisibleTime = new Dictionary<AfflictionPrefab, float>();
1074  private const float HideStatusIconDelay = 5.0f;
1075 
1076  public void UpdateStatusHUD(float deltaTime)
1077  {
1078  if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
1079  {
1080  statusIcons.Clear();
1081  if (Character.InPressure)
1082  {
1083  statusIcons.Add(pressureAffliction);
1084  }
1085  if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold)
1086  {
1087  statusIcons.Add(oxygenLowAffliction);
1088  }
1089 
1090  foreach (Affliction affliction in currentDisplayedAfflictions)
1091  {
1092  statusIcons.Add(affliction);
1093  }
1094 
1095  int spacing = GUI.IntScale(10);
1096  if (Character.ShouldLockHud())
1097  {
1098  // Push the icons down since the portrait doesn't get rendered
1099  afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, HUDLayoutSettings.PortraitArea.Height);
1100  hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing + HUDLayoutSettings.PortraitArea.Height);
1101  }
1102  else
1103  {
1104  afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, 0);
1105  hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing);
1106  }
1107  //remove affliction icons for afflictions that no longer exist
1108 
1109  RemoveNonExistentIcons(afflictionIconContainer);
1110  RemoveNonExistentIcons(hiddenAfflictionIconContainer);
1111  void RemoveNonExistentIcons(GUIComponent container)
1112  {
1113  for (int i = container.CountChildren - 1; i >= 0; i--)
1114  {
1115  var child = container.GetChild(i);
1116  if (child.UserData is not AfflictionPrefab afflictionPrefab) { continue; }
1117  if (!statusIcons.Any(s => s.Prefab == afflictionPrefab))
1118  {
1119  container.RemoveChild(child);
1120  statusIconVisibleTime.Remove(afflictionPrefab);
1121  }
1122  }
1123  }
1124 
1125  foreach (var statusIcon in statusIcons)
1126  {
1127  Affliction affliction = statusIcon;
1128  AfflictionPrefab afflictionPrefab = affliction.Prefab;
1129 
1130  if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
1131  statusIconVisibleTime[afflictionPrefab] += deltaTime;
1132 
1133  var matchingIcon =
1134  afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
1135  hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
1136  if (matchingIcon == null)
1137  {
1138  matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
1139  {
1140  UserData = afflictionPrefab,
1141  ToolTip = affliction.Prefab.Name,
1142  CanBeSelected = false
1143  };
1144  if (affliction == pressureAffliction)
1145  {
1146  matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
1147  }
1148  else if (affliction == pressureAffliction)
1149  {
1150  matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
1151  }
1152  new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
1153  {
1154  CanBeFocused = false
1155  };
1156  }
1157  if (afflictionPrefab.HideIconAfterDelay && statusIconVisibleTime[afflictionPrefab] > HideStatusIconDelay)
1158  {
1159  matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
1160  }
1161  var image = matchingIcon.GetChild<GUIImage>();
1162  image.Color = GetAfflictionIconColor(afflictionPrefab, affliction);
1163  image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);
1164 
1165  if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)
1166  {
1167  matchingIcon.Flash(useCircularFlash: true, flashDuration: 1.5f, flashRectInflate: Vector2.One * 15.0f * GUI.Scale);
1168  image.Pulsate(Vector2.One, Vector2.One * 1.2f, 1.0f);
1169  }
1170  }
1171 
1172  afflictionIconRefreshTimer -= deltaTime;
1173  if (afflictionIconRefreshTimer <= 0.0f)
1174  {
1175  afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
1176  {
1177  if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
1178  if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
1179  var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
1180  var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
1181  return index1.CompareTo(index2);
1182  });
1183  (afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
1184  afflictionIconRefreshTimer = AfflictionIconRefreshInterval;
1185  }
1186 
1187  Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
1188  foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
1189  {
1190  hiddenAfflictionHoverArea = Rectangle.Union(hiddenAfflictionHoverArea, child.Rect);
1191  }
1192 
1193  afflictionIconContainer.Visible = true;
1194  hiddenAfflictionIconContainer.Visible =
1195  showHiddenAfflictionsButton.Rect.Contains(PlayerInput.MousePosition) ||
1196  (hiddenAfflictionIconContainer.Visible && hiddenAfflictionHoverArea.Contains(PlayerInput.MousePosition));
1197  showHiddenAfflictionsButton.Visible = hiddenAfflictionIconContainer.CountChildren > 0;
1198  showHiddenAfflictionsButton.IgnoreLayoutGroups = !showHiddenAfflictionsButton.Visible;
1199  showHiddenAfflictionsButton.Text = $"+{hiddenAfflictionIconContainer.CountChildren}";
1200 
1201  if (Vitality > 0.0f)
1202  {
1203  float currHealth = healthBar.BarSize;
1204  Color prevColor = healthBar.Color;
1205  healthBarShadow.BarSize = healthShadowSize;
1206  healthBarShadow.Color = Color.Lerp(GUIStyle.Red, Color.Black, 0.5f);
1207  healthBarShadow.Visible = true;
1208  healthBar.BarSize = currHealth;
1209  healthBar.Color = prevColor;
1210  }
1211  else
1212  {
1213  healthBarShadow.Visible = false;
1214  }
1215  }
1216  else
1217  {
1218  afflictionIconContainer.Visible = hiddenAfflictionIconContainer.Visible = false;
1219  if (Vitality > 0.0f)
1220  {
1221  float currHealth = healthWindowHealthBar.BarSize;
1222  Color prevColor = healthWindowHealthBar.Color;
1223  healthWindowHealthBarShadow.BarSize = healthShadowSize;
1224  healthWindowHealthBarShadow.Color = GUIStyle.Red;
1225  healthWindowHealthBarShadow.Visible = true;
1226  healthWindowHealthBar.BarSize = currHealth;
1227  healthWindowHealthBar.Color = prevColor;
1228  }
1229  else
1230  {
1231  healthWindowHealthBarShadow.Visible = false;
1232  }
1233  }
1234  }
1235 
1236  public static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction)
1237  {
1238  return GetAfflictionIconColor(prefab, affliction.Strength);
1239  }
1240 
1241  public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
1242  {
1243  //use sqrt to make the color change rapidly when strength is low
1244  //(low strength is where seeing the severity of the affliction makes more difference - at high strengths the character is already unconscious or dead)
1245  float colorT = MathF.Sqrt(afflictionStrength / prefab.MaxStrength);
1246  // No specific colors, use generic
1247  if (prefab.IconColors == null)
1248  {
1249  if (prefab.IsBuff)
1250  {
1251  return ToolBox.GradientLerp(colorT, GUIStyle.BuffColorLow, GUIStyle.BuffColorMedium, GUIStyle.BuffColorHigh);
1252  }
1253 
1254  return ToolBox.GradientLerp(colorT, GUIStyle.DebuffColorLow, GUIStyle.DebuffColorMedium, GUIStyle.DebuffColorHigh);
1255  }
1256  return ToolBox.GradientLerp(colorT, prefab.IconColors);
1257  }
1258 
1259  public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
1260 
1261  private readonly List<(Affliction affliction, float strength)> displayedAfflictions = new List<(Affliction affliction, float strength)>();
1262 
1263  private void UpdateAfflictionContainer(LimbHealth selectedLimb)
1264  {
1265  if (selectedLimb == null)
1266  {
1267  afflictionIconList.Content.ClearChildren();
1268  return;
1269  }
1270 
1271  if (afflictionsDirty() || selectedLimb != currentDisplayedLimb)
1272  {
1273  var currentAfflictions = afflictions.Where(a => ShouldDisplayAfflictionOnLimb(a, selectedLimb)).Select(a => a.Key);
1274  CreateAfflictionInfos(currentAfflictions);
1275  CreateRecommendedTreatments();
1276  }
1277  //update recommended treatments if the strength of some displayed affliction has changed by > 1
1278  else if (displayedAfflictions.Any(d => Math.Abs(d.strength - d.affliction.Strength) > 1.0f))
1279  {
1280  CreateRecommendedTreatments();
1281  }
1282 
1283  bool afflictionsDirty()
1284  {
1285  //not displaying one of the current afflictions -> dirty
1286  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
1287  {
1288  if (!ShouldDisplayAfflictionOnLimb(kvp, selectedLimb)) { continue; }
1289  if (!displayedAfflictions.Any(d => d.affliction == kvp.Key)) { return true; }
1290  }
1291  //displaying an affliction we no longer have -> dirty
1292  foreach ((Affliction affliction, float strength) in displayedAfflictions)
1293  {
1294  if (afflictions.None(a => a.Key == affliction && a.Key.ShouldShowIcon(Character))) { return true; }
1295  }
1296  return false;
1297  }
1298  }
1299 
1300  private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
1301  {
1302  afflictionIconList.ClearChildren();
1303  displayedAfflictions.Clear();
1304 
1305  Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault();
1306  GUIButton buttonToSelect = null;
1307 
1308  foreach (Affliction affliction in afflictions)
1309  {
1310  displayedAfflictions.Add((affliction, affliction.Strength));
1311 
1312  var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconList.Content.RectTransform), style: "ListBoxElement")
1313  {
1314  UserData = affliction,
1315  OnClicked = SelectAffliction
1316  };
1317 
1318  new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: "GUIFrameListBox") { CanBeFocused = false };
1319 
1320  var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), frame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
1321  {
1322  Stretch = true,
1323  CanBeFocused = false
1324  };
1325 
1326  var progressbarBg = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.18f), content.RectTransform), 0.0f, GUIStyle.Green, style: "GUIAfflictionBar")
1327  {
1328  UserData = "afflictionstrengthprediction",
1329  CanBeFocused = false
1330  };
1331  new GUIProgressBar(new RectTransform(Vector2.One, progressbarBg.RectTransform), 0.0f, Color.Transparent, showFrame: false, style: "GUIAfflictionBar")
1332  {
1333  UserData = "afflictionstrength",
1334  CanBeFocused = false
1335  };
1336 
1337  //spacing
1338  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), style: null) { CanBeFocused = false };
1339 
1340  if (affliction == mostSevereAffliction)
1341  {
1342  buttonToSelect = frame;
1343  }
1344 
1345  var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, content.RectTransform), affliction.Prefab.Icon, scaleToFit: true)
1346  {
1347  Color = GetAfflictionIconColor(affliction),
1348  CanBeFocused = false
1349  };
1350  afflictionIcon.PressedColor = afflictionIcon.Color;
1351  afflictionIcon.HoverColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.6f);
1352  afflictionIcon.SelectedColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.5f);
1353 
1354  var nameText = new GUITextBlock(new RectTransform(new Vector2(1.1f, 0.0f), content.RectTransform),
1355  affliction.Prefab.Name, font: GUIStyle.SmallFont, textAlignment: Alignment.BottomCenter)
1356  {
1357  CanBeFocused = false
1358  };
1359  nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width);
1360  nameText.RectTransform.MinSize = new Point(0, (int)(nameText.TextSize.Y));
1361  nameText.RectTransform.SizeChanged += () =>
1362  {
1363  nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width);
1364  };
1365 
1366  content.Recalculate();
1367  }
1368 
1369  buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData);
1370  afflictionIconList.RecalculateChildren();
1371  }
1372 
1373  private void CreateRecommendedTreatments()
1374  {
1375  ItemPrefab prevHighlightedItem = null;
1376  if (GUI.MouseOn?.UserData is ItemPrefab && recommendedTreatmentContainer.Content.IsParentOf(GUI.MouseOn))
1377  {
1378  prevHighlightedItem = (ItemPrefab)GUI.MouseOn.UserData;
1379  }
1380 
1381  recommendedTreatmentContainer.Content.ClearChildren();
1382 
1383  float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel("medical");
1384 
1385  //key = item identifier
1386  //float = suitability
1387  Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>();
1388  GetSuitableTreatments(treatmentSuitability,
1389  user: Character.Controlled,
1390  ignoreHiddenAfflictions: true,
1391  limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
1392 
1393  foreach (Identifier treatment in treatmentSuitability.Keys.ToList())
1394  {
1395  //prefer suggestions for items the player has
1396  if (Character.Controlled.Inventory.FindItemByIdentifier(treatment, recursive: true) != null)
1397  {
1398  treatmentSuitability[treatment] *= 10.0f;
1399  }
1400  }
1401 
1402  if (!treatmentSuitability.Any())
1403  {
1404  new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center)
1405  {
1406  CanBeFocused = false
1407  };
1408  recommendedTreatmentContainer.ScrollBarVisible = false;
1409  recommendedTreatmentContainer.AutoHideScrollBar = false;
1410  }
1411  else
1412  {
1413  recommendedTreatmentContainer.ScrollBarVisible = true;
1414  recommendedTreatmentContainer.AutoHideScrollBar = true;
1415  }
1416 
1417  List<KeyValuePair<Identifier, float>> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList();
1418 
1419  int count = 0;
1420  foreach (KeyValuePair<Identifier, float> treatment in treatmentSuitabilities)
1421  {
1422  //don't list negative treatments
1423  if (treatment.Value < 0) { continue; }
1424 
1425  count++;
1426  if (count > 5) { break; }
1427  if (MapEntityPrefab.FindByIdentifier(treatment.Key) is not ItemPrefab item) { continue; }
1428 
1429  var itemSlot = new GUIFrame(new RectTransform(new Vector2(1.0f / 6.0f, 1.0f), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopLeft),
1430  style: null)
1431  {
1432  UserData = item
1433  };
1434 
1435  var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader")
1436  {
1437  UserData = item,
1438  DisabledColor = Color.White * 0.1f,
1439  PlaySoundOnSelect = false,
1440  OnClicked = (btn, userdata) =>
1441  {
1442  if (userdata is not ItemPrefab itemPrefab) { return false; }
1443  var item = AIObjectiveRescue.FindMedicalItem(Character.Controlled.Inventory, it => it.Prefab == itemPrefab);
1444  if (item == null) { return false; }
1445  Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex);
1446  item.ApplyTreatment(Character.Controlled, Character, targetLimb);
1447  SoundPlayer.PlayUISound(GUISoundType.Select);
1448  return true;
1449  }
1450  };
1451 
1452  new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow")
1453  {
1454  CanBeFocused = false,
1455  Color = GUIStyle.Green,
1456  HoverColor = Color.White,
1457  PressedColor = Color.DarkGray,
1458  SelectedColor = Color.Transparent,
1459  DisabledColor = Color.Transparent
1460  };
1461 
1462  Sprite itemSprite = item.InventoryIcon ?? item.Sprite;
1463  Color itemColor = itemSprite == item.Sprite ? item.SpriteColor : item.InventoryIconColor;
1464  var itemIcon = new GUIImage(new RectTransform(new Vector2(0.8f, 0.8f), innerFrame.RectTransform, Anchor.Center),
1465  itemSprite, scaleToFit: true)
1466  {
1467  CanBeFocused = false,
1468  Color = itemColor * 0.9f,
1469  HoverColor = itemColor,
1470  SelectedColor = itemColor,
1471  DisabledColor = itemColor * 0.8f
1472  };
1473 
1474  if (item == prevHighlightedItem)
1475  {
1476  innerFrame.State = GUIComponent.ComponentState.Hover;
1477  innerFrame.Children.ForEach(c => c.State = GUIComponent.ComponentState.Hover);
1478  }
1479  }
1480 
1481  recommendedTreatmentContainer.RecalculateChildren();
1482 
1483  afflictionIconList.Content.RectTransform.SortChildren((r1, r2) =>
1484  {
1485  var first = r1.GUIComponent.UserData as Affliction;
1486  var second = r2.GUIComponent.UserData as Affliction;
1487  int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond);
1488  return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(second.Strength - first.Strength);
1489  });
1490 
1491  if (count > 0)
1492  {
1493  var treatmentIconSize = recommendedTreatmentContainer.Content.Children.Sum(c => c.Rect.Width + recommendedTreatmentContainer.Spacing);
1494  if (treatmentIconSize < recommendedTreatmentContainer.Content.Rect.Width)
1495  {
1496  var spacing = new GUIFrame(new RectTransform(new Point((recommendedTreatmentContainer.Content.Rect.Width - treatmentIconSize) / 2, 0), recommendedTreatmentContainer.Content.RectTransform), style: null)
1497  {
1498  CanBeFocused = false
1499  };
1500  spacing.RectTransform.SetAsFirstChild();
1501  }
1502  }
1503  }
1504 
1505  private void CreateAfflictionInfoElements(GUIComponent parent, Affliction affliction)
1506  {
1507  var labelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), parent.RectTransform), isHorizontal: true)
1508  {
1509  Stretch = true,
1510  AbsoluteSpacing = 10,
1511  UserData = "label",
1512  CanBeFocused = false
1513  };
1514 
1515  var afflictionName = new GUITextBlock(new RectTransform(new Vector2(0.65f, 1.0f), labelContainer.RectTransform), affliction.Prefab.Name, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont)
1516  {
1517  CanBeFocused = false,
1518  AutoScaleHorizontal = true
1519  };
1520  var afflictionStrength = new GUITextBlock(new RectTransform(new Vector2(0.35f, 0.6f), labelContainer.RectTransform), "", textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont)
1521  {
1522  UserData = "strength",
1523  CanBeFocused = false
1524  };
1525  var vitality = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), labelContainer.RectTransform, Anchor.BottomRight), "", textAlignment: Alignment.BottomRight)
1526  {
1527  Padding = afflictionStrength.Padding,
1528  IgnoreLayoutGroups = true,
1529  UserData = "vitality",
1530  CanBeFocused = false
1531  };
1532 
1533  var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
1534  affliction.Prefab.GetDescription(
1535  affliction.Strength,
1536  Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter),
1537  textAlignment: Alignment.TopLeft, wrap: true)
1538  {
1539  CanBeFocused = false
1540  };
1541 
1542  if (description.Font.MeasureString(description.WrappedText).Y > description.Rect.Height)
1543  {
1544  description.Font = GUIStyle.SmallFont;
1545  }
1546 
1547  Point nameDims = new Point(afflictionName.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f));
1548 
1549  afflictionStrength.Text = affliction.GetStrengthText();
1550 
1551  Vector2 strengthDims = GUIStyle.SubHeadingFont.MeasureString(afflictionStrength.Text);
1552 
1553  labelContainer.RectTransform.Resize(new Point(labelContainer.Rect.Width, nameDims.Y));
1554  afflictionName.RectTransform.Resize(new Point((int)(labelContainer.Rect.Width - strengthDims.X * 0.99f), nameDims.Y));
1555  afflictionStrength.RectTransform.Resize(new Point(labelContainer.Rect.Width - afflictionName.Rect.Width, nameDims.Y));
1556 
1557  afflictionStrength.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
1558  affliction.Strength / affliction.Prefab.MaxStrength);
1559 
1560  description.RectTransform.Resize(new Point(description.Rect.Width, (int)(description.TextSize.Y + 10)));
1561 
1562  int vitalityDecrease = (int)GetVitalityDecreaseWithVitalityMultipliers(affliction);
1563  if (vitalityDecrease == 0)
1564  {
1565  vitality.Visible = false;
1566  }
1567  else
1568  {
1569  vitality.Visible = true;
1570  vitality.Text = TextManager.Get("Vitality") + " -" + vitalityDecrease;
1571  vitality.TextColor = vitalityDecrease <= 0 ? GUIStyle.Green :
1572  Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
1573  }
1574 
1575  vitality.AutoDraw = true;
1576  }
1577 
1578  private bool SelectAffliction(GUIButton button, object userData)
1579  {
1580  bool selected = button.Selected;
1581  foreach (var child in afflictionIconList.Content.Children)
1582  {
1583  if (child is GUIButton btn)
1584  {
1585  btn.Selected = btn == button && !selected;
1586  }
1587  }
1588 
1589  return false;
1590  }
1591 
1592  private void UpdateAfflictionInfos(IEnumerable<Affliction> afflictions)
1593  {
1594  var potentialTreatment = Inventory.DraggingItems.FirstOrDefault();
1595  if (potentialTreatment == null && GUI.MouseOn?.UserData is ItemPrefab itemPrefab)
1596  {
1597  potentialTreatment = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true);
1598  }
1599  potentialTreatment ??= Inventory.SelectedSlot?.Item;
1600 
1601  foreach (Affliction affliction in afflictions)
1602  {
1603  float afflictionVitalityDecrease = GetVitalityDecreaseWithVitalityMultipliers(affliction);
1604  Color afflictionEffectColor = Color.White;
1605  if (afflictionVitalityDecrease > 0.0f)
1606  {
1607  afflictionEffectColor = GUIStyle.Red;
1608  }
1609  else if (afflictionVitalityDecrease < 0.0f)
1610  {
1611  afflictionEffectColor = GUIStyle.Green;
1612  }
1613 
1614  var child = afflictionIconList.Content.FindChild(affliction);
1615 
1616  var afflictionStrengthPredictionBar = child.GetChild<GUILayoutGroup>().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar;
1617  afflictionStrengthPredictionBar.BarSize = 0.0f;
1618  var afflictionStrengthBar = afflictionStrengthPredictionBar.GetChildByUserData("afflictionstrength") as GUIProgressBar;
1619  afflictionStrengthBar.BarSize = affliction.Strength / affliction.Prefab.MaxStrength;
1620  afflictionStrengthBar.Color = afflictionEffectColor;
1621 
1622  float afflictionStrengthPrediction = GetAfflictionStrengthPrediction(potentialTreatment, affliction);
1623  if (!MathUtils.NearlyEqual(afflictionStrengthPrediction, affliction.Strength))
1624  {
1625  float t = (float)Math.Max(0.5f, (Math.Sin(Timing.TotalTime * 5) + 1.0f) / 2.0f);
1626  if (afflictionStrengthPrediction < affliction.Strength)
1627  {
1628  afflictionStrengthBar.Color = afflictionEffectColor;
1629  afflictionStrengthPredictionBar.Color = GUIStyle.Blue * t;
1630  afflictionStrengthPredictionBar.BarSize = afflictionStrengthBar.BarSize;
1631  afflictionStrengthBar.BarSize = afflictionStrengthPrediction / affliction.Prefab.MaxStrength;
1632  }
1633  else
1634  {
1635  afflictionStrengthPredictionBar.Color = Color.Red * t;
1636  afflictionStrengthPredictionBar.BarSize = afflictionStrengthPrediction / affliction.Prefab.MaxStrength;
1637  }
1638  }
1639 
1640  if (!affliction.Prefab.ShowBarInHealthMenu)
1641  {
1642  afflictionStrengthBar.BarSize = 1f;
1643  }
1644 
1645  if (afflictionTooltip != null && afflictionTooltip.UserData == affliction)
1646  {
1647  UpdateAfflictionInfo(afflictionTooltip.Content, affliction);
1648  }
1649  }
1650  }
1651 
1652  private float GetAfflictionStrengthPrediction(Item item, Affliction affliction)
1653  {
1654  float strength = affliction.Strength;
1655  if (item == null) { return strength; }
1656 
1657  foreach (ItemComponent ic in item.Components)
1658  {
1659  if (ic.statusEffectLists == null) { continue; }
1660  if (!ic.statusEffectLists.TryGetValue(ActionType.OnUse, out List<StatusEffect> statusEffects)) { continue; }
1661  foreach (StatusEffect effect in statusEffects)
1662  {
1663  foreach (var reduceAffliction in effect.ReduceAffliction)
1664  {
1665  if (reduceAffliction.AfflictionIdentifier != affliction.Identifier && reduceAffliction.AfflictionIdentifier != affliction.Prefab.AfflictionType) { continue; }
1666  strength -= reduceAffliction.ReduceAmount * (effect.Duration > 0 ? effect.Duration : 1.0f);
1667  }
1668  foreach (var addAffliction in effect.Afflictions)
1669  {
1670  if (addAffliction.Prefab != affliction.Prefab) { continue; }
1671  strength += addAffliction.Strength * (effect.Duration > 0 ? effect.Duration : 1.0f);
1672  }
1673  }
1674  }
1675  return strength;
1676  }
1677 
1678  private void UpdateAfflictionInfo(GUIComponent parent, Affliction affliction)
1679  {
1680  var labelContainer = parent.GetChildByUserData("label");
1681 
1682  var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock;
1683 
1684  strengthText.Text = affliction.GetStrengthText();
1685 
1686  strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
1687  affliction.Strength / affliction.Prefab.MaxStrength);
1688 
1689  var vitalityText = labelContainer.GetChildByUserData("vitality") as GUITextBlock;
1690  int vitalityDecrease = (int)GetVitalityDecreaseWithVitalityMultipliers(affliction);
1691  if (vitalityDecrease == 0)
1692  {
1693  vitalityText.Visible = false;
1694  }
1695  else
1696  {
1697  vitalityText.Visible = true;
1698  vitalityText.Text = TextManager.Get("Vitality") + " -" + vitalityDecrease;
1699  vitalityText.TextColor = vitalityDecrease <= 0 ? GUIStyle.Green :
1700  Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
1701  }
1702  }
1703 
1704  public bool OnItemDropped(Item item, bool ignoreMousePos)
1705  {
1706  //items can be dropped outside the health window
1707  if (!ignoreMousePos &&
1708  !healthWindow.Rect.Contains(PlayerInput.MousePosition))
1709  {
1710  return false;
1711  }
1712 
1713  //can't apply treatment to dead characters
1714  if (Character.IsDead) { return true; }
1715  if (item == null || !item.UseInHealthInterface) { return true; }
1716  if (!ignoreMousePos)
1717  {
1718  if (highlightedLimbIndex > -1)
1719  {
1720  selectedLimbIndex = highlightedLimbIndex;
1721  }
1722  }
1723 
1724  Limb targetLimb =
1725  Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex) ??
1727  item.ApplyTreatment(Character.Controlled, Character, targetLimb);
1728  return true;
1729  }
1730  private void UpdateLimbIndicators(float deltaTime, Rectangle drawArea)
1731  {
1732  if (!GameMain.Instance.Paused)
1733  {
1734  limbIndicatorOverlayAnimState += deltaTime * 8.0f;
1735  }
1736 
1737  highlightedLimbIndex = -1;
1738  int i = 0;
1739  foreach (LimbHealth limbHealth in limbHealths)
1740  {
1741  if (limbHealth.IndicatorSprite == null) { continue; }
1742 
1743  float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
1744 
1745  Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea);
1746 
1747  if (highlightArea.Contains(PlayerInput.MousePosition))
1748  {
1749  highlightedLimbIndex = i;
1750  }
1751  i++;
1752  }
1753 
1754  if (PlayerInput.PrimaryMouseButtonClicked() && highlightedLimbIndex > -1)
1755  {
1756  selectedLimbIndex = highlightedLimbIndex;
1757  }
1758  }
1759 
1760  private static readonly List<Affliction> afflictionsDisplayedOnLimb = new List<Affliction>();
1761  private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight)
1762  {
1763  if (Character.Removed) { return; }
1764 
1765  spriteBatch.End();
1766  spriteBatch.Begin(SpriteSortMode.Immediate, blendState: BlendState.NonPremultiplied, rasterizerState: GameMain.ScissorTestEnable, effect: GameMain.GameScreen.GradientEffect);
1767 
1768  int i = 0;
1769  foreach (LimbHealth limbHealth in limbHealths)
1770  {
1771  if (limbHealth.IndicatorSprite == null) { continue; }
1772 
1773  Rectangle limbEffectiveArea = new Rectangle(limbHealth.IndicatorSprite.SourceRect.X + limbHealth.HighlightArea.X,
1774  limbHealth.IndicatorSprite.SourceRect.Y + limbHealth.HighlightArea.Y,
1775  limbHealth.HighlightArea.Width,
1776  limbHealth.HighlightArea.Height);
1777 
1778  float totalDamage = GetTotalDamage(limbHealth);
1779 
1780  float damageLerp = totalDamage > 0.0f ? MathHelper.Lerp(0.2f, 1.0f, totalDamage / 100.0f) : 0.0f;
1781 
1782  float negativeEffect = 0.0f, positiveEffect = 0.0f;
1783  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
1784  {
1785  if (kvp.Value != limbHealth) { continue; }
1786  var affliction = kvp.Key;
1787  if (!affliction.ShouldShowIcon(Character)) { continue; }
1788  if (!affliction.Prefab.IsBuff)
1789  {
1790  negativeEffect += affliction.Strength * GetVitalityMultiplier(affliction, limbHealth);
1791  }
1792  else
1793  {
1794  positiveEffect += affliction.Strength * 0.2f;
1795  }
1796  }
1797 
1798  float midPoint = (float)limbEffectiveArea.Center.Y / (float)limbHealth.IndicatorSprite.Texture.Height;
1799  float fadeDist = 0.6f * (float)limbEffectiveArea.Height / (float)limbHealth.IndicatorSprite.Texture.Height;
1800 
1801  if (negativeEffect > 0.0f && negativeEffect < 5.0f) { negativeEffect = 10.0f; }
1802  if (positiveEffect > 0.0f && positiveEffect < 5.0f) { positiveEffect = 10.0f; }
1803 
1804  Color positiveColor = Color.Lerp(Color.Orange, Color.Lime, Math.Min(positiveEffect / 25.0f, 1.0f));
1805  Color negativeColor = Color.Lerp(Color.Orange, Color.Red, Math.Min(negativeEffect / 25.0f, 1.0f));
1806 
1807  Color color1 = Color.Orange;
1808  Color color2 = Color.Orange;
1809 
1810  if (negativeEffect + positiveEffect > 0.0f)
1811  {
1812  if (negativeEffect >= positiveEffect)
1813  {
1814  color1 = Color.Lerp(positiveColor, negativeColor, (negativeEffect - positiveEffect) / negativeEffect);
1815  color2 = negativeColor;
1816  }
1817  else
1818  {
1819  color1 = positiveColor;
1820  color2 = Color.Lerp(negativeColor, positiveColor, (positiveEffect - negativeEffect) / positiveEffect);
1821  }
1822  }
1823 
1824  if (Character.IsDead)
1825  {
1826  color1 = Color.Lerp(color1, Color.Black, 0.75f);
1827  color2 = Color.Lerp(color2, Color.Black, 0.75f);
1828  }
1829 
1830  GameMain.GameScreen.GradientEffect.Parameters["color1"].SetValue(color1.ToVector4());
1831  GameMain.GameScreen.GradientEffect.Parameters["color2"].SetValue(color2.ToVector4());
1832  GameMain.GameScreen.GradientEffect.Parameters["midPoint"].SetValue(midPoint);
1833  GameMain.GameScreen.GradientEffect.Parameters["fadeDist"].SetValue(fadeDist);
1834 
1835  float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
1836 
1837  limbHealth.IndicatorSprite.Draw(spriteBatch,
1838  drawArea.Center.ToVector2(), Color.White,
1839  limbHealth.IndicatorSprite.Origin,
1840  0, scale);
1841 
1842  if (GameMain.DebugDraw)
1843  {
1844  Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea);
1845 
1846  GUI.DrawRectangle(spriteBatch, highlightArea, Color.Red, false);
1847  GUI.DrawRectangle(spriteBatch, drawArea, Color.Red, false);
1848  }
1849 
1850  i++;
1851  }
1852 
1853  spriteBatch.End();
1854 
1855  spriteBatch.Begin(SpriteSortMode.Deferred, Lights.CustomBlendStates.Multiplicative);
1856 
1857  if (limbIndicatorOverlay != null)
1858  {
1859  float overlayScale = Math.Min(
1860  drawArea.Width / (float)limbIndicatorOverlay.FrameSize.X,
1861  drawArea.Height / (float)limbIndicatorOverlay.FrameSize.Y);
1862 
1863  int frame;
1864  int frameCount = 17;
1865  if (limbIndicatorOverlayAnimState >= frameCount * 2) limbIndicatorOverlayAnimState = 0.0f;
1866  if (limbIndicatorOverlayAnimState < frameCount)
1867  {
1868  frame = (int)limbIndicatorOverlayAnimState;
1869  }
1870  else
1871  {
1872  frame = frameCount - (int)(limbIndicatorOverlayAnimState - (frameCount - 1));
1873  }
1874 
1875  limbIndicatorOverlay.Draw(spriteBatch, frame, drawArea.Center.ToVector2(), Color.Gray, origin: limbIndicatorOverlay.FrameSize.ToVector2() / 2, rotate: 0.0f,
1876  scale: Vector2.One * overlayScale);
1877  }
1878 
1879  if (allowHighlight)
1880  {
1881  i = 0;
1882  foreach (LimbHealth limbHealth in limbHealths)
1883  {
1884  if (limbHealth.HighlightSprite == null) { continue; }
1885 
1886  float scale = Math.Min(drawArea.Width / (float)limbHealth.HighlightSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.HighlightSprite.SourceRect.Height);
1887 
1888  int drawCount = 0;
1889  if (i == highlightedLimbIndex) { drawCount++; }
1890  if (i == selectedLimbIndex) { drawCount++; }
1891  for (int j = 0; j < drawCount; j++)
1892  {
1893  limbHealth.HighlightSprite.Draw(spriteBatch,
1894  drawArea.Center.ToVector2(), Color.White,
1895  limbHealth.HighlightSprite.Origin,
1896  0, scale);
1897  }
1898  i++;
1899  }
1900  }
1901  spriteBatch.End();
1902  spriteBatch.Begin(SpriteSortMode.Deferred, blendState: BlendState.NonPremultiplied, rasterizerState: GameMain.ScissorTestEnable);
1903 
1904  i = 0;
1905  foreach (LimbHealth limbHealth in limbHealths)
1906  {
1907  afflictionsDisplayedOnLimb.Clear();
1908  foreach (var affliction in afflictions)
1909  {
1910  if (ShouldDisplayAfflictionOnLimb(affliction, limbHealth)) { afflictionsDisplayedOnLimb.Add(affliction.Key); }
1911  }
1912 
1913  if (!afflictionsDisplayedOnLimb.Any()) { i++; continue; }
1914  if (limbHealth.IndicatorSprite == null) { continue; }
1915 
1916  float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
1917 
1918  Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea);
1919 
1920  float iconScale = 0.25f * scale;
1921  Vector2 iconPos = highlightArea.Center.ToVector2();
1922 
1923  //Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
1924  Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictionsDisplayedOnLimb, excludeBuffs: false).FirstOrDefault();
1925  if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); }
1926 
1927  if (afflictionsDisplayedOnLimb.Count() > 1)
1928  {
1929  string additionalAfflictionCount = $"+{afflictionsDisplayedOnLimb.Count() - 1}";
1930  Vector2 displace = GUIStyle.SubHeadingFont.MeasureString(additionalAfflictionCount);
1931  GUIStyle.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X * 1.1f, -displace.Y * 0.45f), Color.Black * 0.75f);
1932  GUIStyle.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X, -displace.Y * 0.5f), Color.White);
1933  }
1934 
1935  i++;
1936  }
1937 
1938  if (selectedLimbIndex > -1 && afflictionIconList.Content.CountChildren > 0)
1939  {
1940  LimbHealth limbHealth = limbHealths[selectedLimbIndex];
1941  if (limbHealth?.IndicatorSprite != null)
1942  {
1943  Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea);
1944  GUI.DrawLine(spriteBatch,
1945  new Vector2(afflictionIconList.Rect.X, afflictionIconList.Rect.Y),
1946  selectedLimbArea.Center.ToVector2(),
1947  Color.LightGray * 0.5f, width: 4);
1948  }
1949  }
1950  }
1951 
1952 
1953  private bool ShouldDisplayAfflictionOnLimb(KeyValuePair<Affliction, LimbHealth> kvp, LimbHealth limbHealth)
1954  {
1955  if (!kvp.Key.ShouldShowIcon(Character)) { return false; }
1956  if (kvp.Value == limbHealth)
1957  {
1958  return true;
1959  }
1960  else if (kvp.Value == null)
1961  {
1962  Limb indicatorLimb = Character.AnimController.GetLimb(kvp.Key.Prefab.IndicatorLimb);
1963  return indicatorLimb != null && indicatorLimb.HealthIndex == limbHealths.IndexOf(limbHealth);
1964  }
1965  return false;
1966  }
1967 
1968  private void DrawLimbAfflictionIcon(SpriteBatch spriteBatch, Affliction affliction, float iconScale, ref Vector2 iconPos)
1969  {
1970  if (!affliction.ShouldShowIcon(Character) || affliction.Prefab.Icon == null) { return; }
1971  Vector2 iconSize = affliction.Prefab.Icon.size * iconScale;
1972 
1973  float showIconThreshold = Character.Controlled?.CharacterHealth == this ? affliction.Prefab.ShowIconThreshold : affliction.Prefab.ShowIconToOthersThreshold;
1974 
1975  //afflictions that have a strength of less than 10 are faded out slightly
1976  float alpha = MathHelper.Lerp(0.3f, 1.0f,
1977  (affliction.Strength - showIconThreshold) / Math.Min(affliction.Prefab.MaxStrength - showIconThreshold, 10.0f));
1978 
1979  affliction.Prefab.Icon.Draw(spriteBatch, iconPos - iconSize / 2.0f, GetAfflictionIconColor(affliction) * alpha, 0, iconScale);
1980  iconPos += new Vector2(10.0f, 20.0f) * iconScale;
1981  }
1982 
1983  private Rectangle GetLimbHighlightArea(LimbHealth limbHealth, Rectangle drawArea)
1984  {
1985  float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
1986  return new Rectangle(
1987  (int)(drawArea.Center.X - (limbHealth.IndicatorSprite.SourceRect.Width / 2 - limbHealth.HighlightArea.X) * scale),
1988  (int)(drawArea.Center.Y - (limbHealth.IndicatorSprite.SourceRect.Height / 2 - limbHealth.HighlightArea.Y) * scale),
1989  (int)(limbHealth.HighlightArea.Width * scale),
1990  (int)(limbHealth.HighlightArea.Height * scale));
1991  }
1992 
1993  public void SetHealthBarVisibility(bool value)
1994  {
1995  healthBarHolder.Visible = value;
1996  }
1997 
1998  private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>();
1999  private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>();
2000 
2001  public void ClientRead(IReadMessage inc)
2002  {
2003  newAfflictions.Clear();
2004  newPeriodicEffects.Clear();
2005  bool newAdded = false;
2006  byte afflictionCount = inc.ReadByte();
2007  for (int i = 0; i < afflictionCount; i++)
2008  {
2009  uint afflictionID = inc.ReadUInt32();
2010  AfflictionPrefab afflictionPrefab = AfflictionPrefab.Prefabs.Find(p => p.UintIdentifier == afflictionID);
2011  if (afflictionPrefab == null)
2012  {
2013  DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found.");
2014  //read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message
2015  _ = inc.ReadRangedSingle(0.0f, 100.0f, 8);
2016  int _periodicAfflictionCount = inc.ReadByte();
2017  for (int j = 0; j < _periodicAfflictionCount; j++)
2018  {
2019  _ = inc.ReadByte();
2020  }
2021  continue;
2022  }
2023  float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8);
2024  int periodicAfflictionCount = inc.ReadByte();
2025  for (int j = 0; j < periodicAfflictionCount; j++)
2026  {
2027  float periodicAfflictionTimer = inc.ReadRangedSingle(0, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
2028  newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
2029  }
2030  newAfflictions.Add((null, afflictionPrefab, afflictionStrength));
2031  }
2032 
2033  byte limbAfflictionCount = inc.ReadByte();
2034  for (int i = 0; i < limbAfflictionCount; i++)
2035  {
2036  int limbIndex = inc.ReadRangedInteger(0, limbHealths.Count - 1);
2037  uint afflictionID = inc.ReadUInt32();
2038  AfflictionPrefab afflictionPrefab = AfflictionPrefab.Prefabs.Find(p => p.UintIdentifier == afflictionID);
2039  if (afflictionPrefab == null)
2040  {
2041  DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found.");
2042  //read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message
2043  _ = inc.ReadRangedSingle(0.0f, 100.0f, 8);
2044  int _periodicAfflictionCount = inc.ReadByte();
2045  for (int j = 0; j < _periodicAfflictionCount; j++)
2046  {
2047  _ = inc.ReadByte();
2048  }
2049  continue;
2050  }
2051  float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8);
2052  int periodicAfflictionCount = inc.ReadByte();
2053  for (int j = 0; j < periodicAfflictionCount; j++)
2054  {
2055  float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
2056  newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
2057  }
2058  newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
2059  }
2060 
2061  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
2062  {
2063  //deactivate afflictions that weren't included in the network message
2064  if (!newAfflictions.Any(a => kvp.Key.Prefab == a.afflictionPrefab && kvp.Value == a.limb))
2065  {
2066  kvp.Key.Strength = 0.0f;
2067  }
2068  }
2069 
2070  foreach (var (limb, afflictionPrefab, strength) in newAfflictions)
2071  {
2072  Affliction existingAffliction = null;
2073  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
2074  {
2075  if (kvp.Key.Prefab == afflictionPrefab && kvp.Value == limb)
2076  {
2077  existingAffliction = kvp.Key;
2078  break;
2079  }
2080  }
2081  if (existingAffliction == null)
2082  {
2083  existingAffliction = afflictionPrefab.Instantiate(strength);
2084  afflictions.Add(existingAffliction, limb);
2085  newAdded = true;
2086  }
2087  existingAffliction.SetStrength(strength);
2088  if (existingAffliction == stunAffliction)
2089  {
2090  Character.SetStun(existingAffliction.Strength, true, true);
2091  }
2092  foreach (var periodicEffect in newPeriodicEffects)
2093  {
2094  if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
2095  if (existingAffliction.Strength < periodicEffect.effect.MinStrength) { continue; }
2096  if (periodicEffect.effect.MaxStrength > 0 && existingAffliction.Strength > periodicEffect.effect.MaxStrength) { continue; }
2097  //timer has wrapped around, apply the effect
2098  if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
2099  {
2100  foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
2101  {
2102  Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb));
2103  existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
2104  }
2105  }
2106  existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
2107  }
2108  }
2109 
2110  CalculateVitality();
2111  DisplayedVitality = Vitality;
2112 
2113  if (newAdded)
2114  {
2115  MedicalClinic.OnAfflictionCountChanged(Character);
2116  }
2117  }
2118 
2119  partial void UpdateSkinTint()
2120  {
2121  FaceTint = DefaultFaceTint;
2122  BodyTint = Color.TransparentBlack;
2123 
2124  if (!Character.Params.Health.ApplyAfflictionColors) { return; }
2125 
2126  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
2127  {
2128  var affliction = kvp.Key;
2129  Color faceTint = affliction.GetFaceTint();
2130  if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
2131  Color bodyTint = affliction.GetBodyTint();
2132  if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
2133  }
2134  }
2135 
2136  partial void UpdateLimbAfflictionOverlays()
2137  {
2138  foreach (Limb limb in Character.AnimController.Limbs)
2139  {
2140  if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
2141  limb.BurnOverlayStrength = 0.0f;
2142  limb.DamageOverlayStrength = 0.0f;
2143  foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
2144  {
2145  var affliction = kvp.Key;
2146  float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
2147  if (kvp.Value == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
2148  {
2149  limb.BurnOverlayStrength += burnStrength;
2150  limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
2151  }
2152  else
2153  {
2154  limb.BurnOverlayStrength += burnStrength / 2;
2155  }
2156  }
2157  }
2158  }
2159 
2160  partial void RemoveProjSpecific()
2161  {
2162  foreach (LimbHealth limbHealth in limbHealths)
2163  {
2164  if (limbHealth.IndicatorSprite != null)
2165  {
2166  limbHealth.IndicatorSprite.Remove();
2167  limbHealth.IndicatorSprite = null;
2168  }
2169  }
2170 
2171  medUIExtra?.Remove();
2172  medUIExtra = null;
2173 
2174  Character.OnAttacked -= OnAttacked;
2175 
2176  limbIndicatorOverlay?.Remove();
2177  limbIndicatorOverlay = null;
2178 
2179  if (healthWindow != null)
2180  {
2181  healthWindow.RectTransform.Parent = null;
2182  healthWindow = null;
2183  }
2184  if (healthBarHolder != null)
2185  {
2186  healthBarHolder.RectTransform.Parent = null;
2187  healthBarHolder = null;
2188  }
2189  if (SuicideButton != null)
2190  {
2191  SuicideButton.RectTransform.Parent = null;
2192  SuicideButton = null;
2193  }
2194  if (afflictionTooltip != null)
2195  {
2196  afflictionTooltip.RectTransform.Parent = null;
2197  afflictionTooltip = null;
2198  }
2199  }
2200  }
2201 }
static Item FindMedicalItem(Inventory inventory, Identifier itemIdentifier)
virtual float Strength
Definition: Affliction.cs:31
void SetStrength(float strength)
Use this method to skip clamping and additional logic of the setters. Ideally we would keep this priv...
Definition: Affliction.cs:504
readonly AfflictionPrefab Prefab
Definition: Affliction.cs:12
PeriodicEffect applies StatusEffects to the character periodically.
AfflictionPrefab is a prefab that defines a type of affliction that can be applied to a character....
IList< PeriodicEffect > PeriodicEffects
readonly bool IsBuff
If set to true, the game will recognize this affliction as a buff. This means, among other things,...
readonly bool HideIconAfterDelay
If set to true, this affliction's icon will be hidden from the HUD after 5 seconds.
readonly Color[] IconColors
A gradient that defines which color to render this affliction's icon with, based on the affliction's ...
readonly float MaxStrength
The maximum strength this affliction can have.
readonly LocalizedString Name
static readonly PrefabCollection< AfflictionPrefab > Prefabs
readonly Sprite Icon
An icon that’s used in the UI to represent this affliction.
static Color GetAfflictionIconColor(Affliction affliction)
static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction)
void SetStun(float newStun, bool allowStunDecrease=false, bool isNetworkMessage=false)
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
bool InPressure
Can be used by status effects to check whether the characters is in a high-pressure environment
void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage=false, bool log=true)
bool IsKnockedDown
Is the character knocked down regardless whether the technical state is dead, unconcious,...
bool DisableInteract
Prevents the character from interacting with items or characters
IEnumerable< ContentXElement > Elements()
GUITextBlock TextBlock
Definition: GUIButton.cs:11
LocalizedString Text
Definition: GUIButton.cs:138
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
virtual void RemoveChild(GUIComponent child)
Definition: GUIComponent.cs:87
virtual ComponentState State
virtual void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectInflate=null)
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual float FlashTimer
bool IsParentOf(GUIComponent component, bool recursive=true)
Definition: GUIComponent.cs:75
GUIComponent GetChildByUserData(object obj)
Definition: GUIComponent.cs:66
virtual void ClearChildren()
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
Definition: GUIComponent.cs:95
virtual Rectangle Rect
virtual Color HoverColor
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
override void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
Definition: GUIListBox.cs:814
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
bool AutoHideScrollBar
Automatically hides the scroll bar when the content fits in.
Definition: GUIListBox.cs:235
override void ClearChildren()
Definition: GUIListBox.cs:1243
bool AutoScaleHorizontal
When enabled, the text is automatically scaled down to fit the textblock horizontally.
static int GraphicsWidth
Definition: GameMain.cs:162
static GameSession?? GameSession
Definition: GameMain.cs:88
static int GraphicsHeight
Definition: GameMain.cs:168
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static GameClient Client
Definition: GameMain.cs:188
static GameMain Instance
Definition: GameMain.cs:144
int FindIndex(Item item)
Find the index of the first slot the item is contained in.
void ApplyTreatment(Character user, Character character, Limb targetLimb)
The base class for components holding the different functionalities of the item
readonly Dictionary< ActionType, List< StatusEffect > > statusEffectLists
Point ScreenSpaceOffset
Screen space offset. From top left corner. In pixels.
void SetPosition(Anchor anchor, Pivot? pivot=null)
void RecalculateChildren(bool resize, bool scale=true)
Point AbsoluteOffset
Absolute in pixels but relative to the anchor point. Calculated away from the anchor point,...
void Resize(Point absoluteSize, bool resizeChildren=true)
Anchor Anchor
Does not automatically calculate children. Note also that if you change the anchor point with this pr...
RectTransform GetChild(int index)
RectTransform?? Parent
void SortChildren(Comparison< RectTransform > comparison)
Pivot Pivot
Does not automatically calculate children. Note also that if you change the pivot point with this pro...
RectTransform(Vector2 relativeSize, RectTransform parent, Anchor anchor=Anchor.TopLeft, Pivot? pivot=null, Point? minSize=null, Point? maxSize=null, ScaleBasis scaleBasis=ScaleBasis.Normal)
Vector2 RelativeOffset
Defined as portions of the parent size. Also the direction of the offset is relative,...
Point NonScaledSize
Size before scale multiplications.
bool Contains(string str, StringComparison stringComparison=StringComparison.Ordinal)
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Definition: RichString.cs:67
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
override void Draw(ISpriteBatch spriteBatch, Vector2 pos, Color color, Vector2 origin, float rotate, Vector2 scale, SpriteEffects spriteEffect=SpriteEffects.None, float? depth=default(float?))
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount=0.5f)
Highlights an UI element of some kind. Generally used in tutorials.
int ReadRangedInteger(int min, int max)
Single ReadRangedSingle(Single min, Single max, int bitCount)
GUISoundType
Definition: GUI.cs:21
CursorState
Definition: GUI.cs:40
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19