using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Barotrauma
{
    partial class CharacterHealth
    {
        private static bool toggledThisFrame;

        public class DamageOverlayPrefab : Prefab
        {
            public readonly static PrefabSelector<DamageOverlayPrefab> Prefabs = new PrefabSelector<DamageOverlayPrefab>();

            public readonly Sprite DamageOverlay;

            public DamageOverlayPrefab(ContentXElement element, AfflictionsFile file) : base(file, file.Path.Value.ToIdentifier())
            {
                DamageOverlay = new Sprite(element);
            }

            public override void Dispose()
            {
                DamageOverlay.Remove();
            }
        }

        public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
        
        private Point screenResolution;

        private float uiScale, inventoryScale;

        private Alignment alignment = Alignment.Right;
        public Alignment Alignment
        {
            get { return alignment; }
            set
            {
                if (alignment == value) { return; }
                alignment = value;
                UpdateAlignment();
            }
        }

        public GUIButton SuicideButton { get; private set; }

        // healthbars
        private GUIProgressBar healthBar;
        private GUIProgressBar healthBarShadow;
        private float healthShadowSize;
        private float healthShadowDelay;
        private float healthBarPulsateTimer;
        private float healthBarPulsatePhase;

        private float bloodParticleTimer;

        private GUIFrame healthWindow;

        private GUITextBlock deadIndicator;

        //private GUIComponent lowSkillIndicator;

        private GUIButton cprButton;

        private GUIListBox afflictionTooltip;

        private static readonly Color oxygenLowGrainColor = new Color(0.1f, 0.1f, 0.1f, 1f);

        private SpriteSheet limbIndicatorOverlay;
        private float limbIndicatorOverlayAnimState;

        private SpriteSheet medUIExtra;
        private float medUIExtraAnimState;

        private int highlightedLimbIndex = -1;
        private int selectedLimbIndex = -1;
        private LimbHealth currentDisplayedLimb;

        /// <summary>
        /// Container for the icons above the health bar
        /// </summary>
        private GUIComponent afflictionIconContainer;
        private float afflictionIconRefreshTimer;
        const float AfflictionIconRefreshInterval = 1.0f;

        private GUIButton showHiddenAfflictionsButton;

        /// <summary>
        /// Container for passive afflictions that have been hidden from afflictionIconContainer
        /// </summary>
        private GUIComponent hiddenAfflictionIconContainer;

        private GUIProgressBar healthWindowHealthBar;
        private GUIProgressBar healthWindowHealthBarShadow;

        private GUITextBlock characterName;
        private GUIListBox afflictionIconList;
        private GUILayoutGroup treatmentLayout;
        private GUIListBox recommendedTreatmentContainer;

        private LocalizedString prevHighlightedAfflictionDescription;

        /// <summary>
        /// Timer for updating visuals (limb tints and overlays) caused by the affliction 
        /// </summary>
        private float updateVisualsTimer = Rand.Range(0.0f, UpdateVisualsInterval);
        const float UpdateVisualsInterval = 0.5f;

        private float distortTimer;

        // 0-1
        private float damageIntensity;
        private readonly float damageIntensityDropdownRate = 0.1f;

        public float DamageOverlayTimer { get; private set; }

        private float updateDisplayedAfflictionsTimer;
        private const float UpdateDisplayedAfflictionsInterval = 0.5f;
        private readonly List<Affliction> currentDisplayedAfflictions = new List<Affliction>();

        public float DisplayedVitality, DisplayVitalityDelay;

        public bool MouseOnElement
        {
            get { return highlightedLimbIndex > -1; }
        }

        private static CharacterHealth openHealthWindow;
        public static CharacterHealth OpenHealthWindow
        {
            get
            {
                return openHealthWindow;
            }
            set
            {
                if (openHealthWindow == value) { return; }
                if (value != null)
                {
                    if (!value.UseHealthWindow || value.Character.DisableHealthWindow) { return; }
                }

                var prevOpenHealthWindow = openHealthWindow;

                if (prevOpenHealthWindow != null)
                {
                    prevOpenHealthWindow.highlightedLimbIndex = -1;
                }

                openHealthWindow = value;
                toggledThisFrame = true;
                if (Character.Controlled == null) { return; }

                if (value == null &&
                    Character.Controlled?.SelectedCharacter?.CharacterHealth != null &&
                    Character.Controlled.SelectedCharacter.CharacterHealth == prevOpenHealthWindow)
                {
                    Character.Controlled.DeselectCharacter();
                }

                Character.Controlled.DisableInteract = true;
                if (openHealthWindow != null)
                {
                    if (value.Character.Info == null || value.Character == Character.Controlled || Character.Controlled.HasEquippedItem("healthscanner".ToIdentifier()))
                    {
                        openHealthWindow.characterName.Text = value.Character.Name;
                    }
                    else
                    {
                        openHealthWindow.characterName.Text = value.Character.Info.DisplayName;
                        value.Character.Info.CheckDisguiseStatus(false);
                    }
                    Character.Controlled.SelectedItem = null;
                }

                HintManager.OnShowHealthInterface();
            }
        }

        public GUIButton CPRButton
        {
            get { return cprButton; }
        }

        public GUIComponent InventorySlotContainer
        {
            get;
            private set;
        }

        public float HealthBarPulsateTimer
        {
            get { return healthBarPulsateTimer; }
            set { healthBarPulsateTimer = MathHelper.Clamp(value, 0.0f, 10.0f); }
        }

        private GUIFrame healthBarHolder;

        partial void InitProjSpecific(ContentXElement element, Character character)
        {
            DisplayedVitality = MaxVitality;

            character.OnAttacked += OnAttacked;

            healthWindow = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.6f), GUI.Canvas, anchor: Anchor.Center, scaleBasis: ScaleBasis.Smallest), style: "GUIFrameListBox");

            var healthWindowVerticalLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), healthWindow.RectTransform, Anchor.Center))
            {
                Stretch = true
            };

            var nameContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), healthWindowVerticalLayout.RectTransform) { MinSize = new Point(0, 20) }, isHorizontal: true)
            {
                Stretch = true
            };
            
            new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform, Anchor.CenterLeft),
                onDraw: (spriteBatch, component) =>
                {
                    character.Info?.DrawIcon(spriteBatch, component.Rect.Center.ToVector2(), component.Rect.Size.ToVector2());
                });
            characterName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), nameContainer.RectTransform), "", textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont)
            {
                AutoScaleHorizontal = true
            };
            new GUICustomComponent(new RectTransform(new Vector2(0.2f, 1.0f), nameContainer.RectTransform),
                onDraw: (spriteBatch, component) =>
                {
                    character.Info?.DrawJobIcon(spriteBatch, component.Rect, character != Character.Controlled);
                });


            var healthBarContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.07f), healthWindowVerticalLayout.RectTransform), style: null);
            var healthBarIcon = new GUIFrame(new RectTransform(new Vector2(0.095f, 1.0f), healthBarContainer.RectTransform), style: "GUIHealthBarIcon");
            healthWindowHealthBarShadow = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight),
                barSize: 1.0f, color: GUIStyle.Green, style: "GUIHealthBar")
            {
                IsHorizontal = true
            };
            healthWindowHealthBar = new GUIProgressBar(new RectTransform(new Vector2(0.91f, 1.0f), healthBarContainer.RectTransform, Anchor.CenterRight),
                barSize: 1.0f, color: GUIStyle.Green, style: "GUIHealthBar")
            {
                IsHorizontal = true
            };

            //spacing
            new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), healthWindowVerticalLayout.RectTransform), style: null);

            var characterIndicatorArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), healthWindowVerticalLayout.RectTransform), isHorizontal: true)
            {
                Stretch = true,
                //RelativeSpacing = 0.05f
            };

            InventorySlotContainer = new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1.0f), characterIndicatorArea.RectTransform, Anchor.TopLeft, Pivot.TopRight),
                (spriteBatch, component) =>
                {
                    for (int i = 0; i < character.Inventory.Capacity; i++)
                    {
                        if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
                        if (character.Inventory.HideSlot(i)) { continue; }

                        //don't draw the item if it's being dragged out of the slot
                        bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();

                        Inventory.DrawSlot(spriteBatch, Character.Inventory, Character.Inventory.visualSlots[i], Character.Inventory.GetItemAt(i), i, drawItem, Character.Inventory.SlotTypes[i]);

                        if (medUIExtra != null) 
                        { 
                            float overlayScale = Math.Min(
                                Character.Inventory.visualSlots[i].Rect.Width / (float)medUIExtra.FrameSize.X,
                                Character.Inventory.visualSlots[i].Rect.Height / (float)medUIExtra.FrameSize.Y);

                            int frame = (int)medUIExtraAnimState;

                            medUIExtra.Draw(spriteBatch, frame, Character.Inventory.visualSlots[i].Rect.Center.ToVector2(), Color.Gray, origin: medUIExtra.FrameSize.ToVector2() / 2, rotate: 0.0f,
                                scale: Vector2.One * overlayScale);
                        }
                    }
                },
                (dt, component) =>
                {
                    if (!GameMain.Instance.Paused)
                    {
                        medUIExtraAnimState = (medUIExtraAnimState + dt * 10.0f) % 16.0f;
                    }
                });


            cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
            {
                UserData = UIHighlightAction.ElementId.CPRButton,
                OnClicked = (button, userData) =>
                {
                    Character selectedCharacter = Character.Controlled?.SelectedCharacter;
                    if (selectedCharacter == null || (!selectedCharacter.IsUnconscious && selectedCharacter.Stun <= 0.0f))
                    {
                        return false;
                    }

                    Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ?
                        AnimController.Animation.None : AnimController.Animation.CPR;

                    selectedCharacter.AnimController.ResetPullJoints();

                    if (GameMain.Client != null)
                    {
                        GameMain.Client.CreateEntityEvent(Character.Controlled, new Character.TreatmentEventData());
                    }

                    return true;
                },
                ToolTip = TextManager.Get("doctor.cprobjective"),
                IgnoreLayoutGroups = true,
                Visible = false
            };

            var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform),
                (spriteBatch, component) =>
                {
                    DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
                },
                (deltaTime, component) =>
                {
                    UpdateLimbIndicators(deltaTime, component.RectTransform.Rect);
                }
            );
            deadIndicator = new GUITextBlock(new RectTransform(new Vector2(0.9f, 0.1f), limbSelection.RectTransform, Anchor.Center),
                text: TextManager.Get("Deceased"), font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: "GUIToolTip")
            {
                Visible = false,
                CanBeFocused = false
            };
            if (deadIndicator.Text.Contains(' '))
            {
                deadIndicator.Wrap = true;
            }
            else
            {
                deadIndicator.AutoScaleHorizontal = true;
            }

            afflictionIconList = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null);

            new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform),
                TextManager.Get("SuitableTreatments"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter);

            treatmentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), healthWindowVerticalLayout.RectTransform), true)
            {
                Stretch = false
            };

            recommendedTreatmentContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), treatmentLayout.RectTransform, Anchor.Center, Pivot.Center), isHorizontal: true, style: null)
            {
                Spacing = GUI.IntScale(4),
                KeepSpaceForScrollBar = false,
                ScrollBarVisible = false,
                AutoHideScrollBar = false
            };
            new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center)
            {
                CanBeFocused = false
            };

            characterIndicatorArea.Recalculate();

            healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
            {
                HoverCursor = CursorState.Hand
            };

            healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location;
            healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
            healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;

            healthBarShadow = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight),
                barSize: 1.0f, color: Color.Green, style: "CharacterHealthBar", showFrame: false)
            {
                Visible = false
            };
            healthShadowSize = 1.0f;

            healthBar = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight),
                barSize: 1.0f, color: GUIStyle.HealthBarColorHigh, style: "CharacterHealthBar")
            {
                HoverCursor = CursorState.Hand,
                ToolTip = TextManager.GetWithVariable("hudbutton.healthinterface", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)),
                Enabled = true
            };

            afflictionIconContainer = new GUILayoutGroup(
                HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
                isHorizontal: true, childAnchor: Anchor.CenterRight)
            {
                AbsoluteSpacing = GUI.IntScale(5)
            };

            showHiddenAfflictionsButton = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: "GUIButtonCircular")
            {
                Visible = false,
                CanBeFocused = false
            };

            hiddenAfflictionIconContainer = new GUILayoutGroup(
                HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
                isHorizontal: true, childAnchor: Anchor.CenterRight)
            {
                AbsoluteSpacing = GUI.IntScale(5)
            };

            UpdateAlignment();

            SuicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.02f), GUI.Canvas, Anchor.TopCenter)
            {
                MinSize = new Point(150, 20), RelativeOffset = new Vector2(0.0f, 0.01f)
            },
                TextManager.Get("GiveInButton"), style: "GUIButtonLarge")
            {
                Visible = false,
                ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"),
                OnClicked = (button, userData) =>
                {
                    GUI.ForceMouseOn(null);
                    if (Character.Controlled != null)
                    {
                        if (GameMain.Client != null)
                        {
                            GameMain.Client.CreateEntityEvent(Character.Controlled, new Character.CharacterStatusEventData());
                        }
                        else
                        {
                            var (type, affliction) = GetCauseOfDeath();
                            Character.Controlled.Kill(type, affliction);
                            Character.Controlled = null;
                        }
                    }
                    return true;
                }
            };
            SuicideButton.TextBlock.AutoScaleHorizontal = true;

            if (element != null)
            {
                foreach (var subElement in element.Elements())
                {
                    switch (subElement.Name.ToString().ToLowerInvariant())
                    {
                        case "sprite":
                        case "meduisilhouette":
                            limbIndicatorOverlay = new SpriteSheet(subElement);
                            break;
                        case "meduiextra":
                            medUIExtra = new SpriteSheet(subElement);
                            break;
                    }
                }
            }

            healthWindowVerticalLayout.Recalculate();
        }

        private void OnAttacked(Character attacker, AttackResult attackResult)
        {
            if (Math.Abs(attackResult.Damage) < 0.01f) { return; }

            if (ShowDamageOverlay)
            {
                DamageOverlayTimer = MathHelper.Clamp(attackResult.Damage / MaxVitality, DamageOverlayTimer, 1.0f);
                float additionalIntensity = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 0.1f, attackResult.Damage / MaxVitality));
                damageIntensity = MathHelper.Clamp(damageIntensity + additionalIntensity, 0, 1);
            }
            
            if (healthShadowDelay <= 0.0f) { healthShadowDelay = 1.0f; }
            if (healthBarPulsateTimer <= 0.0f) { healthBarPulsatePhase = 0.0f; }
            healthBarPulsateTimer = 1.0f;
            DisplayVitalityDelay = 0.5f;
        }

        private void UpdateAlignment()
        {
            screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
            inventoryScale = Inventory.UIScale;
            uiScale = GUI.Scale;

            showHiddenAfflictionsButton.RectTransform.NonScaledSize = new Point(afflictionIconContainer.Rect.Height);
            //remove affliction icons so we recreate and resize them
            for (int i = afflictionIconContainer.CountChildren - 1; i >= 0; i--)
            {
                var child = afflictionIconContainer.GetChild(i);
                if (child.UserData is AfflictionPrefab)
                {
                    afflictionIconContainer.RemoveChild(child);
                }
            }

            healthBarHolder.RectTransform.AbsoluteOffset = HUDLayoutSettings.HealthBarArea.Location;
            healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
            healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;

            switch (alignment)
            {
                case Alignment.Left:
                    healthWindow.RectTransform.SetPosition(Anchor.BottomLeft);
                    healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.InventoryAreaLower.X, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
                    break;
                case Alignment.Right:
                    healthWindow.RectTransform.SetPosition(Anchor.BottomRight);
                    healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
                    break;
            }

            healthWindow.RectTransform.RecalculateChildren(false);

            Character.Inventory?.RefreshSlotPositions();
        }

        public void UpdateClientSpecific(float deltaTime)
        {
            if (GameMain.NetworkMember == null)
            {
                DisplayedVitality = Vitality;
            }
            else
            {
                DisplayVitalityDelay -= deltaTime;
                if (DisplayVitalityDelay <= 0.0f)
                {
                    DisplayedVitality = Vitality;
                }
            }
            
            if (damageIntensity > 0)
            {
                damageIntensity -= deltaTime * damageIntensityDropdownRate;
                if (damageIntensity < 0)
                {
                    damageIntensity = 0;
                }
            }
            if (DamageOverlayTimer > 0.0f)
            {
                DamageOverlayTimer -= deltaTime;
            }
        }

        private float timeUntilNextHeartbeatSound = 0.0f;
        private bool nextHeartbeatSoundIsSystole = true;
        private const string diastoleSoundTag = "heartbeatdiastole", systoleSoundTag = "heartbeatsystole";

        partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime)
        {
            if (prevOxygen > 0.0f && OxygenAmount <= 0.0f && Character.Controlled == Character)
            {
                string soundName;
                if (Character.Info != null)
                {
                    soundName = Character.Info.ReplaceVars($"drown[{Character.Info.Prefab.MenuCategoryVar}]");
                }
                else
                {
                    var charInfoPrefab = CharacterPrefab.HumanPrefab.CharacterInfoPrefab;
                    soundName = charInfoPrefab.ReplaceVars($"drown[{charInfoPrefab.MenuCategoryVar}]", charInfoPrefab.Heads.First());
                }
                SoundPlayer.PlaySound(soundName);
            }

            if (Character == Character.Controlled && !IsUnconscious && !Character.IsDead && OxygenAmount < LowOxygenThreshold)
            {
                timeUntilNextHeartbeatSound -= deltaTime;
                if (timeUntilNextHeartbeatSound < 0.0f)
                {
                    if (nextHeartbeatSoundIsSystole)
                    {
                        SoundPlayer.PlaySound(systoleSoundTag, 1.0f - (OxygenAmount / LowOxygenThreshold));
                        timeUntilNextHeartbeatSound = MathHelper.Lerp(0.18f, 0.3f, Math.Clamp(OxygenAmount / InsufficientOxygenThreshold, 0.0f, 1.0f));
                    }
                    else
                    {
                        SoundPlayer.PlaySound(diastoleSoundTag, 1.0f - (OxygenAmount / LowOxygenThreshold));
                        timeUntilNextHeartbeatSound = MathHelper.Lerp(0.3f, 0.5f, Math.Clamp(OxygenAmount / InsufficientOxygenThreshold, 0.0f, 1.0f));
                    }
                    nextHeartbeatSoundIsSystole = !nextHeartbeatSoundIsSystole;
                }
            }
        }

        partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime)
        {
            if (Character.InvisibleTimer > 0.0f) { return; }

            bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
            if (bloodParticleTimer <= 0.0f)
            {
                Limb limb = targetLimb ?? Character.AnimController.MainLimb;

                bool inWater = Character.AnimController.InWater;
                var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air;
                var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab?.DrawTarget == drawTarget || e.Prefab.ParticlePrefab?.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
                float particleMinScale = emitter?.Prefab.Properties.ScaleMin ?? 0.5f;
                float particleMaxScale = emitter?.Prefab.Properties.ScaleMax ?? 1;
                float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
                float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity);

                Vector2 velocity = Rand.Vector(affliction.Strength * 0.1f);
                if (!inWater)
                {
                    bloodParticleSize *= 2.0f;
                    velocity = limb.LinearVelocity * 100.0f;
                }

                // TODO: use the blood emitter?
                var blood = GameMain.ParticleManager.CreateParticle(
                    inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
                    limb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull);

                if (blood != null)
                {
                    blood.Size *= bloodParticleSize;
                    if (!inWater && !string.IsNullOrEmpty(Character.BloodDecalName) && Rand.Range(0.0f, 1.0f) < 0.05f)
                    {
                        blood.OnCollision += (Vector2 pos, Hull hull) =>
                        {
                            var decal = hull?.AddDecal(Character.BloodDecalName, pos, Rand.Range(1.0f, 2.0f), isNetworkEvent: true);
                            if (decal != null)
                            {
                                decal.FadeTimer = decal.LifeTime - decal.FadeOutTime * 2;
                            }
                        };
                    }
                }
                bloodParticleTimer = MathHelper.Lerp(2, 0.5f, severity);
            }
        }

        public static bool IsMouseOnHealthBar()
        {
            if (Character.Controlled?.CharacterHealth == null) { return false; }
            return Character.Controlled.CharacterHealth.healthBar.State == GUIComponent.ComponentState.Hover;
        }

        public void UpdateHUD(float deltaTime)
        {
            if (GUI.DisableHUD) { return; }
            if (openHealthWindow != null)
            {
                if (openHealthWindow != Character.Controlled?.CharacterHealth && openHealthWindow != Character.Controlled?.SelectedCharacter?.CharacterHealth)
                {
                    openHealthWindow = null;
                    return;
                }
            }

            bool forceAfflictionContainerUpdate = false;
            if (updateDisplayedAfflictionsTimer > 0.0f)
            {
                updateDisplayedAfflictionsTimer -= deltaTime;
            }
            else
            {
                forceAfflictionContainerUpdate = true;
                currentDisplayedAfflictions.Clear();
                currentDisplayedAfflictions.AddRange(GetAllAfflictions(mergeSameAfflictions: true, predicate: a => a.ShouldShowIcon(Character) && a.Prefab.Icon != null));
                currentDisplayedAfflictions.Sort((a1, a2) =>
                {
                    int dmgPerSecond = Math.Sign(a1.DamagePerSecond - a2.DamagePerSecond);
                    if (dmgPerSecond != 0) { return dmgPerSecond; }
                    return Math.Sign(GetStr(a1) - GetStr(a2));
                    static float GetStr(Affliction affliction)
                    {
                        return affliction.Strength / affliction.Prefab.MaxStrength * (affliction.Prefab.IsBuff ? 1.0f : 10.0f);
                    }
                });
                HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions);
                updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval;
            }
            
            if (healthShadowDelay > 0.0f)
            {
                healthShadowDelay -= deltaTime;
            }
            else
            {
                healthShadowSize = healthBar.BarSize > healthShadowSize ?
                    Math.Min(healthShadowSize + deltaTime, healthBar.BarSize) :
                    Math.Max(healthShadowSize - deltaTime, healthBar.BarSize);
            }
            
            float blurStrength = 0.0f;
            float distortStrength = 0.0f;
            float distortSpeed = 0.0f;
            float radialDistortStrength = 0.0f;
            float chromaticAberrationStrength = 0.0f;
            float grainStrength = 0.0f;
            Color grainColor = Color.Transparent;

            float oxygenLowStrength = 0.0f;
            if (Character.IsUnconscious)
            {
                blurStrength = 1.0f;
                distortSpeed = 1.0f;
            }
            else if (OxygenAmount < 100.0f)
            {
                oxygenLowStrength = Math.Min(1.0f - (OxygenAmount - LowOxygenThreshold) / LowOxygenThreshold, 1.0f);
                blurStrength = MathHelper.Lerp(0.5f, 1.0f, 1.0f - Vitality / MaxVitality) * oxygenLowStrength;
                distortStrength = blurStrength * oxygenLowStrength;
                distortSpeed = blurStrength + 1.0f;
                distortSpeed *= distortSpeed * distortSpeed * distortSpeed;

                grainStrength = MathHelper.Lerp(0.5f, 10.0f, oxygenLowStrength);
                grainColor = oxygenLowGrainColor;
            }

            foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
            {
                var affliction = kvp.Key;
                distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength());
                blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
                radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
                chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());

                float afflictionGrainStrength = affliction.GetScreenGrainStrength();
                if (afflictionGrainStrength > 0.0f)
                {
                    grainStrength = Math.Max(grainStrength, afflictionGrainStrength);
                    Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White;
                    grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
                }
            }

            Character.RadialDistortStrength = radialDistortStrength;
            Character.ChromaticAberrationStrength = chromaticAberrationStrength;
            Character.GrainStrength = grainStrength;
            Character.GrainColor = grainColor;
            if (blurStrength > 0.0f)
            {
                distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi;
                Character.BlurStrength = (float)(Math.Sin(distortTimer) + 1.5f) * 0.25f * blurStrength;
                Character.DistortStrength = (float)(Math.Sin(distortTimer) + 1.0f) * 0.05f * distortStrength;
            }
            else
            {
                Character.BlurStrength = 0.0f;
                Character.DistortStrength = 0.0f;
                distortTimer = 0.0f;
            }

            UpdateStatusHUD(deltaTime);

            if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null &&
                Character.Controlled.AllowInput && !toggledThisFrame)
            {
                if (openHealthWindow != null)
                {
                    OpenHealthWindow = null;
                }
                else if (Character.Controlled == Character && 
                    (Character.Controlled.FocusedCharacter?.CharacterHealth == null || !Character.Controlled.FocusedCharacter.CharacterHealth.UseHealthWindow || Character.Controlled.FocusedCharacter.DisableHealthWindow))
                {
                    OpenHealthWindow = this;
                    forceAfflictionContainerUpdate = true;
                }
            }
            else if (openHealthWindow == this)
            {
                if (HUD.CloseHUD(healthWindow.Rect))
                {
                    //emulate a Health input to get the character to deselect the item server-side
                    if (GameMain.Client != null)
                    {
                        Character.Controlled.EmulateInput(InputType.Health);
                    }
                    OpenHealthWindow = null;
                }

                foreach (GUIComponent afflictionIcon in afflictionIconList.Content.Children)
                {
                    if (afflictionIcon.UserData is not Affliction affliction) { continue; }
                    if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
                    {
                        afflictionIcon.Flash(GUIStyle.Red);
                    }
                    else if (affliction.AppliedAsSuccessfulTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
                    {
                        afflictionIcon.Flash(GUIStyle.Green);
                    }
                }

                if (GUI.MouseOn?.UserData is Affliction)
                {
                    Affliction affliction = GUI.MouseOn?.UserData as Affliction;

                    if (afflictionTooltip == null || afflictionTooltip.UserData != affliction)
                    {
                        afflictionTooltip = new GUIListBox(new RectTransform(new Vector2(0.4f, 0.2f), GUI.Canvas, scaleBasis: ScaleBasis.Smallest))
                        {
                            UserData = affliction,
                            CanBeFocused = false
                        };

                        CreateAfflictionInfoElements(afflictionTooltip.Content, affliction);

                        int height = afflictionTooltip.Content.Children.Sum(c => c.Rect.Height) + 10;
                        afflictionTooltip.RectTransform.Resize(new Point(afflictionTooltip.Rect.Width, height), true);
                        if (Alignment == Alignment.Right)
                        {
                            afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.X, GUI.MouseOn.Rect.Y);
                            afflictionTooltip.RectTransform.Pivot = Pivot.TopRight;
                        }
                        else
                        {
                            afflictionTooltip.RectTransform.AbsoluteOffset = new Point(GUI.MouseOn.Rect.Right, GUI.MouseOn.Rect.Y);
                            afflictionTooltip.RectTransform.Anchor = Anchor.TopLeft;
                        }

                        afflictionTooltip.ScrollBarVisible = false;

                        var labelContainer = afflictionTooltip.Content.GetChildByUserData("label");

                        labelContainer.RectTransform.Resize(new Point(labelContainer.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f)));
                    }
                }
                else
                {
                    afflictionTooltip = null;
                }
            }
            toggledThisFrame = false;

            if (OpenHealthWindow == this)
            {
                var highlightedLimb = highlightedLimbIndex < 0 ? null : limbHealths[highlightedLimbIndex];
                if (highlightedLimbIndex < 0 && selectedLimbIndex < 0)
                {
                    // If no limb is selected or highlighted, select the one with the most critical afflictions.
                    var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault();
                    if (affliction != null && (affliction.DamagePerSecond > 0 || affliction.Strength > 0))
                    {
                        var limbHealth = GetMatchingLimbHealth(affliction);
                        if (limbHealth != null)
                        {
                            selectedLimbIndex = limbHealths.IndexOf(limbHealth);
                        }
                    }
                    else
                    {
                        // If no affliction is critical, select the limb which has most damage.
                        var limbHealth = limbHealths.OrderByDescending(l => GetTotalDamage(l)).FirstOrDefault();
                        selectedLimbIndex = limbHealths.IndexOf(limbHealth);
                    }
                }
                LimbHealth selectedLimb = selectedLimbIndex < 0 ? highlightedLimb : limbHealths[selectedLimbIndex];
                if (selectedLimb != currentDisplayedLimb || forceAfflictionContainerUpdate)
                {
                    UpdateAfflictionContainer(selectedLimb);
                    currentDisplayedLimb = selectedLimb;
                }

                UpdateAfflictionInfos(displayedAfflictions.Select(d => d.affliction));

                foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children)
                {
                    var treatmentButton = component.GetChild<GUIButton>();
                    if (treatmentButton?.UserData is not ItemPrefab itemPrefab) { continue; }
                    var matchingItem = AIObjectiveRescue.FindMedicalItem(Character.Controlled.Inventory, itemPrefab.Identifier);
                    treatmentButton.Enabled = matchingItem != null;  
                    if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover)
                    {
                        //highlight the slot the treatment item is in
                        var rootContainer = matchingItem.RootContainer ?? matchingItem;
                        var index = Character.Controlled.Inventory.FindIndex(rootContainer);
                        if (Character.Controlled.Inventory.visualSlots != null && index > -1 && index < Character.Controlled.Inventory.visualSlots.Length &&
                            Character.Controlled.Inventory.visualSlots[index].HighlightTimer <= 0.0f)
                        {
                            Character.Controlled.Inventory.visualSlots[index].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f);
                        }
                    }
                    if (matchingItem != null && !treatmentButton.ToolTip.IsNullOrEmpty()) { continue; }
                    treatmentButton.ToolTip = RichString.Rich($"‖color:255,255,255,255‖{itemPrefab.Name}‖color:end‖" + '\n' + itemPrefab.Description);
                    if (treatmentButton.Enabled)
                    {
                        treatmentButton.ToolTip =
                            RichString.Rich(
                                $"‖color:gui.green‖[{PlayerInput.PrimaryMouseLabel}] "
                                + $"{TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n'
                                + treatmentButton.ToolTip.NestedStr);
                    }
                    foreach (GUIComponent child in treatmentButton.Children)
                    {
                        child.Enabled = treatmentButton.Enabled;
                    }
                }
            }

            if (Character.IsDead)
            {
                healthBar.Color = healthWindowHealthBar.Color = Color.Black;
                healthBar.BarSize = healthWindowHealthBar.BarSize = 1.0f;
            }
            else
            {
                healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(DisplayedVitality / MaxVitality, GUIStyle.HealthBarColorLow, GUIStyle.HealthBarColorMedium, GUIStyle.HealthBarColorHigh);
                healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f;
                healthBar.BarSize = healthWindowHealthBar.BarSize = 
                    (DisplayedVitality > 0.0f) ? 
                    (MaxVitality > 0.0f ? DisplayedVitality / MaxVitality : 0.0f) : 
                    (Math.Abs(MinVitality) > 0.0f ? 1.0f - DisplayedVitality / MinVitality : 0.0f);

                if (healthBarPulsateTimer > 0.0f)
                {
                    //0-1
                    float pulsateAmount = (float)(Math.Sin(healthBarPulsatePhase) + 1.0f) / 2.0f;

                    healthBar.RectTransform.LocalScale = healthBarShadow.RectTransform.LocalScale = new Vector2(1.0f, (1.0f + pulsateAmount * healthBarPulsateTimer * 0.5f));
                    healthBarPulsatePhase += deltaTime * 5.0f;
                    healthBarPulsateTimer -= deltaTime;
                }
                else
                {
                    healthBar.RectTransform.LocalScale = Vector2.One;
                }
            }

            if (OpenHealthWindow == this)
            {
                if (Character == Character.Controlled && !Character.AllowInput)
                {
                    openHealthWindow = null;
                }

                if (Inventory.DraggingItems.Any())
                {
                    if (highlightedLimbIndex > -1)
                    {
                        selectedLimbIndex = highlightedLimbIndex;
                    }
                }
            }
            else
            {
                if (openHealthWindow != null && Character != Character.Controlled && Character != Character.Controlled?.SelectedCharacter)
                {
                    openHealthWindow = null;
                }
                highlightedLimbIndex = -1;
            }

            healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
            if (Character.AllowInput && UseHealthWindow && !Character.DisableHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
                (GUI.IsMouseOn(healthBar) || GUI.MouseOn?.UserData is AfflictionPrefab) && Inventory.SelectedSlot == null)
            {
                healthBar.State = GUIComponent.ComponentState.Hover;
                if (PlayerInput.PrimaryMouseButtonClicked())
                {
                    OpenHealthWindow = openHealthWindow == this ? null : this;
                }
            }
            else
            {
                healthBar.State = GUIComponent.ComponentState.None;
            }

            SuicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated;

            if (GameMain.GameSession?.Campaign is { } campaign)
            {
                RectTransform endRoundButton = campaign?.EndRoundButton?.RectTransform;
                RectTransform readyCheckButton = campaign?.ReadyCheckButton?.RectTransform;
                if (endRoundButton != null)
                {
                    if (SuicideButton.Visible)
                    {
                        Point offset = new Point(0, SuicideButton.Rect.Height);
                        endRoundButton.ScreenSpaceOffset = offset;
                    }
                    else if (endRoundButton.ScreenSpaceOffset != Point.Zero)
                    {
                        endRoundButton.ScreenSpaceOffset = Point.Zero;
                    }
                    if (readyCheckButton != null)
                    {
                        readyCheckButton.ScreenSpaceOffset = endRoundButton.ScreenSpaceOffset;
                    }
                }
            }

            cprButton.Visible =
                Character == Character.Controlled?.SelectedCharacter
                && !Character.IsDead
                && Character.IsKnockedDown
                && openHealthWindow == this;
            cprButton.Selected =  
                Character.Controlled != null && 
                Character == Character.Controlled.SelectedCharacter && 
                Character.Controlled.AnimController.Anim == AnimController.Animation.CPR;

            deadIndicator.Visible = Character.IsDead;
        }

        public void AddToGUIUpdateList()
        {
            if (GUI.DisableHUD) { return; }
            if (OpenHealthWindow == this)
            {
                healthWindow.AddToGUIUpdateList();
                afflictionTooltip?.AddToGUIUpdateList();
            }
            else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen)
            {
                healthBarHolder.AddToGUIUpdateList();
                afflictionIconContainer.AddToGUIUpdateList();
                if (hiddenAfflictionIconContainer.Visible)
                {
                    hiddenAfflictionIconContainer.AddToGUIUpdateList();
                }
            }
            if (SuicideButton.Visible && Character == Character.Controlled)
            {
                SuicideButton.AddToGUIUpdateList();
            }
            if (cprButton != null && cprButton.Visible)
            {
                cprButton.AddToGUIUpdateList();
            }
        }

        public void DrawHUD(SpriteBatch spriteBatch)
        {
            if (GUI.DisableHUD || Character.Removed) { return; }
            if (GameMain.GraphicsWidth != screenResolution.X ||
                GameMain.GraphicsHeight != screenResolution.Y ||
                Math.Abs(inventoryScale - Inventory.UIScale) > 0.01f ||
                Math.Abs(uiScale - GUI.Scale) > 0.01f)
            {
                UpdateAlignment();
            }

            foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
            {
                var affliction = kvp.Key;
                if (affliction.Prefab is AfflictionPrefab { AfflictionOverlay: not null } afflictionPrefab)
                {
                    Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
                    if (afflictionPrefab.AfflictionOverlay is SpriteSheet spriteSheet)
                    {
                        spriteSheet.Draw(spriteBatch,
                            spriteIndex: spriteSheet.GetAnimatedSpriteIndex(afflictionPrefab.AfflictionOverlayAnimSpeed),
                            pos: Vector2.Zero,
                            color: Color.White * affliction.GetAfflictionOverlayMultiplier(),
                            origin: Vector2.Zero,
                            rotate: 0,
                            scale: screenSize / spriteSheet.FrameSize.ToVector2());
                    }
                    else if (afflictionPrefab.AfflictionOverlay is Sprite sprite)
                    {
                        sprite.Draw(spriteBatch,
                            pos: Vector2.Zero,
                            color: Color.White * affliction.GetAfflictionOverlayMultiplier(),
                            origin: Vector2.Zero,
                            rotate: 0,
                            scale: screenSize / sprite.size);                        
                    }
                }

                var activeEffect = affliction.GetActiveEffect();
                if (activeEffect is { ThermalOverlayRange: > 0.0f })
                {
                    StatusHUD.DrawThermalOverlay(spriteBatch, Character, Character, activeEffect.ThermalOverlayColor, activeEffect.ThermalOverlayRange, effectState: (float)Timing.TotalTimeUnpaused, showDeadCharacters: false);
                }
            }

            float damageOverlayAlpha = DamageOverlayTimer;
            if (Vitality < MaxVitality * 0.1f)
            {
                damageOverlayAlpha = Math.Max(1.0f - (Vitality / UnmodifiedMaxVitality * 10.0f), damageOverlayAlpha);
            }
            else
            {
                float pulsateAmount = (float)(Math.Sin(healthBarPulsatePhase) + 1.0f) / 2.0f;
                damageOverlayAlpha = pulsateAmount * healthBarPulsateTimer * damageIntensity;
            }

            if (damageOverlayAlpha > 0.0f)
            {
                DamageOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * damageOverlayAlpha, Vector2.Zero, 0.0f,
                    new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
            }

            if (Character.Inventory != null)
            {
                healthBar.RectTransform.ScreenSpaceOffset = healthBarShadow.RectTransform.ScreenSpaceOffset = Point.Zero;
            }

            if (healthBarHolder != null)
            {
                // If manning a turret the portrait doesn't get rendered so we push the health bar to remove the empty gap
                healthBarHolder.RectTransform.ScreenSpaceOffset = Character.ShouldLockHud() ? new Point(0, HUDLayoutSettings.PortraitArea.Height) : Point.Zero;
            }
        }

        //private (Affliction Affliction, LocalizedString NameToolTip)? highlightedAfflictionIcon = null;

        private readonly List<Affliction> statusIcons = new List<Affliction>();
        private readonly Dictionary<AfflictionPrefab, float> statusIconVisibleTime = new Dictionary<AfflictionPrefab, float>();
        private const float HideStatusIconDelay = 5.0f;

        public void UpdateStatusHUD(float deltaTime)
        {
            if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
            {
                statusIcons.Clear();
                if (Character.InPressure)
                {
                    statusIcons.Add(pressureAffliction);
                }
                if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold)
                {
                    statusIcons.Add(oxygenLowAffliction);
                }
                
                foreach (Affliction affliction in currentDisplayedAfflictions)
                {
                    statusIcons.Add(affliction);
                }

                int spacing = GUI.IntScale(10);
                if (Character.ShouldLockHud())
                {
                    // Push the icons down since the portrait doesn't get rendered
                    afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, HUDLayoutSettings.PortraitArea.Height);
                    hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing + HUDLayoutSettings.PortraitArea.Height);
                }
                else
                {
                    afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, 0);
                    hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing);
                }
                //remove affliction icons for afflictions that no longer exist

                RemoveNonExistentIcons(afflictionIconContainer);
                RemoveNonExistentIcons(hiddenAfflictionIconContainer);
                void RemoveNonExistentIcons(GUIComponent container)
                {
                    for (int i = container.CountChildren - 1; i >= 0; i--)
                    {
                        var child = container.GetChild(i);
                        if (child.UserData is not AfflictionPrefab afflictionPrefab) { continue; }
                        if (!statusIcons.Any(s => s.Prefab == afflictionPrefab))
                        {
                            container.RemoveChild(child);
                            statusIconVisibleTime.Remove(afflictionPrefab);
                        }
                    }
                }

                foreach (var statusIcon in statusIcons)
                {
                    Affliction affliction = statusIcon;
                    AfflictionPrefab afflictionPrefab = affliction.Prefab;

                    if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
                    statusIconVisibleTime[afflictionPrefab] += deltaTime;

                    Color color = GetAfflictionIconColor(afflictionPrefab, affliction);
                    var matchingIcon = 
                        afflictionIconContainer.GetChildByUserData(afflictionPrefab) ?? 
                        hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
                    if (matchingIcon == null)
                    {
                        matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
                        {
                            UserData = afflictionPrefab,
                            ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖",
                            CanBeSelected = false
                        };
                        new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
                        {
                            CanBeFocused = false                            
                        };
                    }
                    if (afflictionPrefab.HideIconAfterDelay && statusIconVisibleTime[afflictionPrefab] > HideStatusIconDelay)
                    {
                        matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
                    }
                    else
                    {
                        if (affliction.Prefab.ShowDescriptionInTooltip)
                        {
                            matchingIcon.ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖" + "\n" + affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.Self);
                        }
                        if (affliction == pressureAffliction)
                        {
                            matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
                        }
                        else if (affliction == pressureAffliction)
                        {
                            matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
                        }
                        matchingIcon.ToolTip = RichString.Rich(matchingIcon.ToolTip);

                    }
                    var image = matchingIcon.GetChild<GUIImage>();
                    image.Color = color;
                    image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);

                    if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)
                    {
                        matchingIcon.Flash(useCircularFlash: true, flashDuration: 1.5f, flashRectInflate: Vector2.One * 15.0f * GUI.Scale);
                        image.Pulsate(Vector2.One, Vector2.One * 1.2f, 1.0f);
                    }
                }

                afflictionIconRefreshTimer -= deltaTime;
                if (afflictionIconRefreshTimer <= 0.0f)
                {
                    afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
                    {
                        if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
                        if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
                        var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
                        var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
                        return index1.CompareTo(index2);
                    });
                    (afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
                    afflictionIconRefreshTimer = AfflictionIconRefreshInterval;
                }

                Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
                foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
                {
                    hiddenAfflictionHoverArea = Rectangle.Union(hiddenAfflictionHoverArea, child.Rect);
                }

                afflictionIconContainer.Visible = true;
                hiddenAfflictionIconContainer.Visible = 
                    showHiddenAfflictionsButton.Rect.Contains(PlayerInput.MousePosition) ||
                    (hiddenAfflictionIconContainer.Visible && hiddenAfflictionHoverArea.Contains(PlayerInput.MousePosition));
                showHiddenAfflictionsButton.Visible = hiddenAfflictionIconContainer.CountChildren > 0;
                showHiddenAfflictionsButton.IgnoreLayoutGroups = !showHiddenAfflictionsButton.Visible;
                showHiddenAfflictionsButton.Text = $"+{hiddenAfflictionIconContainer.CountChildren}";

                if (Vitality > 0.0f)
                {
                    float currHealth = healthBar.BarSize;
                    Color prevColor = healthBar.Color;
                    healthBarShadow.BarSize = healthShadowSize;
                    healthBarShadow.Color = Color.Lerp(GUIStyle.Red, Color.Black, 0.5f);
                    healthBarShadow.Visible = true;
                    healthBar.BarSize = currHealth;
                    healthBar.Color = prevColor;
                }
                else
                {
                    healthBarShadow.Visible = false;
                }
            }
            else
            {
                afflictionIconContainer.Visible = hiddenAfflictionIconContainer.Visible = false;
                if (Vitality > 0.0f)
                {
                    float currHealth = healthWindowHealthBar.BarSize;
                    Color prevColor = healthWindowHealthBar.Color;
                    healthWindowHealthBarShadow.BarSize = healthShadowSize;
                    healthWindowHealthBarShadow.Color = GUIStyle.Red;
                    healthWindowHealthBarShadow.Visible = true;
                    healthWindowHealthBar.BarSize = currHealth;
                    healthWindowHealthBar.Color = prevColor;
                }
                else
                {
                    healthWindowHealthBarShadow.Visible = false;
                }
            }
        }

        public static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction)
        {
            return GetAfflictionIconColor(prefab, affliction.Strength);
        }

        public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
        {
            //use sqrt to make the color change rapidly when strength is low
            //(low strength is where seeing the severity of the affliction makes more difference - at high strengths the character is already unconscious or dead)
            float colorT = MathF.Sqrt(afflictionStrength / prefab.MaxStrength);
            // No specific colors, use generic
            if (prefab.IconColors == null)
            {
                if (prefab.IsBuff)
                {
                    return ToolBox.GradientLerp(colorT, GUIStyle.BuffColorLow, GUIStyle.BuffColorMedium, GUIStyle.BuffColorHigh);
                }

                return ToolBox.GradientLerp(colorT, GUIStyle.DebuffColorLow, GUIStyle.DebuffColorMedium, GUIStyle.DebuffColorHigh);
            }
            return ToolBox.GradientLerp(colorT, prefab.IconColors);
        }

        public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);

        private readonly List<(Affliction affliction, float strength)> displayedAfflictions = new List<(Affliction affliction, float strength)>();

        private void UpdateAfflictionContainer(LimbHealth selectedLimb)
        {
            if (selectedLimb == null)
            {
                afflictionIconList.Content.ClearChildren();
                return;
            }

            if (afflictionsDirty() || selectedLimb != currentDisplayedLimb)
            {
                var currentAfflictions = afflictions.Where(a => ShouldDisplayAfflictionOnLimb(a, selectedLimb)).Select(a => a.Key);
                CreateAfflictionInfos(currentAfflictions);
                CreateRecommendedTreatments();
            }
            //update recommended treatments if the strength of some displayed affliction has changed by > 1
            else if (displayedAfflictions.Any(d => Math.Abs(d.strength - d.affliction.Strength) > 1.0f))
            {
                CreateRecommendedTreatments();
            }

            bool afflictionsDirty()
            {
                //not displaying one of the current afflictions -> dirty
                foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
                {
                    if (!ShouldDisplayAfflictionOnLimb(kvp, selectedLimb)) { continue; }
                    if (!displayedAfflictions.Any(d => d.affliction == kvp.Key)) { return true; }
                }
                //displaying an affliction we no longer have -> dirty
                foreach ((Affliction affliction, float strength) in displayedAfflictions)
                {
                    if (afflictions.None(a => a.Key == affliction && a.Key.ShouldShowIcon(Character))) { return true; }
                }
                return false;
            }
        }

        private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
        {
            afflictionIconList.ClearChildren();
            displayedAfflictions.Clear();

            Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault();
            GUIButton buttonToSelect = null;

            foreach (Affliction affliction in afflictions)
            {
                displayedAfflictions.Add((affliction, affliction.Strength));

                var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconList.Content.RectTransform), style: "ListBoxElement")
                {
                    UserData = affliction,
                    OnClicked = SelectAffliction
                };

                new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: "GUIFrameListBox") { CanBeFocused = false };

                var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), frame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
                {
                    Stretch = true,
                    CanBeFocused = false
                };

                var progressbarBg = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.18f), content.RectTransform), 0.0f, GUIStyle.Green, style: "GUIAfflictionBar")
                {
                    UserData = "afflictionstrengthprediction",
                    CanBeFocused = false
                };
                new GUIProgressBar(new RectTransform(Vector2.One, progressbarBg.RectTransform), 0.0f, Color.Transparent, showFrame: false, style: "GUIAfflictionBar")
                {
                    UserData = "afflictionstrength",
                    CanBeFocused = false
                };

                //spacing
                new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), style: null) { CanBeFocused = false };

                if (affliction == mostSevereAffliction)
                {
                    buttonToSelect = frame;
                }

                var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, content.RectTransform), affliction.Prefab.Icon, scaleToFit: true)
                {
                    Color = GetAfflictionIconColor(affliction),
                    CanBeFocused = false
                };
                afflictionIcon.PressedColor = afflictionIcon.Color;
                afflictionIcon.HoverColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.6f);
                afflictionIcon.SelectedColor = Color.Lerp(afflictionIcon.Color, Color.White, 0.5f);
                
                var nameText = new GUITextBlock(new RectTransform(new Vector2(1.1f, 0.0f), content.RectTransform),
                    affliction.Prefab.Name, font: GUIStyle.SmallFont, textAlignment: Alignment.BottomCenter)
                {
                    CanBeFocused = false
                };
                nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width);
                nameText.RectTransform.MinSize = new Point(0, (int)(nameText.TextSize.Y));
                nameText.RectTransform.SizeChanged += () =>
                {
                    nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width);
                };

                content.Recalculate();
            }

            buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData);
            afflictionIconList.RecalculateChildren();
        }

        private void CreateRecommendedTreatments()
        {
            ItemPrefab prevHighlightedItem = null;
            if (GUI.MouseOn?.UserData is ItemPrefab && recommendedTreatmentContainer.Content.IsParentOf(GUI.MouseOn))
            {
                prevHighlightedItem = (ItemPrefab)GUI.MouseOn.UserData;
            }

            recommendedTreatmentContainer.Content.ClearChildren();

            float characterSkillLevel = Character.Controlled == null ? 0.0f : Character.Controlled.GetSkillLevel(Tags.MedicalSkill);

            //key = item identifier
            //float = suitability
            Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>();
            GetSuitableTreatments(treatmentSuitability,
                user: Character.Controlled,
                ignoreHiddenAfflictions: true,
                limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex),
                checkTreatmentSuggestionThreshold: true,
                checkTreatmentThreshold: false);

            foreach (Identifier treatment in treatmentSuitability.Keys.ToList())
            {
                //prefer suggestions for items the player has
                if (Character.Controlled.Inventory.FindItemByIdentifier(treatment, recursive: true) != null)
                {
                    treatmentSuitability[treatment] *= 10.0f;
                }
            }

            if (!treatmentSuitability.Any())
            {
                new GUITextBlock(new RectTransform(Vector2.One, recommendedTreatmentContainer.Content.RectTransform), TextManager.Get("none"), textAlignment: Alignment.Center)
                {
                    CanBeFocused = false
                };
                recommendedTreatmentContainer.ScrollBarVisible = false;
                recommendedTreatmentContainer.AutoHideScrollBar = false;
            }
            else
            {
                recommendedTreatmentContainer.ScrollBarVisible = true;
                recommendedTreatmentContainer.AutoHideScrollBar = true;
            }

            List<KeyValuePair<Identifier, float>> treatmentSuitabilities = treatmentSuitability.OrderByDescending(t => t.Value).ToList();

            int count = 0;
            foreach (KeyValuePair<Identifier, float> treatment in treatmentSuitabilities)
            {
                //don't list negative treatments
                if (treatment.Value < 0) { continue; }

                count++;
                if (count > 5) { break; }
                if (MapEntityPrefab.FindByIdentifier(treatment.Key) is not ItemPrefab item) { continue; }

                var itemSlot = new GUIFrame(new RectTransform(new Vector2(1.0f / 6.0f, 1.0f), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopLeft),
                    style: null)
                {
                    UserData = item
                };

                var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader")
                {                    
                    UserData = item,
                    DisabledColor = Color.White * 0.1f,
                    PlaySoundOnSelect = false,
                    OnClicked = (btn, userdata) =>
                    {
                        if (userdata is not ItemPrefab itemPrefab) { return false; }
                        var item = AIObjectiveRescue.FindMedicalItem(Character.Controlled.Inventory, it => it.Prefab == itemPrefab);
                        if (item == null) { return false; }
                        Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex);
                        item.ApplyTreatment(Character.Controlled, Character, targetLimb);
                        SoundPlayer.PlayUISound(GUISoundType.Select);
                        return true;
                    }
                };

                new GUIImage(new RectTransform(Vector2.One, innerFrame.RectTransform, Anchor.Center), style: "TalentBackgroundGlow")
                {
                    CanBeFocused = false,
                    Color = GUIStyle.Green,
                    HoverColor = Color.White,
                    PressedColor = Color.DarkGray,
                    SelectedColor = Color.Transparent,
                    DisabledColor = Color.Transparent
                };

                Sprite itemSprite = item.InventoryIcon ?? item.Sprite;
                Color itemColor = itemSprite == item.Sprite ? item.SpriteColor : item.InventoryIconColor;
                var itemIcon = new GUIImage(new RectTransform(new Vector2(0.8f, 0.8f), innerFrame.RectTransform, Anchor.Center),
                    itemSprite, scaleToFit: true)
                {
                    CanBeFocused = false,
                    Color = itemColor * 0.9f,
                    HoverColor = itemColor,
                    SelectedColor = itemColor,
                    DisabledColor = itemColor * 0.8f
                };

                if (item == prevHighlightedItem)
                {
                    innerFrame.State = GUIComponent.ComponentState.Hover;
                    innerFrame.Children.ForEach(c => c.State = GUIComponent.ComponentState.Hover);
                }
            }

            recommendedTreatmentContainer.RecalculateChildren();

            afflictionIconList.Content.RectTransform.SortChildren((r1, r2) =>
            {
                var first = r1.GUIComponent.UserData as Affliction;
                var second = r2.GUIComponent.UserData as Affliction;
                int dmgPerSecond = Math.Sign(second.DamagePerSecond - first.DamagePerSecond);
                return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(second.Strength - first.Strength);
            });

            if (count > 0)
            {
                var treatmentIconSize = recommendedTreatmentContainer.Content.Children.Sum(c => c.Rect.Width + recommendedTreatmentContainer.Spacing);
                if (treatmentIconSize < recommendedTreatmentContainer.Content.Rect.Width)
                {
                    var spacing = new GUIFrame(new RectTransform(new Point((recommendedTreatmentContainer.Content.Rect.Width - treatmentIconSize) / 2, 0), recommendedTreatmentContainer.Content.RectTransform), style: null)
                    {
                        CanBeFocused = false
                    };
                    spacing.RectTransform.SetAsFirstChild();
                }
            }
        }

        private void CreateAfflictionInfoElements(GUIComponent parent, Affliction affliction)
        {
            var labelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), parent.RectTransform), isHorizontal: true)
            {
                Stretch = true,
                AbsoluteSpacing = 10,
                UserData = "label",
                CanBeFocused = false
            };

            var afflictionName = new GUITextBlock(new RectTransform(new Vector2(0.65f, 1.0f), labelContainer.RectTransform), affliction.Prefab.Name, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont)
            {
                CanBeFocused = false,
                AutoScaleHorizontal = true
            };
            var afflictionStrength = new GUITextBlock(new RectTransform(new Vector2(0.35f, 0.6f), labelContainer.RectTransform), "", textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont)
            {
                UserData = "strength",
                CanBeFocused = false
            };
            var vitality = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), labelContainer.RectTransform, Anchor.BottomRight), "", textAlignment: Alignment.BottomRight)
            {
                Padding = afflictionStrength.Padding,
                IgnoreLayoutGroups = true,
                UserData = "vitality",
                CanBeFocused = false
            };

            prevHighlightedAfflictionDescription = affliction.Prefab.GetDescription(
                    affliction.Strength,
                    Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter);
            var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
                RichString.Rich(prevHighlightedAfflictionDescription), 
                textAlignment: Alignment.TopLeft, wrap: true)
            {
                UserData = "description",
                CanBeFocused = false
            };

            if (description.Font.MeasureString(description.WrappedText).Y > description.Rect.Height)
            {
                description.Font = GUIStyle.SmallFont;
            }

            Point nameDims = new Point(afflictionName.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f));

            afflictionStrength.Text = affliction.GetStrengthText();

            Vector2 strengthDims = GUIStyle.SubHeadingFont.MeasureString(afflictionStrength.Text);

            labelContainer.RectTransform.Resize(new Point(labelContainer.Rect.Width, nameDims.Y));
            afflictionName.RectTransform.Resize(new Point((int)(labelContainer.Rect.Width - strengthDims.X * 0.99f), nameDims.Y));
            afflictionStrength.RectTransform.Resize(new Point(labelContainer.Rect.Width - afflictionName.Rect.Width, nameDims.Y));
            
            afflictionStrength.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
                affliction.Strength / affliction.Prefab.MaxStrength);

            description.RectTransform.Resize(new Point(description.Rect.Width, (int)(description.TextSize.Y + 10)));

            int vitalityDecrease = (int)GetVitalityDecreaseWithVitalityMultipliers(affliction);
            if (vitalityDecrease == 0)
            {
                vitality.Visible = false;
            }
            else
            {
                vitality.Visible = true;
                vitality.Text = TextManager.Get("Vitality") + " -" + vitalityDecrease;
                vitality.TextColor = vitalityDecrease <= 0 ? GUIStyle.Green :
                Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
            }

            vitality.AutoDraw = true;
        }

        private bool SelectAffliction(GUIButton button, object userData)
        {
            bool selected = button.Selected;
            foreach (var child in afflictionIconList.Content.Children)
            {
                if (child is GUIButton btn)
                {
                    btn.Selected = btn == button && !selected;
                }
            }

            return false;
        }

        private void UpdateAfflictionInfos(IEnumerable<Affliction> afflictions)
        {
            var potentialTreatment = Inventory.DraggingItems.FirstOrDefault();
            if (potentialTreatment == null && GUI.MouseOn?.UserData is ItemPrefab itemPrefab)
            {
                potentialTreatment = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true);
            }
            potentialTreatment ??= Inventory.SelectedSlot?.Item;

            foreach (Affliction affliction in afflictions)
            {
                float afflictionVitalityDecrease = GetVitalityDecreaseWithVitalityMultipliers(affliction);
                Color afflictionEffectColor = Color.White;
                if (afflictionVitalityDecrease > 0.0f)
                {
                    afflictionEffectColor = GUIStyle.Red;
                }
                else if (afflictionVitalityDecrease < 0.0f)
                {
                    afflictionEffectColor = GUIStyle.Green;
                }

                var child = afflictionIconList.Content.FindChild(affliction);

                var afflictionStrengthPredictionBar = child.GetChild<GUILayoutGroup>().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar;
                afflictionStrengthPredictionBar.BarSize = 0.0f;
                var afflictionStrengthBar = afflictionStrengthPredictionBar.GetChildByUserData("afflictionstrength") as GUIProgressBar;
                afflictionStrengthBar.BarSize = affliction.Strength / affliction.Prefab.MaxStrength;
                afflictionStrengthBar.Color = afflictionEffectColor;

                float afflictionStrengthPrediction = GetAfflictionStrengthPrediction(potentialTreatment, affliction);
                if (!MathUtils.NearlyEqual(afflictionStrengthPrediction, affliction.Strength))
                {
                    float t = (float)Math.Max(0.5f, (Math.Sin(Timing.TotalTime * 5) + 1.0f) / 2.0f);
                    if (afflictionStrengthPrediction < affliction.Strength)
                    {
                        afflictionStrengthBar.Color =  afflictionEffectColor;
                        afflictionStrengthPredictionBar.Color = GUIStyle.Blue * t;
                        afflictionStrengthPredictionBar.BarSize = afflictionStrengthBar.BarSize;
                        afflictionStrengthBar.BarSize = afflictionStrengthPrediction / affliction.Prefab.MaxStrength;
                    }
                    else
                    {
                        afflictionStrengthPredictionBar.Color = Color.Red * t;
                        afflictionStrengthPredictionBar.BarSize = afflictionStrengthPrediction / affliction.Prefab.MaxStrength;
                    }
                }

                if (!affliction.Prefab.ShowBarInHealthMenu)
                {
                    afflictionStrengthBar.BarSize = 1f;
                }

                if (afflictionTooltip != null && afflictionTooltip.UserData == affliction)
                {
                    UpdateAfflictionInfo(afflictionTooltip.Content, affliction);
                }
            }
        }

        private float GetAfflictionStrengthPrediction(Item item, Affliction affliction)
        {
            float strength = affliction.Strength;
            if (item == null) { return strength; }

            foreach (ItemComponent ic in item.Components)
            {
                if (ic.statusEffectLists == null) { continue; }
                if (!ic.statusEffectLists.TryGetValue(ActionType.OnUse, out List<StatusEffect> statusEffects)) { continue; }
                foreach (StatusEffect effect in statusEffects)
                {
                    foreach (var reduceAffliction in effect.ReduceAffliction)
                    {
                        if (reduceAffliction.AfflictionIdentifier != affliction.Identifier && reduceAffliction.AfflictionIdentifier != affliction.Prefab.AfflictionType) { continue; }
                        strength -= reduceAffliction.ReduceAmount * (effect.Duration > 0 ? effect.Duration : 1.0f);
                    }
                    foreach (var addAffliction in effect.Afflictions)
                    {
                        if (addAffliction.Prefab != affliction.Prefab) { continue; }
                        strength += addAffliction.Strength * (effect.Duration > 0 ? effect.Duration : 1.0f);
                    }
                }
            }
            return strength;
        }

        private void UpdateAfflictionInfo(GUIComponent parent, Affliction affliction)
        {
            var labelContainer = parent.GetChildByUserData("label");

            var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock;
            strengthText.Text = affliction.GetStrengthText();

            strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
                affliction.Strength / affliction.Prefab.MaxStrength);

            var vitalityText = labelContainer.GetChildByUserData("vitality") as GUITextBlock;
            int vitalityDecrease = (int)GetVitalityDecreaseWithVitalityMultipliers(affliction);
            if (vitalityDecrease == 0)
            {
                vitalityText.Visible = false;
            }
            else
            {
                vitalityText.Visible = true;
                vitalityText.Text = TextManager.Get("Vitality") + " -" + vitalityDecrease;
                vitalityText.TextColor = vitalityDecrease <= 0 ? GUIStyle.Green :
                Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
            }

            var newDescription =
                affliction.Prefab.GetDescription(
                    affliction.Strength,
                    Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter);
            if (newDescription != prevHighlightedAfflictionDescription)
            {
                if (parent.GetChildByUserData("description") is GUITextBlock descriptionText)
                {
                    descriptionText.Text = RichString.Rich(newDescription);
                }
                prevHighlightedAfflictionDescription = newDescription;
            }
        }

        public bool OnItemDropped(Item item, bool ignoreMousePos)
        {
            //items can be dropped outside the health window
            if (!ignoreMousePos &&
                !healthWindow.Rect.Contains(PlayerInput.MousePosition))
            {
                return false;
            }

            //can't apply treatment to dead characters
            if (Character.IsDead) { return true; }
            if (item == null || !item.UseInHealthInterface) { return true; }
            if (!ignoreMousePos)
            {
                if (highlightedLimbIndex > -1)
                {
                    selectedLimbIndex = highlightedLimbIndex;
                }
            }

            Limb targetLimb = 
                Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex) ?? 
                Character.AnimController.MainLimb;
            item.ApplyTreatment(Character.Controlled, Character, targetLimb);
            return true;
        }
        private void UpdateLimbIndicators(float deltaTime, Rectangle drawArea)
        {
            if (!GameMain.Instance.Paused)
            {
                limbIndicatorOverlayAnimState += deltaTime * 8.0f;
            }

            highlightedLimbIndex = -1;
            int i = 0;
            foreach (LimbHealth limbHealth in limbHealths)
            {
                if (limbHealth.IndicatorSprite == null) { continue; }
                
                float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);

                Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea);

                if (highlightArea.Contains(PlayerInput.MousePosition))
                {
                    highlightedLimbIndex = i;
                }
                i++;
            }

            if (PlayerInput.PrimaryMouseButtonClicked() && highlightedLimbIndex > -1)
            {
                selectedLimbIndex = highlightedLimbIndex;
            }
        }

        private static readonly List<Affliction> afflictionsDisplayedOnLimb = new List<Affliction>();
        private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight)
        {
            if (Character.Removed) { return; }

            spriteBatch.End();
            spriteBatch.Begin(SpriteSortMode.Immediate, blendState: BlendState.NonPremultiplied, rasterizerState: GameMain.ScissorTestEnable, effect: GameMain.GameScreen.GradientEffect);

            int i = 0;
            foreach (LimbHealth limbHealth in limbHealths)
            {
                if (limbHealth.IndicatorSprite == null) { continue; }

                Rectangle limbEffectiveArea = new Rectangle(limbHealth.IndicatorSprite.SourceRect.X + limbHealth.HighlightArea.X,
                                                            limbHealth.IndicatorSprite.SourceRect.Y + limbHealth.HighlightArea.Y,
                                                            limbHealth.HighlightArea.Width,
                                                            limbHealth.HighlightArea.Height);

                float totalDamage = GetTotalDamage(limbHealth);

                float damageLerp = totalDamage > 0.0f ? MathHelper.Lerp(0.2f, 1.0f, totalDamage / 100.0f) : 0.0f;

                float negativeEffect = 0.0f, positiveEffect = 0.0f;
                foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
                {
                    if (kvp.Value != limbHealth) { continue; }
                    var affliction = kvp.Key;
                    if (!affliction.ShouldShowIcon(Character)) { continue; }
                    if (!affliction.Prefab.IsBuff)
                    {
                        negativeEffect += affliction.Strength * GetVitalityMultiplier(affliction, limbHealth);
                    }
                    else
                    {
                        positiveEffect += affliction.Strength * 0.2f;
                    }
                }

                float midPoint = (float)limbEffectiveArea.Center.Y / (float)limbHealth.IndicatorSprite.Texture.Height;
                float fadeDist = 0.6f * (float)limbEffectiveArea.Height / (float)limbHealth.IndicatorSprite.Texture.Height;

                if (negativeEffect > 0.0f && negativeEffect < 5.0f) { negativeEffect = 10.0f; }
                if (positiveEffect > 0.0f && positiveEffect < 5.0f) { positiveEffect = 10.0f; }

                Color positiveColor = Color.Lerp(Color.Orange, Color.Lime, Math.Min(positiveEffect / 25.0f, 1.0f));
                Color negativeColor = Color.Lerp(Color.Orange, Color.Red, Math.Min(negativeEffect / 25.0f, 1.0f));

                Color color1 = Color.Orange;
                Color color2 = Color.Orange;

                if (negativeEffect + positiveEffect > 0.0f)
                {
                    if (negativeEffect >= positiveEffect)
                    {
                        color1 = Color.Lerp(positiveColor, negativeColor, (negativeEffect - positiveEffect) / negativeEffect);
                        color2 = negativeColor;
                    }
                    else
                    {
                        color1 = positiveColor;
                        color2 = Color.Lerp(negativeColor, positiveColor, (positiveEffect - negativeEffect) / positiveEffect);
                    }
                }

                if (Character.IsDead)
                {
                    color1 = Color.Lerp(color1, Color.Black, 0.75f);
                    color2 = Color.Lerp(color2, Color.Black, 0.75f);
                }

                GameMain.GameScreen.GradientEffect.Parameters["color1"].SetValue(color1.ToVector4());
                GameMain.GameScreen.GradientEffect.Parameters["color2"].SetValue(color2.ToVector4());
                GameMain.GameScreen.GradientEffect.Parameters["midPoint"].SetValue(midPoint);
                GameMain.GameScreen.GradientEffect.Parameters["fadeDist"].SetValue(fadeDist);

                float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);

                limbHealth.IndicatorSprite.Draw(spriteBatch,
                    drawArea.Center.ToVector2(), Color.White,
                    limbHealth.IndicatorSprite.Origin,
                    0, scale);

                if (GameMain.DebugDraw)
                {
                    Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea);

                    GUI.DrawRectangle(spriteBatch, highlightArea, Color.Red, false);
                    GUI.DrawRectangle(spriteBatch, drawArea, Color.Red, false);
                }

                i++;
            }

            spriteBatch.End();

            spriteBatch.Begin(SpriteSortMode.Deferred, Lights.CustomBlendStates.Multiplicative);

            if (limbIndicatorOverlay != null)
            {
                float overlayScale = Math.Min(
                    drawArea.Width / (float)limbIndicatorOverlay.FrameSize.X,
                    drawArea.Height / (float)limbIndicatorOverlay.FrameSize.Y);

                int frame;
                int frameCount = 17;
                if (limbIndicatorOverlayAnimState >= frameCount * 2) limbIndicatorOverlayAnimState = 0.0f;
                if (limbIndicatorOverlayAnimState < frameCount)
                {
                    frame = (int)limbIndicatorOverlayAnimState;
                }
                else
                {
                    frame = frameCount - (int)(limbIndicatorOverlayAnimState - (frameCount - 1));
                }

                limbIndicatorOverlay.Draw(spriteBatch, frame, drawArea.Center.ToVector2(), Color.Gray, origin: limbIndicatorOverlay.FrameSize.ToVector2() / 2, rotate: 0.0f,
                    scale: Vector2.One * overlayScale);
            }

            if (allowHighlight)
            {
                i = 0;
                foreach (LimbHealth limbHealth in limbHealths)
                {
                    if (limbHealth.HighlightSprite == null) { continue; }

                    float scale = Math.Min(drawArea.Width / (float)limbHealth.HighlightSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.HighlightSprite.SourceRect.Height);

                    int drawCount = 0;
                    if (i == highlightedLimbIndex) { drawCount++; }
                    if (i == selectedLimbIndex) { drawCount++; }
                    for (int j = 0; j < drawCount; j++)
                    {
                        limbHealth.HighlightSprite.Draw(spriteBatch,
                            drawArea.Center.ToVector2(), Color.White,
                            limbHealth.HighlightSprite.Origin,
                            0, scale);
                    }
                    i++;
                }
            }
            spriteBatch.End();
            spriteBatch.Begin(SpriteSortMode.Deferred, blendState: BlendState.NonPremultiplied, rasterizerState: GameMain.ScissorTestEnable);

            i = 0;
            foreach (LimbHealth limbHealth in limbHealths)
            {
                afflictionsDisplayedOnLimb.Clear();
                foreach (var affliction in afflictions)
                {
                    if (ShouldDisplayAfflictionOnLimb(affliction, limbHealth)) { afflictionsDisplayedOnLimb.Add(affliction.Key); }
                }

                if (!afflictionsDisplayedOnLimb.Any()) { i++; continue; }
                if (limbHealth.IndicatorSprite == null) { continue; }

                float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
                
                Rectangle highlightArea = GetLimbHighlightArea(limbHealth, drawArea);
                
                float iconScale = 0.25f * scale;
                Vector2 iconPos = highlightArea.Center.ToVector2();

                //Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
                Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictionsDisplayedOnLimb, excludeBuffs: false).FirstOrDefault();
                if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); }

                if (afflictionsDisplayedOnLimb.Count() > 1)
                {
                    string additionalAfflictionCount = $"+{afflictionsDisplayedOnLimb.Count() - 1}";
                    Vector2 displace = GUIStyle.SubHeadingFont.MeasureString(additionalAfflictionCount);
                    GUIStyle.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X * 1.1f, -displace.Y * 0.45f), Color.Black * 0.75f);
                    GUIStyle.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X, -displace.Y * 0.5f), Color.White);
                }

                i++;
            }

            if (selectedLimbIndex > -1 && afflictionIconList.Content.CountChildren > 0)
            {
                LimbHealth limbHealth = limbHealths[selectedLimbIndex];
                if (limbHealth?.IndicatorSprite != null)
                {
                    Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea);
                    GUI.DrawLine(spriteBatch,
                        new Vector2(afflictionIconList.Rect.X, afflictionIconList.Rect.Y),
                        selectedLimbArea.Center.ToVector2(),
                        Color.LightGray * 0.5f, width: 4);
                }
            }
        }

        private bool ShouldDisplayAfflictionOnLimb(KeyValuePair<Affliction, LimbHealth> kvp, LimbHealth limbHealth)
        {
            if (!kvp.Key.ShouldShowIcon(Character)) { return false; }
            if (kvp.Value == limbHealth)
            {
                return true;
            }
            else if (kvp.Value == null)
            {
                Limb indicatorLimb = Character.AnimController.GetLimb(kvp.Key.Prefab.IndicatorLimb);
                return indicatorLimb != null && indicatorLimb.HealthIndex == limbHealths.IndexOf(limbHealth);
            }
            return false;
        }

        private void DrawLimbAfflictionIcon(SpriteBatch spriteBatch, Affliction affliction, float iconScale, ref Vector2 iconPos)
        {
            if (!affliction.ShouldShowIcon(Character) || affliction.Prefab.Icon == null) { return; }
            Vector2 iconSize = affliction.Prefab.Icon.size * iconScale;

            float showIconThreshold = Character.Controlled?.CharacterHealth == this ? affliction.Prefab.ShowIconThreshold : affliction.Prefab.ShowIconToOthersThreshold;

            //afflictions that have a strength of less than 10 are faded out slightly
            float alpha = MathHelper.Lerp(0.3f, 1.0f,
                (affliction.Strength - showIconThreshold) / Math.Min(affliction.Prefab.MaxStrength - showIconThreshold, 10.0f));

            affliction.Prefab.Icon.Draw(spriteBatch, iconPos - iconSize / 2.0f, GetAfflictionIconColor(affliction) * alpha, 0, iconScale);
            iconPos += new Vector2(10.0f, 20.0f) * iconScale;
        }

        private Rectangle GetLimbHighlightArea(LimbHealth limbHealth, Rectangle drawArea)
        {
            float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
            return new Rectangle(
                (int)(drawArea.Center.X - (limbHealth.IndicatorSprite.SourceRect.Width / 2 - limbHealth.HighlightArea.X) * scale),
                (int)(drawArea.Center.Y - (limbHealth.IndicatorSprite.SourceRect.Height / 2 - limbHealth.HighlightArea.Y) * scale),
                (int)(limbHealth.HighlightArea.Width * scale),
                (int)(limbHealth.HighlightArea.Height * scale));
        }

        public void SetHealthBarVisibility(bool value)
        {
            healthBarHolder.Visible = value;
        }

        private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>();
        private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>();

        public void ClientRead(IReadMessage inc)
        {
            newAfflictions.Clear();
            newPeriodicEffects.Clear();
            bool newAdded = false;
            byte afflictionCount = inc.ReadByte();
            for (int i = 0; i < afflictionCount; i++)
            {
                uint afflictionID = inc.ReadUInt32();
                AfflictionPrefab afflictionPrefab = AfflictionPrefab.Prefabs.Find(p => p.UintIdentifier == afflictionID);
                if (afflictionPrefab == null)
                {
                    DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found.");
                    //read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message
                    _ = inc.ReadRangedSingle(0.0f, 100.0f, 8);
                    int _periodicAfflictionCount = inc.ReadByte();
                    for (int j = 0; j < _periodicAfflictionCount; j++)
                    {
                        _ = inc.ReadByte();
                    }
                    continue;
                }
                float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8);
                int periodicAfflictionCount = inc.ReadByte();
                for (int j = 0; j < periodicAfflictionCount; j++)
                {
                    float periodicAfflictionTimer = inc.ReadRangedSingle(0, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
                    newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
                }
                newAfflictions.Add((null, afflictionPrefab, afflictionStrength));
            }

            byte limbAfflictionCount = inc.ReadByte();
            for (int i = 0; i < limbAfflictionCount; i++)
            {
                int limbIndex = inc.ReadRangedInteger(0, limbHealths.Count - 1);
                uint afflictionID = inc.ReadUInt32();
                AfflictionPrefab afflictionPrefab = AfflictionPrefab.Prefabs.Find(p => p.UintIdentifier == afflictionID);
                if (afflictionPrefab == null)
                {
                    DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found.");
                    //read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message
                    _ = inc.ReadRangedSingle(0.0f, 100.0f, 8);
                    int _periodicAfflictionCount = inc.ReadByte();
                    for (int j = 0; j < _periodicAfflictionCount; j++)
                    {
                        _ = inc.ReadByte();
                    }
                    continue;
                }
                float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8);
                int periodicAfflictionCount = inc.ReadByte();
                for (int j = 0; j < periodicAfflictionCount; j++)
                {
                    float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
                    newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
                }
                newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
            }

            foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
            {
                //deactivate afflictions that weren't included in the network message
                if (newAfflictions.None(a => affliction.Prefab == a.afflictionPrefab && limbHealth == a.limb))
                {
                    affliction.Strength = 0.0f;
                }
            }

            foreach (var (limb, afflictionPrefab, strength) in newAfflictions)
            {
                Affliction existingAffliction = null;
                foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
                {
                    if (affliction.Prefab == afflictionPrefab && limbHealth == limb)
                    {
                        existingAffliction = affliction;
                        break;
                    }
                }
                if (existingAffliction == null)
                {
                    existingAffliction = afflictionPrefab.Instantiate(strength);
                    afflictions.Add(existingAffliction, limb);
                    newAdded = true;
                }
                existingAffliction.SetStrength(strength);
                if (existingAffliction == stunAffliction)
                {
                    Character.SetStun(existingAffliction.Strength, true, true);
                }
                foreach (var periodicEffect in newPeriodicEffects)
                {
                    if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
                    if (existingAffliction.Strength < periodicEffect.effect.MinStrength) { continue; }
                    if (periodicEffect.effect.MaxStrength > 0 && existingAffliction.Strength > periodicEffect.effect.MaxStrength) { continue; }
                    //timer has wrapped around, apply the effect
                    if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
                    {
                        foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
                        {
                            Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb));
                            existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
                        }
                    }
                    existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
                }
            }

            CalculateVitality();
            DisplayedVitality = Vitality;

            if (newAdded)
            {
                MedicalClinic.OnAfflictionCountChanged(Character);
            }
        }

        partial void UpdateSkinTint()
        {
            FaceTint = DefaultFaceTint;
            BodyTint = Color.TransparentBlack;

            if (!Character.Params.Health.ApplyAfflictionColors) { return; }

            foreach ((Affliction affliction, LimbHealth _) in afflictions)
            {
                Color faceTint = affliction.GetFaceTint();
                if (faceTint.A > FaceTint.A) { FaceTint = faceTint; }
                Color bodyTint = affliction.GetBodyTint();
                if (bodyTint.A > BodyTint.A) { BodyTint = bodyTint; }
            }            
        }

        partial void UpdateLimbAfflictionOverlays()
        {
            foreach (Limb limb in Character.AnimController.Limbs)
            {
                limb.BurnOverlayStrength = 0.0f;
                limb.DamageOverlayStrength = 0.0f;
            }

            foreach ((Affliction affliction, LimbHealth limbHealth) in afflictions)
            {
                if (affliction.Prefab.BurnOverlayAlpha <= 0.0f && affliction.Prefab.DamageOverlayAlpha <= 0.0f) { continue; }

                float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
                float damageOverlayStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
                foreach (Limb limb in Character.AnimController.Limbs)
                {
                    if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
                    if (limbHealth == limbHealths[limb.HealthIndex] || !affliction.Prefab.LimbSpecific)
                    {
                        limb.BurnOverlayStrength += burnStrength;
                        limb.DamageOverlayStrength += damageOverlayStrength;
                    }
                    else
                    {
                        limb.BurnOverlayStrength += burnStrength / 2;
                    }
                }
            }
        }

        partial void RemoveProjSpecific()
        {
            foreach (LimbHealth limbHealth in limbHealths)
            {
                if (limbHealth.IndicatorSprite != null)
                {
                    limbHealth.IndicatorSprite.Remove();
                    limbHealth.IndicatorSprite = null;
                }
            }

            medUIExtra?.Remove();
            medUIExtra = null;

            Character.OnAttacked -= OnAttacked;

            limbIndicatorOverlay?.Remove();
            limbIndicatorOverlay = null;

            if (healthWindow != null)
            {
                healthWindow.RectTransform.Parent = null;
                healthWindow = null;
            }
            if (healthBarHolder != null)
            {
                healthBarHolder.RectTransform.Parent = null;
                healthBarHolder = null;
            }
            if (SuicideButton != null)
            {
                SuicideButton.RectTransform.Parent = null;
                SuicideButton = null;
            }
            if (afflictionTooltip != null)
            {
                afflictionTooltip.RectTransform.Parent = null;
                afflictionTooltip = null;
            }
        }
    }
}
