using Barotrauma.Extensions; using Barotrauma.Items.Components; using FarseerPhysics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; namespace Barotrauma { partial class CharacterHUD { abstract class ProgressBar { public float FadeTimer; public readonly GUIComponent TopContainer; public readonly GUIComponent SideContainer; public readonly GUIProgressBar TopBar; public readonly GUIProgressBar SideBar; public abstract bool Completed { get; } public abstract bool Interrupted { get; } public abstract float State { get; } public abstract string NumberToDisplay { get; } public abstract Color Color { get; } public ProgressBar(LocalizedString label, float fadeTimer = 120.0f) { FadeTimer = fadeTimer; TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter) { MinSize = new Point(100, 50), RelativeOffset = new Vector2(0.0f, 0.01f) }, isHorizontal: false, childAnchor: Anchor.TopCenter); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), label, textAlignment: Alignment.Center, textColor: GUIStyle.Red); TopBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform) { MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y) }, barSize: 0.0f, style: "CharacterHealthBarCentered") { Color = GUIStyle.Red }; CreateNumberText(TopBar); SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform) { MinSize = new Point(80, 60) }, isHorizontal: false, childAnchor: Anchor.TopRight); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), label, textAlignment: Alignment.CenterRight, textColor: GUIStyle.Red); SideBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar") { Color = GUIStyle.Red }; CreateNumberText(SideBar); TopContainer.Visible = SideContainer.Visible = false; TopContainer.CanBeFocused = false; TopContainer.Children.ForEach(c => c.CanBeFocused = false); SideContainer.CanBeFocused = false; SideContainer.Children.ForEach(c => c.CanBeFocused = false); void CreateNumberText(GUIComponent parent) { new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform) { AbsoluteOffset = new Point(2) }, string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDark) { TextGetter = () => NumberToDisplay }; new GUITextBlock(new RectTransform(Vector2.One, parent.RectTransform), string.Empty, textAlignment: Alignment.Center, textColor: GUIStyle.TextColorBright) { TextGetter = () => NumberToDisplay }; } } public abstract bool IsDuplicate(object targetObject); } class HealthBar : ProgressBar { public readonly Character Character; public override float State => Character.Vitality / Character.MaxVitality; public override bool Completed => Character.IsDead; public override bool Interrupted => Character.Removed || !Character.Enabled; public override Color Color => Character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.PoisonType) > 0 || Character.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.ParalysisType) > 0 ? GUIStyle.HealthBarColorPoisoned : GUIStyle.Red; public override string NumberToDisplay => string.Empty; public HealthBar(Character character) : base(character.DisplayName) { Character = character; } public override bool IsDuplicate(object targetObject) { return targetObject is Character character && Character == character; } } class MissionProgressBar : ProgressBar { public readonly Mission Mission; public override float State => Mission.State / (float)Mission.Prefab.MaxProgressState; public override bool Completed => Mission.State >= Mission.Prefab.MaxProgressState; public override bool Interrupted => Mission.Failed || GameMain.GameSession?.Missions == null || !GameMain.GameSession.Missions.Contains(Mission); public override Color Color => Mission.Prefab.ProgressBarColor; public override string NumberToDisplay => Mission.Prefab.ShowProgressInNumbers ? $"{Mission.State}/{Mission.Prefab.MaxProgressState}" : string.Empty; public MissionProgressBar(Mission mission) : base(mission.Prefab.ProgressBarLabel, fadeTimer: float.PositiveInfinity) { Mission = mission; } public override bool IsDuplicate(object targetObject) { return targetObject is Mission mission && Mission == mission; } } private static readonly Dictionary orderIndicatorCount = new Dictionary(); const float ItemOverlayDelay = 1.0f; private static Item focusedItem; private static float focusedItemOverlayTimer; private static readonly List brokenItems = new List(); private static float brokenItemsCheckTimer; private static readonly List bossProgressBars = new List(); private static readonly Dictionary cachedHudTexts = new Dictionary(); private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None; private static GUILayoutGroup bossHealthContainer; private static GUIFrame hudFrame; public static GUIFrame HUDFrame { get { if (hudFrame == null) { hudFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas), style: null) { CanBeFocused = false }; bossHealthContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.5f), hudFrame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.005f, 0.0f) }) { AbsoluteSpacing = GUI.IntScale(10) }; } return hudFrame; } } public static bool RecreateHudTexts { get; set; } = true; private static bool lastHudTextsContextual; private static float timeHealthWindowClosed; public static bool IsCampaignInterfaceOpen => GameMain.GameSession?.Campaign != null && (GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI); public static bool ShouldDrawInventory(Character character) { var controller = character.SelectedItem?.GetComponent(); return character?.Inventory != null && !character.Removed && !character.IsKnockedDown && (controller?.User != character || !controller.HideHUD || Screen.Selected.IsEditor) && !IsCampaignInterfaceOpen && !ConversationAction.FadeScreenToBlack; } public static LocalizedString GetCachedHudText(string textTag, InputType keyBind) { if (cachedHudTextLanguage != GameSettings.CurrentConfig.Language) { cachedHudTexts.Clear(); } Identifier key = (textTag + keyBind + GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).ToIdentifier(); if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; } text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value; cachedHudTexts.Add(key, text); cachedHudTextLanguage = GameSettings.CurrentConfig.Language; return text; } public static void AddToGUIUpdateList(Character character) { if (GUI.DisableHUD) { return; } if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { if (character.Inventory != null) { for (int i = 0; i < character.Inventory.Capacity; i++) { var item = character.Inventory.GetItemAt(i); if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; } foreach (ItemComponent ic in item.Components) { if (ic.DrawHudWhenEquipped) { ic.AddToGUIUpdateList(); } } } } if (character.Params.CanInteract && character.SelectedCharacter != null) { character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList(); } } HUDFrame.AddToGUIUpdateList(); } public static void Update(float deltaTime, Character character, Camera cam) { UpdateBossProgressBars(deltaTime); if (GUI.DisableHUD) { if (character.Inventory != null && !LockInventory(character)) { character.Inventory.UpdateSlotInput(); } return; } if (character.ShowInteractionLabels && character.ViewTarget == null) { InteractionLabelManager.Update(character, cam); } if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) { if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen) { bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null; bool healthWindowOpen = CharacterHealth.OpenHealthWindow != null || timeHealthWindowClosed < 0.2f; if (mouseOnPortrait && !healthWindowOpen && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None()) { CharacterHealth.OpenHealthWindow = character.CharacterHealth; } } if (character.Inventory != null) { if (!LockInventory(character)) { character.Inventory.Update(deltaTime, cam); } else { character.Inventory.ClearSubInventories(); } } if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { if (character.SelectedCharacter.IsInventoryAccessibleTo(character)) { character.SelectedCharacter.Inventory.Update(deltaTime, cam); } character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime); } Inventory.UpdateDragging(); } if (focusedItem != null) { if (character.FocusedItem != null) { focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f); } else { focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f); if (focusedItemOverlayTimer <= 0.0f) { focusedItem = null; RecreateHudTexts = true; } } } if (brokenItemsCheckTimer > 0.0f) { brokenItemsCheckTimer -= deltaTime; } else { brokenItems.Clear(); brokenItemsCheckTimer = 1.0f; foreach (Item item in Item.ItemList) { if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; } if (!item.Repairables.Any(r => r.IsBelowRepairIconThreshold)) { continue; } if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; } Vector2 diff = item.WorldPosition - character.WorldPosition; if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null) { brokenItems.Add(item); } } } if (CharacterHealth.OpenHealthWindow != null) { timeHealthWindowClosed = 0.0f; } else { timeHealthWindowClosed += deltaTime; } } public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam) { if (GUI.DisableHUD) { return; } character.CharacterHealth.Alignment = Alignment.Right; if (GameMain.GameSession?.CrewManager != null) { orderIndicatorCount.Clear(); foreach (CrewManager.ActiveOrder activeOrder in GameMain.GameSession.CrewManager.ActiveOrders) { if (!DrawIcon(activeOrder.Order)) { continue; } if (activeOrder.FadeOutTime.HasValue) { DrawOrderIndicator(spriteBatch, cam, character, activeOrder.Order, iconAlpha: MathHelper.Clamp(activeOrder.FadeOutTime.Value / 10.0f, 0.2f, 1.0f)); } else { float iconAlpha = GetDistanceBasedIconAlpha(activeOrder.Order.TargetSpatialEntity, maxDistance: 450.0f); if (iconAlpha <= 0.0f) { continue; } DrawOrderIndicator(spriteBatch, cam, character, activeOrder.Order, iconAlpha: iconAlpha, createOffset: false, scaleMultiplier: 0.5f, overrideAlpha: true); } } if (character.GetCurrentOrderWithTopPriority() is Order currentOrder && DrawIcon(currentOrder)) { DrawOrderIndicator(spriteBatch, cam, character, currentOrder, 1.0f); } static bool DrawIcon(Order o) => o != null && (o.TargetEntity is not Item i || o.DrawIconWhenContained || i.GetRootInventoryOwner() == i); } if (GameMain.GameSession != null) { foreach (var mission in GameMain.GameSession.Missions) { if (!mission.DisplayTargetHudIcons) { continue; } foreach (var target in mission.HudIconTargets) { if (target.Submarine != character.Submarine) { continue; } if (target.Removed) { continue; } float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance); if (alpha <= 0.0f) { continue; } GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha); } } } foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities) { DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f); } foreach (Item brokenItem in brokenItems) { if (!brokenItem.IsInteractable(character)) { continue; } float alpha = GetDistanceBasedIconAlpha(brokenItem); if (alpha <= 0.0f) { continue; } GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite, Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); } if (OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder)) { foreach (Item deconstructItem in Item.DeconstructItems) { if (deconstructItem.ParentInventory != null) { continue; } if (deconstructItem.OrderedToBeIgnored) { continue; } if (deconstructItem.Submarine != character.Submarine || !deconstructItem.IsInteractable(character)) { continue; } float alpha = GetDistanceBasedIconAlpha(deconstructItem, maxDistance: 450) * 0.7f; if (alpha <= 0.0f) { continue; } GUI.DrawIndicator(spriteBatch, deconstructItem.DrawPosition, cam, 100.0f, deconstructOrder.SymbolSprite, GUIStyle.Red, scaleMultiplier: 0.5f, overrideAlpha: alpha); } } float GetDistanceBasedIconAlpha(ISpatialEntity target, float maxDistance = 1000.0f) { float dist = Vector2.Distance(character.WorldPosition, target.WorldPosition); return Math.Min((maxDistance - dist) / maxDistance * 2.0f, 1.0f); } if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen && (!character.IsKeyDown(InputType.Aim) || character.HeldItems.None(it => it?.GetComponent() != null))) { if (!character.ShowInteractionLabels) { if (character.FocusedCharacter is { CanBeSelected: true }) { DrawCharacterHoverTexts(spriteBatch, cam, character); } if (character.FocusedItem != null) { if (focusedItem != character.FocusedItem) { focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer); RecreateHudTexts = true; } focusedItem = character.FocusedItem; } if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay) { Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition); float circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f; circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f); if (circleSize > 0.0f) { Vector2 scale = new Vector2(circleSize / GUIStyle.FocusIndicator.FrameSize.X); GUIStyle.FocusIndicator.Draw(spriteBatch, (int)((focusedItemOverlayTimer - 1.0f) * GUIStyle.FocusIndicator.FrameCount * 3.0f), circlePos, Color.LightBlue * 0.3f, origin: GUIStyle.FocusIndicator.FrameSize.ToVector2() / 2, rotate: (float)Timing.TotalTime, scale: scale); } if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld) { bool hudTextsContextual = PlayerInput.KeyDown(InputType.ContextualCommand); if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual) { RecreateHudTexts = true; lastHudTextsContextual = hudTextsContextual; } var hudTexts = focusedItem.GetHUDTexts(character, RecreateHudTexts); RecreateHudTexts = false; int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X); Vector2 textSize = GUIStyle.Font.MeasureString(hudTexts.First().Text); Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(hudTexts.First().Text); Vector2 startPos = cam.WorldToScreen(focusedItem.DrawPosition); startPos.Y -= (hudTexts.Count + 1) * textSize.Y; if (focusedItem.Sprite != null) { startPos.X += (int)(circleSize * 0.4f * dir); startPos.Y -= (int)(circleSize * 0.4f); } Vector2 textPos = startPos; if (dir == -1) { textPos.X -= largeTextSize.X; } float alpha = MathHelper.Clamp((focusedItemOverlayTimer - ItemOverlayDelay) * 2.0f, 0.0f, 1.0f); GUI.DrawString(spriteBatch, textPos, hudTexts.First().Text, hudTexts.First().Color * alpha, Color.Black * alpha * 0.7f, 2, font: GUIStyle.SubHeadingFont, ForceUpperCase.No); startPos.X += dir * 10.0f * GUI.Scale; textPos.X += dir * 10.0f * GUI.Scale; textPos.Y += largeTextSize.Y; foreach (ColoredText coloredText in hudTexts.Skip(1)) { if (dir == -1) { textPos.X = (int)(startPos.X - GUIStyle.SmallFont.MeasureString(coloredText.Text).X); } GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color * alpha, Color.Black * alpha * 0.7f, 2, GUIStyle.SmallFont); textPos.Y += textSize.Y; } } } } if (character.ShowInteractionLabels && character.ViewTarget == null) { InteractionLabelManager.DrawLabels(spriteBatch, cam, character); } foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values) { progressBar.Draw(spriteBatch, cam); } void DrawInteractionIcon(Entity entity, Identifier iconStyle) { if (entity == null || entity.Removed) { return; } Hull currentHull = entity switch { Character character => character.CurrentHull, Item item => item.CurrentHull, _ => null }; Range visibleRange = new Range(currentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity); LocalizedString label = null; if (entity is Character characterEntity) { if (characterEntity.IsDead || characterEntity.IsIncapacitated) { return; } if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine) { //TODO: we could probably do better than just hardcoding //a check for InteractionType.Examine here. if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; } var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true); if (body != null && body.UserData != entity) { return; } visibleRange = new Range(-100f, 500f); } label = characterEntity?.Info?.Title; } if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; } float dist = Vector2.Distance(character.WorldPosition, entity.WorldPosition); float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist); float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor); GUI.DrawIndicator( spriteBatch, entity.DrawPosition, cam, visibleRange, style.GetDefaultSprite(), style.Color * alpha, label: label); } foreach (Character npc in Character.CharacterList) { if (npc.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; } DrawInteractionIcon(npc, ("CampaignInteractionIcon." + npc.CampaignInteractionType).ToIdentifier()); } if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is not null) { foreach (var (entity, iconStyle) in tutorialMode.Tutorial.Icons) { DrawInteractionIcon(entity, iconStyle); } } foreach (Item item in Item.ItemList) { if (item.IconStyle is null || item.Submarine != character.Submarine) { continue; } if (Vector2.DistanceSquared(character.Position, item.Position) > 500f * 500f) { continue; } var body = Submarine.CheckVisibility(character.SimPosition, item.SimPosition, ignoreLevel: true); if (body != null && body.UserData as Item != item) { continue; } GUI.DrawIndicator(spriteBatch, item.DrawPosition + new Vector2(0f, item.RectHeight * 0.65f), cam, new Range(-100f, 500.0f), item.IconStyle.GetDefaultSprite(), item.IconStyle.Color, createOffset: false); } } if (character.SelectedItem != null && (character.CanInteractWith(character.SelectedItem) || Screen.Selected == GameMain.SubEditorScreen)) { character.SelectedItem.DrawHUD(spriteBatch, cam, character); } if (character.Inventory != null) { foreach (Item item in character.Inventory.AllItems) { if (character.HasEquippedItem(item)) { item.DrawHUD(spriteBatch, cam, character); } } } if (IsCampaignInterfaceOpen) { return; } if (character.Inventory != null) { for (int i = 0; i < character.Inventory.Capacity; i++) { var item = character.Inventory.GetItemAt(i); if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; } //if the item is also equipped in another slot we already went through, don't draw the hud again bool duplicateFound = false; for (int j = 0; j < i; j++) { if (character.Inventory.SlotTypes[j] != InvSlotType.Any && character.Inventory.GetItemAt(j) == item) { duplicateFound = true; break; } } if (duplicateFound) { continue; } foreach (ItemComponent ic in item.Components) { if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); } } } } bool mouseOnPortrait = false; if (character.Stun <= 0.1f && !character.IsDead) { bool wiringMode = Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode; if (CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null && !wiringMode) { if (character.Info != null && !character.ShouldLockHud()) { character.Info.DrawBackground(spriteBatch); character.Info.DrawJobIcon(spriteBatch, new Rectangle( (int)(HUDLayoutSettings.BottomRightInfoArea.X + HUDLayoutSettings.BottomRightInfoArea.Width * 0.05f), (int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f), (int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2), (int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)), character.Info.IsDisguisedAsAnother); float yOffset = (GameMain.GameSession?.Campaign is MultiPlayerCampaign ? -10 : 4) * GUI.Scale; character.Info?.DrawIcon(spriteBatch, new Vector2(HUDLayoutSettings.PortraitArea.Center.X - 12 * GUI.Scale, HUDLayoutSettings.PortraitArea.Center.Y), HUDLayoutSettings.PortraitArea.Size.ToVector2(), flip: true); character.Info.DrawForeground(spriteBatch); } mouseOnPortrait = MouseOnCharacterPortrait() && !character.ShouldLockHud(); if (mouseOnPortrait) { GUIStyle.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUIStyle.Green * 0.5f); } } if (ShouldDrawInventory(character)) { character.Inventory.Locked = character == Character.Controlled && LockInventory(character); character.Inventory.DrawOwn(spriteBatch); character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ? CharacterInventory.Layout.Default : CharacterInventory.Layout.Right; } } if (!character.IsIncapacitated && character.Stun <= 0.0f) { if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) { if (character.SelectedCharacter.IsInventoryAccessibleTo(character)) { character.SelectedCharacter.Inventory.Locked = false; character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left; character.SelectedCharacter.Inventory.DrawOwn(spriteBatch); } if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth) { character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left; //character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch); } } else if (character.Inventory != null) { //character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left; } } if (mouseOnPortrait) { GUIComponent.DrawToolTip( spriteBatch, character.Info?.Job == null ? character.DisplayName : character.DisplayName + " (" + character.Info.Job.Name + ")", HUDLayoutSettings.PortraitArea); } } public static bool MouseOnCharacterPortrait() { if (Character.Controlled == null) { return false; } if (CharacterHealth.OpenHealthWindow != null || Character.Controlled.SelectedCharacter != null) { return false; } return HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition); } private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character) { var allItems = character.Inventory?.AllItems; if (allItems != null) { foreach (Item item in allItems) { var statusHUD = item?.GetComponent(); if (statusHUD != null && statusHUD.IsActive && statusHUD.VisibleCharacters.Contains(character.FocusedCharacter)) { return; } } } float dist = Vector2.Distance(character.FocusedCharacter.DrawPosition, character.DrawPosition); Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) / dist * Math.Min(dist, Character.MaxDragDistance); startPos = cam.WorldToScreen(startPos); string focusName = character.FocusedCharacter.Info == null ? character.FocusedCharacter.DisplayName : character.FocusedCharacter.Info.DisplayName; Vector2 textPos = startPos; //measure arbitrary one-line text instead of the potentially-multi-line name Vector2 textSize = GUIStyle.Font.MeasureString("T"); Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString("T"); textPos -= new Vector2(textSize.X / 2, textSize.Y); Color nameColor = character.FocusedCharacter.GetNameColor(); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y; if (character.FocusedCharacter.Info?.Title != null && !character.FocusedCharacter.Info.Title.IsNullOrEmpty() && character.FocusedCharacter.TeamID != CharacterTeamType.Team1) { GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y; } textPos.X += 10.0f * GUI.Scale; if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet && character.FocusedCharacter.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior.CanPlayWith(character)) { GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use), GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont); textPos.Y += largeTextSize.Y; } if (character.FocusedCharacter.CanBeDraggedBy(character)) { string text = character.CanEat ? "EatHint" : "GrabHint"; GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, InputType.Grab), GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont); textPos.Y += largeTextSize.Y; } if (character.FocusedCharacter.CanBeHealedBy(character)) { GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health), GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont); textPos.Y += textSize.Y; } if (character.FocusedCharacter.ShouldShowCustomInteractText) { GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.CustomInteractHUDText, GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont); textPos.Y += textSize.Y; } } public static void ShowBossHealthBar(Character character, float damage) { if (character == null || character.IsDead || character.Removed) { return; } if (bossProgressBars.Any(b => b.IsDuplicate(character))) { return; } AddBossProgressBar(new HealthBar(character)); } public static void ShowMissionProgressBar(Mission mission) { if (mission == null || mission.Completed || mission.Failed) { return; } if (bossProgressBars.Any(b => b.IsDuplicate(mission))) { return; } AddBossProgressBar(new MissionProgressBar(mission)); } public static void ClearBossProgressBars() { for (int i = bossProgressBars.Count - 1; i>= 0; i--) { RemoveBossProgressBar(bossProgressBars[i]); } bossProgressBars.Clear(); } private static void RemoveBossProgressBar(ProgressBar progressBar) { progressBar.SideContainer.Parent?.RemoveChild(progressBar.SideContainer); progressBar.TopContainer.Parent?.RemoveChild(progressBar.TopContainer); bossProgressBars.Remove(progressBar); } private static void AddBossProgressBar(ProgressBar progressBar) { var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; if (healthBarMode == EnemyHealthBarMode.HideAll && progressBar is not MissionProgressBar) { return; } if (bossProgressBars.Count > 5) { ProgressBar oldestHealthBar = bossProgressBars.First(); foreach (var bar in bossProgressBars) { if (bar.TopBar.BarSize < oldestHealthBar.TopBar.BarSize) { oldestHealthBar = bar; } } oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f); } bossProgressBars.Add(progressBar); } public static void UpdateBossProgressBars(float deltaTime) { var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars; for (int i = 0; i < bossProgressBars.Count; i++) { var bossHealthBar = bossProgressBars[i]; bool showTopBar = i == bossProgressBars.Count - 1; if (showTopBar && !bossHealthBar.TopContainer.Visible) { bossHealthBar.SideContainer.SetAsLastChild(); SetColor(bossHealthBar, bossHealthBar.SideContainer, 0); } bossHealthBar.TopContainer.Visible = showTopBar; bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible; bossHealthBar.TopBar.BarSize = bossHealthBar.SideBar.BarSize = bossHealthBar.State; float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f); if (bossHealthBar.TopContainer.Visible) { SetColor(bossHealthBar, bossHealthBar.TopContainer, alpha); } if (bossHealthBar.SideContainer.Visible) { SetColor(bossHealthBar, bossHealthBar.SideContainer, alpha); } static void SetColor(ProgressBar bossHealthBar, GUIComponent container, float alpha) { foreach (var component in container.GetAllChildren()) { component.Color = new Color(bossHealthBar.Color, (byte)(alpha * 255)); if (component is GUITextBlock textBlock) { textBlock.TextColor = new Color(bossHealthBar.Completed ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255)); } } } if (bossHealthBar.Interrupted) { bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 1.0f); } else if (bossHealthBar.Completed) { bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 5.0f); } bossHealthBar.FadeTimer -= deltaTime; } for (int i = bossProgressBars.Count - 1; i >= 0 ; i--) { var bossHealthBar = bossProgressBars[i]; if (bossHealthBar.FadeTimer <= 0 || (healthBarMode == EnemyHealthBarMode.HideAll && bossHealthBar is not MissionProgressBar)) { bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer); bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer); bossProgressBars.RemoveAt(i); bossHealthContainer.Recalculate(); } } } private static bool LockInventory(Character character) { if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; } return character.ShouldLockHud(); } /// Override the distance-based alpha value with the iconAlpha parameter value private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order, float iconAlpha = 1.0f, bool createOffset = true, float scaleMultiplier = 1.0f, bool overrideAlpha = false) { if (order?.SymbolSprite == null) { return; } if (order.IsReport && order.OrderGiver != character && !order.HasAppropriateJob(character)) { return; } ISpatialEntity target = order.ConnectedController?.Item ?? order.TargetSpatialEntity; if (target == null) { return; } //don't show the indicator if far away and not inside the same sub //prevents exploiting the indicators in locating the sub if (character.Submarine != target.Submarine && Vector2.DistanceSquared(character.WorldPosition, target.WorldPosition) > 1000.0f * 1000.0f) { return; } if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); } Vector2 drawPos = target is Entity entity ? entity.DrawPosition : target.Submarine == null ? target.Position : target.Position + target.Submarine.DrawPosition; drawPos += Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target]; GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha, createOffset: createOffset, scaleMultiplier: scaleMultiplier, overrideAlpha: overrideAlpha ? (float?)iconAlpha : null); orderIndicatorCount[target] = orderIndicatorCount[target] + 1; } private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f) { if (objectiveEntity == null) return; Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f; GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha); } static partial void RecreateHudTextsIfControllingProjSpecific(Character character) { if (character == Character.Controlled) { RecreateHudTexts = true; } } static partial void RecreateHudTextsIfFocusedProjSpecific(params Item[] items) { foreach (var item in items) { if (item == Character.Controlled?.FocusedItem) { RecreateHudTexts = true; break; } } } } }