v1.4.4.1 (Blood in the Water Update)

This commit is contained in:
Regalis11
2024-04-24 18:09:05 +03:00
parent 89b91d1c3e
commit ff1b8951a7
397 changed files with 15250 additions and 6479 deletions
@@ -109,7 +109,7 @@ namespace Barotrauma
}
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove && !character.IsRagdolled ? 0.01f : 0.2f;
float errorTolerance = character.CanMove && (!character.IsRagdolled || character.AnimController.IsHangingWithRope) ? 0.01f : 0.2f;
if (distSqrd > errorTolerance)
{
if (distSqrd > 10.0f || !character.CanMove)
@@ -1,3 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Particles;
@@ -9,7 +10,6 @@ using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -42,6 +42,8 @@ namespace Barotrauma
private set;
} = true;
public bool ShowInteractionLabels { get; private set; }
//the Character that the player is currently controlling
private static Character controlled;
@@ -230,14 +232,51 @@ namespace Barotrauma
}
}
private float pressureEffectTimer;
private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>();
public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities
{
get { return activeObjectiveEntities; }
}
private static readonly List<SpeechBubble> speechBubbles = new List<SpeechBubble>();
private SpeechBubble textlessSpeechBubble;
sealed class SpeechBubble
{
public float LifeTime;
public Vector2 PrevPosition;
public Vector2 Position;
public Vector2 DrawPosition;
public float MoveUpAmount;
public readonly string Text;
public readonly Character Character;
public readonly Submarine Submarine;
public readonly Vector2 TextSize;
public Color Color;
public bool Moving;
public SpeechBubble(Character character, float lifeTime, Color color, string text = "")
{
Text = ToolBox.WrapText(text, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
TextSize = GUIStyle.SmallFont.MeasureString(Text);
Character = character;
Position = GetDesiredPosition();
Submarine = character.Submarine;
LifeTime = lifeTime;
Color = color;
}
public Vector2 GetDesiredPosition()
{
return Character.Position + Vector2.UnitY * 100;
}
}
private float pressureEffectTimer;
partial void InitProjSpecific(ContentXElement mainElement)
{
soundTimer = Rand.Range(0.0f, Params.SoundInterval);
@@ -272,6 +311,9 @@ namespace Barotrauma
}
}
private readonly List<Item> previousInteractablesInRange = new();
private readonly List<Item> interactablesInRange = new();
private bool wasFiring;
/// <summary>
@@ -279,6 +321,7 @@ namespace Barotrauma
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (DisableControls || GUI.InputBlockingMenuOpen)
{
foreach (Key key in keys)
@@ -316,6 +359,13 @@ namespace Barotrauma
}
}
ShowInteractionLabels = keys[(int)InputType.ShowInteractionLabels].Held;
if (ShowInteractionLabels)
{
focusedItem = InteractionLabelManager.HoveredItem;
}
//if we were firing (= pressing the aim and shoot keys at the same time)
//and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items
if (wasFiring && !keys[(int)InputType.Shoot].Held)
@@ -549,19 +599,61 @@ namespace Barotrauma
if (Lights.LightManager.ViewTarget == this) { Lights.LightManager.ViewTarget = null; }
}
private void UpdateInteractablesInRange()
{
// keep two lists to detect changes to the current state of interactables in range
previousInteractablesInRange.Clear();
previousInteractablesInRange.AddRange(interactablesInRange);
interactablesInRange.Clear();
//use the list of visible entities if it exists
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
foreach (MapEntity entity in entityList)
{
if (entity is not Item item) { continue; }
if (item.body != null && !item.body.Enabled) { continue; }
if (item.ParentInventory != null) { continue; }
if (item.Prefab.RequireCampaignInteract &&
item.CampaignInteractionType == CampaignMode.InteractionType.None)
{
continue;
}
if (Screen.Selected is SubEditorScreen { WiringMode: true } &&
item.GetComponent<ConnectionPanel>() == null)
{
continue;
}
if (CanInteractWith(item))
{
interactablesInRange.Add(item);
}
}
if (!interactablesInRange.SequenceEqual(previousInteractablesInRange))
{
InteractionLabelManager.RefreshInteractablesInRange(interactablesInRange);
}
}
private readonly List<Item> debugInteractablesInRange = new List<Item>();
private readonly List<Item> debugInteractablesAtCursor = new List<Item>();
private readonly List<(Item item, float dist)> debugInteractablesNearCursor = new List<(Item item, float dist)>();
/// <summary>
/// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item.
/// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item.
/// </summary>
/// <param name="character">The Character who is looking for the interactable item, only items that are close enough to this character are returned</param>
/// <param name="simPosition">The item at the simPosition, with the lowest depth, is returned</param>
/// <param name="allowFindingNearestItem">If this is true and an item cannot be found at simPosition then a nearest item will be returned if possible</param>
/// <param name="hull">If a hull is specified, only items within that hull are returned</param>
public Item FindItemAtPosition(Vector2 simPosition, float aimAssistModifier = 0.0f, Item[] ignoredItems = null)
/// <param name="itemCollection">Item collection to look in</param>
/// <param name="simPosition">sim position for distance comparison (such as mouse position)</param>
/// <param name="aimAssistModifier">aim assist modifier</param>
/// <returns></returns>
public Item FindClosestItem(List<Item> itemCollection, Vector2 simPosition, float aimAssistModifier = 0.0f)
{
if (Submarine != null)
{
@@ -580,24 +672,11 @@ namespace Barotrauma
float aimAssistAmount = SelectedItem == null ? 100.0f * aimAssistModifier : 1.0f;
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
//use the list of visible entities if it exists
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
Item closestItem = null;
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
foreach (MapEntity entity in entityList)
foreach (var item in itemCollection)
{
if (entity is not Item item)
{
continue;
}
if (item.body != null && !item.body.Enabled) { continue; }
if (item.ParentInventory != null) { continue; }
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
if (item.Prefab.RequireCampaignInteract && item.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; }
if (Screen.Selected is SubEditorScreen editor && editor.WiringMode && item.GetComponent<ConnectionPanel>() == null) { continue; }
if (draggingItemToWorld)
{
if (item.OwnInventory == null ||
@@ -644,7 +723,7 @@ namespace Barotrauma
distanceToItem = 2.0f + Vector2.Distance(rectIntersectionPoint, displayPosition);
}
}
if (distanceToItem > closestItemDistance) { continue; }
if (!CanInteractWith(item)) { continue; }
@@ -661,7 +740,7 @@ namespace Barotrauma
Character closestCharacter = null;
maxDist = ConvertUnits.ToSimUnits(maxDist);
float closestDist = maxDist * maxDist;
float closestDist = maxDist;
foreach (Character c in CharacterList)
{
if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; }
@@ -704,6 +783,12 @@ namespace Barotrauma
}
guiMessages.RemoveAll(m => m.Timer >= m.Lifetime);
if (textlessSpeechBubble != null)
{
textlessSpeechBubble.LifeTime -= deltaTime;
if (textlessSpeechBubble.LifeTime <= 0) { textlessSpeechBubble = null; }
}
if (!enabled) { return; }
if (!IsIncapacitated)
@@ -890,13 +975,6 @@ namespace Barotrauma
pos.Y = -pos.Y;
if (speechBubbleTimer > 0.0f)
{
GUIStyle.SpeechBubbleIcon.Value.Sprite.Draw(spriteBatch, pos - Vector2.UnitY * 5,
speechBubbleColor * Math.Min(speechBubbleTimer, 1.0f), 0.0f,
Math.Min(speechBubbleTimer, 1.0f));
}
if (this == controlled)
{
if (DebugDrawInteract)
@@ -921,113 +999,232 @@ namespace Barotrauma
ToolBox.GradientLerp(dist, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green), width: 2);
}
}
return;
}
float hoverRange = 300.0f;
float fadeOutRange = 200.0f;
float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition));
float hudInfoAlpha =
CampaignInteractionType == CampaignMode.InteractionType.None ?
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f;
if (!GUI.DisableCharacterNames && hudInfoVisible &&
(controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f)
else
{
if (info != null)
float hoverRange = 300.0f;
float fadeOutRange = 200.0f;
float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition));
float hudInfoAlpha =
CampaignInteractionType == CampaignMode.InteractionType.None ?
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f;
if (!GUI.DisableCharacterNames && hudInfoVisible &&
(controlled == null || this != controlled.FocusedCharacter || IsPet) && cam.Zoom > 0.4f)
{
LocalizedString name = Info.DisplayName;
if (controlled == null && name != Info.Name)
{
name += " " + TextManager.Get("Disguised");
}
else if (Info.Title != null && TeamID != CharacterTeamType.Team1)
if (info != null)
{
name += '\n' + Info.Title;
LocalizedString name = Info.DisplayName;
if (controlled == null && name != Info.Name)
{
name += " " + TextManager.Get("Disguised");
}
else if (Info.Title != null && TeamID != CharacterTeamType.Team1)
{
name += '\n' + Info.Title;
}
Vector2 nameSize = GUIStyle.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Color nameColor = GetNameColor();
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y;
namePos *= screenSize / viewportSize;
namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y);
namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{
var iconStyle = GUIStyle.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = (30.0f / icon.Sprite.size.X / cam.Zoom) * GUI.Scale;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
}
GUIStyle.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUIStyle.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
}
}
Vector2 nameSize = GUIStyle.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Color nameColor = GetNameColor();
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y;
namePos *= screenSize / viewportSize;
namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y);
namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
var petBehavior = (AIController as EnemyAIController)?.PetBehavior;
if (petBehavior != null && !IsDead && !IsUnconscious)
{
var iconStyle = GUIStyle.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType);
var petStatus = petBehavior.GetCurrentStatusIndicatorType();
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = (30.0f / icon.Sprite.size.X / cam.Zoom) * GUI.Scale;
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
}
}
GUIStyle.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
if (IsDead) { return; }
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode != EnemyHealthBarMode.ShowAll)
{
if (Controlled == null)
{
GUIStyle.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
if (!IsOnPlayerTeam) { return; }
}
else
{
if (!HumanAIController.IsFriendly(Controlled, this) ||
(AIController is HumanAIController humanAi && humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective && HumanAIController.IsFriendly(Controlled, combatObjective.Enemy)))
{
return;
}
}
}
var petBehavior = (AIController as EnemyAIController)?.PetBehavior;
if (petBehavior != null && !IsDead && !IsUnconscious)
if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
{
var petStatus = petBehavior.GetCurrentStatusIndicatorType();
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
CharacterHealth.DisplayedVitality / MaxVitality,
Color.Lerp(GUIStyle.Red, GUIStyle.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha,
new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
}
}
if (IsDead) { return; }
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode != EnemyHealthBarMode.ShowAll)
if (textlessSpeechBubble != null)
{
if (Controlled == null)
Vector2 iconPos = pos - Vector2.UnitY * 5;
GUIStyle.SpeechBubbleIcon.Value.Sprite.Draw(spriteBatch, iconPos,
textlessSpeechBubble.Color * Math.Min(textlessSpeechBubble.LifeTime, 1.0f), 0.0f,
Math.Min(textlessSpeechBubble.LifeTime, 1.0f));
}
}
public void ShowSpeechBubble(Color color, string text)
{
if (!GameSettings.CurrentConfig.ChatSpeechBubbles)
{
ShowTextlessSpeechBubble(1.0f, color);
return;
}
float duration = MathHelper.Lerp(1.0f, 8.0f, Math.Min(text.Length / 100.0f, 1.0f));
speechBubbles.Add(new SpeechBubble(this, duration, color, text));
textlessSpeechBubble = null;
}
public void ShowTextlessSpeechBubble(float duration, Color color)
{
if (speechBubbles.Any(sb => sb.Character == this)) { return; }
if (textlessSpeechBubble == null)
{
textlessSpeechBubble = new SpeechBubble(this, duration, color);
}
else
{
textlessSpeechBubble.Color = color;
textlessSpeechBubble.LifeTime = Math.Max(textlessSpeechBubble.LifeTime, duration);
}
}
public static void DrawSpeechBubbles(SpriteBatch spriteBatch, Camera cam)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, DepthStencilState.None, null, null, cam.Transform);
foreach (var bubble in speechBubbles)
{
Vector2 iconPos = Timing.Interpolate(bubble.PrevPosition, bubble.Position);
iconPos += Vector2.UnitY * bubble.MoveUpAmount;
if (bubble.Submarine != null)
{
if (!IsOnPlayerTeam) { return; }
iconPos += bubble.Submarine.DrawPosition;
}
else
float alpha = 1.0f;
float mouseDist = Vector2.Distance(cam.WorldToScreen(iconPos), PlayerInput.MousePosition);
//treat the size of the bubble from corner to corner as the
float textSize = bubble.TextSize.Length();
if (mouseDist < textSize)
{
if (!HumanAIController.IsFriendly(Controlled, this) ||
(AIController is HumanAIController humanAi && humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective && HumanAIController.IsFriendly(Controlled, combatObjective.Enemy)))
{
return;
alpha *= Math.Max(mouseDist / textSize, 0.5f);
}
iconPos.Y = -iconPos.Y;
if (GUIStyle.SpeechBubbleIconSliced.Value is { } speechBubbleIconSliced)
{
Vector2 bubbleSize = bubble.TextSize + Vector2.One * GUI.IntScale(15);
speechBubbleIconSliced.Draw(spriteBatch, new RectangleF(iconPos - bubbleSize / 2, bubbleSize), bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha);
}
GUI.DrawString(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, font: GUIStyle.SmallFont);
}
spriteBatch.End();
}
static partial void UpdateSpeechBubbles(float deltaTime)
{
for (int i = speechBubbles.Count - 1; i >= 0; i--)
{
var bubble = speechBubbles[i];
bubble.LifeTime -= deltaTime;
if (bubble.LifeTime <= 0 || bubble.Character is { Removed: true })
{
speechBubbles.RemoveAt(i);
continue;
}
bubble.PrevPosition = bubble.Position;
Vector2 desiredPos = bubble.GetDesiredPosition();
Vector2 diff = desiredPos - bubble.Position;
float dist = diff.Length();
//how far the bubble needs to be from the desired position to start moving
const float MoveThreshold = 100.0f;
const float MaxSpeed = 1000.0f;
if (dist < 1)
{
bubble.Moving = false;
}
else if (dist > MoveThreshold || bubble.Moving)
{
Vector2 moveAmount = diff / dist * MathHelper.Clamp(dist * 5, 0, MaxSpeed) * deltaTime;
//slower vertical movement (don't want to interfere too much with the bubbles floating up
//and the overlap prevention which works vertically)
moveAmount.Y *= 0.1f;
bubble.Position += moveAmount;
bubble.Moving = true;
}
bubble.MoveUpAmount += deltaTime * 5.0f;
//go through the newer bubbles, move this one out of the way if one is overlapping
for (int j = i + 1; j < speechBubbles.Count; j++)
{
var otherBubble = speechBubbles[j];
{
if (Math.Abs(bubble.Position.X - otherBubble.Position.X) < (bubble.TextSize.X + otherBubble.TextSize.X) / 2 &&
Math.Abs(bubble.Position.Y - otherBubble.Position.Y) < (bubble.TextSize.Y + otherBubble.TextSize.Y) / 2 + 10)
{
bubble.Position += Vector2.UnitY * deltaTime * 50.0f;
}
}
}
}
if (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
{
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
CharacterHealth.DisplayedVitality / MaxVitality,
Color.Lerp(GUIStyle.Red, GUIStyle.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha,
new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
}
}
public Color GetNameColor()
@@ -207,7 +207,7 @@ namespace Barotrauma
{
cachedHudTexts.Clear();
}
Identifier key = (textTag + keyBind).ToIdentifier();
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);
@@ -246,6 +246,7 @@ namespace Barotrauma
public static void Update(float deltaTime, Character character, Camera cam)
{
UpdateBossProgressBars(deltaTime);
if (GUI.DisableHUD)
@@ -256,6 +257,11 @@ namespace Barotrauma
}
return;
}
if (character.ShowInteractionLabels && character.ViewTarget == null)
{
InteractionLabelManager.Update(character, cam);
}
if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
{
@@ -375,7 +381,7 @@ namespace Barotrauma
static bool DrawIcon(Order o) =>
o != null &&
(!(o.TargetEntity is Item i) ||
(o.TargetEntity is not Item i ||
o.DrawIconWhenContained ||
i.GetRootInventoryOwner() == i);
}
@@ -405,90 +411,115 @@ namespace Barotrauma
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,
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<Sprayer>() != null)))
{
if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
if (!character.ShowInteractionLabels)
{
DrawCharacterHoverTexts(spriteBatch, cam, character);
}
if (character.FocusedItem != null)
{
if (focusedItem != character.FocusedItem)
if (character.FocusedCharacter is { CanBeSelected: true })
{
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
RecreateHudTexts = true;
DrawCharacterHoverTexts(spriteBatch, cam, character);
}
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)
if (character.FocusedItem != null)
{
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.IsShiftDown();
if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
if (focusedItem != character.FocusedItem)
{
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
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)
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)
{
startPos.X += (int)(circleSize * 0.4f * dir);
startPos.Y -= (int)(circleSize * 0.4f);
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);
}
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 (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{
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;
}
}
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)
@@ -673,6 +704,8 @@ namespace Barotrauma
}
}
public static bool MouseOnCharacterPortrait()
{
if (Character.Controlled == null) { return false; }
@@ -1,4 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Linq;
@@ -46,6 +46,7 @@ namespace Barotrauma
ContentPath tintMaskPath = maskElement.GetAttributeContentPath("texture");
if (!tintMaskPath.IsNullOrEmpty())
{
VerifySpriteTagsLoaded();
tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this));
tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f);
tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f);
@@ -61,7 +62,7 @@ namespace Barotrauma
//Stretch = true
};
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.322f), paddedFrame.RectTransform), isHorizontal: true);
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawInfoFrameCharacterIcon(sb, component.Rect));
@@ -185,7 +186,7 @@ namespace Barotrauma
private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect)
{
if (_headSprite == null) { return; }
if (HeadSprite == null) { return; }
Vector2 targetAreaSize = componentRect.Size.ToVector2();
float scale = Math.Min(targetAreaSize.X / _headSprite.size.X, targetAreaSize.Y / _headSprite.size.Y);
DrawIcon(sb, componentRect.Location.ToVector2() + _headSprite.size / 2 * scale, targetAreaSize);
@@ -537,8 +538,7 @@ namespace Barotrauma
Color skinColor = inc.ReadColorR8G8B8();
Color hairColor = inc.ReadColorR8G8B8();
Color facialHairColor = inc.ReadColorR8G8B8();
string ragdollFile = inc.ReadString();
Identifier npcId = inc.ReadIdentifier();
Identifier factionId = inc.ReadIdentifier();
@@ -560,18 +560,15 @@ namespace Barotrauma
throw new Exception($"Error while reading {nameof(CharacterInfo)} received from the server: could not find a job prefab with the identifier \"{jobIdentifier}\".");
}
byte skillCount = inc.ReadByte();
List<SkillPrefab> jobSkills = jobPrefab?.Skills.OrderBy(s => s.Identifier).ToList();
for (int i = 0; i < skillCount; i++)
{
Identifier skillIdentifier = inc.ReadIdentifier();
float skillLevel = inc.ReadSingle();
if (jobSkills != null && i < jobSkills.Count)
{
skillLevels.Add(jobSkills[i].Identifier, skillLevel);
}
skillLevels.Add(skillIdentifier, skillLevel);
}
}
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId)
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, variant, npcIdentifier: npcId)
{
ID = infoID,
MinReputationToHire = (factionId, minReputationToHire)
@@ -1,5 +1,6 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Immutable;
@@ -532,6 +533,47 @@ namespace Barotrauma
bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
}
break;
case EventType.LatchOntoTarget:
bool attached = msg.ReadBoolean();
if (attached)
{
Vector2 characterSimPos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
Vector2 attachSurfaceNormal = new Vector2(msg.ReadSingle(), msg.ReadSingle());
Vector2 attachPos = new Vector2(msg.ReadSingle(), msg.ReadSingle());
int attachWallIndex = msg.ReadInt32();
UInt16 attachTargetId = msg.ReadUInt16();
if (AIController is EnemyAIController { LatchOntoAI: { } latchOntoAi })
{
var attachTargetEntity = FindEntityByID(attachTargetId);
switch (attachTargetEntity)
{
case Character attachTargetCharacter:
latchOntoAi.SetAttachTarget(attachTargetCharacter);
break;
case Structure attachTargetStructure:
latchOntoAi.SetAttachTarget(attachTargetStructure, attachPos, attachSurfaceNormal);
break;
default:
var allLevelWalls = Level.Loaded.GetAllCells();
if (attachWallIndex >= 0 && attachWallIndex <= allLevelWalls.Count)
{
latchOntoAi.SetAttachTarget(allLevelWalls[attachWallIndex]);
}
break;
}
latchOntoAi.AttachToBody(attachPos, attachSurfaceNormal, characterSimPos);
}
}
else
{
if (AIController is EnemyAIController { LatchOntoAI: { } latchOntoAi })
{
latchOntoAi.DeattachFromBody(reset: false);
}
}
break;
}
msg.ReadPadBits();
@@ -858,8 +858,8 @@ namespace Barotrauma
foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children)
{
var treatmentButton = component.GetChild<GUIButton>();
if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; }
var matchingItem = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true);
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)
{
@@ -1387,7 +1387,6 @@ namespace Barotrauma
//float = suitability
Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>();
GetSuitableTreatments(treatmentSuitability,
normalize: true,
user: Character.Controlled,
ignoreHiddenAfflictions: true,
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
@@ -1421,9 +1420,12 @@ namespace Barotrauma
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.Find(name: null, identifier: treatment.Key, showErrorMessages: false) is ItemPrefab item)) { continue; }
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)
@@ -1439,7 +1441,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) =>
{
if (userdata is not ItemPrefab itemPrefab) { return false; }
var item = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true);
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);
@@ -0,0 +1,328 @@
#nullable enable
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using Barotrauma.Items.Components;
namespace Barotrauma;
public static class InteractionLabelManager
{
private class LabelData
{
private readonly Camera drawCamera;
public readonly Item Item;
public RectangleF TextRect { get; set; }
public readonly Vector2 OriginalItemPosition;
public bool OverlapPreventionDone;
public LabelData(Item item, RectangleF textRect, Camera drawCamera)
{
Item = item;
TextRect = textRect;
OriginalItemPosition = item.Position;
this.drawCamera = drawCamera;
}
public RectangleF GetScreenDrawRect(Camera cam)
{
float scale = cam.Zoom;
RectangleF screenDrawRect = TextRect;
screenDrawRect.Location = drawCamera
.WorldToScreen(screenDrawRect.Location + (Item.Submarine?.DrawPosition ?? Vector2.Zero));
return new RectangleF(
screenDrawRect.X,
screenDrawRect.Y,
screenDrawRect.Width * scale,
screenDrawRect.Height * scale);
}
public Vector2 GetInteractableDrawPositionScreen()
{
return drawCamera.WorldToScreen(Item.DrawPosition);
}
}
private static readonly List<LabelData> labels = new();
private const int TextBoxMarginPx = 4;
/// <summary>
/// Multiplier on the scale of the labels. Ad-hoc formula: since the zoom affects the size of the labels,
/// and high resolutions are more zoomed in to keep the view range the same, let's scale down the labels on large resolutions to compensate.
/// </summary>
private static float LabelScale => 1.0f / GUI.Scale;
private static InteractionLabelDisplayMode displayMode;
private static int graphicsWidth, graphicsHeight;
private static bool shouldRecalculate;
private static bool recalculateEverything;
private static readonly List<Item> interactablesInRange = new();
internal static Item? HoveredItem { get; private set; }
internal static void RefreshInteractablesInRange(List<Item> interactables)
{
interactablesInRange.Clear();
interactablesInRange.AddRange(interactables);
shouldRecalculate = true;
}
private static void RecalculateLabelPositions(Camera cam, Character character)
{
if (recalculateEverything)
{
labels.Clear();
recalculateEverything = false;
}
labels.RemoveAll(l => !interactablesInRange.Contains(l.Item));
// for every interactable, create a label data object with relevant info for real-time drawing
foreach (var interactableInRange in interactablesInRange)
{
// this removes the hidden vents from the list
if (interactableInRange.HasTag(Tags.HiddenItemContainer)) { continue; }
// filter out items depending on visibility filter setting
switch (displayMode)
{
case InteractionLabelDisplayMode.InteractionAvailable when !interactableInRange.HasVisibleInteraction(character):
case InteractionLabelDisplayMode.LooseItems when !IsLooseItem(interactableInRange):
continue;
}
RectangleF textRect = GetLabelRect(interactableInRange, cam);
if (labels.None(l => l.Item == interactableInRange))
{
var labelData = new LabelData(interactableInRange, textRect, cam);
labels.Add(labelData);
}
}
PreventInteractionLabelOverlap(centerPos: character.Position);
}
private static bool IsLooseItem(Item item)
{
bool hasActivePhysics = item.body is { Enabled: true };
bool hasPickableComponent = item.GetComponent<Pickable>() != null;
return hasActivePhysics && hasPickableComponent;
}
private static RectangleF GetLabelRect(Item item, Camera cam)
{
// create rectangle for overlap prevention
Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(item.Name) * LabelScale;
Vector2 interactablePosScreen = cam.WorldToScreen(item.Position);
RectangleF textRect = new RectangleF(interactablePosScreen.X, interactablePosScreen.Y, itemTextSizeScreen.X, itemTextSizeScreen.Y);
// center the rectangle on the item
textRect.X -= textRect.Width / 2;
textRect.Y += textRect.Height / 2;
// inflate by a bit, because the text is drawn with padding
textRect.Inflate(TextBoxMarginPx * LabelScale, TextBoxMarginPx * LabelScale);
// the rect has screen space size, and sub-relative position
textRect.Location = cam.ScreenToWorld(textRect.Location);
return textRect;
}
private static void PreventInteractionLabelOverlap(Vector2 centerPos)
{
//sort by distance from "centerPos": moving labels further away from the character (or whatever the center is) is preferred
labels.Sort((l1, l2) =>
Vector2.DistanceSquared(l1.TextRect.Center, centerPos).CompareTo(
Vector2.DistanceSquared(l2.TextRect.Center, centerPos)));
const float MoveStep = 10.0f;
bool intersections = true;
int iterations = 0;
int maxIterations = System.Math.Max(labels.Count * labels.Count, 100);
while (intersections && iterations < maxIterations)
{
intersections = false;
foreach (var label in labels)
{
if (label.OverlapPreventionDone) { continue; }
foreach (var otherLabel in labels)
{
if (label == otherLabel) { continue; }
//allow labels to overlap if there's multiple instances of the same item at (roughly) the same position
if (label.Item.Prefab == otherLabel.Item.Prefab &&
Vector2.DistanceSquared(label.Item.WorldPosition, otherLabel.Item.WorldPosition) < 1.0f)
{
continue;
}
if (!label.TextRect.Intersects(otherLabel.TextRect))
{
continue;
}
intersections = true;
Vector2 moveAmount = Vector2.Normalize(label.TextRect.Center - centerPos) * MoveStep;
label.TextRect = new RectangleF(label.TextRect.Location + moveAmount, label.TextRect.Size);
}
if (intersections) { break; }
}
iterations++;
}
foreach (var labelData in labels)
{
labelData.OverlapPreventionDone = true;
}
}
private static int GetMouseHoveredLabelIndex(Camera cam)
{
for (int i = 0; i < labels.Count; i++)
{
var labelData = labels[i];
var drawRect = labelData.GetScreenDrawRect(cam);
if (drawRect.Contains(PlayerInput.MousePosition))
{
return i;
}
}
return -1;
}
private static bool RefreshSettings()
{
bool settingsChanged = false;
if (GameSettings.CurrentConfig.InteractionLabelDisplayMode != displayMode)
{
displayMode = GameSettings.CurrentConfig.InteractionLabelDisplayMode;
settingsChanged = true;
}
if (GameMain.GraphicsWidth != graphicsWidth || GameMain.GraphicsHeight != graphicsHeight)
{
graphicsWidth = GameMain.GraphicsWidth;
graphicsHeight = GameMain.GraphicsHeight;
settingsChanged = true;
}
return settingsChanged;
}
internal static void Update(Character character, Camera cam)
{
if (RefreshSettings()) { shouldRecalculate = true; recalculateEverything = true; }
if (shouldRecalculate)
{
RecalculateLabelPositions(cam, character);
}
}
internal static void DrawLabels(SpriteBatch spriteBatch, Camera cam, Character character)
{
//if any item changes subs or moves significantly, we need to recalculate the label position
foreach (var label in labels)
{
const float MoveThreshold = 150.0f;
if (Vector2.DistanceSquared(label.OriginalItemPosition, label.Item.Position) > MoveThreshold * MoveThreshold)
{
label.TextRect = GetLabelRect(label.Item, cam);
}
}
// find out if mouse is on top of any of the labels
int mouseOnLabelIndex = GetMouseHoveredLabelIndex(cam);
bool isMouseOnLabel = mouseOnLabelIndex >= 0;
const float LineAlpha = 0.5f;
if (!isMouseOnLabel)
{
HoveredItem = null;
}
// draw order: draw lines for labels first
for (int i = 0; i < labels.Count; i++)
{
// Skip the box that the mouse is on, it will be drawn last
if (i == mouseOnLabelIndex) { continue; }
DrawLineForLabel(spriteBatch, cam, labels[i], GUIStyle.InteractionLabelColor * LineAlpha);
}
// Then draw labels
for (int i = 0; i < labels.Count; i++)
{
// Skip the box that the mouse is on, it will be drawn last
if (i == mouseOnLabelIndex) { continue; }
DrawLabelForItem(spriteBatch, cam, labels[i], GUIStyle.InteractionLabelColor);
}
// Draw the label and line that the mouse is on last (for draw order)
if (isMouseOnLabel)
{
var labelData = labels[mouseOnLabelIndex];
HoveredItem = labelData.Item;
DrawLineForLabel(spriteBatch, cam, labelData, GUIStyle.InteractionLabelHoverColor * LineAlpha);
DrawLabelForItem(spriteBatch, cam,labelData, GUIStyle.InteractionLabelHoverColor);
}
}
private static void DrawLineForLabel(SpriteBatch spriteBatch, Camera cam, LabelData labelData, Color color)
{
var drawRect = labelData.GetScreenDrawRect(cam);
// deflate by one pixel to avoid gap between line and box graphic edge
const int lineAnchorInsetPx = 1;
var deflateAmount = lineAnchorInsetPx * GUI.Scale;
deflateAmount = MathHelper.Max(deflateAmount * Screen.Selected.Cam.Zoom, lineAnchorInsetPx);
drawRect.Inflate(-deflateAmount, -deflateAmount);
var itemDrawPosScreen = labelData.GetInteractableDrawPositionScreen();
// if item position is inside the box, don't draw a line
if (drawRect.Contains(itemDrawPosScreen)) { return; }
// find the point on the box edge that is closest to the item
Vector2 textLineAnchorScreenPos = new Vector2(
MathHelper.Clamp(itemDrawPosScreen.X, drawRect.Left, drawRect.Right),
MathHelper.Clamp(itemDrawPosScreen.Y, drawRect.Top, drawRect.Bottom));
// draw line from label to item in the world
GUI.DrawLine(spriteBatch, textLineAnchorScreenPos, itemDrawPosScreen, color, depth: 0f, width: 2f);
}
private static void DrawLabelForItem(SpriteBatch spriteBatch, Camera cam, LabelData labelData, Color color)
{
float scale = Screen.Selected.Cam.Zoom * LabelScale;
var textDrawRect = labelData.GetScreenDrawRect(cam);
RectangleF backgroundRect = textDrawRect;
// remove margin from the box the text is drawn in
textDrawRect.Inflate(-TextBoxMarginPx * scale, -TextBoxMarginPx * scale);
Vector2 textDrawPosScreen = new Vector2(textDrawRect.X, textDrawRect.Y);
GUIStyle.InteractionLabelBackground.Draw(spriteBatch, backgroundRect, color * 0.7f);
GUIStyle.SubHeadingFont.DrawString(spriteBatch,
labelData.Item.Name,
textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None, layerDepth: 0.0f,
forceUpperCase: ForceUpperCase.No);
}
}
@@ -483,9 +483,11 @@ namespace Barotrauma
//2. check if the base prefab defines the texture
if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty)
{
Identifier speciesName = character.GetBaseCharacterSpeciesName();
RagdollParams parentRagdollParams = character.IsHumanoid ?
RagdollParams.GetRagdollParams<HumanRagdollParams>(character.Prefab.VariantOf) :
RagdollParams.GetRagdollParams<FishRagdollParams>(character.Prefab.VariantOf);
RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(speciesName, character.Params, character.Prefab.ContentPackage) :
RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(speciesName, character.Params, character.Prefab.ContentPackage);
texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture");
}
//3. "default case", get the texture from this character's XML
@@ -514,8 +516,8 @@ namespace Barotrauma
if (characterInfo != null)
{
spritePath = characterInfo.ReplaceVars(spritePath);
if (characterInfo.HeadSprite != null && characterInfo.SpriteTags.Any())
characterInfo.VerifySpriteTagsLoaded();
if (characterInfo.SpriteTags.Any())
{
string tags = "";
characterInfo.SpriteTags.ForEach(tag => tags += $"[{tag}]");
@@ -695,7 +697,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{
var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; }
if (spriteParams == null || Alpha <= 0) { return; }
float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
float brightness = Math.Max(1.0f - burn, 0.2f);
Color clr = spriteParams.Color;
@@ -724,6 +726,8 @@ namespace Barotrauma
color = overrideColor ?? color;
blankColor = overrideColor ?? blankColor;
color *= Alpha;
blankColor *= Alpha;
if (isSevered)
{
@@ -779,7 +783,7 @@ namespace Barotrauma
else
{
bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null;
if (useTintMask)
if (useTintMask && Sprite?.Texture != null && TintMask?.Texture != null)
{
tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
tintEffectParams.Params ??= new Dictionary<string, object>();