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
@@ -277,7 +277,7 @@ namespace Barotrauma
velocity = Vector2.Lerp(velocity, moveInput, deltaTime * 10.0f); velocity = Vector2.Lerp(velocity, moveInput, deltaTime * 10.0f);
moveCam = velocity * moveSpeed * deltaTime * FreeCamMoveSpeed * 60.0f; moveCam = velocity * moveSpeed * deltaTime * FreeCamMoveSpeed * 60.0f;
if (Screen.Selected == GameMain.GameScreen && (followSub ?? FollowSub)) if (Screen.Selected == GameMain.GameScreen && (followSub ?? FollowSub) && GameMain.Instance is not { Paused: true })
{ {
var closestSub = Submarine.FindClosest(WorldViewCenter); var closestSub = Submarine.FindClosest(WorldViewCenter);
if (closestSub != null) if (closestSub != null)
@@ -109,7 +109,7 @@ namespace Barotrauma
} }
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); 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 > errorTolerance)
{ {
if (distSqrd > 10.0f || !character.CanMove) if (distSqrd > 10.0f || !character.CanMove)
@@ -1,3 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
@@ -9,7 +10,6 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -42,6 +42,8 @@ namespace Barotrauma
private set; private set;
} = true; } = true;
public bool ShowInteractionLabels { get; private set; }
//the Character that the player is currently controlling //the Character that the player is currently controlling
private static Character controlled; private static Character controlled;
@@ -230,14 +232,51 @@ namespace Barotrauma
} }
} }
private float pressureEffectTimer;
private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>(); private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>();
public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities
{ {
get { return 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) partial void InitProjSpecific(ContentXElement mainElement)
{ {
soundTimer = Rand.Range(0.0f, Params.SoundInterval); 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; private bool wasFiring;
/// <summary> /// <summary>
@@ -279,6 +321,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true) public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{ {
if (DisableControls || GUI.InputBlockingMenuOpen) if (DisableControls || GUI.InputBlockingMenuOpen)
{ {
foreach (Key key in keys) 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) //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 //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) if (wasFiring && !keys[(int)InputType.Shoot].Held)
@@ -549,19 +599,61 @@ namespace Barotrauma
if (Lights.LightManager.ViewTarget == this) { Lights.LightManager.ViewTarget = null; } 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> debugInteractablesInRange = new List<Item>();
private readonly List<Item> debugInteractablesAtCursor = 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)>(); private readonly List<(Item item, float dist)> debugInteractablesNearCursor = new List<(Item item, float dist)>();
/// <summary> /// <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> /// </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="itemCollection">Item collection to look in</param>
/// <param name="simPosition">The item at the simPosition, with the lowest depth, is returned</param> /// <param name="simPosition">sim position for distance comparison (such as mouse position)</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="aimAssistModifier">aim assist modifier</param>
/// <param name="hull">If a hull is specified, only items within that hull are returned</param> /// <returns></returns>
public Item FindItemAtPosition(Vector2 simPosition, float aimAssistModifier = 0.0f, Item[] ignoredItems = null) public Item FindClosestItem(List<Item> itemCollection, Vector2 simPosition, float aimAssistModifier = 0.0f)
{ {
if (Submarine != null) if (Submarine != null)
{ {
@@ -581,23 +673,10 @@ namespace Barotrauma
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition); Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
//use the list of visible entities if it exists
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
Item closestItem = null; Item closestItem = null;
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f); 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 (draggingItemToWorld)
{ {
if (item.OwnInventory == null || if (item.OwnInventory == null ||
@@ -661,7 +740,7 @@ namespace Barotrauma
Character closestCharacter = null; Character closestCharacter = null;
maxDist = ConvertUnits.ToSimUnits(maxDist); maxDist = ConvertUnits.ToSimUnits(maxDist);
float closestDist = maxDist * maxDist; float closestDist = maxDist;
foreach (Character c in CharacterList) foreach (Character c in CharacterList)
{ {
if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; } if (!CanInteractWith(c, checkVisibility: false) || (c.AnimController?.SimplePhysicsEnabled ?? true)) { continue; }
@@ -704,6 +783,12 @@ namespace Barotrauma
} }
guiMessages.RemoveAll(m => m.Timer >= m.Lifetime); guiMessages.RemoveAll(m => m.Timer >= m.Lifetime);
if (textlessSpeechBubble != null)
{
textlessSpeechBubble.LifeTime -= deltaTime;
if (textlessSpeechBubble.LifeTime <= 0) { textlessSpeechBubble = null; }
}
if (!enabled) { return; } if (!enabled) { return; }
if (!IsIncapacitated) if (!IsIncapacitated)
@@ -890,13 +975,6 @@ namespace Barotrauma
pos.Y = -pos.Y; 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 (this == controlled)
{ {
if (DebugDrawInteract) if (DebugDrawInteract)
@@ -921,113 +999,232 @@ namespace Barotrauma
ToolBox.GradientLerp(dist, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green), width: 2); ToolBox.GradientLerp(dist, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green), width: 2);
} }
} }
return;
} }
else
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)
{ {
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 (info != null)
if (controlled == null && name != Info.Name)
{ {
name += " " + TextManager.Get("Disguised"); LocalizedString name = Info.DisplayName;
} if (controlled == null && name != Info.Name)
else if (Info.Title != null && TeamID != CharacterTeamType.Team1) {
{ name += " " + TextManager.Get("Disguised");
name += '\n' + Info.Title; }
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); var petBehavior = (AIController as EnemyAIController)?.PetBehavior;
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; if (petBehavior != null && !IsDead && !IsUnconscious)
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); var petStatus = petBehavior.GetCurrentStatusIndicatorType();
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null) if (iconStyle != null)
{ {
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f; Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos; Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y; iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First(); 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); 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); if (IsDead) { return; }
GUIStyle.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw) 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 (Params.ShowHealthBar && CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
if (petBehavior != null && !IsDead && !IsUnconscious)
{ {
var petStatus = petBehavior.GetCurrentStatusIndicatorType(); hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
var iconStyle = GUIStyle.GetComponentStyle("PetIcon." + petStatus);
if (iconStyle != null) Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
{ GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.body?.DrawPosition ?? DrawPosition + Vector2.UnitY * 100.0f; CharacterHealth.DisplayedVitality / MaxVitality,
Vector2 iconPos = headPos; Color.Lerp(GUIStyle.Red, GUIStyle.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha,
iconPos.Y = -iconPos.Y; new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
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);
}
} }
} }
if (IsDead) { return; } if (textlessSpeechBubble != null)
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode != EnemyHealthBarMode.ShowAll)
{ {
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) || alpha *= Math.Max(mouseDist / textSize, 0.5f);
(AIController is HumanAIController humanAi && humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective && HumanAIController.IsFriendly(Controlled, combatObjective.Enemy))) }
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];
{ {
return; 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() public Color GetNameColor()
@@ -207,7 +207,7 @@ namespace Barotrauma
{ {
cachedHudTexts.Clear(); 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; } if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value; text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
cachedHudTexts.Add(key, text); cachedHudTexts.Add(key, text);
@@ -246,6 +246,7 @@ namespace Barotrauma
public static void Update(float deltaTime, Character character, Camera cam) public static void Update(float deltaTime, Character character, Camera cam)
{ {
UpdateBossProgressBars(deltaTime); UpdateBossProgressBars(deltaTime);
if (GUI.DisableHUD) if (GUI.DisableHUD)
@@ -257,6 +258,11 @@ namespace Barotrauma
return; return;
} }
if (character.ShowInteractionLabels && character.ViewTarget == null)
{
InteractionLabelManager.Update(character, cam);
}
if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
{ {
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen) if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
@@ -375,7 +381,7 @@ namespace Barotrauma
static bool DrawIcon(Order o) => static bool DrawIcon(Order o) =>
o != null && o != null &&
(!(o.TargetEntity is Item i) || (o.TargetEntity is not Item i ||
o.DrawIconWhenContained || o.DrawIconWhenContained ||
i.GetRootInventoryOwner() == i); i.GetRootInventoryOwner() == i);
} }
@@ -409,6 +415,20 @@ namespace Barotrauma
Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); 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 GetDistanceBasedIconAlpha(ISpatialEntity target, float maxDistance = 1000.0f)
{ {
float dist = Vector2.Distance(character.WorldPosition, target.WorldPosition); float dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
@@ -417,80 +437,91 @@ namespace Barotrauma
if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen && (!character.IsKeyDown(InputType.Aim) || character.HeldItems.None(it => it?.GetComponent<Sprayer>() != null))) 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.FocusedCharacter is { CanBeSelected: true })
}
if (character.FocusedItem != null)
{
if (focusedItem != character.FocusedItem)
{ {
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer); DrawCharacterHoverTexts(spriteBatch, cam, character);
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) if (character.FocusedItem != null)
{ {
bool hudTextsContextual = PlayerInput.IsShiftDown(); if (focusedItem != character.FocusedItem)
if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
{ {
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
RecreateHudTexts = true; RecreateHudTexts = true;
lastHudTextsContextual = hudTextsContextual;
} }
var hudTexts = focusedItem.GetHUDTexts(character, RecreateHudTexts); focusedItem = character.FocusedItem;
RecreateHudTexts = false; }
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X); if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay)
{
Vector2 textSize = GUIStyle.Font.MeasureString(hudTexts.First().Text); Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition);
Vector2 largeTextSize = GUIStyle.SubHeadingFont.MeasureString(hudTexts.First().Text); 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);
Vector2 startPos = cam.WorldToScreen(focusedItem.DrawPosition); if (circleSize > 0.0f)
startPos.Y -= (hudTexts.Count + 1) * textSize.Y;
if (focusedItem.Sprite != null)
{ {
startPos.X += (int)(circleSize * 0.4f * dir); Vector2 scale = new Vector2(circleSize / GUIStyle.FocusIndicator.FrameSize.X);
startPos.Y -= (int)(circleSize * 0.4f); 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 (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
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); bool hudTextsContextual = PlayerInput.KeyDown(InputType.ContextualCommand);
GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color * alpha, Color.Black * alpha * 0.7f, 2, GUIStyle.SmallFont); if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
textPos.Y += textSize.Y; {
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) foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
{ {
progressBar.Draw(spriteBatch, cam); progressBar.Draw(spriteBatch, cam);
@@ -673,6 +704,8 @@ namespace Barotrauma
} }
} }
public static bool MouseOnCharacterPortrait() public static bool MouseOnCharacterPortrait()
{ {
if (Character.Controlled == null) { return false; } if (Character.Controlled == null) { return false; }
@@ -1,4 +1,4 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
using System; using System;
using System.Linq; using System.Linq;
@@ -46,6 +46,7 @@ namespace Barotrauma
ContentPath tintMaskPath = maskElement.GetAttributeContentPath("texture"); ContentPath tintMaskPath = maskElement.GetAttributeContentPath("texture");
if (!tintMaskPath.IsNullOrEmpty()) if (!tintMaskPath.IsNullOrEmpty())
{ {
VerifySpriteTagsLoaded();
tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this)); tintMask = new Sprite(maskElement, file: Limb.GetSpritePath(tintMaskPath, this));
tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f); tintHighlightThreshold = maskElement.GetAttributeFloat("highlightthreshold", 0.6f);
tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f); tintHighlightMultiplier = maskElement.GetAttributeFloat("highlightmultiplier", 0.8f);
@@ -61,7 +62,7 @@ namespace Barotrauma
//Stretch = true //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), new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawInfoFrameCharacterIcon(sb, component.Rect)); onDraw: (sb, component) => DrawInfoFrameCharacterIcon(sb, component.Rect));
@@ -185,7 +186,7 @@ namespace Barotrauma
private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect) private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect)
{ {
if (_headSprite == null) { return; } if (HeadSprite == null) { return; }
Vector2 targetAreaSize = componentRect.Size.ToVector2(); Vector2 targetAreaSize = componentRect.Size.ToVector2();
float scale = Math.Min(targetAreaSize.X / _headSprite.size.X, targetAreaSize.Y / _headSprite.size.Y); float scale = Math.Min(targetAreaSize.X / _headSprite.size.X, targetAreaSize.Y / _headSprite.size.Y);
DrawIcon(sb, componentRect.Location.ToVector2() + _headSprite.size / 2 * scale, targetAreaSize); DrawIcon(sb, componentRect.Location.ToVector2() + _headSprite.size / 2 * scale, targetAreaSize);
@@ -538,7 +539,6 @@ namespace Barotrauma
Color hairColor = inc.ReadColorR8G8B8(); Color hairColor = inc.ReadColorR8G8B8();
Color facialHairColor = inc.ReadColorR8G8B8(); Color facialHairColor = inc.ReadColorR8G8B8();
string ragdollFile = inc.ReadString();
Identifier npcId = inc.ReadIdentifier(); Identifier npcId = inc.ReadIdentifier();
Identifier factionId = 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}\"."); 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(); byte skillCount = inc.ReadByte();
List<SkillPrefab> jobSkills = jobPrefab?.Skills.OrderBy(s => s.Identifier).ToList();
for (int i = 0; i < skillCount; i++) for (int i = 0; i < skillCount; i++)
{ {
Identifier skillIdentifier = inc.ReadIdentifier();
float skillLevel = inc.ReadSingle(); float skillLevel = inc.ReadSingle();
if (jobSkills != null && i < jobSkills.Count) skillLevels.Add(skillIdentifier, skillLevel);
{
skillLevels.Add(jobSkills[i].Identifier, 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, ID = infoID,
MinReputationToHire = (factionId, minReputationToHire) MinReputationToHire = (factionId, minReputationToHire)
@@ -1,5 +1,6 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -532,6 +533,47 @@ namespace Barotrauma
bool removeOnDeath = msg.ReadBoolean(); bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true); 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; break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
@@ -858,8 +858,8 @@ namespace Barotrauma
foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children) foreach (GUIComponent component in recommendedTreatmentContainer.Content.Children)
{ {
var treatmentButton = component.GetChild<GUIButton>(); var treatmentButton = component.GetChild<GUIButton>();
if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; } if (treatmentButton?.UserData is not ItemPrefab itemPrefab) { continue; }
var matchingItem = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true); var matchingItem = AIObjectiveRescue.FindMedicalItem(Character.Controlled.Inventory, itemPrefab.Identifier);
treatmentButton.Enabled = matchingItem != null; treatmentButton.Enabled = matchingItem != null;
if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover) if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover)
{ {
@@ -1387,7 +1387,6 @@ namespace Barotrauma
//float = suitability //float = suitability
Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>(); Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>();
GetSuitableTreatments(treatmentSuitability, GetSuitableTreatments(treatmentSuitability,
normalize: true,
user: Character.Controlled, user: Character.Controlled,
ignoreHiddenAfflictions: true, ignoreHiddenAfflictions: true,
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
@@ -1421,9 +1420,12 @@ namespace Barotrauma
int count = 0; int count = 0;
foreach (KeyValuePair<Identifier, float> treatment in treatmentSuitabilities) foreach (KeyValuePair<Identifier, float> treatment in treatmentSuitabilities)
{ {
//don't list negative treatments
if (treatment.Value < 0) { continue; }
count++; count++;
if (count > 5) { break; } 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), var itemSlot = new GUIFrame(new RectTransform(new Vector2(1.0f / 6.0f, 1.0f), recommendedTreatmentContainer.Content.RectTransform, Anchor.TopLeft),
style: null) style: null)
@@ -1439,7 +1441,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (userdata is not ItemPrefab itemPrefab) { return false; } 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; } if (item == null) { return false; }
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex);
item.ApplyTreatment(Character.Controlled, Character, targetLimb); 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 //2. check if the base prefab defines the texture
if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty) if (texturePath.IsNullOrEmpty() && !character.Prefab.VariantOf.IsEmpty)
{ {
Identifier speciesName = character.GetBaseCharacterSpeciesName();
RagdollParams parentRagdollParams = character.IsHumanoid ? RagdollParams parentRagdollParams = character.IsHumanoid ?
RagdollParams.GetRagdollParams<HumanRagdollParams>(character.Prefab.VariantOf) : RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(speciesName, character.Params, character.Prefab.ContentPackage) :
RagdollParams.GetRagdollParams<FishRagdollParams>(character.Prefab.VariantOf); RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(speciesName, character.Params, character.Prefab.ContentPackage);
texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture"); texturePath = parentRagdollParams.OriginalElement?.GetAttributeContentPath("texture");
} }
//3. "default case", get the texture from this character's XML //3. "default case", get the texture from this character's XML
@@ -514,8 +516,8 @@ namespace Barotrauma
if (characterInfo != null) if (characterInfo != null)
{ {
spritePath = characterInfo.ReplaceVars(spritePath); spritePath = characterInfo.ReplaceVars(spritePath);
characterInfo.VerifySpriteTagsLoaded();
if (characterInfo.HeadSprite != null && characterInfo.SpriteTags.Any()) if (characterInfo.SpriteTags.Any())
{ {
string tags = ""; string tags = "";
characterInfo.SpriteTags.ForEach(tag => tags += $"[{tag}]"); 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) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null, bool disableDeformations = false)
{ {
var spriteParams = Params.GetSprite(); var spriteParams = Params.GetSprite();
if (spriteParams == null) { return; } if (spriteParams == null || Alpha <= 0) { return; }
float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength; float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
float brightness = Math.Max(1.0f - burn, 0.2f); float brightness = Math.Max(1.0f - burn, 0.2f);
Color clr = spriteParams.Color; Color clr = spriteParams.Color;
@@ -724,6 +726,8 @@ namespace Barotrauma
color = overrideColor ?? color; color = overrideColor ?? color;
blankColor = overrideColor ?? blankColor; blankColor = overrideColor ?? blankColor;
color *= Alpha;
blankColor *= Alpha;
if (isSevered) if (isSevered)
{ {
@@ -779,7 +783,7 @@ namespace Barotrauma
else else
{ {
bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null; bool useTintMask = TintMask != null && spriteBatch.GetCurrentEffect() is null;
if (useTintMask) if (useTintMask && Sprite?.Texture != null && TintMask?.Texture != null)
{ {
tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect; tintEffectParams.Effect ??= GameMain.GameScreen.ThresholdTintEffect;
tintEffectParams.Params ??= new Dictionary<string, object>(); tintEffectParams.Params ??= new Dictionary<string, object>();
@@ -65,7 +65,7 @@ namespace Barotrauma
bool isEditor = Screen.Selected is { IsEditor: true }; bool isEditor = Screen.Selected is { IsEditor: true };
GUILayoutGroup titleHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), listBox.Content.RectTransform)); GUILayoutGroup titleHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), listBox.Content.RectTransform));
new GUITextBlock(new RectTransform(Vector2.One, titleHolder.RectTransform), Item.Name, font: GUIStyle.LargeFont) new GUITextBlock(new RectTransform(Vector2.One, titleHolder.RectTransform), Item.Prefab.Name, font: GUIStyle.LargeFont)
{ {
TextColor = Color.White, TextColor = Color.White,
Color = Color.Black Color = Color.Black
@@ -84,7 +84,10 @@ namespace Barotrauma
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style: "HorizontalLine");
var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame: !isEditor, showName: false, titleFont: GUIStyle.SubHeadingFont); var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame: !isEditor, showName: false, titleFont: GUIStyle.SubHeadingFont)
{
Readonly = CircuitBox.Locked
};
fieldCount += componentEditor.Fields.Count; fieldCount += componentEditor.Fields.Count;
ic.CreateEditingHUD(componentEditor); ic.CreateEditingHUD(componentEditor);
@@ -0,0 +1,184 @@
#nullable enable
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal sealed partial class CircuitBoxLabelNode
{
private CircuitBoxLabel headerLabel;
private readonly GUITextBlock bodyLabel;
private const string PromptUserData = "LabelEditPrompt";
public override void DrawHeader(SpriteBatch spriteBatch, RectangleF rect, Color color)
{
GUI.DrawString(spriteBatch, new Vector2(rect.X + CircuitBoxSizes.NodeHeaderTextPadding, rect.Center.Y - headerLabel.Size.Y / 2f), headerLabel.Value, GUIStyle.TextColorNormal, font: GUIStyle.LargeFont);
}
public override void DrawBody(SpriteBatch spriteBatch, RectangleF rect, Color color)
{
bodyLabel.TextOffset = rect.Location - bodyLabel.Rect.Location.ToVector2() + new Vector2(CircuitBoxSizes.NodeBodyTextPadding);
bodyLabel.DrawManually(spriteBatch);
}
public override void OnResized(RectangleF rect)
=> UpdateTextSizes(rect);
private void UpdateTextSizes(RectangleF rect)
{
var size = new Point((int)rect.Width - CircuitBoxSizes.NodeBodyTextPadding * 2, (int)rect.Height - CircuitBoxSizes.NodeBodyTextPadding * 2);
bodyLabel.RectTransform.NonScaledSize = size;
bodyLabel.Text = GetLocalizedText(BodyText);
if (bodyLabel.Font != null)
{
bodyLabel.Text = ToolBox.LimitStringHeight(bodyLabel.WrappedText.Value, bodyLabel.Font!, size.Y);
}
headerLabel = new CircuitBoxLabel(ToolBox.LimitString(GetLocalizedText(HeaderText), GUIStyle.LargeFont, size.X), GUIStyle.LargeFont);
static LocalizedString GetLocalizedText(NetLimitedString text) => TextManager.Get(text.Value).Fallback(text.Value);
}
public void PromptEditText(GUIComponent parent)
{
Color newColor = Color;
CircuitBox.UI?.SetMenuVisibility(false);
GUIFrame backgroundBlocker = new(new RectTransform(Vector2.One, parent.RectTransform), style: "GUIBackgroundBlocker")
{
UserData = PromptUserData
};
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.8f), backgroundBlocker.RectTransform, Anchor.Center), isHorizontal: false, childAnchor: Anchor.TopCenter);
GUILayoutGroup colorLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), mainLayout.RectTransform));
new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), colorLayout.RectTransform)) { IgnoreLayoutGroups = true };
GUILayoutGroup colorArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), colorLayout.RectTransform), isHorizontal: true);
GUIFrame labelArea = new(new RectTransform(new Vector2(1f, 0.65f), mainLayout.RectTransform, Anchor.Center));
GUIFrame header = new GUIFrame(new RectTransform(new Vector2(1f, 0.15f), labelArea.RectTransform, Anchor.TopLeft), style: "CircuitBoxTop");
GUIFrame frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.86f), labelArea.RectTransform, Anchor.BottomLeft), style: "CircuitBoxFrame");
header.Color = frame.Color = Color;
GUITextBox headerTextBox = new GUITextBox(new RectTransform(Vector2.One, header.RectTransform, Anchor.Center), text: HeaderText.Value, font: headerLabel.Font, style: "GUITextBoxNoStyle")
{
MaxTextLength = NetLimitedString.MaxLength,
Text = HeaderText.Value
};
GUITextBox bodyTextBox = new GUITextBox(new RectTransform(ToolBox.PaddingSizeParentRelative(frame.RectTransform, 0.95f), frame.RectTransform, Anchor.Center), text: BodyText.Value, font: GUIStyle.Font, style: "GUITextBoxNoStyle", textAlignment: Alignment.TopLeft, wrap: true)
{
MaxTextLength = NetLimitedString.MaxLength
};
bodyTextBox.OnEnterPressed += (textBox, text) =>
{
int caretIndex = textBox.CaretIndex;
textBox.Text = $"{text[..caretIndex]}\n{text[caretIndex..]}";
textBox.CaretIndex = caretIndex + 1;
return true;
};
static void UpdateLabelColor(GUITextBox box)
{
bool found = TextManager.ContainsTag(box.Text);
box.TextColor = found
? GUIStyle.Orange
: GUIStyle.TextColorNormal;
if (found)
{
box.ToolTip = TextManager.GetWithVariable("StringPropertyTranslate", "[translation]", TextManager.Get(box.Text));
}
else
{
box.ToolTip = string.Empty;
}
}
bodyTextBox.OnDeselected += (textBox, _) => UpdateLabelColor(textBox);
headerTextBox.OnDeselected += (textBox, _) => UpdateLabelColor(textBox);
UpdateLabelColor(bodyTextBox);
UpdateLabelColor(headerTextBox);
mainLayout.Recalculate();
headerTextBox.ForceUpdate();
new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), mainLayout.RectTransform), text: TextManager.Get("confirm"))
{
OnClicked = (_, _) =>
{
CircuitBox.RenameLabel(this, newColor, new NetLimitedString(headerTextBox.Text), new NetLimitedString(bodyTextBox.Text));
RemoveEditPrompt(parent);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), mainLayout.RectTransform), text: TextManager.Get("cancel"))
{
OnClicked = (_, _) =>
{
RemoveEditPrompt(parent);
return true;
}
};
LocalizedString[] colorComponentLabels =
{
TextManager.Get("spriteeditor.colorcomponentr"),
TextManager.Get("spriteeditor.colorcomponentg"),
TextManager.Get("spriteeditor.colorcomponentb")
};
for (int i = 0; i <= 2; i++)
{
var element = new GUIFrame(new RectTransform(new Vector2(0.33f, 1), colorArea.RectTransform), style: null);
var colorLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), colorComponentLabels[i],
font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
var numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight), NumberType.Int)
{
Font = GUIStyle.SubHeadingFont,
MinValueInt = 0,
MaxValueInt = 255
};
switch (i)
{
case 0:
colorLabel.TextColor = GUIStyle.Red;
numberInput.IntValue = Color.R;
numberInput.OnValueChanged += numInput =>
{
newColor.R = (byte)numInput.IntValue;
header.Color = frame.Color = newColor;
};
break;
case 1:
colorLabel.TextColor = GUIStyle.Green;
numberInput.IntValue = Color.G;
numberInput.OnValueChanged += numInput =>
{
newColor.G = (byte)numInput.IntValue;
header.Color = frame.Color = newColor;
};
break;
case 2:
colorLabel.TextColor = GUIStyle.Blue;
numberInput.IntValue = Color.B;
numberInput.OnValueChanged += numInput =>
{
newColor.B = (byte)numInput.IntValue;
header.Color = frame.Color = newColor;
};
break;
}
}
}
public void RemoveEditPrompt(GUIComponent parent)
{
if (parent.FindChild(PromptUserData) is not { } promptParent) { return; }
parent.RemoveChild(promptParent);
}
}
}
@@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
@@ -15,7 +16,17 @@ namespace Barotrauma
/// </summary> /// </summary>
internal sealed class CircuitBoxMouseDragSnapshotHandler internal sealed class CircuitBoxMouseDragSnapshotHandler
{ {
public IEnumerable<CircuitBoxNode> Nodes => circuitBoxUi.CircuitBox.Components.Union<CircuitBoxNode>(circuitBoxUi.CircuitBox.InputOutputNodes); public IEnumerable<CircuitBoxNode> Nodes
{
get
{
var cb = circuitBoxUi.CircuitBox;
foreach (var label in cb.Labels) { yield return label; }
foreach (var component in cb.Components) { yield return component; }
foreach (var node in cb.InputOutputNodes) { yield return node; }
}
}
private IReadOnlyList<CircuitBoxWire> Wires => circuitBoxUi.CircuitBox.Wires; private IReadOnlyList<CircuitBoxWire> Wires => circuitBoxUi.CircuitBox.Wires;
@@ -29,6 +40,8 @@ namespace Barotrauma
// Nodes that should be moved when dragging // Nodes that should be moved when dragging
moveAffectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty; moveAffectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
public Option<(CircuitBoxResizeDirection, CircuitBoxNode)> LastResizeAffectedNode = Option.None;
public ImmutableHashSet<CircuitBoxNode> GetLastComponentsUnderCursor() => lastNodesUnderCursor; public ImmutableHashSet<CircuitBoxNode> GetLastComponentsUnderCursor() => lastNodesUnderCursor;
public ImmutableHashSet<CircuitBoxNode> GetMoveAffectedComponents() => moveAffectedComponents; public ImmutableHashSet<CircuitBoxNode> GetMoveAffectedComponents() => moveAffectedComponents;
@@ -45,6 +58,11 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool IsWiring { get; private set; } public bool IsWiring { get; private set; }
/// <summary>
/// If the user grabbed a side of a node and is resizing a node
/// </summary>
public bool IsResizing { get; private set; }
private Vector2 startClick = Vector2.Zero; private Vector2 startClick = Vector2.Zero;
private readonly CircuitBoxUI circuitBoxUi; private readonly CircuitBoxUI circuitBoxUi;
@@ -147,6 +165,41 @@ namespace Barotrauma
lastNodesUnderCursor = FindNodesUnderCursor(cursorPos); lastNodesUnderCursor = FindNodesUnderCursor(cursorPos);
LastConnectorUnderCursor = FindConnectorUnderCursor(cursorPos); LastConnectorUnderCursor = FindConnectorUnderCursor(cursorPos);
LastWireUnderCursor = FindWireUnderCursor(cursorPos); LastWireUnderCursor = FindWireUnderCursor(cursorPos);
LastResizeAffectedNode = FindResizeBorderUnderCursor(lastNodesUnderCursor, cursorPos);
}
private static Option<(CircuitBoxResizeDirection, CircuitBoxNode)> FindResizeBorderUnderCursor(ImmutableHashSet<CircuitBoxNode> nodes, Vector2 cursorPos)
{
foreach (var node in nodes)
{
if (!node.IsResizable) { continue; }
const float borderSize = 32f;
var rect = node.Rect;
RectangleF bottomBorder = new(rect.X, rect.Top, rect.Width, borderSize);
RectangleF rightBorder = new(rect.Right - borderSize, rect.Y, borderSize, rect.Height);
RectangleF leftBorder = new(rect.X, rect.Y, borderSize, rect.Height);
bool hoverBottom = bottomBorder.Contains(cursorPos),
hoverRight = rightBorder.Contains(cursorPos),
hoverLeft = leftBorder.Contains(cursorPos);
var dir = CircuitBoxResizeDirection.None;
if (hoverBottom) { dir |= CircuitBoxResizeDirection.Down; }
if (hoverRight) { dir |= CircuitBoxResizeDirection.Right; }
if (hoverLeft) { dir |= CircuitBoxResizeDirection.Left; }
if (dir is CircuitBoxResizeDirection.None)
{
continue;
}
return Option.Some((dir, node));
}
return Option.None;
} }
/// <summary> /// <summary>
@@ -193,6 +246,7 @@ namespace Barotrauma
startClick = Vector2.Zero; startClick = Vector2.Zero;
IsDragging = false; IsDragging = false;
IsWiring = false; IsWiring = false;
IsResizing = false;
lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty; lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty;
} }
@@ -210,23 +264,34 @@ namespace Barotrauma
IsDragging = false; IsDragging = false;
} }
if (LastResizeAffectedNode.IsNone())
{
IsResizing = false;
}
// startClick is set to zero when the user releases the mouse button, so we should be neither dragging nor wiring in this state // startClick is set to zero when the user releases the mouse button, so we should be neither dragging nor wiring in this state
if (startClick == Vector2.Zero) if (startClick == Vector2.Zero)
{ {
IsDragging = false; IsDragging = false;
IsWiring = false; IsWiring = false;
IsResizing = false;
return; return;
} }
bool isDragTresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold; if (circuitBoxUi.Locked) { return; }
bool isDragThresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold;
if (LastConnectorUnderCursor.IsNone()) if (LastResizeAffectedNode.IsSome())
{ {
IsDragging |= isDragTresholdExceeded; IsResizing |= isDragThresholdExceeded;
}
else if (LastConnectorUnderCursor.IsSome())
{
IsWiring |= isDragThresholdExceeded;
} }
else else
{ {
IsWiring |= isDragTresholdExceeded; IsDragging |= isDragThresholdExceeded;
} }
} }
} }
@@ -7,10 +7,10 @@ namespace Barotrauma
{ {
internal partial class CircuitBoxNode internal partial class CircuitBoxNode
{ {
private RectangleF DrawRect, public RectangleF DrawRect;
TopDrawRect; private RectangleF TopDrawRect;
private void UpdateDrawRects() protected void UpdateDrawRects()
{ {
var drawRect = new RectangleF(Position - Size / 2f, Size); var drawRect = new RectangleF(Position - Size / 2f, Size);
drawRect.Y = -drawRect.Y; drawRect.Y = -drawRect.Y;
@@ -26,6 +26,8 @@ namespace Barotrauma
UpdatePositions(); UpdatePositions();
} }
public virtual void OnResized(RectangleF drawRect) { }
public void DrawBackground(SpriteBatch spriteBatch, RectangleF drawRect, RectangleF topDrawRect, Color color) public void DrawBackground(SpriteBatch spriteBatch, RectangleF drawRect, RectangleF topDrawRect, Color color)
{ {
CircuitBox.NodeFrameSprite?.Draw(spriteBatch, drawRect, color); CircuitBox.NodeFrameSprite?.Draw(spriteBatch, drawRect, color);
@@ -39,6 +41,7 @@ namespace Barotrauma
DrawBackground(spriteBatch, drawRect, topDrawRect, color); DrawBackground(spriteBatch, drawRect, topDrawRect, color);
DrawHeader(spriteBatch, topDrawRect, color); DrawHeader(spriteBatch, topDrawRect, color);
DrawBody(spriteBatch, drawRect, color);
DrawConnectors(spriteBatch, drawPos); DrawConnectors(spriteBatch, drawPos);
} }
@@ -52,6 +55,7 @@ namespace Barotrauma
} }
public virtual void DrawHeader(SpriteBatch spriteBatch, RectangleF rect, Color color) { } public virtual void DrawHeader(SpriteBatch spriteBatch, RectangleF rect, Color color) { }
public virtual void DrawBody(SpriteBatch spriteBatch, RectangleF rect, Color color) { }
public void DrawConnectors(SpriteBatch spriteBatch, Vector2 drawPos) public void DrawConnectors(SpriteBatch spriteBatch, Vector2 drawPos)
{ {
@@ -35,6 +35,8 @@ namespace Barotrauma
public List<CircuitBoxWireRenderer> VirtualWires = new(); public List<CircuitBoxWireRenderer> VirtualWires = new();
public bool Locked => CircuitBox.Locked;
public CircuitBoxUI(CircuitBox box) public CircuitBoxUI(CircuitBox box)
{ {
camera = new Camera camera = new Camera
@@ -47,7 +49,7 @@ namespace Barotrauma
MouseSnapshotHandler = new CircuitBoxMouseDragSnapshotHandler(this); MouseSnapshotHandler = new CircuitBoxMouseDragSnapshotHandler(this);
} }
#region UI #region UI
public void CreateGUI(GUIFrame parent) public void CreateGUI(GUIFrame parent)
{ {
@@ -63,7 +65,7 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
DrawHUD(spriteBatch); DrawHUD(spriteBatch, component.Rect);
spriteBatch.End(); spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
@@ -82,6 +84,8 @@ namespace Barotrauma
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
componentMenuOpen = !componentMenuOpen; componentMenuOpen = !componentMenuOpen;
if (Locked) { componentMenuOpen = false; }
foreach (GUIComponent child in btn.Children) foreach (GUIComponent child in btn.Children)
{ {
child.SpriteEffects = componentMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically; child.SpriteEffects = componentMenuOpen ? SpriteEffects.None : SpriteEffects.FlipVertically;
@@ -139,65 +143,68 @@ namespace Barotrauma
}; };
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), parent.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) }, var settingsIcon = new GUIButton(new RectTransform(new Point(buttonHeight), parent.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
style: "GUIButtonSettings") style: "GUIButtonSettings")
{
OnClicked = (btn, userdata) =>
{ {
OnClicked = (btn, userdata) => GUIContextMenu.CreateContextMenu(
{ new ContextMenuOption("circuitboxsetting.resetview", isEnabled: true, onSelected: ResetCamera)
GUIContextMenu.CreateContextMenu( {
new ContextMenuOption("circuitboxsetting.resetview", isEnabled: true, onSelected: ResetCamera) Tooltip = TextManager.Get("circuitboxsettingdescription.resetview")
},
new ContextMenuOption("circuitboxsetting.find", isEnabled: true,
new ContextMenuOption("circuitboxsetting.focusinput", isEnabled: true, onSelected: () => FindInputOutput(CircuitBoxInputOutputNode.Type.Input))
{ {
Tooltip = TextManager.Get("circuitboxsettingdescription.resetview") Tooltip = TextManager.Get("circuitboxsettingdescription.focusinput")
}, },
new ContextMenuOption("circuitboxsetting.find", isEnabled: true, new ContextMenuOption("circuitboxsetting.focusoutput", isEnabled: true, onSelected: () => FindInputOutput(CircuitBoxInputOutputNode.Type.Output))
new ContextMenuOption("circuitboxsetting.focusinput", isEnabled: true, onSelected: () => FindInputOuput(CircuitBoxInputOutputNode.Type.Input)) {
{ Tooltip = TextManager.Get("circuitboxsettingdescription.focusoutput")
Tooltip = TextManager.Get("circuitboxsettingdescription.focusinput") },
}, new ContextMenuOption("circuitboxsetting.focuscircuits", isEnabled: CircuitBox.Components.Any(), onSelected: FindCircuit)
new ContextMenuOption("circuitboxsetting.focusoutput", isEnabled: true, onSelected: () => FindInputOuput(CircuitBoxInputOutputNode.Type.Output)) {
{ Tooltip = TextManager.Get("circuitboxsettingdescription.focuscircuits")
Tooltip = TextManager.Get("circuitboxsettingdescription.focusoutput") }));
},
new ContextMenuOption("circuitboxsetting.focuscircuits", isEnabled: CircuitBox.Components.Any(), onSelected: FindCircuit)
{
Tooltip = TextManager.Get("circuitboxsettingdescription.focuscircuits")
}));
void ResetCamera() void ResetCamera()
{ {
// Vector2.One because Vector2.Zero means no value // Vector2.One because Vector2.Zero means no value
camera.TargetPos = Vector2.One; camera.TargetPos = Vector2.One;
}
void FindInputOuput(CircuitBoxInputOutputNode.Type type)
{
var input = CircuitBox.InputOutputNodes.FirstOrDefault(n => n.NodeType == type);
if (input is null) { return; }
camera.TargetPos = input.Position;
}
void FindCircuit()
{
var closestComponent = CircuitBox.Components.MinBy(c => Vector2.DistanceSquared(c.Position, camera.Position));
if (closestComponent is null) { return; }
camera.TargetPos = closestComponent.Position;
}
return true;
} }
};
void FindInputOutput(CircuitBoxInputOutputNode.Type type)
{
var input = CircuitBox.InputOutputNodes.FirstOrDefault(n => n.NodeType == type);
if (input is null) { return; }
camera.TargetPos = input.Position;
}
void FindCircuit()
{
var closestComponent = CircuitBox.Components.MinBy(c => Vector2.DistanceSquared(c.Position, camera.Position));
if (closestComponent is null) { return; }
camera.TargetPos = closestComponent.Position;
}
return true;
}
};
MouseSnapshotHandler.UpdateConnections(); MouseSnapshotHandler.UpdateConnections();
// update scales of everything // update scales of everything
foreach (var node in CircuitBox.Components) { node.OnUICreated(); } foreach (var node in CircuitBox.Components) { node.OnUICreated(); }
foreach (var node in CircuitBox.InputOutputNodes) { node.OnUICreated(); } foreach (var node in CircuitBox.InputOutputNodes) { node.OnUICreated(); }
foreach (var wire in CircuitBox.Wires) { wire.Update(); } foreach (var wire in CircuitBox.Wires) { wire.Update(); }
} }
private string GetInventoryText() private string GetInventoryText() =>
=> CircuitBox.ComponentContainer is { } container CircuitBox.ComponentContainer is { } container
? $"{container.Inventory.AllItems.Count()}/{container.Capacity}" ? $"{container.Inventory.AllItems.Count()}/{container.Capacity}"
: "0/0"; : "0/0";
@@ -209,7 +216,8 @@ namespace Barotrauma
} }
if (componentList is null) { return; } if (componentList is null) { return; }
var playerInventory = CircuitBox.GetSortedCircuitBoxSortedItemsFromPlayer(Character.Controlled);
var playerInventory = CircuitBox.GetSortedCircuitBoxItemsFromPlayer(Character.Controlled);
foreach (GUIComponent child in componentList.Content.Children) foreach (GUIComponent child in componentList.Content.Children)
{ {
@@ -304,9 +312,9 @@ namespace Barotrauma
} }
} }
#endregion #endregion
private void DrawHUD(SpriteBatch spriteBatch) private void DrawHUD(SpriteBatch spriteBatch, Rectangle screenRect)
{ {
float scale = GUI.Scale / 1.5f; float scale = GUI.Scale / 1.5f;
Vector2 offset = new Vector2(20, 40) * scale; Vector2 offset = new Vector2(20, 40) * scale;
@@ -352,6 +360,16 @@ namespace Barotrauma
{ {
n.DrawHUD(spriteBatch, camera); n.DrawHUD(spriteBatch, camera);
} }
if (Locked)
{
LocalizedString lockedText = TextManager.Get("CircuitBoxLocked")
.Fallback(TextManager.Get("ConnectionLocked"));
Vector2 size = GUIStyle.LargeFont.MeasureString(lockedText);
Vector2 pos = new Vector2(screenRect.Center.X - size.X / 2, screenRect.Top + screenRect.Height * 0.05f);
GUI.DrawString(spriteBatch, pos, lockedText, Color.Red, Color.Black, 8, GUIStyle.LargeFont);
}
} }
private void DrawSelection(SpriteBatch spriteBatch, Vector2 pos1, Vector2 pos2, Color color) private void DrawSelection(SpriteBatch spriteBatch, Vector2 pos1, Vector2 pos2, Color color)
@@ -367,6 +385,12 @@ namespace Barotrauma
private static float lineWidth; private static float lineWidth;
public static void DrawRectangleWithBorder(SpriteBatch spriteBatch, RectangleF rect, Color fillColor, Color borderColor) public static void DrawRectangleWithBorder(SpriteBatch spriteBatch, RectangleF rect, Color fillColor, Color borderColor)
{
GUI.DrawFilledRectangle(spriteBatch, rect, fillColor);
DrawRectangleOnlyBorder(spriteBatch, rect, borderColor);
}
private static void DrawRectangleOnlyBorder(SpriteBatch spriteBatch, RectangleF rect, Color borderColor)
{ {
Vector2 topRight = new Vector2(rect.Right, rect.Top), Vector2 topRight = new Vector2(rect.Right, rect.Top),
topLeft = new Vector2(rect.Left, rect.Top), topLeft = new Vector2(rect.Left, rect.Top),
@@ -375,8 +399,6 @@ namespace Barotrauma
Vector2 offset = new Vector2(0f, lineWidth / 2f); Vector2 offset = new Vector2(0f, lineWidth / 2f);
GUI.DrawFilledRectangle(spriteBatch, rect, fillColor);
spriteBatch.DrawLine(topRight, topLeft, borderColor, thickness: lineWidth); spriteBatch.DrawLine(topRight, topLeft, borderColor, thickness: lineWidth);
spriteBatch.DrawLine(topLeft - offset, bottomLeft + offset, borderColor, thickness: lineWidth); spriteBatch.DrawLine(topLeft - offset, bottomLeft + offset, borderColor, thickness: lineWidth);
spriteBatch.DrawLine(bottomLeft, bottomRight, borderColor, thickness: lineWidth); spriteBatch.DrawLine(bottomLeft, bottomRight, borderColor, thickness: lineWidth);
@@ -393,6 +415,16 @@ namespace Barotrauma
Vector2 mousePos = GetCursorPosition(); Vector2 mousePos = GetCursorPosition();
mousePos.Y = -mousePos.Y; mousePos.Y = -mousePos.Y;
foreach (var label in CircuitBox.Labels)
{
if (label.IsSelected)
{
label.DrawSelection(spriteBatch, GetSelectionColor(label));
}
label.Draw(spriteBatch, label.Position, label.Color);
}
foreach (CircuitBoxWire wire in CircuitBox.Wires) foreach (CircuitBoxWire wire in CircuitBox.Wires)
{ {
wire.Renderer.Draw(spriteBatch, GetSelectionColor(wire)); wire.Renderer.Draw(spriteBatch, GetSelectionColor(wire));
@@ -428,6 +460,7 @@ namespace Barotrauma
Color color = moveable switch Color color = moveable switch
{ {
CircuitBoxComponent node => node.Item.Prefab.SignalComponentColor, CircuitBoxComponent node => node.Item.Prefab.SignalComponentColor,
CircuitBoxLabelNode label => label.Color,
CircuitBoxInputOutputNode ioNode => ioNode.NodeType is CircuitBoxInputOutputNode.Type.Input ? GUIStyle.Green : GUIStyle.Red, CircuitBoxInputOutputNode ioNode => ioNode.NodeType is CircuitBoxInputOutputNode.Type.Input ? GUIStyle.Green : GUIStyle.Red,
_ => Color.White _ => Color.White
}; };
@@ -435,17 +468,49 @@ namespace Barotrauma
} }
} }
if (MouseSnapshotHandler.IsResizing && MouseSnapshotHandler.LastResizeAffectedNode.TryUnwrap(out var resize))
{
var (dir, node) = resize;
Vector2 dragOffset = MouseSnapshotHandler.GetDragAmount(GetCursorPosition());
var rect = node.Rect;
rect.Y = -rect.Y;
rect.Y -= rect.Height;
if (dir.HasFlag(CircuitBoxResizeDirection.Down))
{
rect.Height -= dragOffset.Y;
rect.Height = Math.Max(rect.Height, CircuitBoxLabelNode.MinSize.Y + CircuitBoxSizes.NodeHeaderHeight);
}
if (dir.HasFlag(CircuitBoxResizeDirection.Right))
{
rect.Width += dragOffset.X;
rect.Width = Math.Max(rect.Width, CircuitBoxLabelNode.MinSize.X);
}
if (dir.HasFlag(CircuitBoxResizeDirection.Left))
{
float oldWidth = rect.Width;
rect.Width -= dragOffset.X;
rect.Width = Math.Max(rect.Width, CircuitBoxLabelNode.MinSize.X);
float actualResize = rect.Width - oldWidth;
rect.X -= actualResize;
}
DrawRectangleOnlyBorder(spriteBatch, rect, GUIStyle.Yellow);
}
if (DraggedWire.TryUnwrap(out CircuitBoxWireRenderer? draggedWire)) if (DraggedWire.TryUnwrap(out CircuitBoxWireRenderer? draggedWire))
{ {
draggedWire.Draw(spriteBatch, GUIStyle.Yellow); draggedWire.Draw(spriteBatch, GUIStyle.Yellow);
} }
} }
private Color GetSelectionColor(CircuitBoxNode node) private Color GetSelectionColor(CircuitBoxNode node) => GetSelectionColor(node.SelectedBy, node.IsSelectedByMe);
=> GetSelectionColor(node.SelectedBy, node.IsSelectedByMe);
private Color GetSelectionColor(CircuitBoxWire wire) private Color GetSelectionColor(CircuitBoxWire wire) => GetSelectionColor(wire.SelectedBy, wire.IsSelectedByMe);
=> GetSelectionColor(wire.SelectedBy, wire.IsSelectedByMe);
private Color GetSelectionColor(ushort selectedBy, bool isSelectedByMe) private Color GetSelectionColor(ushort selectedBy, bool isSelectedByMe)
{ {
@@ -489,6 +554,7 @@ namespace Barotrauma
{ {
node.UpdateEditing(circuitComponent.RectTransform); node.UpdateEditing(circuitComponent.RectTransform);
} }
break; break;
} }
@@ -503,6 +569,7 @@ namespace Barotrauma
{ {
Character.DisableControls = true; Character.DisableControls = true;
} }
camera.MoveCamera(deltaTime, allowMove: true, allowZoom: isMouseOn, allowInput: isMouseOn, followSub: false); camera.MoveCamera(deltaTime, allowMove: true, allowZoom: isMouseOn, allowInput: isMouseOn, followSub: false);
if (camera.TargetPos != Vector2.Zero && MathUtils.NearlyEqual(camera.Position, camera.TargetPos, 0.01f)) if (camera.TargetPos != Vector2.Zero && MathUtils.NearlyEqual(camera.Position, camera.TargetPos, 0.01f))
@@ -547,7 +614,7 @@ namespace Barotrauma
} }
else else
{ {
DraggedWire = Option.Some(new CircuitBoxWireRenderer(Option.None,start, end, GUIStyle.Red, CircuitBox.WireSprite)); DraggedWire = Option.Some(new CircuitBoxWireRenderer(Option.None, start, end, GUIStyle.Red, CircuitBox.WireSprite));
} }
} }
else else
@@ -562,6 +629,12 @@ namespace Barotrauma
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
{ {
if (MouseSnapshotHandler.IsResizing && MouseSnapshotHandler.LastResizeAffectedNode.TryUnwrap(out var r))
{
var (dir, node) = r;
CircuitBox.ResizeNode(node, dir, MouseSnapshotHandler.GetDragAmount(cursorPos));
}
if (CircuitBox.HeldComponent.TryUnwrap(out ItemPrefab? prefab)) if (CircuitBox.HeldComponent.TryUnwrap(out ItemPrefab? prefab))
{ {
CircuitBox.AddComponent(prefab, cursorPos); CircuitBox.AddComponent(prefab, cursorPos);
@@ -604,22 +677,32 @@ namespace Barotrauma
{ {
CircuitBox.RemoveComponents(CircuitBox.Components.Where(static node => node.IsSelectedByMe).ToArray()); CircuitBox.RemoveComponents(CircuitBox.Components.Where(static node => node.IsSelectedByMe).ToArray());
CircuitBox.RemoveWires(CircuitBox.Wires.Where(static wire => wire.IsSelectedByMe).ToImmutableArray()); CircuitBox.RemoveWires(CircuitBox.Wires.Where(static wire => wire.IsSelectedByMe).ToImmutableArray());
CircuitBox.RemoveLabel(CircuitBox.Labels.Where(static label => label.IsSelectedByMe).ToImmutableArray());
} }
} }
if (componentMenu is { } menu && toggleMenuButton is { } button) if (componentMenu is { } menu && toggleMenuButton is { } button)
{ {
componentMenuOpenState = componentMenuOpen ? Math.Min(componentMenuOpenState + deltaTime * 5.0f, 1.0f) : Math.Max(componentMenuOpenState - deltaTime * 5.0f, 0.0f); button.Enabled = !Locked;
componentMenuOpenState = componentMenuOpen && !Locked ? Math.Min(componentMenuOpenState + deltaTime * 5.0f, 1.0f) : Math.Max(componentMenuOpenState - deltaTime * 5.0f, 0.0f);
menu.RectTransform.ScreenSpaceOffset = Vector2.Lerp(new Vector2(0.0f, menu.Rect.Height - 10), Vector2.Zero, componentMenuOpenState).ToPoint(); menu.RectTransform.ScreenSpaceOffset = Vector2.Lerp(new Vector2(0.0f, menu.Rect.Height - 10), Vector2.Zero, componentMenuOpenState).ToPoint();
button.RectTransform.AbsoluteOffset = new Point(menu.Rect.X + ((menu.Rect.Width / 2) - (button.Rect.Width / 2)), menu.Rect.Y - button.Rect.Height); button.RectTransform.AbsoluteOffset = new Point(menu.Rect.X + ((menu.Rect.Width / 2) - (button.Rect.Width / 2)), menu.Rect.Y - button.Rect.Height);
} }
if (selectedWireFrame is { } wireFrame)
{
wireFrame.Visible = !Locked;
}
camera.Position = Vector2.Clamp(camera.Position, camera.Position = Vector2.Clamp(camera.Position,
new Vector2(-CircuitBoxSizes.PlayableAreaSize / 2f), new Vector2(-CircuitBoxSizes.PlayableAreaSize / 2f),
new Vector2(CircuitBoxSizes.PlayableAreaSize / 2f)); new Vector2(CircuitBoxSizes.PlayableAreaSize / 2f));
} }
public void SetMenuVisibility(bool state)
=> componentMenuOpen = state;
private void UpdateSelection() private void UpdateSelection()
{ {
if (!PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonDown()) if (!PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonDown())
@@ -662,35 +745,55 @@ namespace Barotrauma
var wireSelection = CircuitBox.Wires.Where(static w => w.IsSelectedByMe).ToImmutableArray(); var wireSelection = CircuitBox.Wires.Where(static w => w.IsSelectedByMe).ToImmutableArray();
var nodeOption = GetTopmostNode(MouseSnapshotHandler.FindNodesUnderCursor(cursorPos)); var nodeOption = GetTopmostNode(MouseSnapshotHandler.FindNodesUnderCursor(cursorPos));
var nodeSelection = CircuitBox.Components.Where(static n => n.IsSelectedByMe).ToImmutableArray(); var nodeSelection = CircuitBox.Components.Where(static n => n.IsSelectedByMe).ToImmutableArray();
var labels = CircuitBox.Labels.Where(static l => l.IsSelectedByMe).ToImmutableArray();
var option = new ContextMenuOption(TextManager.Get("delete"), isEnabled: wireOption.IsSome() || nodeOption is CircuitBoxComponent, () => var option = new ContextMenuOption(TextManager.Get("delete"), isEnabled: (wireOption.IsSome() || nodeOption is CircuitBoxComponent or CircuitBoxLabelNode) && !Locked, () =>
{ {
if (wireOption.TryUnwrap(out var wire)) if (wireOption.TryUnwrap(out var wire))
{ {
CircuitBox.RemoveWires(wire.IsSelected ? wireSelection : ImmutableArray.Create(wire)); CircuitBox.RemoveWires(wire.IsSelected ? wireSelection : ImmutableArray.Create(wire));
} }
if (nodeOption is CircuitBoxComponent node) switch (nodeOption)
{ {
CircuitBox.RemoveComponents(node.IsSelected ? nodeSelection : ImmutableArray.Create(node)); case CircuitBoxComponent node:
CircuitBox.RemoveComponents(node.IsSelected ? nodeSelection : ImmutableArray.Create(node));
break;
case CircuitBoxLabelNode label:
CircuitBox.RemoveLabel(label.IsSelected ? labels : ImmutableArray.Create(label));
break;
} }
}); });
var editLabel = new ContextMenuOption(TextManager.Get("circuitboxeditlabel"), isEnabled: nodeOption is CircuitBoxLabelNode && !Locked, () =>
{
if (nodeOption is not CircuitBoxLabelNode label || circuitComponent is null) { return; }
label.PromptEditText(circuitComponent);
});
var addLabelOption = new ContextMenuOption(TextManager.Get("circuitboxaddlabel"), isEnabled: !Locked, () =>
{
CircuitBox.AddLabel(cursorPos);
});
ContextMenuOption[] allOptions = { addLabelOption, editLabel, option };
// show component name in the header to better indicate what is about to be deleted // show component name in the header to better indicate what is about to be deleted
if (nodeOption is CircuitBoxComponent comp) if (nodeOption is CircuitBoxComponent comp)
{ {
GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, comp.Item.Name, comp.Item.Prefab.SignalComponentColor, option); GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, comp.Item.Name, comp.Item.Prefab.SignalComponentColor, allOptions);
return; return;
} }
// also check if a wire is being deleted // also check if a wire is being deleted
if (wireOption.TryUnwrap(out var foundWire)) if (wireOption.TryUnwrap(out var foundWire))
{ {
GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, foundWire.UsedItemPrefab.Name, foundWire.Color, option); GUIContextMenu.CreateContextMenu(PlayerInput.MousePosition, foundWire.UsedItemPrefab.Name, foundWire.Color, allOptions);
return; return;
} }
GUIContextMenu.CreateContextMenu(option); GUIContextMenu.CreateContextMenu(allOptions);
} }
public CircuitBoxNode? GetTopmostNode(ImmutableHashSet<CircuitBoxNode> nodes) public CircuitBoxNode? GetTopmostNode(ImmutableHashSet<CircuitBoxNode> nodes)
@@ -2434,6 +2434,29 @@ namespace Barotrauma
} }
})); }));
commands.Add(new Command("converttowreck", "", (string[] args) =>
{
if (Screen.Selected is not SubEditorScreen)
{
ThrowError("The command can only be used in the submarine editor.");
return;
}
if (Submarine.MainSub == null)
{
ThrowError("Load a submarine first to convert it to a wreck.");
return;
}
if (Submarine.MainSub.Info.SubmarineElement == null)
{
ThrowError("The submarine must be saved before you can convert it to a wreck.");
return;
}
var wreckedSubmarineInfo = new SubmarineInfo(filePath: string.Empty, element: WreckConverter.ConvertToWreck(Submarine.MainSub.Info.SubmarineElement));
wreckedSubmarineInfo.Name += "_Wrecked";
wreckedSubmarineInfo.Type = SubmarineType.Wreck;
GameMain.SubEditorScreen.LoadSub(wreckedSubmarineInfo);
}));
commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) => commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) =>
{ {
float defaultZoom = Screen.Selected.Cam.DefaultZoom; float defaultZoom = Screen.Selected.Cam.DefaultZoom;
@@ -2485,6 +2508,49 @@ namespace Barotrauma
} }
})); }));
commands.Add(new Command("listcontainertags", "Lists all container tags on the submarine.", (string[] args) =>
{
if (Screen.Selected != GameMain.SubEditorScreen)
{
ThrowError("This command can only be used in the sub editor.");
return;
}
HashSet<Identifier> allContainerTagsInTheGame = new();
foreach (var itemPrefab in ItemPrefab.Prefabs)
{
foreach (var pc in itemPrefab.PreferredContainers)
{
foreach (Identifier identifier in Enumerable.Union(pc.Primary, pc.Secondary))
{
allContainerTagsInTheGame.Add(identifier);
}
}
}
Dictionary<Identifier, float> prefab = new();
foreach (Item it in Item.ItemList)
{
foreach (var tag in allContainerTagsInTheGame)
{
if (it.GetTags().All(t => tag != t)) { continue; }
prefab.TryAdd(tag, 0.0f);
prefab[tag]++;
}
}
StringBuilder sb = new();
foreach (var (tag, amount) in prefab.OrderByDescending(kvp => kvp.Value))
{
sb.AppendLine($"{tag}: {amount}");
}
NewMessage(sb.ToString());
}, isCheat: false));
commands.Add(new Command("refreshrect", "Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (string[] args) => commands.Add(new Command("refreshrect", "Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (string[] args) =>
{ {
//TODO: maybe do this automatically during loading when possible? //TODO: maybe do this automatically during loading when possible?
@@ -3015,6 +3081,46 @@ namespace Barotrauma
ContentPackageManager.EnabledPackages.ReloadCore(); ContentPackageManager.EnabledPackages.ReloadCore();
})); }));
commands.Add(new Command("reloadpackage", "reloapackage [name]: reloads a content package.", (string[] args) =>
{
if (args.Length < 1)
{
ThrowError("Please specify the name of the package to reload.");
return;
}
if (args.Length < 2)
{
if (Screen.Selected == GameMain.GameScreen)
{
ThrowError("Reloading the package while in GameScreen may break things; to do it anyway, type 'reloadcorepackage [name] force'");
return;
}
if (Screen.Selected == GameMain.SubEditorScreen)
{
ThrowError("Reloading the core package while in sub editor may break thingg; to do it anyway, type 'reloadcorepackage [name] force'");
return;
}
}
if (GameMain.NetworkMember != null)
{
ThrowError("Cannot change content packages while playing online");
return;
}
var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]);
if (package == null)
{
ThrowError($"Could not find the package {args[0]}!");
return;
}
ContentPackageManager.EnabledPackages.ReloadPackage(package);
}, getValidArgs: () => new[]
{
ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray()
}));
#if WINDOWS #if WINDOWS
commands.Add(new Command("startdedicatedserver", "", (string[] args) => commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
{ {
@@ -3396,6 +3502,29 @@ namespace Barotrauma
character.AnimController.ResetRagdoll(forceReload: true); character.AnimController.ResetRagdoll(forceReload: true);
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("loadanimation", "Loads an animation variation by name for the controlled character. The animation file has to be in the correct animations folder. Note: the changes are not saved!", (string[] args) =>
{
var character = Character.Controlled;
if (character == null)
{
ThrowError("Not controlling any character!");
return;
}
if (args.Length < 2)
{
ThrowError("Insufficient parameters: Have to pass the type of animation (Walk, Run, SwimSlow, SwimFast, or Crouch) and the filename!");
return;
}
string type = args[0];
if (!Enum.TryParse(type, ignoreCase: true, out AnimationType animationType))
{
ThrowError($"Failed to parse animation type from {type}. Supported types are Walk, Run, SwimSlow, SwimFast, and Crouch!");
return;
}
string fileName = args[1];
character.AnimController.TryLoadAnimation(animationType, Path.GetFileNameWithoutExtension(fileName), out _, throwErrors: true);
}, isCheat: true));
commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args => commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args =>
{ {
var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true); var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true);
@@ -3545,7 +3674,10 @@ namespace Barotrauma
} }
try try
{ {
var subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase)); var subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s =>
//accept both the localized and the non-localized name of the sub
s.DisplayName.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
s.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase));
if (subInfo == null) if (subInfo == null)
{ {
ThrowError($"Could not find a submarine with the name \"{args[0]}\"."); ThrowError($"Could not find a submarine with the name \"{args[0]}\".");
@@ -1,8 +1,10 @@
using Barotrauma.Tutorials;
using Segment = Barotrauma.ObjectiveManager.Segment; using Segment = Barotrauma.ObjectiveManager.Segment;
namespace Barotrauma; namespace Barotrauma;
/// <summary>
/// Checks the state of an Objective created using <see cref="EventObjectiveAction"/>.
/// </summary>
partial class CheckObjectiveAction : BinaryOptionAction partial class CheckObjectiveAction : BinaryOptionAction
{ {
public enum CheckType public enum CheckType
@@ -12,10 +14,10 @@ partial class CheckObjectiveAction : BinaryOptionAction
Incomplete Incomplete
} }
[Serialize(CheckType.Completed, IsPropertySaveable.Yes)] [Serialize(CheckType.Completed, IsPropertySaveable.Yes, description: "The objective must be in this state for the check to succeed.")]
public CheckType Type { get; set; } public CheckType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)] [Serialize("", IsPropertySaveable.Yes, description: "The identifier of the objective to check.")]
public Identifier Identifier { get; set; } public Identifier Identifier { get; set; }
partial void DetermineSuccessProjSpecific(ref bool success) partial void DetermineSuccessProjSpecific(ref bool success)
@@ -1,4 +1,3 @@
using Barotrauma.Tutorials;
using System; using System;
using System.Linq; using System.Linq;
@@ -12,7 +12,7 @@ namespace Barotrauma
{ {
LocalizedString rewardText = GetRewardAmountText(sub); LocalizedString rewardText = GetRewardAmountText(sub);
LocalizedString retVal; LocalizedString retVal;
if (rewardPerCrate.HasValue) if (rewardPerCrate.HasValue) // If every crate has the same value
{ {
LocalizedString rewardPerCrateText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", rewardPerCrate.Value)); LocalizedString rewardPerCrateText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", rewardPerCrate.Value));
retVal = TextManager.GetWithVariables("missionrewardcargopercrate", retVal = TextManager.GetWithVariables("missionrewardcargopercrate",
@@ -21,7 +21,7 @@ namespace Barotrauma
("[maxitemcount]", maxItemCount.ToString()), ("[maxitemcount]", maxItemCount.ToString()),
("[totalreward]", $"‖color:gui.orange‖{rewardText}‖end‖")); ("[totalreward]", $"‖color:gui.orange‖{rewardText}‖end‖"));
} }
else else // Crates have differing values, so only show the total value
{ {
retVal = TextManager.GetWithVariables("missionrewardcargo", retVal = TextManager.GetWithVariables("missionrewardcargo",
("[totalreward]", $"‖color:gui.orange‖{rewardText}‖end‖"), ("[totalreward]", $"‖color:gui.orange‖{rewardText}‖end‖"),
@@ -151,10 +151,10 @@ namespace Barotrauma
message = ModifyMessage(message); message = ModifyMessage(message);
} }
CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message)); CoroutineManager.StartCoroutine(ShowMessageBoxWhenRoundSummaryIsNotActive(header, message));
} }
private IEnumerable<CoroutineStatus> ShowMessageBoxAfterRoundSummary(LocalizedString header, LocalizedString message) private IEnumerable<CoroutineStatus> ShowMessageBoxWhenRoundSummaryIsNotActive(LocalizedString header, LocalizedString message)
{ {
while (GUIMessageBox.VisibleBox?.UserData is RoundSummary) while (GUIMessageBox.VisibleBox?.UserData is RoundSummary)
{ {
@@ -8,6 +8,22 @@ namespace Barotrauma
public override bool DisplayAsCompleted => false; public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false; public override bool DisplayAsFailed => false;
private void TryShowRetrievedMessage()
{
if (DetermineCompleted())
{
if (!allRetrievedMessage.IsNullOrEmpty()) { CreateMessageBox(string.Empty, allRetrievedMessage); }
//no need to show this again, clear it
allRetrievedMessage = string.Empty;
}
else
{
if (!partiallyRetrievedMessage.IsNullOrEmpty()) { CreateMessageBox(string.Empty, partiallyRetrievedMessage); }
//no need to show this again, clear it
partiallyRetrievedMessage = string.Empty;
}
}
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
base.ClientReadInitial(msg); base.ClientReadInitial(msg);
@@ -30,6 +46,15 @@ namespace Barotrauma
else else
{ {
target.Item = Item.ReadSpawnData(msg); target.Item = Item.ReadSpawnData(msg);
target.Item.HighlightColor = GUIStyle.Orange;
target.Item.ExternalHighlight = true;
ushort parentTargetId = msg.ReadUInt16();
if (parentTargetId != Entity.NullEntityID)
{
target.OriginalContainer = Entity.FindEntityByID(parentTargetId) as Item;
}
if (target.Item == null) if (target.Item == null)
{ {
throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")"); throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")");
@@ -45,7 +70,7 @@ namespace Barotrauma
target.Item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: target.Item.Position); target.Item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: target.Item.Position);
} }
if (target.Item.body != null) if (target.Item.body != null && target.Item.CurrentHull == null)
{ {
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic; target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
} }
@@ -55,15 +80,25 @@ namespace Barotrauma
public override void ClientRead(IReadMessage msg) public override void ClientRead(IReadMessage msg)
{ {
base.ClientRead(msg); base.ClientRead(msg);
bool atLeastOneTargetWasRetrieved = false;
int targetCount = msg.ReadByte(); int targetCount = msg.ReadByte();
for (int i = 0; i < targetCount; i++) for (int i = 0; i < targetCount; i++)
{ {
var state = (Target.RetrievalState)msg.ReadByte(); var state = (Target.RetrievalState)msg.ReadByte();
if (i < targets.Count) if (i < targets.Count)
{ {
bool wasRetrieved = targets[i].Retrieved;
targets[i].State = state; targets[i].State = state;
if (!wasRetrieved && targets[i].Retrieved)
{
atLeastOneTargetWasRetrieved = true;
}
} }
} }
if (atLeastOneTargetWasRetrieved)
{
TryShowRetrievedMessage();
}
} }
} }
} }
@@ -128,6 +128,11 @@ namespace Barotrauma
{ {
case "sprite": case "sprite":
UISprite newSprite = new UISprite(subElement); UISprite newSprite = new UISprite(subElement);
Rectangle sourceRect = newSprite.Sprite.SourceRect;
if ((sourceRect.Width <= 1 || sourceRect.Height <= 1) && newSprite.Tile)
{
DebugConsole.AddWarning($"Sprite \"{subElement.GetAttributeString("name", Name)}\" has a size of 1 or less which may cause performance problems.", contentPackage: element.ContentPackage);
}
GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None; GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None;
if (subElement.GetAttribute("state") != null) if (subElement.GetAttribute("state") != null)
@@ -187,7 +187,16 @@ namespace Barotrauma
Spacing = 1 Spacing = 1
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUIStyle.SubHeadingFont); var crewHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUIStyle.SubHeadingFont);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), crewHeader.RectTransform, Anchor.CenterRight), string.Empty, textAlignment: Alignment.CenterRight)
{
TextGetter = () =>
{
int crewSize = campaign?.CrewManager?.GetCharacterInfos()?.Count() ?? 0;
return $"{crewSize}/{CrewManager.MaxCrewSize}";
}
};
crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform)) crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform))
{ {
Spacing = 1 Spacing = 1
@@ -207,7 +216,7 @@ namespace Barotrauma
{ {
ClickSound = GUISoundType.ConfirmTransaction, ClickSound = GUISoundType.ConfirmTransaction,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
OnClicked = (b, o) => ValidateHires(PendingHires, true) OnClicked = (b, o) => ValidateHires(PendingHires, createNetworkEvent: true)
}; };
clearAllButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall")) clearAllButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall"))
{ {
@@ -647,7 +656,7 @@ namespace Barotrauma
private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true) private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true)
{ {
if (PendingHires.Count + campaign.CrewManager.GetCharacters().Count() >= CrewManager.MaxCrewSize) if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
{ {
return false; return false;
} }
@@ -703,7 +712,7 @@ namespace Barotrauma
validateHiresButton.Enabled = enoughMoney && HasPermission && pendingList.Content.RectTransform.Children.Any(); validateHiresButton.Enabled = enoughMoney && HasPermission && pendingList.Content.RectTransform.Children.Any();
} }
public bool ValidateHires(List<CharacterInfo> hires, bool createNetworkEvent = false) public bool ValidateHires(List<CharacterInfo> hires, bool takeMoney = true, bool createNetworkEvent = false)
{ {
if (hires == null || hires.None()) { return false; } if (hires == null || hires.None()) { return false; }
@@ -718,14 +727,16 @@ namespace Barotrauma
if (nonDuplicateHires.None()) { return false; } if (nonDuplicateHires.None()) { return false; }
int total = HireManager.GetSalaryFor(nonDuplicateHires); if (takeMoney)
{
if (!campaign.CanAfford(total)) { return false; } int total = HireManager.GetSalaryFor(nonDuplicateHires);
if (!campaign.CanAfford(total)) { return false; }
}
bool atLeastOneHired = false; bool atLeastOneHired = false;
foreach (CharacterInfo ci in nonDuplicateHires) foreach (CharacterInfo ci in nonDuplicateHires)
{ {
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, Character.Controlled)) if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, takeMoney: takeMoney))
{ {
atLeastOneHired = true; atLeastOneHired = true;
} }
@@ -951,8 +962,8 @@ namespace Barotrauma
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifierUsingOriginalName() == identifier); CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifierUsingOriginalName() == identifier);
if (match != null) if (match != null)
{ {
PendingHires.Add(match);
AddPendingHire(match, createNetworkMessage: false); AddPendingHire(match, createNetworkMessage: false);
System.Diagnostics.Debug.Assert(PendingHires.Contains(match));
} }
else else
{ {
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Barotrauma.IO; using Barotrauma.IO;
@@ -641,19 +641,9 @@ namespace Barotrauma
sprite?.Draw(spriteBatch, PlayerInput.MousePosition, scale: Math.Min(64 / sprite.size.X, 64 / sprite.size.Y) * Scale); sprite?.Draw(spriteBatch, PlayerInput.MousePosition, scale: Math.Min(64 / sprite.size.X, 64 / sprite.size.Y) * Scale);
break; break;
} }
case ItemAssemblyPrefab iPrefab: case ItemAssemblyPrefab itemAssemblyPrefab:
{ {
var (x, y) = PlayerInput.MousePosition; itemAssemblyPrefab.Draw(spriteBatch, PlayerInput.MousePosition.FlipY());
foreach (var pair in iPrefab.DisplayEntities)
{
Rectangle dRect = pair.Item2;
dRect = new Rectangle(x: (int)(dRect.X * iPrefab.Scale + x),
y: (int)(dRect.Y * iPrefab.Scale - y),
width: (int)(dRect.Width * iPrefab.Scale),
height: (int)(dRect.Height * iPrefab.Scale));
MapEntityPrefab prefab = MapEntityPrefab.Find("", pair.Item1);
prefab.DrawPlacing(spriteBatch, dRect, prefab.Scale * iPrefab.Scale);
}
break; break;
} }
} }
@@ -716,7 +706,7 @@ namespace Barotrauma
spriteBatch.Draw(backgroundSprite.Texture, spriteBatch.Draw(backgroundSprite.Texture,
area.Center.ToVector2() + pos, area.Center.ToVector2() + pos,
null, color, 0.0f, backgroundSprite.size / 2, backgroundSprite.SourceRect, color, 0.0f, backgroundSprite.size / 2,
scale, spriteEffects, 0.0f); scale, spriteEffects, 0.0f);
} }
@@ -1050,12 +1040,31 @@ namespace Barotrauma
{ {
return dragHandle.Dragging ? CursorState.Dragging : CursorState.Hand; return dragHandle.Dragging ? CursorState.Dragging : CursorState.Hand;
} }
//do not show the hover cursor when the cursor is on a listbox (on the listbox itself, not any of elements inside it!)
if (c is GUIListBox && (parent == null || parent == c))
{
return CursorState.Default;
}
// Some parent elements take priority // Some parent elements take priority
// but not when the child is a GUIButton or GUITickBox // but not when the child is a GUIButton or GUITickBox
if (!(parent is GUIButton) && !(parent is GUIListBox) || if (parent is not GUIButton && parent is not GUIListBox ||
(c is GUIButton) || (c is GUITickBox)) (c is GUIButton) || (c is GUITickBox))
{ {
if (!c.Rect.Equals(monitorRect)) { return c.HoverCursor; } if (!c.Rect.Equals(monitorRect))
{
if (c is GUITickBox)
{
//tickboxes have some special logic: not all of the component is hoverable (just the box and the text area)
if (c.State is GUIComponent.ComponentState.Hover or GUIComponent.ComponentState.HoverSelected)
{
return c.HoverCursor;
}
}
else
{
return c.HoverCursor;
}
}
} }
} }
@@ -2399,30 +2408,31 @@ namespace Barotrauma
} }
iterations++; iterations++;
} }
static Vector2 ClampMoveAmount(Rectangle Rect, Rectangle clampTo, Vector2 moveAmount)
{
if (Rect.Y < clampTo.Y)
{
moveAmount.Y = Math.Max(moveAmount.Y, 0.0f);
}
else if (Rect.Bottom > clampTo.Bottom)
{
moveAmount.Y = Math.Min(moveAmount.Y, 0.0f);
}
if (Rect.X < clampTo.X)
{
moveAmount.X = Math.Max(moveAmount.X, 0.0f);
}
else if (Rect.Right > clampTo.Right)
{
moveAmount.X = Math.Min(moveAmount.X, 0.0f);
}
return moveAmount;
}
} }
#endregion private static Vector2 ClampMoveAmount(Rectangle rect, Rectangle clampTo, Vector2 moveAmount)
{
if (rect.Y < clampTo.Y)
{
moveAmount.Y = Math.Max(moveAmount.Y, 0.0f);
}
else if (rect.Bottom > clampTo.Bottom)
{
moveAmount.Y = Math.Min(moveAmount.Y, 0.0f);
}
if (rect.X < clampTo.X)
{
moveAmount.X = Math.Max(moveAmount.X, 0.0f);
}
else if (rect.Right > clampTo.Right)
{
moveAmount.X = Math.Min(moveAmount.X, 0.0f);
}
return moveAmount;
}
#endregion
#region Misc #region Misc
public static void TogglePauseMenu() public static void TogglePauseMenu()
@@ -2477,7 +2487,7 @@ namespace Barotrauma
GameMain.GameSession.LoadPreviousSave(); GameMain.GameSession.LoadPreviousSave();
}); });
if (IsFriendlyOutpostLevel()) if (IsFriendlyOutpostLevel() && !spMode.CrewDead)
{ {
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToMainMenuVerification", action: () => CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToMainMenuVerification", action: () =>
{ {
@@ -1,4 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -734,7 +734,7 @@ namespace Barotrauma
DrawToolTip(spriteBatch, ToolTip, Rect); DrawToolTip(spriteBatch, ToolTip, Rect);
} }
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos) public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos, Color? textColor = null, Color? backgroundColor = null)
{ {
if (ObjectiveManager.ContentRunning) { return; } if (ObjectiveManager.ContentRunning) { return; }
@@ -745,6 +745,8 @@ namespace Barotrauma
if (toolTipBlock == null || (RichString)toolTipBlock.UserData != toolTip) if (toolTipBlock == null || (RichString)toolTipBlock.UserData != toolTip)
{ {
toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), toolTip, font: GUIStyle.SmallFont, wrap: true, style: "GUIToolTip"); toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), toolTip, font: GUIStyle.SmallFont, wrap: true, style: "GUIToolTip");
if (textColor != null) { toolTipBlock.TextColor = textColor.Value; }
if (backgroundColor != null) { toolTipBlock.Color = backgroundColor.Value; }
toolTipBlock.RectTransform.NonScaledSize = new Point( toolTipBlock.RectTransform.NonScaledSize = new Point(
(int)(GUIStyle.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z), (int)(GUIStyle.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z),
(int)(GUIStyle.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W)); (int)(GUIStyle.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W));
@@ -754,6 +756,15 @@ namespace Barotrauma
toolTipBlock.RectTransform.AbsoluteOffset = pos.ToPoint(); toolTipBlock.RectTransform.AbsoluteOffset = pos.ToPoint();
toolTipBlock.SetTextPos(); toolTipBlock.SetTextPos();
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width,0);
}
if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(0, toolTipBlock.Rect.Height);
}
toolTipBlock.DrawManually(spriteBatch); toolTipBlock.DrawManually(spriteBatch);
} }
@@ -982,6 +993,24 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Sets the minimum height of the transfrom to equal to the sum of the minimum heights of the children
/// (i.e. makes the element at least large enough to fit all the children vertically)
/// </summary>
public void InheritTotalChildrenMinHeight()
{
RectTransform.InheritTotalChildrenMinHeight();
}
/// <summary>
/// Sets the minimum height of the transfrom to equal to the sum of the heights of the children
/// (i.e. makes the element at least large enough to fit all the children vertically)
/// </summary>
public void InheritTotalChildrenHeight()
{
RectTransform.InheritTotalChildrenHeight();
}
public static GUIComponent FromXML(ContentXElement element, RectTransform parent) public static GUIComponent FromXML(ContentXElement element, RectTransform parent)
{ {
GUIComponent component = null; GUIComponent component = null;
@@ -15,20 +15,73 @@ namespace Barotrauma
public GUITextBox TextBox { get; private set; } public GUITextBox TextBox { get; private set; }
public override RichString ToolTip
{
get
{
return base.ToolTip;
}
set
{
base.ToolTip = value;
TextBox.ToolTip = value;
}
}
public GUIButton PlusButton { get; private set; } public GUIButton PlusButton { get; private set; }
public GUIButton MinusButton { get; private set; } public GUIButton MinusButton { get; private set; }
public enum ButtonVisibility { Automatic, Manual, ForceVisible, ForceHidden }
private ButtonVisibility _plusMinusButtonVisibility;
/// <summary>
/// Whether or not the default +- buttons should be shown. Defaults to Automatic,
/// which enables it for all integers and for those floats that have a defined
/// range, because for these it is implicitly more obvious how to increment them.
/// </summary>
public ButtonVisibility PlusMinusButtonVisibility
{
get { return _plusMinusButtonVisibility; }
set
{
if (_plusMinusButtonVisibility != value)
{
_plusMinusButtonVisibility = value;
UpdatePlusMinusButtonVisibility();
}
}
}
private void UpdatePlusMinusButtonVisibility() private void UpdatePlusMinusButtonVisibility()
{ {
if (ForceShowPlusMinusButtons switch (PlusMinusButtonVisibility)
|| inputType == NumberType.Int
|| (inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{ {
ShowPlusMinusButtons(); case ButtonVisibility.ForceHidden:
} {
else HidePlusMinusButtons();
{ break;
HidePlusMinusButtons(); }
case ButtonVisibility.ForceVisible:
{
ShowPlusMinusButtons();
break;
}
case ButtonVisibility.Automatic:
{
if (inputType == NumberType.Int
|| (inputType == NumberType.Float
&& MinValueFloat > float.MinValue
&& MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
break;
}
case ButtonVisibility.Manual:
return;
} }
} }
@@ -86,19 +139,6 @@ namespace Barotrauma
} }
} }
private bool forceShowPlusMinusButtons;
public bool ForceShowPlusMinusButtons
{
get { return forceShowPlusMinusButtons; }
set
{
if (forceShowPlusMinusButtons == value) { return; }
forceShowPlusMinusButtons = value;
UpdatePlusMinusButtonVisibility();
}
}
private int decimalsToDisplay = 1; private int decimalsToDisplay = 1;
public int DecimalsToDisplay public int DecimalsToDisplay
{ {
@@ -163,6 +203,17 @@ namespace Barotrauma
} }
} }
public bool Readonly
{
get { return TextBox.Readonly; }
set
{
TextBox.Readonly = value;
PlusButton.Enabled = !value;
MinusButton.Enabled = !value;
}
}
public override GUIFont Font public override GUIFont Font
{ {
get get
@@ -189,11 +240,19 @@ namespace Barotrauma
public float ValueStep; public float ValueStep;
// Enable holding to scroll through values faster
private float pressedTimer; private float pressedTimer;
private readonly float pressedDelay = 0.5f; private readonly float pressedDelay = 0.5f;
private bool IsPressedTimerRunning { get { return pressedTimer > 0; } } private bool IsPressedTimerRunning { get { return pressedTimer > 0; } }
public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null, bool hidePlusMinusButtons = false) : base(style, rectT) public GUINumberInput(
RectTransform rectT,
NumberType inputType,
string style = "",
Alignment textAlignment = Alignment.Center,
float? relativeButtonAreaWidth = null,
ButtonVisibility buttonVisibility = ButtonVisibility.Automatic,
(GUIButton PlusButton, GUIButton MinusButton)? customPlusMinusButtons = null) : base(style, rectT)
{ {
LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
@@ -233,9 +292,23 @@ namespace Barotrauma
return true; return true;
}; };
var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null); if (customPlusMinusButtons.HasValue)
PlusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null); {
GUIStyle.Apply(PlusButton, "PlusButton", this); PlusButton = customPlusMinusButtons.Value.PlusButton;
MinusButton = customPlusMinusButtons.Value.MinusButton;
}
else // generate the default +- buttons
{
var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null);
PlusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null);
GUIStyle.Apply(PlusButton, "PlusButton", this);
MinusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null);
GUIStyle.Apply(MinusButton, "MinusButton", this);
}
// Set up default and custom +- buttons the same way to ensure uniform functionality
PlusButton.ClickSound = GUISoundType.Increase; PlusButton.ClickSound = GUISoundType.Increase;
PlusButton.OnButtonDown += () => PlusButton.OnButtonDown += () =>
{ {
@@ -255,9 +328,6 @@ namespace Barotrauma
} }
return true; return true;
}; };
MinusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null);
GUIStyle.Apply(MinusButton, "MinusButton", this);
MinusButton.ClickSound = GUISoundType.Decrease; MinusButton.ClickSound = GUISoundType.Decrease;
MinusButton.OnButtonDown += () => MinusButton.OnButtonDown += () =>
{ {
@@ -278,10 +348,7 @@ namespace Barotrauma
return true; return true;
}; };
if (inputType != NumberType.Int || hidePlusMinusButtons) PlusMinusButtonVisibility = buttonVisibility;
{
HidePlusMinusButtons();
}
if (inputType == NumberType.Int) if (inputType == NumberType.Int)
{ {
@@ -324,16 +391,16 @@ namespace Barotrauma
private void HidePlusMinusButtons() private void HidePlusMinusButtons()
{ {
PlusButton.Parent.Visible = false; PlusButton.Parent.Visible = MinusButton.Parent.Visible = false;
PlusButton.Parent.IgnoreLayoutGroups = true; PlusButton.Parent.IgnoreLayoutGroups = MinusButton.Parent.IgnoreLayoutGroups = true;
TextBox.RectTransform.RelativeSize = Vector2.One; TextBox.RectTransform.RelativeSize = Vector2.One;
LayoutGroup.Recalculate(); LayoutGroup.Recalculate();
} }
private void ShowPlusMinusButtons() private void ShowPlusMinusButtons()
{ {
PlusButton.Parent.Visible = true; PlusButton.Parent.Visible = MinusButton.Parent.Visible = true;
PlusButton.Parent.IgnoreLayoutGroups = false; PlusButton.Parent.IgnoreLayoutGroups = MinusButton.Parent.IgnoreLayoutGroups = false;
TextBox.RectTransform.RelativeSize = new Vector2(1.0f - PlusButton.Parent.RectTransform.RelativeSize.X, 1.0f); TextBox.RectTransform.RelativeSize = new Vector2(1.0f - PlusButton.Parent.RectTransform.RelativeSize.X, 1.0f);
LayoutGroup.Recalculate(); LayoutGroup.Recalculate();
} }
@@ -427,6 +494,11 @@ namespace Barotrauma
Math.Min(floatValue, MaxValueFloat.Value); Math.Min(floatValue, MaxValueFloat.Value);
PlusButton.Enabled = WrapAround || floatValue < MaxValueFloat; PlusButton.Enabled = WrapAround || floatValue < MaxValueFloat;
} }
if (Readonly)
{
PlusButton.Enabled = MinusButton.Enabled = false;
}
} }
private void ClampIntValue() private void ClampIntValue()
@@ -441,8 +513,16 @@ namespace Barotrauma
intValue = WrapAround && MinValueInt.HasValue ? MinValueInt.Value : Math.Min(intValue, MaxValueInt.Value); intValue = WrapAround && MinValueInt.HasValue ? MinValueInt.Value : Math.Min(intValue, MaxValueInt.Value);
UpdateText(); UpdateText();
} }
PlusButton.Enabled = WrapAround || MaxValueInt == null || intValue < MaxValueInt;
MinusButton.Enabled = WrapAround || MinValueInt == null || intValue > MinValueInt; if (Readonly)
{
PlusButton.Enabled = MinusButton.Enabled = false;
}
else
{
PlusButton.Enabled = WrapAround || MaxValueInt == null || intValue < MaxValueInt;
MinusButton.Enabled = WrapAround || MinValueInt == null || intValue > MinValueInt;
}
} }
private void UpdateText() private void UpdateText()
@@ -135,7 +135,8 @@ namespace Barotrauma
if (subElement.NameAsIdentifier() != "override") { continue; } if (subElement.NameAsIdentifier() != "override") { continue; }
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag)) if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag))
{ {
return new ScalableFont(subElement, font?.Size ?? 14, GameMain.Instance.GraphicsDevice); uint overrideFontSize = GetFontSize(subElement, defaultSize: font?.Size ?? 14);
return new ScalableFont(subElement, overrideFontSize, GameMain.Instance.GraphicsDevice);
} }
} }
@@ -380,6 +381,11 @@ namespace Barotrauma
public static implicit operator UISprite?(GUISprite reference) => reference.Value; public static implicit operator UISprite?(GUISprite reference) => reference.Value;
public void Draw(SpriteBatch spriteBatch, RectangleF rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None)
{
Value?.Draw(spriteBatch, rect, color, spriteEffects);
}
public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None) public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None)
{ {
Value?.Draw(spriteBatch, rect, color, spriteEffects); Value?.Draw(spriteBatch, rect, color, spriteEffects);
@@ -214,7 +214,7 @@ namespace Barotrauma
Bar.HoverCursor = CursorState.Default; Bar.HoverCursor = CursorState.Default;
break; break;
case "GUISlider": case "GUISlider":
HoverCursor = CursorState.Default; HoverCursor = CursorState.Hand;
Bar.HoverCursor = CursorState.Hand; Bar.HoverCursor = CursorState.Hand;
break; break;
default: default:
@@ -0,0 +1,191 @@
#nullable enable
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Allows accessing the element selected in the carousel in contexts where the type of T isn't known.
/// Pretty hacky, but I could not think of a better way to do this (<see cref="Networking.ServerSettings.NetPropertyData"/> in which this is used).
/// </summary>
public interface IGUISelectionCarouselAccessor
{
object? GetSelectedElement();
void SelectElement(object? value);
}
/// <summary>
/// An UI element that allows toggling through a set of options with buttons to the left and right
/// </summary>
public class GUISelectionCarousel<T> : GUIComponent, IGUISelectionCarouselAccessor
{
public record class Element(T value, LocalizedString text, LocalizedString toolTip);
public delegate void OnValueChangedHandler(GUISelectionCarousel<T> carousel);
public OnValueChangedHandler? OnValueChanged;
public GUITextBlock TextBlock { get; private set; }
public GUIButton RightButton { get; private set; }
public GUIButton LeftButton { get; private set; }
private readonly List<Element> elements = new List<Element>();
private readonly GUILayoutGroup layoutGroup;
public Element? SelectedElement { get; private set; }
public T? SelectedValue => SelectedElement == null ? default : SelectedElement.value;
public LocalizedString SelectedText => SelectedElement?.text ?? string.Empty;
public override bool Enabled
{
get => base.Enabled;
set
{
base.Enabled = RightButton.Enabled = LeftButton.Enabled = TextBlock.Enabled = value;
}
}
public override Color Color
{
get { return color; }
set
{
color = value;
TextBlock.Color = color;
}
}
public Color TextColor
{
get { return TextBlock.TextColor; }
set { TextBlock.TextColor = value; }
}
public override Color HoverColor
{
get => base.HoverColor;
set
{
base.HoverColor = value;
TextBlock.HoverColor = value;
}
}
public GUISelectionCarousel(RectTransform rectT, string style = "", params (T value, LocalizedString text)[] newElements) : base(style, rectT)
{
layoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
RelativeSpacing = 0.05f,
Stretch = true
};
LeftButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), layoutGroup.RectTransform), style: "GUIButtonToggleLeft");
GUIStyle.Apply(LeftButton, "LeftButton", this);
TextBlock = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), layoutGroup.RectTransform), "", textAlignment: Alignment.Center, style: "GUITextBox");
GUIStyle.Apply(TextBlock, "TextBlock", this);
RightButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), layoutGroup.RectTransform), style: "GUIButtonToggleRight");
GUIStyle.Apply(RightButton, "RightButton", this);
RightButton.OnClicked += (btn, userData) =>
{
if (elements.Count < 2) { return false; }
if (SelectedElement == null)
{
SelectElement(elements.First());
}
else
{
int newIndex = (elements.IndexOf(SelectedElement) + 1) % elements.Count;
SelectElement(elements[newIndex]);
}
return true;
};
LeftButton.OnClicked += (btn, userData) =>
{
if (elements.Count < 2) { return false; }
if (SelectedElement == null)
{
SelectElement(elements.First());
}
else
{
int newIndex = MathUtils.PositiveModulo((elements.IndexOf(SelectedElement) - 1), elements.Count);
SelectElement(elements[newIndex]);
}
return true;
};
if (newElements != null && newElements.Any())
{
SetElements(newElements);
}
}
public object? GetSelectedElement()
{
return SelectedValue;
}
/// <summary>
/// Select the element whose value matches the specified value. If null, deselects the currently selected element.
/// </summary>
public void SelectElement(object? value)
{
if (value == null)
{
SelectElement(null);
return;
}
if (elements.FirstOrDefault(e => value.Equals(e.value)) is { } element)
{
SelectElement(element);
}
}
public void SelectElement(Element? element)
{
SelectedElement = element;
TextBlock.Text = element?.text ?? string.Empty;
TextBlock.ToolTip = element?.toolTip ?? string.Empty;
OnValueChanged?.Invoke(this);
}
/// <summary>
/// Clears all existing elements from the carousels and adds the specified new elements to it
/// </summary>
public void SetElements(params (T value, LocalizedString text)[] elements)
{
this.elements.Clear();
foreach ((T value, LocalizedString text) in elements)
{
AddElement(value, text);
}
}
/// <summary>
/// Clears all existing elements from the carousels and adds the specified new elements to it
/// </summary>
public void SetElements(params (T value, LocalizedString text, LocalizedString toolTip)[] elements)
{
this.elements.Clear();
foreach ((T value, LocalizedString text, LocalizedString toolTip) in elements)
{
AddElement(value, text, toolTip);
}
}
public void AddElement(T value, LocalizedString text, LocalizedString? tooltip = null)
{
var newElement = new Element(value, text, tooltip ?? string.Empty);
elements.Add(newElement);
if (SelectedElement == null)
{
SelectElement(newElement);
}
}
}
}
@@ -1,6 +1,6 @@
using System; using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -47,6 +47,8 @@ namespace Barotrauma
public readonly static GUISprite SubmarineLocationIcon = new GUISprite("SubmarineLocationIcon"); public readonly static GUISprite SubmarineLocationIcon = new GUISprite("SubmarineLocationIcon");
public readonly static GUISprite Arrow = new GUISprite("Arrow"); public readonly static GUISprite Arrow = new GUISprite("Arrow");
public readonly static GUISprite SpeechBubbleIcon = new GUISprite("SpeechBubbleIcon"); public readonly static GUISprite SpeechBubbleIcon = new GUISprite("SpeechBubbleIcon");
public readonly static GUISprite SpeechBubbleIconSliced = new GUISprite("SpeechBubbleIconSliced");
public readonly static GUISprite InteractionLabelBackground = new GUISprite("InteractionLabelBackground");
public readonly static GUISprite BrokenIcon = new GUISprite("BrokenIcon"); public readonly static GUISprite BrokenIcon = new GUISprite("BrokenIcon");
public readonly static GUISprite YouAreHereCircle = new GUISprite("YouAreHereCircle"); public readonly static GUISprite YouAreHereCircle = new GUISprite("YouAreHereCircle");
@@ -125,6 +127,9 @@ namespace Barotrauma
public readonly static GUIColor ColorReputationHigh = new GUIColor("ColorReputationHigh", new Color(51, 152, 64, 255)); public readonly static GUIColor ColorReputationHigh = new GUIColor("ColorReputationHigh", new Color(51, 152, 64, 255));
public readonly static GUIColor ColorReputationVeryHigh = new GUIColor("ColorReputationVeryHigh", new Color(71, 160, 164, 255)); public readonly static GUIColor ColorReputationVeryHigh = new GUIColor("ColorReputationVeryHigh", new Color(71, 160, 164, 255));
public readonly static GUIColor InteractionLabelColor = new GUIColor("InteractionLabelColor", new Color(255, 255, 255, 255));
public readonly static GUIColor InteractionLabelHoverColor = new GUIColor("InteractionLabelHoverColor", new Color(0, 255, 255, 255));
// Inventory // Inventory
public readonly static GUIColor EquipmentSlotIconColor = new GUIColor("EquipmentSlotIconColor", new Color(99, 70, 64, 255)); public readonly static GUIColor EquipmentSlotIconColor = new GUIColor("EquipmentSlotIconColor", new Color(99, 70, 64, 255));
@@ -195,8 +200,7 @@ namespace Barotrauma
if (parentStyle == null) if (parentStyle == null)
{ {
Identifier parentStyleName = parent.GetType().Name.ToIdentifier(); Identifier parentStyleName = ReflectionUtils.GetTypeNameWithoutGenericArity(parent.GetType());
if (!ComponentStyles.ContainsKey(parentStyleName)) if (!ComponentStyles.ContainsKey(parentStyleName))
{ {
DebugConsole.ThrowError($"Couldn't find a GUI style \"{parentStyleName}\""); DebugConsole.ThrowError($"Couldn't find a GUI style \"{parentStyleName}\"");
@@ -204,7 +208,7 @@ namespace Barotrauma
} }
parentStyle = ComponentStyles[parentStyleName]; parentStyle = ComponentStyles[parentStyleName];
} }
Identifier childStyleName = styleName.IsEmpty ? targetComponent.GetType().Name.ToIdentifier() : styleName; Identifier childStyleName = styleName.IsEmpty ? ReflectionUtils.GetTypeNameWithoutGenericArity(targetComponent.GetType()) : styleName;
parentStyle.ChildStyles.TryGetValue(childStyleName, out componentStyle); parentStyle.ChildStyles.TryGetValue(childStyleName, out componentStyle);
} }
else else
@@ -212,7 +216,7 @@ namespace Barotrauma
Identifier styleIdentifier = styleName.ToIdentifier(); Identifier styleIdentifier = styleName.ToIdentifier();
if (styleIdentifier == Identifier.Empty) if (styleIdentifier == Identifier.Empty)
{ {
styleIdentifier = targetComponent.GetType().Name.ToIdentifier(); styleIdentifier = ReflectionUtils.GetTypeNameWithoutGenericArity(targetComponent.GetType());
} }
if (!ComponentStyles.ContainsKey(styleIdentifier)) if (!ComponentStyles.ContainsKey(styleIdentifier))
{ {
@@ -143,6 +143,9 @@ namespace Barotrauma
} }
} }
/// <summary>
/// When enabled, clips the left side of the text if it's too long to fit in the box (i.e. allows you to enter longer texts without the text overflowing from the box).
/// </summary>
public bool OverflowClip public bool OverflowClip
{ {
get { return textBlock.OverflowClip; } get { return textBlock.OverflowClip; }
@@ -325,7 +328,7 @@ namespace Barotrauma
textBlock.Text = text; textBlock.Text = text;
ClearSelection(); ClearSelection();
if (Text == null) textBlock.Text = ""; if (Text == null) textBlock.Text = "";
if (Text != "" && !Wrap) if (Text != "")
{ {
if (maxTextLength != null) if (maxTextLength != null)
{ {
@@ -334,7 +337,7 @@ namespace Barotrauma
textBlock.Text = Text.Substring(0, (int)maxTextLength); textBlock.Text = Text.Substring(0, (int)maxTextLength);
} }
} }
else else if (!Wrap)
{ {
while (ClampText && textBlock.Text.Length > 0 && Font.MeasureString(textBlock.Text).X * TextBlock.TextScale > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z)) while (ClampText && textBlock.Text.Length > 0 && Font.MeasureString(textBlock.Text).X * TextBlock.TextScale > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
{ {
@@ -1,4 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
namespace Barotrauma namespace Barotrauma
@@ -198,7 +198,9 @@ namespace Barotrauma
base.Update(deltaTime); base.Update(deltaTime);
if (GUI.MouseOn == this && Enabled) if (GUI.MouseOn == this && Enabled &&
//allow clicking on the text area, but not further to the right (the dimensions of the component itself can extend further than the text)
PlayerInput.MousePosition.X < Rect.X + ContentWidth)
{ {
State = Selected ? State = Selected ?
ComponentState.HoverSelected : ComponentState.HoverSelected :
@@ -779,6 +779,24 @@ namespace Barotrauma
NonScaledSize = targetSize; NonScaledSize = targetSize;
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
/// <summary>
/// Sets the minimum height of the transfrom to equal to the sum of the minimum heights of the children
/// (i.e. makes the rect at least large enough to fit all the children vertically)
/// </summary>
public void InheritTotalChildrenMinHeight()
{
MinSize = new Point(MinSize.X, children.Sum(c => c.MinSize.Y));
}
/// <summary>
/// Sets the minimum height of the transfrom to equal to the sum of the heights of the children
/// (i.e. makes the rect at least large enough to fit all the children vertically)
/// </summary>
public void InheritTotalChildrenHeight()
{
MinSize = new Point(MinSize.X, children.Sum(c => c.Rect.Height));
}
#endregion #endregion
#region Static methods #region Static methods
@@ -1733,6 +1733,9 @@ namespace Barotrauma
if (!subItem.Components.All(c => c is not Holdable h || !h.Attachable || !h.Attached)) { continue; } if (!subItem.Components.All(c => c is not Holdable h || !h.Attachable || !h.Attached)) { continue; }
if (!subItem.Components.All(c => c is not Wire w || w.Connections.All(c => c == null))) { continue; } if (!subItem.Components.All(c => c is not Wire w || w.Connections.All(c => c == null))) { continue; }
if (!ItemAndAllContainersInteractable(subItem)) { continue; } if (!ItemAndAllContainersInteractable(subItem)) { continue; }
//don't list items in a character inventory (the ones in a crew member's inventory are counted below)
var rootInventoryOwner = subItem.GetRootInventoryOwner();
if (rootInventoryOwner != null) { continue; }
AddOwnedItem(subItem); AddOwnedItem(subItem);
} }
} }
@@ -1066,7 +1066,7 @@ namespace Barotrauma
GUIButton centerButton = new GUIButton(new RectTransform(new Vector2(1f), centerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight, anchor: Anchor.Center), style: "GUIButtonTransferArrow"); GUIButton centerButton = new GUIButton(new RectTransform(new Vector2(1f), centerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight, anchor: Anchor.Center), style: "GUIButtonTransferArrow");
GUILayoutGroup inputLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center); GUILayoutGroup inputLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedTransferMenuLayout.RectTransform), childAnchor: Anchor.Center);
GUINumberInput transferAmountInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), inputLayout.RectTransform), NumberType.Int, hidePlusMinusButtons: true) GUINumberInput transferAmountInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), inputLayout.RectTransform), NumberType.Int, buttonVisibility: GUINumberInput.ButtonVisibility.ForceHidden)
{ {
MinValueInt = 0 MinValueInt = 0
}; };
@@ -131,7 +131,7 @@ namespace Barotrauma
GUIFrame characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null) { Visible = false }; GUIFrame characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null) { Visible = false };
GUILayoutGroup characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform)); GUILayoutGroup characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null); GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null); GUILayoutGroup playerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), containerFrame.RectTransform, Anchor.TopCenter));
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false); GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight), GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
@@ -423,7 +423,7 @@ namespace Barotrauma
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null); GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null) GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{ {
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)), ToolTip = CreateTooltip(talent, characterInfo),
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
Enabled = info.Character != null, Enabled = info.Character != null,
@@ -489,6 +489,24 @@ namespace Barotrauma
}, },
}; };
static RichString CreateTooltip(TalentPrefab talent, CharacterInfo? character)
{
LocalizedString progress = string.Empty;
if (character is not null && talent.TrackedStat.TryUnwrap(out var stat))
{
var statValue = character.GetSavedStatValue(StatTypes.None, stat.PermanentStatIdentifier);
var intValue = (int)MathF.Round(statValue);
progress = "\n\n";
progress += statValue < stat.Max
? TextManager.GetWithVariables("talentprogress", ("[amount]", intValue.ToString()), ("[max]", stat.Max.ToString()))
: TextManager.Get("talentprogresscompleted");
}
RichString tooltip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖\n\n{ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)}{progress}");
return tooltip;
}
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent; talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
GUIComponent iconImage; GUIComponent iconImage;
@@ -1305,7 +1305,7 @@ namespace Barotrauma
const int maxUpgrades = 4; const int maxUpgrades = 4;
Item? item = entity as Item; Item? item = entity as Item;
itemName.Text = item?.Name ?? TextManager.Get("upgradecategory.walls"); itemName.Text = item?.Prefab.Name ?? TextManager.Get("upgradecategory.walls");
if (slotIndex > -1) if (slotIndex > -1)
{ {
itemName.Text = TextManager.GetWithVariables("weaponslotwithname", ("[number]", slotIndex.ToString()), ("[weaponname]", itemName.Text)); itemName.Text = TextManager.GetWithVariables("weaponslotwithname", ("[number]", slotIndex.ToString()), ("[weaponname]", itemName.Text));
@@ -7,46 +7,46 @@ using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
class Widget public enum WidgetShape
{ {
public enum Shape Rectangle,
{ Circle,
Rectangle, Cross
Circle, }
Cross
}
public Shape shape; internal class Widget
public LocalizedString tooltip; {
public bool showTooltip = true; public WidgetShape Shape;
public Rectangle DrawRect => new Rectangle((int)(DrawPos.X - (float)size / 2), (int)(DrawPos.Y - (float)size / 2), size, size); public LocalizedString Tooltip;
public bool ShowTooltip = true;
public Rectangle DrawRect => new Rectangle((int)(DrawPos.X - (float)Size / 2), (int)(DrawPos.Y - (float)Size / 2), Size, Size);
public Rectangle InputRect public Rectangle InputRect
{ {
get get
{ {
var inputRect = DrawRect; var inputRect = DrawRect;
inputRect.Inflate(inputAreaMargin, inputAreaMargin); inputRect.Inflate(InputAreaMargin, InputAreaMargin);
return inputRect; return inputRect;
} }
} }
public Vector2 DrawPos { get; set; } public Vector2 DrawPos { get; set; }
public int size = 10; public int Size = 10;
public float thickness = 1f; public float Thickness = 1f;
/// <summary> /// <summary>
/// Used only for circles. /// Used only for circles.
/// </summary> /// </summary>
public int sides = 40; public int Sides = 40;
/// <summary> /// <summary>
/// Currently used only for rectangles. /// Currently used only for rectangles.
/// </summary> /// </summary>
public bool isFilled; public bool IsFilled;
public int inputAreaMargin; public int InputAreaMargin;
public Color color = GUIStyle.Red; public Color Color = GUIStyle.Red;
public Color? secondaryColor; public Color? SecondaryColor;
public Color textColor = Color.White; public Color TextColor = Color.White;
public Color textBackgroundColor = Color.Black * 0.5f; public Color TextBackgroundColor = Color.Black * 0.5f;
public readonly string id; public readonly string Id;
public event Action Selected; public event Action Selected;
public event Action Deselected; public event Action Deselected;
@@ -61,11 +61,11 @@ namespace Barotrauma
public bool RequireMouseOn = true; public bool RequireMouseOn = true;
public Action refresh; public Action Refresh;
public object data; public object Data;
public bool IsSelected => enabled && selectedWidgets.Contains(this); public bool IsSelected => enabled && SelectedWidgets.Contains(this);
public bool IsControlled => IsSelected && PlayerInput.PrimaryMouseButtonHeld(); public bool IsControlled => IsSelected && PlayerInput.PrimaryMouseButtonHeld();
public bool IsMouseOver => GUI.MouseOn == null && InputRect.Contains(PlayerInput.MousePosition); public bool IsMouseOver => GUI.MouseOn == null && InputRect.Contains(PlayerInput.MousePosition);
private bool enabled = true; private bool enabled = true;
@@ -75,9 +75,9 @@ namespace Barotrauma
set set
{ {
enabled = value; enabled = value;
if (!enabled && selectedWidgets.Contains(this)) if (!enabled && SelectedWidgets.Contains(this))
{ {
selectedWidgets.Remove(this); SelectedWidgets.Remove(this);
} }
} }
} }
@@ -89,46 +89,46 @@ namespace Barotrauma
set set
{ {
multiselect = value; multiselect = value;
if (!multiselect && selectedWidgets.Multiple()) if (!multiselect && SelectedWidgets.Multiple())
{ {
selectedWidgets = selectedWidgets.Take(1).ToList(); SelectedWidgets = SelectedWidgets.Take(1).ToList();
} }
} }
} }
public Vector2? tooltipOffset; public Vector2? TooltipOffset;
public Widget linkedWidget; public Widget LinkedWidget;
public static List<Widget> selectedWidgets = new List<Widget>(); public static List<Widget> SelectedWidgets = new List<Widget>();
public Widget(string id, int size, Shape shape) public Widget(string id, int size, WidgetShape shape)
{ {
this.id = id; Id = id;
this.size = size; Size = size;
this.shape = shape; Shape = shape;
} }
public virtual void Update(float deltaTime) public virtual void Update(float deltaTime)
{ {
PreUpdate?.Invoke(deltaTime); PreUpdate?.Invoke(deltaTime);
if (!enabled) { return; } if (!enabled) { return; }
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld())) if (IsMouseOver || (!RequireMouseOn && SelectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
{ {
Hovered?.Invoke(); Hovered?.Invoke();
System.Diagnostics.Debug.WriteLine("hovered"); System.Diagnostics.Debug.WriteLine("hovered");
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown()) if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
{ {
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None()) if ((multiselect && !SelectedWidgets.Contains(this)) || SelectedWidgets.None())
{ {
selectedWidgets.Add(this); SelectedWidgets.Add(this);
Selected?.Invoke(); Selected?.Invoke();
} }
} }
} }
else if (selectedWidgets.Contains(this)) else if (SelectedWidgets.Contains(this))
{ {
System.Diagnostics.Debug.WriteLine("selectedWidgets.Contains(this) -> remove"); System.Diagnostics.Debug.WriteLine("selectedWidgets.Contains(this) -> remove");
selectedWidgets.Remove(this); SelectedWidgets.Remove(this);
Deselected?.Invoke(); Deselected?.Invoke();
} }
if (IsSelected) if (IsSelected)
@@ -153,40 +153,40 @@ namespace Barotrauma
{ {
PreDraw?.Invoke(spriteBatch, deltaTime); PreDraw?.Invoke(spriteBatch, deltaTime);
var drawRect = DrawRect; var drawRect = DrawRect;
switch (shape) switch (Shape)
{ {
case Shape.Rectangle: case WidgetShape.Rectangle:
if (secondaryColor.HasValue) if (SecondaryColor.HasValue)
{ {
GUI.DrawRectangle(spriteBatch, drawRect, secondaryColor.Value, isFilled, thickness: 2); GUI.DrawRectangle(spriteBatch, drawRect, SecondaryColor.Value, IsFilled, thickness: 2);
} }
GUI.DrawRectangle(spriteBatch, drawRect, color, isFilled, thickness: IsSelected ? (int)(thickness * 3) : (int)thickness); GUI.DrawRectangle(spriteBatch, drawRect, Color, IsFilled, thickness: IsSelected ? (int)(Thickness * 3) : (int)Thickness);
break; break;
case Shape.Circle: case WidgetShape.Circle:
if (secondaryColor.HasValue) if (SecondaryColor.HasValue)
{ {
ShapeExtensions.DrawCircle(spriteBatch, DrawPos, size / 2, sides, secondaryColor.Value, thickness: 2); ShapeExtensions.DrawCircle(spriteBatch, DrawPos, Size / 2, Sides, SecondaryColor.Value, thickness: 2);
} }
ShapeExtensions.DrawCircle(spriteBatch, DrawPos, size / 2, sides, color, thickness: IsSelected ? 3 : 1); ShapeExtensions.DrawCircle(spriteBatch, DrawPos, Size / 2, Sides, Color, thickness: IsSelected ? 3 : 1);
break; break;
case Shape.Cross: case WidgetShape.Cross:
float halfSize = size / 2; float halfSize = Size / 2;
if (secondaryColor.HasValue) if (SecondaryColor.HasValue)
{ {
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, secondaryColor.Value, width: 2); GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, SecondaryColor.Value, width: 2);
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, secondaryColor.Value, width: 2); GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, SecondaryColor.Value, width: 2);
} }
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, color, width: IsSelected ? 3 : 1); GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, Color, width: IsSelected ? 3 : 1);
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, color, width: IsSelected ? 3 : 1); GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, Color, width: IsSelected ? 3 : 1);
break; break;
default: throw new NotImplementedException(shape.ToString()); default: throw new NotImplementedException(Shape.ToString());
} }
if (IsSelected) if (IsSelected)
{ {
if (showTooltip && !tooltip.IsNullOrEmpty()) if (ShowTooltip && !Tooltip.IsNullOrEmpty())
{ {
var offset = tooltipOffset ?? new Vector2(size, -size / 2f); var offset = TooltipOffset ?? new Vector2(Size, -Size / 2f);
GUI.DrawString(spriteBatch, DrawPos + offset, tooltip, textColor, textBackgroundColor); GUIComponent.DrawToolTip(spriteBatch, Tooltip, DrawPos + offset, TextColor, TextBackgroundColor);
} }
} }
PostDraw?.Invoke(spriteBatch, deltaTime); PostDraw?.Invoke(spriteBatch, deltaTime);
@@ -1,4 +1,4 @@
using Barotrauma.IO; using Barotrauma.IO;
using Barotrauma.Media; using Barotrauma.Media;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
@@ -131,7 +131,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public event Action ResolutionChanged; public event Action ResolutionChanged;
private bool exiting; public static bool IsExiting { get; private set; }
public static bool IsFirstLaunch public static bool IsFirstLaunch
{ {
@@ -175,7 +175,7 @@ namespace Barotrauma
{ {
try try
{ {
return Instance != null && !Instance.exiting && Instance.IsActive; return Instance != null && !IsExiting && Instance.IsActive;
} }
catch (NullReferenceException) catch (NullReferenceException)
{ {
@@ -462,7 +462,15 @@ namespace Barotrauma
{ {
Thread.Sleep((int)(Timing.Step * 1000)); Thread.Sleep((int)(Timing.Step * 1000));
} }
LanguageIdentifier selectedLanguage = GameSettings.CurrentConfig.Language;
//unload text files at this point - we only loaded for the purposes of the language selection screen,
//they will be loaded "normally" with the rest of the files later
ContentPackageManager.VanillaCorePackage.UnloadFilesOfType<TextFile>(); ContentPackageManager.VanillaCorePackage.UnloadFilesOfType<TextFile>();
//the selected language got unloaded, need to reselect it
var config = GameSettings.CurrentConfig;
config.Language = selectedLanguage;
GameSettings.SetCurrentConfig(config);
GameSettings.SaveCurrentConfig();
} }
SoundManager = new Sounds.SoundManager(); SoundManager = new Sounds.SoundManager();
@@ -485,8 +493,10 @@ namespace Barotrauma
.Select(p => p.Result).Successes()) .Select(p => p.Result).Successes())
{ {
const float min = 1f, max = 70f; const float min = 1f, max = 70f;
if (IsExiting) { break; }
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress); TitleScreen.LoadState = MathHelper.Lerp(min, max, progress);
} }
if (IsExiting) { return; }
var corePackage = ContentPackageManager.EnabledPackages.Core; var corePackage = ContentPackageManager.EnabledPackages.Core;
if (corePackage.EnableError.TryUnwrap(out var error)) if (corePackage.EnableError.TryUnwrap(out var error))
@@ -578,6 +588,8 @@ namespace Barotrauma
MainMenuScreen.Select(); MainMenuScreen.Select();
ContainerTagPrefab.CheckForContainerTagErrors();
foreach (Identifier steamError in SteamManager.InitializationErrors) foreach (Identifier steamError in SteamManager.InitializationErrors)
{ {
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError)); new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError));
@@ -1205,7 +1217,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), linkHolder.RectTransform), TextManager.Get("bugreportgithubform"), style: "MainMenuGUIButton", textAlignment: Alignment.Left) new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), linkHolder.RectTransform), TextManager.Get("bugreportgithubform"), style: "MainMenuGUIButton", textAlignment: Alignment.Left)
{ {
UserData = "https://github.com/Regalis11/Barotrauma/issues/new/choose", UserData = "https://github.com/FakeFishGames/Barotrauma/discussions/new?category=bug-reports",
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
ShowOpenUriPrompt(userdata as string); ShowOpenUriPrompt(userdata as string);
@@ -1229,7 +1241,7 @@ namespace Barotrauma
protected override void OnExiting(object sender, EventArgs args) protected override void OnExiting(object sender, EventArgs args)
{ {
exiting = true; IsExiting = true;
CreatureMetrics.Save(); CreatureMetrics.Save();
DebugConsole.NewMessage("Exiting..."); DebugConsole.NewMessage("Exiting...");
Client?.Quit(); Client?.Quit();
@@ -157,6 +157,7 @@ namespace Barotrauma
Character.Controlled.Info.Name, Character.Controlled.Info.Name,
msg, messageType, msg, messageType,
Character.Controlled); Character.Controlled);
Character.Controlled.ShowSpeechBubble(ChatMessage.MessageColor[(int)messageType], text);
if (messageType == ChatMessageType.Radio && headset != null) if (messageType == ChatMessageType.Radio && headset != null)
{ {
Signal s = new Signal(msg, sender: Character.Controlled, source: headset.Item); Signal s = new Signal(msg, sender: Character.Controlled, source: headset.Item);
@@ -620,7 +621,7 @@ namespace Barotrauma
private void OnCrewListRearranged(GUIListBox crewList, object draggedElementData) private void OnCrewListRearranged(GUIListBox crewList, object draggedElementData)
{ {
if (crewList != this.crewList) { return; } if (crewList != this.crewList) { return; }
if (!(draggedElementData is Character)) { return; } if (draggedElementData is not Character) { return; }
if (!IsSinglePlayer) { return; } if (!IsSinglePlayer) { return; }
if (crewList.HasDraggedElementIndexChanged) if (crewList.HasDraggedElementIndexChanged)
{ {
@@ -645,7 +646,7 @@ namespace Barotrauma
for (int i = 0; i < crewList.Content.CountChildren; i++) for (int i = 0; i < crewList.Content.CountChildren; i++)
{ {
var characterComponent = crewList.Content.GetChild(i); var characterComponent = crewList.Content.GetChild(i);
if (!(characterComponent?.UserData is Character c)) { continue; } if (characterComponent?.UserData is not Character c) { continue; }
if (c.Info == null) { continue; } if (c.Info == null) { continue; }
c.Info.CrewListIndex = i; c.Info.CrewListIndex = i;
} }
@@ -681,6 +682,7 @@ namespace Barotrauma
{ {
AddSinglePlayerChatMessage(senderName.Value, text.Value, messageType, sender); AddSinglePlayerChatMessage(senderName.Value, text.Value, messageType, sender);
} }
public void AddSinglePlayerChatMessage(string senderName, string text, ChatMessageType messageType, Character sender) public void AddSinglePlayerChatMessage(string senderName, string text, ChatMessageType messageType, Character sender)
{ {
if (!IsSinglePlayer) if (!IsSinglePlayer)
@@ -798,12 +800,27 @@ namespace Barotrauma
hull ??= order.OrderGiver.CurrentHull; hull ??= order.OrderGiver.CurrentHull;
AddOrder(order.WithTargetEntity(hull), order.FadeOutTime); AddOrder(order.WithTargetEntity(hull), order.FadeOutTime);
} }
if (order.IsDeconstructOrder)
{
if (order.TargetEntity is Item item)
{
if (order.Identifier == Tags.DeconstructThis)
{
Item.DeconstructItems.Add(item);
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
}
else
{
Item.DeconstructItems.Remove(item);
}
}
}
else if (order.IsIgnoreOrder) else if (order.IsIgnoreOrder)
{ {
WallSection ws = null; WallSection ws = null;
if (order.TargetType == Order.OrderTargetType.Entity && order.TargetEntity is IIgnorable ignorable) if (order.TargetType == Order.OrderTargetType.Entity && order.TargetEntity is IIgnorable ignorable)
{ {
ignorable.OrderedToBeIgnored = order.Identifier == "ignorethis"; ignorable.OrderedToBeIgnored = order.Identifier == Tags.IgnoreThis;
AddOrder(order.Clone(), null); AddOrder(order.Clone(), null);
} }
else if (order.TargetType == Order.OrderTargetType.WallSection && order.TargetEntity is Structure s) else if (order.TargetType == Order.OrderTargetType.WallSection && order.TargetEntity is Structure s)
@@ -812,7 +829,7 @@ namespace Barotrauma
ws = s.GetSection(wallSectionIndex); ws = s.GetSection(wallSectionIndex);
if (ws != null) if (ws != null)
{ {
ws.OrderedToBeIgnored = order.Identifier == "ignorethis"; ws.OrderedToBeIgnored = order.Identifier == Tags.IgnoreThis;
AddOrder(order.WithWallSection(s, wallSectionIndex), null); AddOrder(order.WithWallSection(s, wallSectionIndex), null);
} }
} }
@@ -835,7 +852,9 @@ namespace Barotrauma
} }
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
order.OrderGiver?.Speak(order.GetChatMessage("", hull?.DisplayName?.Value, givingOrderToSelf: character == order.OrderGiver, isNewOrder: isNewOrder), ChatMessageType.Order); order.OrderGiver?.Speak(
order.GetChatMessage("", hull?.DisplayName?.Value, givingOrderToSelf: character == order.OrderGiver, isNewOrder: isNewOrder),
ChatMessageType.Order);
} }
else else
{ {
@@ -1404,9 +1423,10 @@ namespace Barotrauma
if (PlayerInput.KeyDown(InputType.Command) && if (PlayerInput.KeyDown(InputType.Command) &&
(GUI.KeyboardDispatcher.Subscriber == null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.IsChildOf(crewList)))) && (GUI.KeyboardDispatcher.Subscriber == null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.IsChildOf(crewList)))) &&
commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false) && commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false) &&
Character.Controlled?.SelectedItem?.Prefab is not { DisableCommandMenuWhenSelected: true }) Character.Controlled?.SelectedItem?.Prefab is not { DisableCommandMenuWhenSelected: true } &&
!Inventory.IsMouseOnInventory)
{ {
if (PlayerInput.IsShiftDown()) if (PlayerInput.KeyDown(InputType.ContextualCommand))
{ {
CreateCommandUI(FindEntityContext(), true); CreateCommandUI(FindEntityContext(), true);
} }
@@ -1535,14 +1555,14 @@ namespace Barotrauma
{ {
if (node.Keys != Keys.None && PlayerInput.KeyHit(node.Keys)) if (node.Keys != Keys.None && PlayerInput.KeyHit(node.Keys))
{ {
var b = node.Button as GUIButton; var button = node.Button;
if (PlayerInput.IsShiftDown() && b?.OnSecondaryClicked != null) if (PlayerInput.IsShiftDown() && button?.OnSecondaryClicked != null)
{ {
b.OnSecondaryClicked.Invoke(node.Button as GUIButton, node.Button.UserData); button.OnSecondaryClicked.Invoke(button, button.UserData);
} }
else else
{ {
b?.OnClicked?.Invoke(node.Button as GUIButton, node.Button.UserData); button?.OnClicked?.Invoke(button, button.UserData);
} }
ResetNodeSelection(); ResetNodeSelection();
hotkeyHit = true; hotkeyHit = true;
@@ -1612,7 +1632,7 @@ namespace Barotrauma
{ {
foreach (var orderIcon in currentOrderIconList.Content.Children) foreach (var orderIcon in currentOrderIconList.Content.Children)
{ {
if (!(orderIcon.UserData is Order order)) { continue; } if (orderIcon.UserData is not Order order) { continue; }
if (order.ColoredWhenControllingGiver && order.OrderGiver != Character.Controlled) if (order.ColoredWhenControllingGiver && order.OrderGiver != Character.Controlled)
{ {
orderIcon.Color = AIObjective.ObjectiveIconColor; orderIcon.Color = AIObjective.ObjectiveIconColor;
@@ -1701,7 +1721,7 @@ namespace Barotrauma
bool foundMatch = false; bool foundMatch = false;
foreach (var orderIcon in currentOrderIconList.Content.Children) foreach (var orderIcon in currentOrderIconList.Content.Children)
{ {
if (!(orderIcon.GetChildByUserData("glow") is GUIComponent glowComponent)) { continue; } if (orderIcon.GetChildByUserData("glow") is not GUIComponent glowComponent) { continue; }
glowComponent.Color = orderIcon.Color; glowComponent.Color = orderIcon.Color;
if (foundMatch) if (foundMatch)
{ {
@@ -1946,6 +1966,13 @@ namespace Barotrauma
} }
} }
public void OpenCommandUI(Entity entityContext = null, bool forceContextual = false)
{
CreateCommandUI(entityContext, forceContextual);
SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
clicklessSelectionActive = isOpeningClick = true;
}
private void CreateCommandUI(Entity entityContext = null, bool forceContextual = false) private void CreateCommandUI(Entity entityContext = null, bool forceContextual = false)
{ {
if (commandFrame != null) { DisableCommandUI(); } if (commandFrame != null) { DisableCommandUI(); }
@@ -2018,7 +2045,7 @@ namespace Barotrauma
new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center), new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center),
(spriteBatch, _) => (spriteBatch, _) =>
{ {
if (!(entityContext is Character character) || character?.Info == null) { return; } if (entityContext is not Character character || character?.Info == null) { return; }
var node = startNode; var node = startNode;
character.Info.DrawJobIcon(spriteBatch, character.Info.DrawJobIcon(spriteBatch,
new Rectangle((int)(node.Rect.X + node.Rect.Width * 0.5f), (int)(node.Rect.Y + node.Rect.Height * 0.1f), (int)(node.Rect.Width * 0.6f), (int)(node.Rect.Height * 0.8f))); new Rectangle((int)(node.Rect.X + node.Rect.Width * 0.5f), (int)(node.Rect.Y + node.Rect.Height * 0.1f), (int)(node.Rect.Width * 0.6f), (int)(node.Rect.Height * 0.8f)));
@@ -2082,7 +2109,7 @@ namespace Barotrauma
returnNodeMargin = returnNodeSize.X * 0.5f; returnNodeMargin = returnNodeSize.X * 0.5f;
nodeDistance = (int)(150 * GUI.Scale); nodeDistance = (int)(150 * GUI.Scale);
shorcutCenterNodeOffset = new Point(0, (int)(1.25f * nodeDistance)); shorcutCenterNodeOffset = new Point(0, (int)(1.35f * nodeDistance));
} }
private List<OrderCategory> GetAvailableCategories() private List<OrderCategory> GetAvailableCategories()
@@ -2115,7 +2142,7 @@ namespace Barotrauma
if (centerNode == null || optionNodes == null) { return; } if (centerNode == null || optionNodes == null) { return; }
var startNodePos = centerNode.Rect.Center.ToVector2(); var startNodePos = centerNode.Rect.Center.ToVector2();
// Don't draw connectors for assignment nodes // Don't draw connectors for assignment nodes
if (!(optionNodes.FirstOrDefault()?.Button.UserData is Character)) if (optionNodes.FirstOrDefault()?.Button.UserData is not Character)
{ {
// Regular option nodes // Regular option nodes
if (targetFrame == null || !targetFrame.Visible) if (targetFrame == null || !targetFrame.Visible)
@@ -2194,7 +2221,7 @@ namespace Barotrauma
private bool NavigateForward(GUIButton node, object userData) private bool NavigateForward(GUIButton node, object userData)
{ {
if (commandFrame == null) { return false; } if (commandFrame == null) { return false; }
if (!(optionNodes.Find(n => n.Button == node) is OptionNode optionNode) || !optionNodes.Remove(optionNode)) if (optionNodes.Find(n => n.Button == node) is not OptionNode optionNode || !optionNodes.Remove(optionNode))
{ {
shortcutNodes.Remove(node); shortcutNodes.Remove(node);
}; };
@@ -2352,7 +2379,7 @@ namespace Barotrauma
matchingItems = nodeOrder.GetMatchingItems(submarine, true, interactableFor: characterContext ?? Character.Controlled); matchingItems = nodeOrder.GetMatchingItems(submarine, true, interactableFor: characterContext ?? Character.Controlled);
} }
//more than one target item -> create a minimap-like selection with a pic of the sub //more than one target item -> create a minimap-like selection with a pic of the sub
if (itemContext == null && !(nodeOrder.TargetEntity is Item) && matchingItems != null && matchingItems.Count > 1) if (itemContext == null && nodeOrder.TargetEntity is not Item && matchingItems != null && matchingItems.Count > 1)
{ {
CreateMinimapNodes(nodeOrder, submarine, matchingItems); CreateMinimapNodes(nodeOrder, submarine, matchingItems);
} }
@@ -2417,7 +2444,7 @@ namespace Barotrauma
node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration); node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration);
var icon = OrderCategoryIcon.OrderCategoryIcons.FirstOrDefault(ic => ic.Category == category); var icon = OrderCategoryIcon.OrderCategoryIcons.FirstOrDefault(ic => ic.Category == category);
if (!(icon is null)) if (icon is not null)
{ {
var tooltip = TextManager.Get($"ordercategorytitle.{category}"); var tooltip = TextManager.Get($"ordercategorytitle.{category}");
var categoryDescription = TextManager.Get($"ordercategorydescription.{category}"); var categoryDescription = TextManager.Get($"ordercategorydescription.{category}");
@@ -2484,7 +2511,7 @@ namespace Barotrauma
// TODO: Doesn't work for player issued reports, because they don't have a target. // TODO: Doesn't work for player issued reports, because they don't have a target.
bool useSpecificRepairOrder = false; bool useSpecificRepairOrder = false;
if (CanFitMoreNodes() && ShouldDelegateOrder("repairelectrical") && if (CanFitMoreNodes() && ShouldDelegateOrder("repairelectrical") &&
ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "electrical"))) ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is Repairable r && r.RequiredSkills.Any(s => s.Identifier == "electrical")))
{ {
if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["repairelectrical"])) if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["repairelectrical"]))
{ {
@@ -2493,7 +2520,7 @@ namespace Barotrauma
useSpecificRepairOrder = true; useSpecificRepairOrder = true;
} }
if (CanFitMoreNodes() && ShouldDelegateOrder("repairmechanical") && if (CanFitMoreNodes() && ShouldDelegateOrder("repairmechanical") &&
ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is Repairable r && r.requiredSkills.Any(s => s.Identifier == "mechanical"))) ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is Repairable r && r.RequiredSkills.Any(s => s.Identifier == "mechanical")))
{ {
if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["repairmechanical"])) if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["repairmechanical"]))
{ {
@@ -2565,7 +2592,7 @@ namespace Barotrauma
static bool ShouldDelegateOrder(string orderIdentifier) => ShouldDelegateOrderId(orderIdentifier.ToIdentifier()); static bool ShouldDelegateOrder(string orderIdentifier) => ShouldDelegateOrderId(orderIdentifier.ToIdentifier());
static bool ShouldDelegateOrderId(Identifier orderIdentifier) static bool ShouldDelegateOrderId(Identifier orderIdentifier)
{ {
return !(Character.Controlled is Character c) || !(c?.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(orderIdentifier)); return Character.Controlled is not Character c || !(c?.Info?.Job != null && c.Info.Job.Prefab.AppropriateOrders.Contains(orderIdentifier));
} }
bool IsNonDuplicateOrder(Order order) => IsNonDuplicateOrderPrefab(order.Prefab, order.Option); bool IsNonDuplicateOrder(Order order) => IsNonDuplicateOrderPrefab(order.Prefab, order.Option);
bool IsNonDuplicateOrderPrefab(OrderPrefab orderPrefab, Identifier option = default) bool IsNonDuplicateOrderPrefab(OrderPrefab orderPrefab, Identifier option = default)
@@ -2642,6 +2669,7 @@ namespace Barotrauma
new Order(p, itemContext, targetComponent)); new Order(p, itemContext, targetComponent));
} }
} }
// If targeting a periscope connected to a turret, show the 'operateweapons' order // If targeting a periscope connected to a turret, show the 'operateweapons' order
var operateWeaponsPrefab = OrderPrefab.Prefabs["operateweapons"]; var operateWeaponsPrefab = OrderPrefab.Prefabs["operateweapons"];
if (contextualOrders.None(o => o.Identifier == "operateweapons") && itemContext.Components.Any(c => c is Controller)) if (contextualOrders.None(o => o.Identifier == "operateweapons") && itemContext.Components.Any(c => c is Controller))
@@ -2656,11 +2684,11 @@ namespace Barotrauma
// If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order // If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order
if (contextualOrders.None(order => order.Identifier == "repairsystems") && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold)) if (contextualOrders.None(order => order.Identifier == "repairsystems") && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold))
{ {
if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical")))) if (itemContext.Repairables.Any(r => r != null && r.RequiredSkills.Any(s => s != null && s.Identifier.Equals("electrical"))))
{ {
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairelectrical"], itemContext, targetItem: null)); contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairelectrical"], itemContext, targetItem: null));
} }
else if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical")))) else if (itemContext.Repairables.Any(r => r != null && r.RequiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical"))))
{ {
contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairmechanical"], itemContext, targetItem: null)); contextualOrders.Add(new Order(OrderPrefab.Prefabs["repairmechanical"], itemContext, targetItem: null));
} }
@@ -2694,14 +2722,14 @@ namespace Barotrauma
} }
void AddIgnoreOrder(IIgnorable target) void AddIgnoreOrder(IIgnorable target)
{ {
var orderIdentifier = "ignorethis"; var orderIdentifier = Tags.IgnoreThis;
if (!target.OrderedToBeIgnored && contextualOrders.None(order => order.Identifier == orderIdentifier)) if (!target.OrderedToBeIgnored && contextualOrders.None(order => order.Identifier == orderIdentifier))
{ {
AddOrder(); AddOrder();
} }
else else
{ {
orderIdentifier = "unignorethis"; orderIdentifier = Tags.UnignoreThis;
if (target.OrderedToBeIgnored && contextualOrders.None(order => order.Identifier == orderIdentifier)) if (target.OrderedToBeIgnored && contextualOrders.None(order => order.Identifier == orderIdentifier))
{ {
AddOrder(); AddOrder();
@@ -2753,20 +2781,6 @@ namespace Barotrauma
} }
} }
// TODO: there's duplicate logic here and above -> would be better to refactor so that the conditions are only defined in one place
public static bool DoesItemHaveContextualOrders(Item item)
{
if (OrderPrefab.Prefabs.Any(o => o.TargetItemsMatchItem(item))) { return true; }
if (OrderPrefab.Prefabs.Any(o => o.TryGetTargetItemComponent(item, out _))) { return true; }
if (AIObjectiveCleanupItems.IsValidTarget(item, Character.Controlled, checkInventory: false)) { return true; }
if (AIObjectiveCleanupItems.IsValidContainer(item, Character.Controlled)) { return true; }
if (OrderPrefab.Prefabs.TryGet("loaditems", out OrderPrefab loadItemsPrefab) && AIObjectiveLoadItems.IsValidTarget(item, Character.Controlled, targetContainerTags: loadItemsPrefab.GetTargetItems())) { return true; }
if (item.Repairables.Any(r => r.IsBelowRepairThreshold)) { return true; }
return OrderPrefab.Prefabs.TryGet("operateweapons", out OrderPrefab operateWeaponsPrefab) && item.Components.Any(c => c is Controller) &&
(item.GetConnectedComponents<Turret>().Any(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) ||
item.GetConnectedComponents<Turret>(recursive: true).Any(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)));
}
/// <param name="hotkey">Use a negative value (e.g. -1) if there should be no hotkey associated with the node</param> /// <param name="hotkey">Use a negative value (e.g. -1) if there should be no hotkey associated with the node</param>
private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order, int hotkey, bool disableNode = false, bool checkIfOrderCanBeHeard = true) private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order, int hotkey, bool disableNode = false, bool checkIfOrderCanBeHeard = true)
{ {
@@ -3719,16 +3733,16 @@ namespace Barotrauma
switch (order.TargetType) switch (order.TargetType)
{ {
case Order.OrderTargetType.Entity: case Order.OrderTargetType.Entity:
if (!(order.TargetEntity is IIgnorable ignorableEntity)) { break; } if (order.TargetEntity is not IIgnorable ignorableEntity) { break; }
ignorableEntity.OrderedToBeIgnored = order.Identifier == "ignorethis"; ignorableEntity.OrderedToBeIgnored = order.Identifier == Tags.IgnoreThis;
break; break;
case Order.OrderTargetType.Position: case Order.OrderTargetType.Position:
throw new NotImplementedException(); throw new NotImplementedException();
case Order.OrderTargetType.WallSection: case Order.OrderTargetType.WallSection:
if (!order.WallSectionIndex.HasValue) { break; } if (!order.WallSectionIndex.HasValue) { break; }
if (!(order.TargetEntity is Structure s)) { break; } if (order.TargetEntity is not Structure s) { break; }
if (!(s.GetSection(order.WallSectionIndex.Value) is IIgnorable ignorableWall)) { break; } if (s.GetSection(order.WallSectionIndex.Value) is not IIgnorable ignorableWall) { break; }
ignorableWall.OrderedToBeIgnored = order.Identifier == "ignorethis"; ignorableWall.OrderedToBeIgnored = order.Identifier == Tags.IgnoreThis;
break; break;
} }
} }
@@ -3,16 +3,19 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Barotrauma namespace Barotrauma
{ {
abstract partial class CampaignMode : GameMode internal abstract partial class CampaignMode : GameMode
{ {
protected bool crewDead; public bool CrewDead
{
get;
protected set;
}
protected Color overlayColor; protected Color overlayColor;
protected Sprite overlaySprite; protected Sprite overlaySprite;
@@ -67,10 +67,11 @@ namespace Barotrauma
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), layout.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), layout.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, isHorizontal: true)
{ {
Stretch = true,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
var campaignContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), layout.RectTransform, Anchor.BottomLeft), style: "InnerFrame") var campaignContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), layout.RectTransform, Anchor.BottomLeft), style: "GUIFrameListBox")
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -95,6 +96,7 @@ namespace Barotrauma
loadCampaignButton.Selected = false; loadCampaignButton.Selected = false;
newCampaignContainer.Visible = true; newCampaignContainer.Visible = true;
loadCampaignContainer.Visible = false; loadCampaignContainer.Visible = false;
GameMain.NetLobbyScreen?.RefreshStartButtonVisibility();
return true; return true;
}; };
loadCampaignButton.OnClicked = (btn, obj) => loadCampaignButton.OnClicked = (btn, obj) =>
@@ -103,6 +105,7 @@ namespace Barotrauma
loadCampaignButton.Selected = true; loadCampaignButton.Selected = true;
newCampaignContainer.Visible = false; newCampaignContainer.Visible = false;
loadCampaignContainer.Visible = true; loadCampaignContainer.Visible = true;
GameMain.NetLobbyScreen?.RefreshStartButtonVisibility();
return true; return true;
}; };
loadCampaignContainer.Visible = false; loadCampaignContainer.Visible = false;
@@ -297,7 +300,7 @@ namespace Barotrauma
Level prevLevel = Level.Loaded; Level prevLevel = Level.Loaded;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
crewDead = false; CrewDead = false;
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
if (continueButton != null) if (continueButton != null)
@@ -480,7 +483,6 @@ namespace Barotrauma
GameMain.CampaignEndScreen.OnFinished = () => GameMain.CampaignEndScreen.OnFinished = () =>
{ {
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
if (GameMain.NetLobbyScreen.ContinueCampaignButton != null) { GameMain.NetLobbyScreen.ContinueCampaignButton.Enabled = false; }
if (GameMain.NetLobbyScreen.QuitCampaignButton != null) { GameMain.NetLobbyScreen.QuitCampaignButton.Enabled = false; } if (GameMain.NetLobbyScreen.QuitCampaignButton != null) { GameMain.NetLobbyScreen.QuitCampaignButton.Enabled = false; }
}; };
} }
@@ -934,7 +936,7 @@ namespace Barotrauma
{ {
int renamedIdentifier = msg.ReadInt32(); int renamedIdentifier = msg.ReadInt32();
string newName = msg.ReadString(); string newName = msg.ReadString();
CharacterInfo renamedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier); CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
if (renamedCharacter != null) { CrewManager.RenameCharacter(renamedCharacter, newName); } if (renamedCharacter != null) { CrewManager.RenameCharacter(renamedCharacter, newName); }
} }
@@ -942,7 +944,7 @@ namespace Barotrauma
if (fireCharacter) if (fireCharacter)
{ {
int firedIdentifier = msg.ReadInt32(); int firedIdentifier = msg.ReadInt32();
CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier); CharacterInfo firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.GetIdentifier() == firedIdentifier);
// this one might and is allowed to be null since the character is already fired on the original sender's game // this one might and is allowed to be null since the character is already fired on the original sender's game
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); } if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
} }
@@ -952,7 +954,7 @@ namespace Barotrauma
!NetIdUtils.IdMoreRecent(pendingSaveID, LastSaveID)) !NetIdUtils.IdMoreRecent(pendingSaveID, LastSaveID))
{ {
CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires); CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); } if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters, takeMoney: false); }
CampaignUI.CrewManagement.SetPendingHires(pendingHires, map.CurrentLocation); CampaignUI.CrewManagement.SetPendingHires(pendingHires, map.CurrentLocation);
if (renameCrewMember || fireCharacter) { CampaignUI.CrewManagement.UpdateCrew(); } if (renameCrewMember || fireCharacter) { CampaignUI.CrewManagement.UpdateCrew(); }
} }
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -243,7 +244,7 @@ namespace Barotrauma
savedOnStart = true; savedOnStart = true;
} }
crewDead = false; CrewDead = false;
endTimer = 5.0f; endTimer = 5.0f;
CrewManager.InitSinglePlayerRound(); CrewManager.InitSinglePlayerRound();
LoadPets(); LoadPets();
@@ -373,7 +374,7 @@ namespace Barotrauma
SoundPlayer.OverrideMusicType = (success ? "endround" : "crewdead").ToIdentifier(); SoundPlayer.OverrideMusicType = (success ? "endround" : "crewdead").ToIdentifier();
SoundPlayer.OverrideMusicDuration = 18.0f; SoundPlayer.OverrideMusicDuration = 18.0f;
GUI.SetSavingIndicatorState(success); GUI.SetSavingIndicatorState(success);
crewDead = false; CrewDead = false;
if (success) if (success)
{ {
@@ -582,9 +583,12 @@ namespace Barotrauma
HintManager.OnAvailableTransition(transitionType); HintManager.OnAvailableTransition(transitionType);
} }
if (!crewDead) if (!CrewDead)
{ {
if (!CrewManager.GetCharacters().Any(c => !c.IsDead)) { crewDead = true; } if (CrewManager.GetCharacters().None(c => !c.IsDead && !CrewManager.IsFired(c)))
{
CrewDead = true;
}
} }
else else
{ {
@@ -42,6 +42,15 @@ namespace Barotrauma
foreach (Submarine submarine in Submarine.Loaded) foreach (Submarine submarine in Submarine.Loaded)
{ {
submarine.NeutralizeBallast(); submarine.NeutralizeBallast();
//normally the body would be made static during level generation,
//but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
if (submarine.Info.Type == SubmarineType.Outpost ||
submarine.Info.Type == SubmarineType.OutpostModule ||
submarine.Info.Type == SubmarineType.Wreck ||
submarine.Info.Type == SubmarineType.BeaconStation)
{
submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
}
} }
if (SpawnOutpost) if (SpawnOutpost)
@@ -51,14 +60,14 @@ namespace Barotrauma
if (TriggeredEvent != null) if (TriggeredEvent != null)
{ {
scriptedEvent = new List<Event> { TriggeredEvent.CreateInstance() }; scriptedEvent = new List<Event> { TriggeredEvent.CreateInstance(GameMain.GameSession.EventManager.RandomSeed) };
GameMain.GameSession.EventManager.PinnedEvent = scriptedEvent.Last(); GameMain.GameSession.EventManager.PinnedEvent = scriptedEvent.Last();
createEventButton = new GUIButton(new RectTransform(new Point(128, 64), GUI.Canvas, Anchor.TopCenter) { ScreenSpaceOffset = new Point(0, 32) }, TextManager.Get("create")) createEventButton = new GUIButton(new RectTransform(new Point(128, 64), GUI.Canvas, Anchor.TopCenter) { ScreenSpaceOffset = new Point(0, 32) }, TextManager.Get("create"))
{ {
OnClicked = delegate OnClicked = delegate
{ {
scriptedEvent.Add(TriggeredEvent.CreateInstance()); scriptedEvent.Add(TriggeredEvent.CreateInstance(GameMain.GameSession.EventManager.RandomSeed));
GameMain.GameSession.EventManager.PinnedEvent = scriptedEvent.Last(); GameMain.GameSession.EventManager.PinnedEvent = scriptedEvent.Last();
return true; return true;
} }
@@ -188,9 +188,9 @@ namespace Barotrauma.Tutorials
var door = item.GetComponent<Door>(); var door = item.GetComponent<Door>();
if (door != null) if (door != null)
{ {
if (door.requiredItems.Values.None(ris => ris.None(ri => ri.Identifiers.None(i => i == "locked")))) if (door.RequiredItems.Values.None(ris => ris.None(ri => ri.Identifiers.None(i => i == "locked"))))
{ {
door.requiredItems.Clear(); door.RequiredItems.Clear();
} }
} }
} }
@@ -262,7 +262,7 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
if (eventPrefab.CreateInstance() is Event eventInstance) if (eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed) is Event eventInstance)
{ {
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventInstance); GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventInstance);
while (!eventInstance.IsFinished) while (!eventInstance.IsFinished)
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
@@ -110,7 +111,9 @@ namespace Barotrauma
}; };
respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn")) respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn"))
{ {
ToolTip = TextManager.Get("respawnquestionprompt"), ToolTip = TextManager.GetWithVariable(
"respawnquestionprompt", "[percentage]",
(Math.Round(Networking.RespawnManager.SkillLossPercentageOnImmediateRespawn).ToString())),
OnSelected = (tickbox) => OnSelected = (tickbox) =>
{ {
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: !tickbox.Selected); GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: !tickbox.Selected);
@@ -185,6 +185,19 @@ namespace Barotrauma
} }
} }
public static void OnItemMarkedForRelocation()
{
DisplayHint($"onitemmarkedforrelocation".ToIdentifier());
}
public static void OnItemMarkedForDeconstruction(Character character)
{
if (character == Character.Controlled)
{
DisplayHint($"onitemmarkedfordeconstruction".ToIdentifier());
}
}
private static void CheckIsInteracting() private static void CheckIsInteracting()
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
@@ -582,6 +595,24 @@ namespace Barotrauma
{ {
DisplayHint("onballastflorainfected".ToIdentifier()); DisplayHint("onballastflorainfected".ToIdentifier());
} }
if (order.Identifier == "deconstructitems" &&
Item.DeconstructItems.None())
{
DisplayHint("ondeconstructorder".ToIdentifier());
}
}
public static void OnSetOrder(Character character, Order order)
{
if (!CanDisplayHints()) { return; }
if (character == null || order == null) { return; }
if (order.OrderGiver == Character.Controlled &&
order.Identifier == "deconstructitems" &&
Item.DeconstructItems.None())
{
DisplayHint("ondeconstructorder".ToIdentifier());
}
} }
private static void CheckIfDivingGearOutOfOxygen() private static void CheckIfDivingGearOutOfOxygen()
@@ -719,7 +750,7 @@ namespace Barotrauma
HintsIgnoredThisRound.Add(hintIdentifier); HintsIgnoredThisRound.Add(hintIdentifier);
ActiveHintMessageBox = new GUIMessageBox(hintIdentifier, text, icon); ActiveHintMessageBox = new GUIMessageBox(hintIdentifier, TextManager.ParseInputTypes(text), icon);
if (iconColor.HasValue) { ActiveHintMessageBox.IconColor = iconColor.Value; } if (iconColor.HasValue) { ActiveHintMessageBox.IconColor = iconColor.Value; }
OnUpdate = onUpdate; OnUpdate = onUpdate;
@@ -822,7 +822,7 @@ namespace Barotrauma
factionTextContent.Recalculate(); factionTextContent.Recalculate();
new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform),
onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, reputation.NormalizedValue)); onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, reputation.NormalizedValue, reputation.MinReputation, reputation.MaxReputation));
var reputationText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), var reputationText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
string.Empty, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont); string.Empty, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont);
@@ -871,7 +871,7 @@ namespace Barotrauma
return factionFrame; return factionFrame;
} }
public static void DrawReputationBar(SpriteBatch sb, Rectangle rect, float normalizedReputation) public static void DrawReputationBar(SpriteBatch sb, Rectangle rect, float normalizedReputation, float minReputation, float maxReputation)
{ {
int segmentWidth = rect.Width / 5; int segmentWidth = rect.Width / 5;
rect.Width = segmentWidth * 5; rect.Width = segmentWidth * 5;
@@ -885,9 +885,10 @@ namespace Barotrauma
GUI.Arrow.Draw(sb, new Vector2(rect.X + rect.Width * normalizedReputation, rect.Y), GUIStyle.ColorInventoryBackground, scale: GUI.Scale, spriteEffect: SpriteEffects.FlipVertically); GUI.Arrow.Draw(sb, new Vector2(rect.X + rect.Width * normalizedReputation, rect.Y), GUIStyle.ColorInventoryBackground, scale: GUI.Scale, spriteEffect: SpriteEffects.FlipVertically);
GUI.Arrow.Draw(sb, new Vector2(rect.X + rect.Width * normalizedReputation, rect.Y), GUIStyle.TextColorNormal, scale: GUI.Scale * 0.8f, spriteEffect: SpriteEffects.FlipVertically); GUI.Arrow.Draw(sb, new Vector2(rect.X + rect.Width * normalizedReputation, rect.Y), GUIStyle.TextColorNormal, scale: GUI.Scale * 0.8f, spriteEffect: SpriteEffects.FlipVertically);
GUI.DrawString(sb, new Vector2(rect.X, rect.Bottom), "-100", GUIStyle.TextColorNormal, font: GUIStyle.SmallFont); GUI.DrawString(sb, new Vector2(rect.X, rect.Bottom), ((int)minReputation).ToString(), GUIStyle.TextColorNormal, font: GUIStyle.SmallFont);
Vector2 textSize = GUIStyle.SmallFont.MeasureString("100"); string maxRepText = ((int)maxReputation).ToString();
GUI.DrawString(sb, new Vector2(rect.Right - textSize.X, rect.Bottom), "100", GUIStyle.TextColorNormal, font: GUIStyle.SmallFont); Vector2 textSize = GUIStyle.SmallFont.MeasureString(maxRepText);
GUI.DrawString(sb, new Vector2(rect.Right - textSize.X, rect.Bottom), maxRepText, GUIStyle.TextColorNormal, font: GUIStyle.SmallFont);
} }
} }
} }
@@ -1,7 +1,6 @@
#nullable enable #nullable enable
using System;
using System.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -13,7 +12,7 @@ namespace Barotrauma
if (character != null) if (character != null)
{ {
Speak(character); character.Speak(text, ChatMessageType.Default);
return; return;
} }
@@ -21,23 +20,10 @@ namespace Barotrauma
{ {
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade) if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade)
{ {
Speak(npc); npc.Speak(text, ChatMessageType.Default);
break; break;
} }
} }
void Speak(Character npc)
{
ChatMessage message = ChatMessage.Create(npc.Name, text, ChatMessageType.Default, npc);
if (!isSinglePlayer)
{
GameMain.Client?.AddChatMessage(message);
}
else
{
GameMain.GameSession?.CrewManager?.AddSinglePlayerChatMessage(message);
}
}
} }
} }
} }
@@ -771,7 +771,7 @@ namespace Barotrauma
{ {
if (Screen.Selected == GameMain.GameScreen) if (Screen.Selected == GameMain.GameScreen)
{ {
if (item.NonInteractable || item.NonPlayerTeamInteractable) if (!item.IsInteractable(Character.Controlled))
{ {
return QuickUseAction.None; return QuickUseAction.None;
} }
@@ -75,6 +75,7 @@ namespace Barotrauma.Items.Components
private void UpdateConvexHulls() private void UpdateConvexHulls()
{ {
if (item.Removed) { return; } if (item.Removed) { return; }
if (doorSprite == null) { return; }
doorRect = new Rectangle( doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale), item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
@@ -83,7 +84,7 @@ namespace Barotrauma.Items.Components
(int)(doorSprite.size.Y * item.Scale)); (int)(doorSprite.size.Y * item.Scale));
Rectangle rect = doorRect; Rectangle rect = doorRect;
if (IsHorizontal) if (IsConvexHullHorizontal)
{ {
rect.Width = (int)(rect.Width * (1.0f - openState)); rect.Width = (int)(rect.Width * (1.0f - openState));
} }
@@ -94,7 +95,7 @@ namespace Barotrauma.Items.Components
if (Window.Height > 0 && Window.Width > 0) if (Window.Height > 0 && Window.Width > 0)
{ {
if (IsHorizontal) if (IsConvexHullHorizontal)
{ {
rect.Width = (int)(Window.X * item.Scale); rect.Width = (int)(Window.X * item.Scale);
rect.X -= (int)(doorRect.Width * openState); rect.X -= (int)(doorRect.Width * openState);
@@ -164,7 +165,7 @@ namespace Barotrauma.Items.Components
Vector2 center = (verts[0] + verts[2]) / 2; Vector2 center = (verts[0] + verts[2]) / 2;
convexHull.SetVertices( convexHull.SetVertices(
verts, verts,
IsHorizontal ? IsConvexHullHorizontal ?
new Vector2[] { new Vector2(verts[0].X, center.Y), new Vector2(verts[2].X, center.Y) } : new Vector2[] { new Vector2(verts[0].X, center.Y), new Vector2(verts[2].X, center.Y) } :
new Vector2[] { new Vector2(center.X, verts[0].Y), new Vector2(center.X, verts[2].Y) }); new Vector2[] { new Vector2(center.X, verts[0].Y), new Vector2(center.X, verts[2].Y) });
convexHull.MaxMergeLosVerticesDist = 35.0f; convexHull.MaxMergeLosVerticesDist = 35.0f;
@@ -304,6 +305,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
bool stateChanged = open != isOpen;
isOpen = open; isOpen = open;
if (!isNetworkMessage || open != PredictedState) if (!isNetworkMessage || open != PredictedState)
{ {
@@ -314,6 +316,11 @@ namespace Barotrauma.Items.Components
} }
if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); } if (isOpen) { stuck = MathHelper.Clamp(stuck - StuckReductionOnOpen, 0.0f, 100.0f); }
} }
if (stateChanged)
{
ActionType actionType = open ? ActionType.OnOpen : ActionType.OnClose;
item.ApplyStatusEffects(actionType, deltaTime: 1.0f);
}
} }
void PlayInteractionSound() void PlayInteractionSound()
@@ -64,7 +64,8 @@ namespace Barotrauma.Items.Components
spriteBatch, spriteBatch,
new Vector2(attachPos.X, -attachPos.Y), new Vector2(attachPos.X, -attachPos.Y),
item.SpriteColor * 0.5f, item.SpriteColor * 0.5f,
0.0f, item.Scale, SpriteEffects.None, 0.0f); item.RotationRad,
item.Scale, SpriteEffects.None, 0.0f);
GUI.DrawRectangle(spriteBatch, new Vector2(attachPos.X - 2, -attachPos.Y - 2), Vector2.One * 5, GUIStyle.Red, thickness: 3); GUI.DrawRectangle(spriteBatch, new Vector2(attachPos.X - 2, -attachPos.Y - 2), Vector2.One * 5, GUIStyle.Red, thickness: 3);
} }
@@ -18,11 +18,31 @@ namespace Barotrauma.Items.Components
protected float currentCrossHairScale, currentCrossHairPointerScale; protected float currentCrossHairScale, currentCrossHairPointerScale;
private RoundSound chargeSound; private RoundSound chargeSound;
private SoundChannel chargeSoundChannel; private SoundChannel chargeSoundChannel;
[Serialize(defaultValue: "0.5, 1.5", IsPropertySaveable.No, description: "Pitch slides from X to Y over the charge time")]
public Vector2 ChargeSoundWindupPitchSlide
{
get => _chargeSoundWindupPitchSlide;
set
{
_chargeSoundWindupPitchSlide = new Vector2(
Math.Max(value.X, SoundChannel.MinFrequencyMultiplier),
Math.Min(value.Y, SoundChannel.MaxFrequencyMultiplier));
}
}
private Vector2 _chargeSoundWindupPitchSlide;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>();
/// <summary>
/// The orientation of the item is briefly wrong after the character holding it flips and before the holding logic forces it to the correct position.
/// We disable the crosshair briefly during that time to prevent it from momentarily jumping to an incorrect position.
/// </summary>
private float crossHairPosDirtyTimer;
[Serialize(1.0f, IsPropertySaveable.No, description: "The scale of the crosshair sprite (if there is one).")] [Serialize(1.0f, IsPropertySaveable.No, description: "The scale of the crosshair sprite (if there is one).")]
public float CrossHairScale public float CrossHairScale
{ {
@@ -30,9 +50,9 @@ namespace Barotrauma.Items.Components
private set; private set;
} }
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement rangedWeaponElement)
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in rangedWeaponElement.Elements())
{ {
string textureDir = GetTextureDirectory(subElement); string textureDir = GetTextureDirectory(subElement);
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -62,6 +82,7 @@ namespace Barotrauma.Items.Components
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam) public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{ {
crossHairPosDirtyTimer -= deltaTime;
currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom; currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
if (crosshairSprite != null) if (crosshairSprite != null)
{ {
@@ -92,6 +113,15 @@ namespace Barotrauma.Items.Components
crosshairPointerPos = PlayerInput.MousePosition; crosshairPointerPos = PlayerInput.MousePosition;
} }
public override void FlipX(bool relativeToSub)
{
crossHairPosDirtyTimer = 0.02f;
}
public override void FlipY(bool relativeToSub)
{
crossHairPosDirtyTimer = 0.02f;
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
float chargeRatio = currentChargeTime / MaxChargeTime; float chargeRatio = currentChargeTime / MaxChargeTime;
@@ -117,7 +147,7 @@ namespace Barotrauma.Items.Components
} }
else if (chargeSoundChannel != null) else if (chargeSoundChannel != null)
{ {
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio); chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(ChargeSoundWindupPitchSlide.X, ChargeSoundWindupPitchSlide.Y, chargeRatio);
chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f); chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
} }
break; break;
@@ -143,15 +173,19 @@ namespace Barotrauma.Items.Components
if (character == null || !character.IsKeyDown(InputType.Aim) || !character.CanAim) { return; } if (character == null || !character.IsKeyDown(InputType.Aim) || !character.CanAim) { return; }
//camera focused on some other item/device, don't draw the crosshair //camera focused on some other item/device, don't draw the crosshair
if (character.ViewTarget != null && (character.ViewTarget is Item viewTargetItem) && viewTargetItem.Prefab.FocusOnSelected) { return; } if (character.ViewTarget is Item viewTargetItem && viewTargetItem.Prefab.FocusOnSelected) { return; }
//don't draw the crosshair if the item is in some other type of equip slot than hands (e.g. assault rifle in the bag slot) //don't draw the crosshair if the item is in some other type of equip slot than hands (e.g. assault rifle in the bag slot)
if (!character.HeldItems.Contains(item)) { return; } if (!character.HeldItems.Contains(item)) { return; }
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) && GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused; GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;
if (GUI.HideCursor)
if (GUI.HideCursor && !character.AnimController.IsHoldingToRope)
{ {
crosshairSprite?.Draw(spriteBatch, crosshairPos, ReloadTimer <= 0.0f ? Color.White : Color.White * 0.2f, 0, currentCrossHairScale); if (crossHairPosDirtyTimer <= 0.0f)
{
crosshairSprite?.Draw(spriteBatch, crosshairPos, ReloadTimer <= 0.0f ? Color.White : Color.White * 0.2f, 0, currentCrossHairScale);
}
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale); crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale);
} }
@@ -205,7 +205,7 @@ namespace Barotrauma.Items.Components
int buttonSize = GUIStyle.ItemFrameTopBarHeight; int buttonSize = GUIStyle.ItemFrameTopBarHeight;
Point margin = new Point(buttonSize / 4, buttonSize / 6); Point margin = new Point(buttonSize / 4, buttonSize / 6);
GUILayoutGroup buttonArea = new GUILayoutGroup(new RectTransform(new Point(GuiFrame.Rect.Width - margin.X * 2, buttonSize - margin.Y * 2), GuiFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, margin.Y) }, GUILayoutGroup buttonArea = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, buttonSize - margin.Y * 2), content.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, margin.Y) },
isHorizontal: true, childAnchor: Anchor.TopRight) isHorizontal: true, childAnchor: Anchor.TopRight)
{ {
AbsoluteSpacing = margin.X / 2 AbsoluteSpacing = margin.X / 2
@@ -334,7 +334,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
return item?.Name; return item?.Prefab.Name;
} }
} }
@@ -40,6 +40,7 @@ namespace Barotrauma.Items.Components
partial void SetLightSourceState(bool enabled, float brightness) partial void SetLightSourceState(bool enabled, float brightness)
{ {
if (Light == null) { return; } if (Light == null) { return; }
if (item.HiddenInGame) { enabled = false; }
Light.Enabled = enabled; Light.Enabled = enabled;
lightColorMultiplier = brightness; lightColorMultiplier = brightness;
if (enabled) if (enabled)
@@ -57,7 +57,7 @@ namespace Barotrauma.Items.Components
RelativeSpacing = 0.08f RelativeSpacing = 0.08f
}; };
new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform) { MinSize = new Point(0, GUI.IntScale(25)) }, item.Name, font: GUIStyle.SubHeadingFont) new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform) { MinSize = new Point(0, GUI.IntScale(25)) }, item.Prefab.Name, font: GUIStyle.SubHeadingFont)
{ {
TextAlignment = Alignment.Center, TextAlignment = Alignment.Center,
AutoScaleHorizontal = true AutoScaleHorizontal = true
@@ -341,7 +341,7 @@ namespace Barotrauma.Items.Components
GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null) GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
{ {
UserData = identifier, UserData = identifier,
ToolTip = GetTooltip(prefab) ToolTip = prefab.CreateTooltipText()
}; };
Sprite icon = prefab.InventoryIcon ?? prefab.Sprite; Sprite icon = prefab.InventoryIcon ?? prefab.Sprite;
@@ -372,21 +372,6 @@ namespace Barotrauma.Items.Components
textBlock.Text = TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString()); textBlock.Text = TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString());
} }
static RichString GetTooltip(ItemPrefab prefab)
{
LocalizedString toolTip = $"‖color:{Color.White.ToStringHex()}‖{prefab.Name}‖color:end‖";
LocalizedString description = prefab.Description;
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
if (prefab.ContentPackage != GameMain.VanillaContent && prefab.ContentPackage != null)
{
toolTip += $"\n‖color:{Color.MediumPurple.ToStringHex()}‖{prefab.ContentPackage.Name}‖color:end‖";
}
return RichString.Rich(toolTip);
}
} }
partial void OnItemLoadedProjSpecific() partial void OnItemLoadedProjSpecific()
@@ -134,11 +134,11 @@ namespace Barotrauma.Items.Components
spriteIndex += (force / 100.0f) * AnimSpeed * deltaTime; spriteIndex += (force / 100.0f) * AnimSpeed * deltaTime;
if (spriteIndex < 0) if (spriteIndex < 0)
{ {
spriteIndex = propellerSprite.FrameCount; spriteIndex = propellerSprite.FrameCount - Math.Abs(spriteIndex) % propellerSprite.FrameCount;
} }
if (spriteIndex >= propellerSprite.FrameCount) else
{ {
spriteIndex = 0.0f; spriteIndex = spriteIndex % propellerSprite.FrameCount;
} }
} }
@@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter);
// === LABEL === // // === LABEL === //
new GUITextBlock(new RectTransform(new Vector2(1f, 0.05f), paddedFrame.RectTransform), item.Name, font: GUIStyle.SubHeadingFont) new GUITextBlock(new RectTransform(new Vector2(1f, 0.05f), paddedFrame.RectTransform), item.Prefab.Name, font: GUIStyle.SubHeadingFont)
{ {
TextAlignment = Alignment.Center, TextAlignment = Alignment.Center,
AutoScaleVertical = true AutoScaleVertical = true
@@ -326,7 +326,7 @@ namespace Barotrauma.Items.Components
UserData = fi, UserData = fi,
HoverColor = Color.Gold * 0.2f, HoverColor = Color.Gold * 0.2f,
SelectedColor = Color.Gold * 0.5f, SelectedColor = Color.Gold * 0.5f,
ToolTip = fi.TargetItem.Description ToolTip = RichString.Rich(fi.TargetItem.Description)
}; };
var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform),
@@ -339,7 +339,7 @@ namespace Barotrauma.Items.Components
itemIcon, scaleToFit: true) itemIcon, scaleToFit: true)
{ {
Color = fi.TargetItem.InventoryIconColor, Color = fi.TargetItem.InventoryIconColor,
ToolTip = fi.TargetItem.Description ToolTip = RichString.Rich(fi.TargetItem.Description)
}; };
} }
@@ -347,7 +347,7 @@ namespace Barotrauma.Items.Components
{ {
Padding = Vector4.Zero, Padding = Vector4.Zero,
AutoScaleVertical = true, AutoScaleVertical = true,
ToolTip = fi.TargetItem.Description ToolTip = RichString.Rich(fi.TargetItem.Description)
}; };
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight), new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight),
@@ -925,8 +925,6 @@ namespace Barotrauma.Items.Components
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W); nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
if (nameBlock.TextScale < 0.7f) if (nameBlock.TextScale < 0.7f)
{ {
nameBlock.SetRichText(TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName)
.Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName)));
nameBlock.AutoScaleHorizontal = false; nameBlock.AutoScaleHorizontal = false;
nameBlock.TextScale = 0.7f; nameBlock.TextScale = 0.7f;
nameBlock.Wrap = true; nameBlock.Wrap = true;
@@ -937,7 +935,7 @@ namespace Barotrauma.Items.Components
if (!selectedItem.TargetItem.Description.IsNullOrEmpty()) if (!selectedItem.TargetItem.Description.IsNullOrEmpty())
{ {
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
selectedItem.TargetItem.Description, RichString.Rich(selectedItem.TargetItem.Description),
font: GUIStyle.SmallFont, wrap: true); font: GUIStyle.SmallFont, wrap: true);
description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W); description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
@@ -1090,7 +1090,12 @@ namespace Barotrauma.Items.Components
float totalVolume = 0.0f; float totalVolume = 0.0f;
foreach (Hull linkedHull in hullData.LinkedHulls) foreach (Hull linkedHull in hullData.LinkedHulls)
{ {
waterVolume += linkedHull.WaterVolume; //water detector ignores very small amounts of water,
//do it here too so the nav terminal doesn't display the water
if (WaterDetector.GetWaterPercentage(linkedHull) > 0.0f)
{
waterVolume += linkedHull.WaterVolume;
}
totalVolume += linkedHull.Volume; totalVolume += linkedHull.Volume;
} }
hullData.HullWaterAmount = hullData.HullWaterAmount =
@@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character) public override bool Select(Character character)
{ {
if (GameMain.GameSession?.Campaign == null) if (GameMain.GameSession?.Campaign == null || !Level.IsLoadedFriendlyOutpost)
{ {
return false; return false;
} }
@@ -1446,7 +1446,7 @@ namespace Barotrauma.Items.Components
//only relevant in the end levels or maybe custom subs with some kind of non-hulled parts //only relevant in the end levels or maybe custom subs with some kind of non-hulled parts
Rectangle worldBorders = submarine.GetDockedBorders(); Rectangle worldBorders = submarine.GetDockedBorders();
worldBorders.Location += submarine.WorldPosition.ToPoint(); worldBorders.Location += submarine.WorldPosition.ToPoint();
if (Submarine.RectContains(worldBorders, pingSource)) if (Submarine.RectContains(worldBorders, pingSource) || submarine.Info.OutpostGenerationParams is { AlwaysShowStructuresOnSonar: true })
{ {
CreateBlipsForSubmarineWalls(submarine, pingSource, transducerPos, pingRadius, prevPingRadius, range, passive); CreateBlipsForSubmarineWalls(submarine, pingSource, transducerPos, pingRadius, prevPingRadius, range, passive);
continue; continue;
@@ -1502,7 +1502,9 @@ namespace Barotrauma.Items.Components
foreach (Voronoi2.GraphEdge edge in cell.Edges) foreach (Voronoi2.GraphEdge edge in cell.Edges)
{ {
if (!edge.IsSolid) { continue; } if (!edge.IsSolid) { continue; }
float cellDot = Vector2.Dot(cell.Center - pingSource, (edge.Center + cell.Translation) - cell.Center);
//the normal of the edge must be pointing towards the ping source to be visible
float cellDot = Vector2.Dot((edge.Center + cell.Translation) - pingSource, edge.GetNormal(cell));
if (cellDot > 0) { continue; } if (cellDot > 0) { continue; }
float facingDot = Vector2.Dot( float facingDot = Vector2.Dot(
@@ -1543,6 +1545,7 @@ namespace Barotrauma.Items.Components
{ {
if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; } if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; }
if (!c.IsUnconscious && c.Params.HideInSonar) { continue; } if (!c.IsUnconscious && c.Params.HideInSonar) { continue; }
if (c.InDetectable) { continue; }
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; } if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; }
if (c.AnimController.SimplePhysicsEnabled) if (c.AnimController.SimplePhysicsEnabled)
@@ -316,28 +316,28 @@ namespace Barotrauma.Items.Components
{ {
case 0: case 0:
leftText = TextManager.Get("DescentVelocity"); leftText = TextManager.Get("DescentVelocity");
centerText = $"({TextManager.Get("KilometersPerHour")})"; centerText = TextManager.Get("KilometersPerHour");
rightTextGetter = () => rightTextGetter = () =>
{ {
Vector2 vel = controlledSub == null ? Vector2.Zero : controlledSub.Velocity; Vector2 vel = controlledSub == null ? Vector2.Zero : controlledSub.Velocity;
var realWorldVel = ConvertUnits.ToDisplayUnits(vel.Y * Physics.DisplayToRealWorldRatio) * 3.6f; var realWorldVel = ConvertUnits.ToDisplayUnits(vel.Y * Physics.DisplayToRealWorldRatio) * 3.6f;
return ((int)(-realWorldVel)).ToString(); return (-realWorldVel).ToString("0.0");
}; };
break; break;
case 1: case 1:
leftText = TextManager.Get("Velocity"); leftText = TextManager.Get("Velocity");
centerText = $"({TextManager.Get("KilometersPerHour")})"; centerText = TextManager.Get("KilometersPerHour");
rightTextGetter = () => rightTextGetter = () =>
{ {
Vector2 vel = controlledSub == null ? Vector2.Zero : controlledSub.Velocity; Vector2 vel = controlledSub == null ? Vector2.Zero : controlledSub.Velocity;
var realWorldVel = ConvertUnits.ToDisplayUnits(vel.X * Physics.DisplayToRealWorldRatio) * 3.6f; var realWorldVel = ConvertUnits.ToDisplayUnits(vel.X * Physics.DisplayToRealWorldRatio) * 3.6f;
if (controlledSub != null && controlledSub.FlippedX) { realWorldVel *= -1; } if (controlledSub != null && controlledSub.FlippedX) { realWorldVel *= -1; }
return ((int)realWorldVel).ToString(); return realWorldVel.ToString("0.0");
}; };
break; break;
case 2: case 2:
leftText = TextManager.Get("Depth"); leftText = TextManager.Get("Depth");
centerText = $"({TextManager.Get("Meter")})"; centerText = TextManager.Get("Meter");
rightTextGetter = () => rightTextGetter = () =>
{ {
if (Level.Loaded is { IsEndBiome: true }) if (Level.Loaded is { IsEndBiome: true })
@@ -789,9 +789,9 @@ namespace Barotrauma.Items.Components
} }
pressureWarningText.Visible = item.Submarine != null && Timing.TotalTime % 1.0f < 0.8f; pressureWarningText.Visible = item.Submarine != null && Timing.TotalTime % 1.0f < 0.8f;
float depthEffectThreshold = 500.0f;
if (Level.Loaded != null && pressureWarningText.Visible && if (Level.Loaded != null && pressureWarningText.Visible &&
item.Submarine.RealWorldDepth > Level.Loaded.RealWorldCrushDepth - depthEffectThreshold && item.Submarine.RealWorldDepth > item.Submarine.RealWorldCrushDepth - depthEffectThreshold) item.Submarine.RealWorldDepth > Level.Loaded.RealWorldCrushDepth - PressureWarningThreshold &&
item.Submarine.RealWorldDepth > item.Submarine.RealWorldCrushDepth - PressureWarningThreshold)
{ {
pressureWarningText.Visible = true; pressureWarningText.Visible = true;
pressureWarningText.Text = pressureWarningText.Text =
@@ -148,11 +148,29 @@ namespace Barotrauma.Items.Components
Vector2 particlePos = item.WorldPosition; Vector2 particlePos = item.WorldPosition;
float rotation = -item.body.Rotation; float rotation = -item.body.Rotation;
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; } if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
//if the position is in a sub's local coordinates, convert to world coordinates
particlePos = ConvertToWorldCoordinates(particlePos);
//if the start location is in a sub's local coordinates, convert to world coordinates
startLocation = ConvertToWorldCoordinates(startLocation);
//same for end location
endLocation = ConvertToWorldCoordinates(endLocation);
Tuple<Vector2, Vector2> tracerPoints = new Tuple<Vector2, Vector2>(startLocation, endLocation); Tuple<Vector2, Vector2> tracerPoints = new Tuple<Vector2, Vector2>(startLocation, endLocation);
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
{ {
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier, tracerPoints: tracerPoints); emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier, tracerPoints: tracerPoints);
} }
static Vector2 ConvertToWorldCoordinates(Vector2 position)
{
Submarine containing = Submarine.FindContainingInLocalCoordinates(position);
if (containing != null)
{
position += containing.Position;
}
return position;
}
} }
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
@@ -63,10 +63,7 @@ namespace Barotrauma.Items.Components
if (item.HiddenInGame) { return false; } if (item.HiddenInGame) { return false; }
if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; } if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; }
if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; } if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
if (item.ConditionPercentageRelativeToDefaultMaxCondition < RepairThreshold) { return true; }
float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
if (MathUtils.Percentage(item.Condition, defaultMaxCondition) < RepairThreshold) { return true; }
if (CurrentFixer == character) if (CurrentFixer == character)
{ {
@@ -135,13 +132,13 @@ namespace Barotrauma.Items.Components
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont); TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont);
skillTextContainer = paddedFrame; skillTextContainer = paddedFrame;
for (int i = 0; i < requiredSkills.Count; i++) for (int i = 0; i < RequiredSkills.Count; i++)
{ {
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillTextContainer.RectTransform), var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillTextContainer.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) Math.Round(requiredSkills[i].Level * SkillRequirementMultiplier)).ToString()), " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + RequiredSkills[i].Identifier), ((int) Math.Round(RequiredSkills[i].Level * SkillRequirementMultiplier)).ToString()),
font: GUIStyle.SmallFont) font: GUIStyle.SmallFont)
{ {
UserData = requiredSkills[i] UserData = RequiredSkills[i]
}; };
} }
@@ -262,7 +259,7 @@ namespace Barotrauma.Items.Components
} }
} }
float conditionPercentage = item.Condition / (item.MaxCondition / item.MaxRepairConditionMultiplier) * 100f; float conditionPercentage = item.ConditionPercentageRelativeToDefaultMaxCondition;
for (int i = 0; i < particleEmitters.Count; i++) for (int i = 0; i < particleEmitters.Count; i++)
{ {
@@ -2,7 +2,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Xml.Linq; using Barotrauma.Sounds;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -10,6 +10,9 @@ namespace Barotrauma.Items.Components
{ {
private Sprite sprite, startSprite, endSprite; private Sprite sprite, startSprite, endSprite;
private RoundSound snapSound, reelSound;
private SoundChannel reelSoundChannel;
[Serialize(5, IsPropertySaveable.No)] [Serialize(5, IsPropertySaveable.No)]
public int SpriteWidth public int SpriteWidth
{ {
@@ -55,6 +58,19 @@ namespace Barotrauma.Items.Components
} }
} }
[Serialize("1.0, 1.0", IsPropertySaveable.No, description: "When reeling in, the pitch slides from X to Y, depending on the length of the rope.")]
public Vector2 ReelSoundPitchSlide
{
get => _reelSoundPitchSlide;
set
{
_reelSoundPitchSlide = new Vector2(
Math.Max(value.X, SoundChannel.MinFrequencyMultiplier),
Math.Min(value.Y, SoundChannel.MaxFrequencyMultiplier));
}
}
private Vector2 _reelSoundPitchSlide;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
@@ -70,10 +86,29 @@ namespace Barotrauma.Items.Components
case "endsprite": case "endsprite":
endSprite = new Sprite(subElement); endSprite = new Sprite(subElement);
break; break;
case "snapsound":
snapSound = RoundSound.Load(subElement);
break;
case "reelsound":
reelSound = RoundSound.Load(subElement);
break;
} }
} }
} }
partial void UpdateProjSpecific()
{
if (isReelingIn && !Snapped)
{
PlaySound(reelSound, source.WorldPosition);
}
else
{
reelSoundChannel?.FadeOutAndDispose();
reelSoundChannel = null;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{ {
if (target == null || target.Removed) { return; } if (target == null || target.Removed) { return; }
@@ -185,36 +220,70 @@ namespace Barotrauma.Items.Components
} }
} }
private void PlaySound(RoundSound sound, Vector2 position)
{
if (sound == null) { return; }
if (sound == reelSound)
{
if (reelSoundChannel is not { IsPlaying: true })
{
reelSoundChannel = SoundPlayer.PlaySound(sound.Sound, position, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
if (reelSoundChannel != null)
{
reelSoundChannel.Looping = true;
}
}
else
{
reelSoundChannel.Position = new Vector3(position, 0);
reelSoundChannel.Gain = MathHelper.Lerp(0, 1.0f, MathUtils.InverseLerp(MinPullDistance, MaxLength, MathUtils.Pow(currentRopeLength, 1.5f)));
reelSoundChannel.FrequencyMultiplier = MathHelper.Lerp(ReelSoundPitchSlide.X, ReelSoundPitchSlide.Y, MathUtils.InverseLerp(MinPullDistance, MaxLength, currentRopeLength));
}
}
else
{
SoundPlayer.PlaySound(sound.Sound, position, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
}
}
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
{ {
snapped = msg.ReadBoolean(); snapped = msg.ReadBoolean();
if (!snapped) if (!snapped)
{ {
UInt16 targetId = msg.ReadUInt16(); ushort targetId = msg.ReadUInt16();
UInt16 sourceId = msg.ReadUInt16(); ushort sourceId = msg.ReadUInt16();
byte limbIndex = msg.ReadByte(); byte limbIndex = msg.ReadByte();
Item target = Entity.FindEntityByID(targetId) as Item; if (Entity.FindEntityByID(targetId) is not Item target) { return; }
if (target == null) { return; }
var source = Entity.FindEntityByID(sourceId); var source = Entity.FindEntityByID(sourceId);
if (source is Character sourceCharacter && limbIndex >= 0 && limbIndex < sourceCharacter.AnimController.Limbs.Length) switch (source)
{ {
Limb sourceLimb = sourceCharacter.AnimController.Limbs[limbIndex]; case Character sourceCharacter when limbIndex >= 0 && limbIndex < sourceCharacter.AnimController.Limbs.Length:
Attach(sourceLimb, target); {
} Limb sourceLimb = sourceCharacter.AnimController.Limbs[limbIndex];
else if (source is ISpatialEntity spatialEntity) Attach(sourceLimb, target);
{ sourceCharacter.AnimController.DragWithRope();
Attach(spatialEntity, target); break;
}
case ISpatialEntity spatialEntity:
Attach(spatialEntity, target);
break;
} }
} }
} }
protected override void RemoveComponentSpecific() protected override void RemoveComponentSpecific()
{ {
sprite?.Remove(); sprite = null; sprite?.Remove();
startSprite?.Remove(); startSprite = null; sprite = null;
endSprite?.Remove(); endSprite = null; startSprite?.Remove();
startSprite = null;
endSprite?.Remove();
endSprite = null;
reelSoundChannel?.FadeOutAndDispose();
reelSoundChannel = null;
} }
} }
} }
@@ -129,6 +129,7 @@ namespace Barotrauma.Items.Components
public void RemoveComponents(IReadOnlyCollection<CircuitBoxComponent> node) public void RemoveComponents(IReadOnlyCollection<CircuitBoxComponent> node)
{ {
if (Locked) { return; }
var ids = node.Select(static n => n.ID).ToImmutableArray(); var ids = node.Select(static n => n.ID).ToImmutableArray();
if (GameMain.NetworkMember is null) if (GameMain.NetworkMember is null)
@@ -145,6 +146,7 @@ namespace Barotrauma.Items.Components
public void AddWire(CircuitBoxConnection one, CircuitBoxConnection two) public void AddWire(CircuitBoxConnection one, CircuitBoxConnection two)
{ {
if (Locked) { return; }
if (GameMain.NetworkMember is null) if (GameMain.NetworkMember is null)
{ {
Connect(one, two, static delegate { }, CircuitBoxWire.SelectedWirePrefab); Connect(one, two, static delegate { }, CircuitBoxWire.SelectedWirePrefab);
@@ -158,6 +160,7 @@ namespace Barotrauma.Items.Components
public void RemoveWires(IReadOnlyCollection<CircuitBoxWire> wires) public void RemoveWires(IReadOnlyCollection<CircuitBoxWire> wires)
{ {
if (Locked) { return; }
var ids = wires.Select(static w => w.ID).ToImmutableArray(); var ids = wires.Select(static w => w.ID).ToImmutableArray();
if (GameMain.NetworkMember is null) if (GameMain.NetworkMember is null)
{ {
@@ -175,6 +178,7 @@ namespace Barotrauma.Items.Components
var ids = ImmutableArray.CreateBuilder<ushort>(); var ids = ImmutableArray.CreateBuilder<ushort>();
var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>(); var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
var labelIds = ImmutableArray.CreateBuilder<ushort>();
foreach (var moveable in moveables) foreach (var moveable in moveables)
{ {
@@ -188,6 +192,9 @@ namespace Barotrauma.Items.Components
case CircuitBoxInputOutputNode io: case CircuitBoxInputOutputNode io:
ios.Add(io.NodeType); ios.Add(io.NodeType);
break; break;
case CircuitBoxLabelNode label:
labelIds.Add(label.ID);
break;
} }
} }
@@ -195,12 +202,13 @@ namespace Barotrauma.Items.Components
{ {
SelectComponentsInternal(ids, controlledId, overwrite); SelectComponentsInternal(ids, controlledId, overwrite);
SelectInputOutputInternal(ios, controlledId, overwrite); SelectInputOutputInternal(ios, controlledId, overwrite);
SelectLabelsInternal(labelIds, controlledId, overwrite);
return; return;
} }
if ((!ids.Any() && !ios.Any()) && !overwrite) { return; } if (!ids.Any() && !ios.Any() && !labelIds.Any() && !overwrite) { return; }
CreateClientEvent(new CircuitBoxSelectNodesEvent(ids.ToImmutable(), ios.ToImmutable(), overwrite, controlledId)); CreateClientEvent(new CircuitBoxSelectNodesEvent(ids.ToImmutable(), ios.ToImmutable(), labelIds.ToImmutable(), overwrite, controlledId));
} }
public void SelectWires(IReadOnlyCollection<CircuitBoxWire> wires, bool overwrite) public void SelectWires(IReadOnlyCollection<CircuitBoxWire> wires, bool overwrite)
@@ -222,8 +230,10 @@ namespace Barotrauma.Items.Components
public void MoveComponent(Vector2 moveAmount, IReadOnlyCollection<CircuitBoxNode> moveables) public void MoveComponent(Vector2 moveAmount, IReadOnlyCollection<CircuitBoxNode> moveables)
{ {
if (Locked) { return; }
var ids = ImmutableArray.CreateBuilder<ushort>(); var ids = ImmutableArray.CreateBuilder<ushort>();
var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>(); var ios = ImmutableArray.CreateBuilder<CircuitBoxInputOutputNode.Type>();
var labelIds = ImmutableArray.CreateBuilder<ushort>();
foreach (CircuitBoxNode move in moveables) foreach (CircuitBoxNode move in moveables)
{ {
@@ -235,23 +245,27 @@ namespace Barotrauma.Items.Components
case CircuitBoxInputOutputNode io: case CircuitBoxInputOutputNode io:
ios.Add(io.NodeType); ios.Add(io.NodeType);
break; break;
case CircuitBoxLabelNode label:
labelIds.Add(label.ID);
break;
} }
} }
if (GameMain.NetworkMember is null) if (GameMain.NetworkMember is null)
{ {
MoveNodesInternal(ids, ios, moveAmount); MoveNodesInternal(ids, ios, labelIds, moveAmount);
return; return;
} }
if (!ids.Any() && !ios.Any()) { return; } if (!ids.Any() && !ios.Any() && !labelIds.Any()) { return; }
CreateClientEvent(new CircuitBoxMoveComponentEvent(ids.ToImmutable(), ios.ToImmutable(), moveAmount)); CreateClientEvent(new CircuitBoxMoveComponentEvent(ids.ToImmutable(), ios.ToImmutable(), labelIds.ToImmutable(), moveAmount));
} }
public void AddComponent(ItemPrefab prefab, Vector2 pos) public void AddComponent(ItemPrefab prefab, Vector2 pos)
{ {
if (Locked) { return; }
if (GameMain.NetworkMember is null) if (GameMain.NetworkMember is null)
{ {
ItemPrefab resource; ItemPrefab resource;
@@ -276,6 +290,72 @@ namespace Barotrauma.Items.Components
CreateClientEvent(new CircuitBoxAddComponentEvent(prefab.UintIdentifier, pos)); CreateClientEvent(new CircuitBoxAddComponentEvent(prefab.UintIdentifier, pos));
} }
public void RenameLabel(CircuitBoxLabelNode label, Color color, NetLimitedString header, NetLimitedString body)
{
if (Locked) { return; }
if (GameMain.NetworkMember is null)
{
label.EditText(header, body);
label.Color = color;
return;
}
CreateClientEvent(new CircuitBoxRenameLabelEvent(label.ID, color, header, body));
}
public void ResizeNode(CircuitBoxNode node, CircuitBoxResizeDirection dir, Vector2 amount)
{
if (Locked) { return; }
var resize = node.ResizeBy(dir, amount);
if (GameMain.NetworkMember is null)
{
node.ApplyResize(resize.Size, resize.Pos);
return;
}
// TODO this needs to be refactored at some point, probably not now
// the problem here is that the circuit box supports resizing all nodes
// but we limit the resizing to only labels on the client
// and on the server we only have a network message that targets labels
// so if we ever want the ability to resize other nodes (could be useful) the network message
// needs to know what type of ID it's targeting
if (node is not ICircuitBoxIdentifiable identifiable)
{
DebugConsole.ThrowError("Tried to resize a node that doesn't have an ID.");
return;
}
CreateClientEvent(new CircuitBoxResizeLabelEvent(identifiable.ID, resize.Pos, resize.Size));
}
public void AddLabel(Vector2 pos)
{
if (Locked) { return; }
if (GameMain.NetworkMember is null)
{
AddLabelInternal(ICircuitBoxIdentifiable.FindFreeID(Labels), GUIStyle.Blue, pos, CircuitBoxLabelNode.DefaultHeaderText, NetLimitedString.Empty);
return;
}
CreateClientEvent(new CircuitBoxAddLabelEvent(pos, GUIStyle.Blue, CircuitBoxLabelNode.DefaultHeaderText, NetLimitedString.Empty));
}
public void RemoveLabel(IReadOnlyCollection<CircuitBoxLabelNode> labels)
{
if (Locked) { return; }
if (!labels.Any()) { return; }
var ids = labels.Select(static n => n.ID).ToImmutableArray();
if (GameMain.NetworkMember is null)
{
RemoveLabelInternal(ids);
return;
}
CreateClientEvent(new CircuitBoxRemoveLabelEvent(ids));
}
public partial void OnViewUpdateProjSpecific() public partial void OnViewUpdateProjSpecific()
{ {
UI?.MouseSnapshotHandler.UpdateConnections(); UI?.MouseSnapshotHandler.UpdateConnections();
@@ -396,7 +476,7 @@ namespace Barotrauma.Items.Components
case CircuitBoxOpcode.MoveComponent: case CircuitBoxOpcode.MoveComponent:
{ {
var data = INetSerializableStruct.Read<CircuitBoxMoveComponentEvent>(msg); var data = INetSerializableStruct.Read<CircuitBoxMoveComponentEvent>(msg);
MoveNodesInternal(data.TargetIDs, data.IOs, data.MoveAmount); MoveNodesInternal(data.TargetIDs, data.IOs, data.LabelIDs, data.MoveAmount);
break; break;
} }
case CircuitBoxOpcode.UpdateSelection: case CircuitBoxOpcode.UpdateSelection:
@@ -406,8 +486,9 @@ namespace Barotrauma.Items.Components
var nodeDict = data.ComponentIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy); var nodeDict = data.ComponentIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy);
var wireDict = data.WireIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy); var wireDict = data.WireIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy);
var ioDict = data.InputOutputs.ToImmutableDictionary(static s => s.Type, static s => s.SelectedBy); var ioDict = data.InputOutputs.ToImmutableDictionary(static s => s.Type, static s => s.SelectedBy);
var labelDict = data.LabelIds.ToImmutableDictionary(static s => s.ID, static s => s.SelectedBy);
UpdateSelections(nodeDict, wireDict, ioDict); UpdateSelections(nodeDict, wireDict, ioDict, labelDict);
break; break;
} }
case CircuitBoxOpcode.AddWire: case CircuitBoxOpcode.AddWire:
@@ -426,11 +507,18 @@ namespace Barotrauma.Items.Components
{ {
Components.Clear(); Components.Clear();
Wires.Clear(); Wires.Clear();
Labels.Clear();
var data = INetSerializableStruct.Read<CircuitBoxInitializeStateFromServerEvent>(msg); var data = INetSerializableStruct.Read<CircuitBoxInitializeStateFromServerEvent>(msg);
foreach (var compData in data.Components) { AddComponentFromData(compData); } foreach (var compData in data.Components) { AddComponentFromData(compData); }
foreach (var wireData in data.Wires) { AddWireFromData(wireData); } foreach (var wireData in data.Wires) { AddWireFromData(wireData); }
foreach (var labelData in data.Labels)
{
AddLabelInternal(labelData.ID, labelData.Color, labelData.Position, labelData.Header, labelData.Body);
ResizeLabelInternal(labelData.ID, labelData.Position, labelData.Size);
}
foreach (var node in InputOutputNodes) foreach (var node in InputOutputNodes)
{ {
node.Position = node.NodeType switch node.Position = node.NodeType switch
@@ -443,6 +531,31 @@ namespace Barotrauma.Items.Components
wasInitializedByServer = true; wasInitializedByServer = true;
break; break;
} }
case CircuitBoxOpcode.RenameLabel:
{
var data = INetSerializableStruct.Read<CircuitBoxRenameLabelEvent>(msg);
RenameLabelInternal(data.LabelId, data.Color, data.NewHeader, data.NewBody);
break;
}
case CircuitBoxOpcode.AddLabel:
{
var data = INetSerializableStruct.Read<CircuitBoxServerAddLabelEvent>(msg);
AddLabelInternal(data.ID, data.Color, data.Position, data.Header, data.Body);
ResizeLabelInternal(data.ID, data.Position, data.Size);
break;
}
case CircuitBoxOpcode.RemoveLabel:
{
var data = INetSerializableStruct.Read<CircuitBoxRemoveLabelEvent>(msg);
RemoveLabelInternal(data.TargetIDs);
break;
}
case CircuitBoxOpcode.ResizeLabel:
{
var data = INetSerializableStruct.Read<CircuitBoxResizeLabelEvent>(msg);
ResizeLabelInternal(data.ID, data.Position, data.Size);
break;
}
default: default:
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events"); throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
} }
@@ -12,11 +12,13 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{ {
if (!editing || !MapEntity.SelectedList.Contains(item)) { return; } if ((editing && MapEntity.SelectedList.Contains(item)) ||
(ConnectionPanel.ShouldDebugDrawWiring && Character.Controlled?.SelectedItem == item))
Vector2 pos = item.WorldPosition + TransformedDetectOffset; {
pos.Y = -pos.Y; Vector2 pos = item.WorldPosition + TransformedDetectOffset;
GUI.DrawRectangle(spriteBatch, pos - new Vector2(rangeX, rangeY), new Vector2(rangeX, rangeY) * 2.0f, Color.Cyan * 0.5f, isFilled: false, thickness: 2); pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, pos - new Vector2(rangeX, rangeY), new Vector2(rangeX, rangeY) * 2.0f, Color.Cyan * 0.5f, isFilled: false, thickness: 2);
}
} }
} }
} }
@@ -137,6 +137,7 @@ namespace Barotrauma.Items.Components
{ {
if (c == equipper || !c.Enabled || c.Removed) { continue; } if (c == equipper || !c.Enabled || c.Removed) { continue; }
if (!ShowDeadCharacters && c.IsDead) { continue; } if (!ShowDeadCharacters && c.IsDead) { continue; }
if (c.InDetectable) { continue; }
float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition); float dist = Vector2.DistanceSquared(refEntity.WorldPosition, c.WorldPosition);
if (dist < Range * Range) if (dist < Range * Range)
@@ -119,6 +119,19 @@ namespace Barotrauma.Items.Components
private set; private set;
} }
[Serialize(defaultValue: "0.5, 1.5", IsPropertySaveable.No, description: "Pitch slides from X to Y over the charge time")]
public Vector2 ChargeSoundWindupPitchSlide
{
get => _chargeSoundWindupPitchSlide;
set
{
_chargeSoundWindupPitchSlide = new Vector2(
Math.Max(value.X, SoundChannel.MinFrequencyMultiplier),
Math.Min(value.Y, SoundChannel.MaxFrequencyMultiplier));
}
}
private Vector2 _chargeSoundWindupPitchSlide;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
@@ -220,9 +233,9 @@ namespace Barotrauma.Items.Components
{ {
if (moveSound != null) if (moveSound != null)
{ {
moveSoundChannel.FadeOutAndDispose(); moveSoundChannel?.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling, freqMult: moveSound.GetRandomFrequencyMultiplier()); moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling, freqMult: moveSound.GetRandomFrequencyMultiplier());
if (moveSoundChannel != null) moveSoundChannel.Looping = true; if (moveSoundChannel != null) { moveSoundChannel.Looping = true;}
} }
} }
} }
@@ -268,7 +281,7 @@ namespace Barotrauma.Items.Components
} }
else if (chargeSoundChannel != null) else if (chargeSoundChannel != null)
{ {
chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(0.5f, 1.5f, chargeRatio); chargeSoundChannel.FrequencyMultiplier = MathHelper.Lerp(ChargeSoundWindupPitchSlide.X, ChargeSoundWindupPitchSlide.Y, chargeRatio);
chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f); chargeSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
} }
break; break;
@@ -482,12 +495,12 @@ namespace Barotrauma.Items.Components
}; };
widget.MouseDown += () => widget.MouseDown += () =>
{ {
widget.color = GUIStyle.Green; widget.Color = GUIStyle.Green;
prevAngle = minRotation; prevAngle = minRotation;
}; };
widget.Deselected += () => widget.Deselected += () =>
{ {
widget.color = Color.Yellow; widget.Color = Color.Yellow;
item.CreateEditingHUD(); item.CreateEditingHUD();
RotationLimits = RotationLimits; RotationLimits = RotationLimits;
if (SubEditorScreen.IsSubEditor()) if (SubEditorScreen.IsSubEditor())
@@ -513,7 +526,7 @@ namespace Barotrauma.Items.Components
}; };
widget.PreDraw += (sprtBtch, deltaTime) => widget.PreDraw += (sprtBtch, deltaTime) =>
{ {
widget.tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation); widget.Tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation);
widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * coneRadius / Screen.Selected.Cam.Zoom * GUI.Scale; widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * coneRadius / Screen.Selected.Cam.Zoom * GUI.Scale;
}; };
}); });
@@ -526,12 +539,12 @@ namespace Barotrauma.Items.Components
}; };
widget.MouseDown += () => widget.MouseDown += () =>
{ {
widget.color = GUIStyle.Green; widget.Color = GUIStyle.Green;
prevAngle = maxRotation; prevAngle = maxRotation;
}; };
widget.Deselected += () => widget.Deselected += () =>
{ {
widget.color = Color.Yellow; widget.Color = Color.Yellow;
item.CreateEditingHUD(); item.CreateEditingHUD();
RotationLimits = RotationLimits; RotationLimits = RotationLimits;
if (SubEditorScreen.IsSubEditor()) if (SubEditorScreen.IsSubEditor())
@@ -557,7 +570,7 @@ namespace Barotrauma.Items.Components
}; };
widget.PreDraw += (sprtBtch, deltaTime) => widget.PreDraw += (sprtBtch, deltaTime) =>
{ {
widget.tooltip = "Max: " + (int)MathHelper.ToDegrees(maxRotation); widget.Tooltip = "Max: " + (int)MathHelper.ToDegrees(maxRotation);
widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * coneRadius / Screen.Selected.Cam.Zoom * GUI.Scale; widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * coneRadius / Screen.Selected.Cam.Zoom * GUI.Scale;
widget.Update(deltaTime); widget.Update(deltaTime);
}; };
@@ -584,20 +597,20 @@ namespace Barotrauma.Items.Components
Vector2 offset = new Vector2(size / 2 + 5, -10); Vector2 offset = new Vector2(size / 2 + 5, -10);
if (!widgets.TryGetValue(id, out Widget widget)) if (!widgets.TryGetValue(id, out Widget widget))
{ {
widget = new Widget(id, size, Widget.Shape.Rectangle) widget = new Widget(id, size, WidgetShape.Rectangle)
{ {
color = Color.Yellow, Color = Color.Yellow,
tooltipOffset = offset, TooltipOffset = offset,
inputAreaMargin = 20, InputAreaMargin = 20,
RequireMouseOn = false RequireMouseOn = false
}; };
widgets.Add(id, widget); widgets.Add(id, widget);
initMethod?.Invoke(widget); initMethod?.Invoke(widget);
} }
widget.size = size; widget.Size = size;
widget.tooltipOffset = offset; widget.TooltipOffset = offset;
widget.thickness = thickness; widget.Thickness = thickness;
return widget; return widget;
} }
@@ -168,11 +168,14 @@ namespace Barotrauma
//TODO: define this in xml //TODO: define this in xml
slotSpriteSmall = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(10, 6, 119, 120), null, 0); slotSpriteSmall = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(10, 6, 119, 120), null, 0);
// Adjustment to match the old size of 75,71 // Adjustment to match the old size of 75,71
SlotSpriteSmall.size = new Vector2(SlotSpriteSmall.SourceRect.Width * 0.575f, SlotSpriteSmall.SourceRect.Height * 0.575f); SlotSpriteSmall.size = new Vector2(SlotSpriteSmall.SourceRect.Width * SlotSpriteSmallScale, SlotSpriteSmall.SourceRect.Height * SlotSpriteSmallScale);
} }
return slotSpriteSmall; return slotSpriteSmall;
} }
} }
public const float SlotSpriteSmallScale = 0.575f;
public static Sprite DraggableIndicator; public static Sprite DraggableIndicator;
public static Sprite UnequippedIndicator, UnequippedHoverIndicator, UnequippedClickedIndicator, EquippedIndicator, EquippedHoverIndicator, EquippedClickedIndicator; public static Sprite UnequippedIndicator, UnequippedHoverIndicator, UnequippedClickedIndicator, EquippedIndicator, EquippedHoverIndicator, EquippedClickedIndicator;
@@ -211,6 +214,7 @@ namespace Barotrauma
public RichString Tooltip { get; private set; } public RichString Tooltip { get; private set; }
public int tooltipDisplayedCondition; public int tooltipDisplayedCondition;
public bool tooltipShowedContextualOptions;
public bool ForceTooltipRefresh; public bool ForceTooltipRefresh;
@@ -230,6 +234,7 @@ namespace Barotrauma
{ {
if (ForceTooltipRefresh) { return true; } if (ForceTooltipRefresh) { return true; }
if (Item == null) { return false; } if (Item == null) { return false; }
if (PlayerInput.KeyDown(InputType.ContextualCommand) != tooltipShowedContextualOptions) { return true; }
return (int)Item.ConditionPercentage != tooltipDisplayedCondition; return (int)Item.ConditionPercentage != tooltipDisplayedCondition;
} }
@@ -244,6 +249,7 @@ namespace Barotrauma
} }
Tooltip = GetTooltip(Item, itemsInSlot, Character.Controlled); Tooltip = GetTooltip(Item, itemsInSlot, Character.Controlled);
tooltipDisplayedCondition = (int)Item.ConditionPercentage; tooltipDisplayedCondition = (int)Item.ConditionPercentage;
tooltipShowedContextualOptions = PlayerInput.KeyDown(InputType.ContextualCommand);
} }
private static RichString GetTooltip(Item item, IEnumerable<Item> itemsInSlot, Character character) private static RichString GetTooltip(Item item, IEnumerable<Item> itemsInSlot, Character character)
@@ -323,19 +329,19 @@ namespace Barotrauma
.TrimStart(); .TrimStart();
} }
if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable)) if (itemsInSlot.All(it => !it.IsInteractable(Character.Controlled)))
{ {
toolTip += " " + TextManager.Get("connectionlocked"); toolTip += " " + TextManager.Get("connectionlocked");
} }
if (!item.IsFullCondition && !item.Prefab.HideConditionInTooltip) if (!item.IsFullCondition && !item.Prefab.HideConditionInTooltip)
{ {
string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUIStyle.ColorInventoryEmpty, GUIStyle.ColorInventoryHalf, GUIStyle.ColorInventoryFull)); string conditionColorStr = XMLExtensions.ToStringHex(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUIStyle.ColorInventoryEmpty, GUIStyle.ColorInventoryHalf, GUIStyle.ColorInventoryFull));
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖"; toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
} }
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; } if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
if (item.Prefab.ContentPackage != GameMain.VanillaContent && item.Prefab.ContentPackage != null) if (item.Prefab.ContentPackage != GameMain.VanillaContent && item.Prefab.ContentPackage != null)
{ {
colorStr = XMLExtensions.ColorToString(Color.MediumPurple); colorStr = XMLExtensions.ToStringHex(Color.MediumPurple);
toolTip += $"\n‖color:{colorStr}‖{item.Prefab.ContentPackage.Name}‖color:end‖"; toolTip += $"\n‖color:{colorStr}‖{item.Prefab.ContentPackage.Name}‖color:end‖";
} }
} }
@@ -351,6 +357,16 @@ namespace Barotrauma
#if DEBUG #if DEBUG
toolTip += $" ({item.Prefab.Identifier})"; toolTip += $" ({item.Prefab.Identifier})";
#endif #endif
if (PlayerInput.KeyDown(InputType.ContextualCommand))
{
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
}
else
{
var colorStr = XMLExtensions.ToStringHex(Color.LightGray * 0.7f);
toolTip += $"\n‖color:{colorStr}‖{TextManager.Get("itemmsg.morreoptionsavailable")}‖color:end‖";
}
return RichString.Rich(toolTip); return RichString.Rich(toolTip);
} }
} }
@@ -613,10 +629,7 @@ namespace Barotrauma
slot.State = GUIComponent.ComponentState.None; slot.State = GUIComponent.ComponentState.None;
if (mouseOn && (DraggingItems.Any() || selectedSlot == null || selectedSlot.Slot == slot) && DraggingInventory == null) if (mouseOn && (DraggingItems.Any() || selectedSlot == null || selectedSlot.Slot == slot) && DraggingInventory == null)
// &&
//(highlightedSubInventories.Count == 0 || highlightedSubInventories.Contains(this) || highlightedSubInventorySlot?.Slot == slot || highlightedSubInventory.Owner == item))
{ {
slot.State = GUIComponent.ComponentState.Hover; slot.State = GUIComponent.ComponentState.Hover;
if (selectedSlot == null || (!selectedSlot.IsSubSlot && isSubSlot)) if (selectedSlot == null || (!selectedSlot.IsSubSlot && isSubSlot))
@@ -631,27 +644,47 @@ namespace Barotrauma
if (!DraggingItems.Any()) if (!DraggingItems.Any())
{ {
var interactableItems = Screen.Selected == GameMain.GameScreen ? slots[slotIndex].Items.Where(it => !it.NonInteractable && !it.NonPlayerTeamInteractable) : slots[slotIndex].Items; var interactableItems = Screen.Selected == GameMain.GameScreen ? slots[slotIndex].Items.Where(it => it.IsInteractable(Character.Controlled)) : slots[slotIndex].Items;
if (PlayerInput.PrimaryMouseButtonDown() && interactableItems.Any()) if (interactableItems.Any())
{ {
if (PlayerInput.KeyDown(InputType.TakeHalfFromInventorySlot)) if (availableContextualOrder.target != null)
{ {
DraggingItems.AddRange(interactableItems.Skip(interactableItems.Count() / 2)); if (PlayerInput.PrimaryMouseButtonClicked())
{
GameMain.GameSession.CrewManager.SetCharacterOrder(character: null,
new Order(OrderPrefab.Prefabs[availableContextualOrder.orderIdentifier], availableContextualOrder.target, targetItem: null, orderGiver: Character.Controlled));
}
availableContextualOrder = default;
} }
else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot)) else if (PlayerInput.KeyDown(InputType.Command) &&
PlayerInput.KeyDown(InputType.ContextualCommand) &&
GameMain.GameSession?.CrewManager != null)
{ {
DraggingItems.Add(interactableItems.First()); GameMain.GameSession.CrewManager.OpenCommandUI(interactableItems.FirstOrDefault(), forceContextual: true);
} }
else else if (PlayerInput.PrimaryMouseButtonDown())
{ {
DraggingItems.AddRange(interactableItems); if (PlayerInput.KeyDown(InputType.TakeHalfFromInventorySlot))
{
DraggingItems.AddRange(interactableItems.Skip(interactableItems.Count() / 2));
}
else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot))
{
DraggingItems.Add(interactableItems.First());
}
else
{
DraggingItems.AddRange(interactableItems);
}
DraggingSlot = slot;
} }
DraggingSlot = slot;
} }
} }
else if (PlayerInput.PrimaryMouseButtonReleased()) else if (PlayerInput.PrimaryMouseButtonReleased())
{ {
var interactableItems = Screen.Selected == GameMain.GameScreen ? slots[slotIndex].Items.Where(it => !it.NonInteractable && !it.NonPlayerTeamInteractable) : slots[slotIndex].Items; var interactableItems = Screen.Selected == GameMain.GameScreen ?
slots[slotIndex].Items.Where(it => it.IsInteractable(Character.Controlled)) :
slots[slotIndex].Items;
if (PlayerInput.DoubleClicked() && interactableItems.Any()) if (PlayerInput.DoubleClicked() && interactableItems.Any())
{ {
doubleClickedItems.Clear(); doubleClickedItems.Clear();
@@ -1286,7 +1319,7 @@ namespace Barotrauma
if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container && if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container &&
container.Inventory.CanBePut(item)) container.Inventory.CanBePut(item))
{ {
if (!container.AllowDragAndDrop || !container.DrawInventory) if (!container.AllowDragAndDrop || !container.AllowAccess)
{ {
allowCombine = false; allowCombine = false;
} }
@@ -1562,10 +1595,22 @@ namespace Barotrauma
{ {
selectedSlot.RefreshTooltip(); selectedSlot.RefreshTooltip();
} }
DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect);
if (!slotIconTooltip.IsNullOrEmpty())
{
DrawToolTip(spriteBatch, slotIconTooltip, slotRect);
}
else
{
DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect);
}
slotIconTooltip = string.Empty;
} }
} }
private static (Item target, Identifier orderIdentifier) availableContextualOrder;
private static LocalizedString slotIconTooltip;
public static void DrawSlot(SpriteBatch spriteBatch, Inventory inventory, VisualSlot slot, Item item, int slotIndex, bool drawItem = true, InvSlotType type = InvSlotType.Any) public static void DrawSlot(SpriteBatch spriteBatch, Inventory inventory, VisualSlot slot, Item item, int slotIndex, bool drawItem = true, InvSlotType type = InvSlotType.Any)
{ {
Rectangle rect = slot.Rect; Rectangle rect = slot.Rect;
@@ -1730,7 +1775,7 @@ namespace Barotrauma
} }
Color spriteColor = sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor(); Color spriteColor = sprite == item.Sprite ? item.GetSpriteColor() : item.GetInventoryIconColor();
if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))) { spriteColor *= 0.5f; } if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => !it.IsInteractable(Character.Controlled)))) { spriteColor *= 0.5f; }
if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface && !item.AllowedSlots.Contains(InvSlotType.HealthInterface) && item.GetComponent<GeneticMaterial>() == null) if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface && !item.AllowedSlots.Contains(InvSlotType.HealthInterface) && item.GetComponent<GeneticMaterial>() == null)
{ {
spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f); spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f);
@@ -1741,15 +1786,24 @@ namespace Barotrauma
} }
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
if (((item.SpawnedInCurrentOutpost && !item.AllowStealing) || (inventory != null && inventory.slots[slotIndex].Items.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand)) if (item.OrderedToBeIgnored)
{ {
var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand]; if (OrderPrefab.Prefabs.TryGet(Tags.IgnoreThis, out OrderPrefab ignoreOrder))
Vector2 iconSize = new Vector2(25 * GUI.Scale); {
stealIcon.Draw( DrawSideIcon(ignoreOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.ignored"), ignoreOrder.Color, out bool mouseOn);
spriteBatch, if (mouseOn) { availableContextualOrder = (item, Tags.UnignoreThis); }
new Vector2(rect.X + iconSize.X * 0.2f, rect.Bottom - iconSize.Y * 1.2f),
color: GUIStyle.Red, }
scale: iconSize.X / stealIcon.size.X); }
else if (Item.DeconstructItems.Contains(item) &&
OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder))
{
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
if (mouseOn) { availableContextualOrder = (item, Tags.DontDeconstructThis); }
}
else if (((item.SpawnedInCurrentOutpost && !item.AllowStealing) || (inventory != null && inventory.slots[slotIndex].Items.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
{
DrawSideIcon(CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand], Direction.Left, TextManager.Get("tooltip.stolenitem"), GUIStyle.Red, out _);
} }
int maxStackSize = item.Prefab.GetMaxStackSize(inventory); int maxStackSize = item.Prefab.GetMaxStackSize(inventory);
if (inventory is ItemInventory itemInventory) if (inventory is ItemInventory itemInventory)
@@ -1798,6 +1852,22 @@ namespace Barotrauma
SpriteEffects.None, SpriteEffects.None,
layerDepth: 0.0f); layerDepth: 0.0f);
} }
void DrawSideIcon(Sprite icon, Direction side, LocalizedString tooltip, Color color, out bool mouseOn)
{
Vector2 iconSize = new Vector2(25 * GUI.Scale);
float margin = 0.2f;
Vector2 pos = new Vector2(
side == Direction.Left ? rect.X + iconSize.X * margin : rect.Right - iconSize.X * margin,
rect.Bottom - iconSize.Y * 1.2f);
mouseOn = Vector2.Distance(PlayerInput.MousePosition, pos) < iconSize.X / 2;
if (mouseOn)
{
slotIconTooltip = tooltip;
color = Color.Lerp(color, Color.White, 0.5f);
}
icon.Draw(spriteBatch, pos, color: color, scale: iconSize.X / icon.size.X);
}
} }
@@ -9,7 +9,10 @@ using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
namespace Barotrauma namespace Barotrauma
{ {
@@ -216,7 +219,7 @@ namespace Barotrauma
} }
} }
float displayCondition = FakeBroken ? 0.0f : ConditionPercentage; float displayCondition = FakeBroken ? 0.0f : ConditionPercentageRelativeToDefaultMaxCondition;
for (int i = 0; i < Prefab.BrokenSprites.Length;i++) for (int i = 0; i < Prefab.BrokenSprites.Length;i++)
{ {
if (Prefab.BrokenSprites[i].FadeIn) { continue; } if (Prefab.BrokenSprites[i].FadeIn) { continue; }
@@ -340,9 +343,28 @@ namespace Barotrauma
bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null; bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null;
if (renderTransparent) { color *= 0.15f; } if (renderTransparent) { color *= 0.15f; }
if (Character.Controlled != null && Character.DebugDrawInteract)
{
color = Color.Red;
foreach (var ic in components)
{
var interactionType = GetComponentInteractionVisibility(Character.Controlled, ic);
if (interactionType == InteractionVisibility.MissingRequirement)
{
color = Color.Orange;
}
else if (interactionType == InteractionVisibility.Visible)
{
color = Color.LightGreen;
break;
}
}
}
BrokenItemSprite fadeInBrokenSprite = null; BrokenItemSprite fadeInBrokenSprite = null;
float fadeInBrokenSpriteAlpha = 0.0f; float fadeInBrokenSpriteAlpha = 0.0f;
float displayCondition = FakeBroken ? 0.0f : ConditionPercentage;
float displayCondition = FakeBroken ? 0.0f : ConditionPercentageRelativeToDefaultMaxCondition;
Vector2 drawOffset = GetCollapseEffectOffset(); Vector2 drawOffset = GetCollapseEffectOffset();
drawOffset.Y = -drawOffset.Y; drawOffset.Y = -drawOffset.Y;
@@ -849,27 +871,6 @@ namespace Barotrauma
itemEditor.Children.First().Color = Color.Black * 0.7f; itemEditor.Children.First().Color = Color.Black * 0.7f;
if (!inGame) if (!inGame)
{ {
//create a tag picker for item containers to make it easier to pick relevant tags for PreferredContainers
var itemContainer = GetComponent<ItemContainer>();
if (itemContainer != null)
{
var tagsField = itemEditor.Fields["Tags".ToIdentifier()].First().Parent;
//find all the items that can be put inside the container and add their PreferredContainer identifiers/tags to the available tags
ImmutableHashSet<Identifier> availableTags = ItemPrefab.Prefabs
.Where(ip => itemContainer.CanBeContained(ip))
.SelectMany(ip => ip.PreferredContainers.SelectMany(pc => pc.Primary.Union(pc.Secondary)))
//remove identifiers from the available container tags
//(otherwise the list will include many irrelevant options,
//e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc)
.Where(t => !ItemPrefab.Prefabs.ContainsKey(t))
.ToImmutableHashSet();
new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...")
{
OnClicked = (bt, userData) => { CreateTagPicker(tagsField.GetChild<GUITextBox>(), availableTags); return true; }
};
}
if (Linkable) if (Linkable)
{ {
var linkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("HoldToLink"), font: GUIStyle.SmallFont); var linkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), TextManager.Get("HoldToLink"), font: GUIStyle.SmallFont);
@@ -882,6 +883,33 @@ namespace Barotrauma
itemsText.TextColor = GUIStyle.Orange; itemsText.TextColor = GUIStyle.Orange;
} }
//create a tag picker for item containers to make it easier to pick relevant tags for PreferredContainers
var itemContainer = GetComponent<ItemContainer>();
if (itemContainer != null)
{
var tagBox = itemEditor.Fields["Tags".ToIdentifier()].First() as GUITextBox;
var tagsField = tagBox?.Parent;
var containerTagLayout = new GUILayoutGroup(new RectTransform(new Point(editingHUD.Rect.Width, heightScaled), isFixedSize: true), isHorizontal: true);
var containerTagButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1), containerTagLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterRight);
new GUIButton(new RectTransform(new Vector2(0.95f, 1), containerTagButtonLayout.RectTransform), text: TextManager.Get("containertaguibutton"), style: "GUIButtonSmall")
{
OnClicked = (_, _) => { CreateContainerTagPicker(tagBox); return true; },
TextBlock = { AutoScaleHorizontal = true }
};
var containerTagText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1), containerTagLayout.RectTransform), TextManager.Get("containertaguibuttondescription"), font: GUIStyle.SmallFont)
{
TextColor = GUIStyle.Orange
};
var limitedString = ToolBox.LimitString(containerTagText.Text, containerTagText.Font, itemEditor.Rect.Width - containerTagButtonLayout.Rect.Width);
if (limitedString != containerTagText.Text)
{
containerTagText.ToolTip = containerTagText.Text;
containerTagText.Text = limitedString;
}
itemEditor.AddCustomContent(containerTagLayout, 3);
}
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -972,6 +1000,12 @@ namespace Barotrauma
}; };
itemEditor.AddCustomContent(tickBox, 1); itemEditor.AddCustomContent(tickBox, 1);
} }
if (!Layer.IsNullOrEmpty())
{
var layerText = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)) { MinSize = new Point(0, heightScaled) }, TextManager.AddPunctuation(':', TextManager.Get("editor.layer"), Layer));
itemEditor.AddCustomContent(layerText, 1);
}
} }
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
@@ -987,7 +1021,7 @@ namespace Barotrauma
} }
else else
{ {
if (ic.requiredItems.Count == 0 && ic.DisabledRequiredItems.Count == 0 && SerializableProperty.GetProperties<Editable>(ic).Count == 0) { continue; } if (ic.RequiredItems.Count == 0 && ic.DisabledRequiredItems.Count == 0 && SerializableProperty.GetProperties<Editable>(ic).Count == 0) { continue; }
} }
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style: "HorizontalLine");
@@ -1004,7 +1038,7 @@ namespace Barotrauma
} }
List<RelatedItem> requiredItems = new List<RelatedItem>(); List<RelatedItem> requiredItems = new List<RelatedItem>();
foreach (var kvp in ic.requiredItems) foreach (var kvp in ic.RequiredItems)
{ {
foreach (RelatedItem relatedItem in kvp.Value) foreach (RelatedItem relatedItem in kvp.Value)
{ {
@@ -1089,34 +1123,389 @@ namespace Barotrauma
return result; return result;
} }
private void CreateTagPicker(GUITextBox textBox, IEnumerable<Identifier> availableTags) public void CreateContainerTagPicker([MaybeNull] GUITextBox tagTextBox)
{ {
var msgBox = new GUIMessageBox("", "", new LocalizedString[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400)); var msgBox = new GUIMessageBox(string.Empty, string.Empty, new[] { TextManager.Get("Ok") }, new Vector2(0.35f, 0.6f), new Point(400, 400));
msgBox.Buttons[0].OnClicked = msgBox.Close; msgBox.Buttons[0].OnClicked = msgBox.Close;
var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter)) var infoIcon = new GUIImage(new RectTransform(new Vector2(0.066f), msgBox.InnerFrame.RectTransform)
{ {
PlaySoundOnSelect = true, RelativeOffset = new Vector2(0.015f)
OnSelected = (component, userData) => }, style: "GUIButtonInfo")
{
ToolTip = TextManager.Get("containertagui.tutorial"),
IgnoreLayoutGroups = true
};
var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.85f), msgBox.Content.RectTransform));
var list = new GUIListBox(new RectTransform(new Vector2(1f, 1f), layout.RectTransform));
const float NameSize = 0.4f;
const float ItemSize = 0.5f;
const float CountSize = 0.1f;
var headerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), list.Content.RectTransform), isHorizontal: true);
new GUIButton(new RectTransform(new Vector2(NameSize, 1f), headerLayout.RectTransform), TextManager.Get("tagheader.tag"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes, CanBeFocused = false };
new GUIButton(new RectTransform(new Vector2(ItemSize, 1f), headerLayout.RectTransform), TextManager.Get("tagheader.items"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes, CanBeFocused = false };
new GUIButton(new RectTransform(new Vector2(CountSize, 1f), headerLayout.RectTransform), TextManager.Get("tagheader.count"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes, CanBeFocused = false };
var itemsByTag =
ContainerTagPrefab.Prefabs
.ToImmutableDictionary(
ct => ct,
ct => ct.GetItemsAndSpawnProbabilities());
// Group the prefabs by category and turn them into a dictionary where the key is the category and value is the list of identifiers of the prefabs.
// LINQ GroupBy returns GroupedEnumerable where the enumerable is the list of prefabs and key is what we grouped by.
var tagCategories = ContainerTagPrefab.Prefabs
.GroupBy(ct => ct.Category)
.ToImmutableDictionary(
g => g.Key,
g => g.Select(ct => ct.Identifier).ToImmutableArray());
foreach (var (category, categoryTags) in tagCategories)
{
var categoryButton = new GUIButton(new RectTransform(new Vector2(1f, 0.075f), list.Content.RectTransform), style: "GUIButtonSmallFreeScale");
categoryButton.Color *= 0.66f;
var categoryLayout = new GUILayoutGroup(new RectTransform(Vector2.One, categoryButton.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
var categoryText = new GUITextBlock(new RectTransform(Vector2.One, categoryLayout.RectTransform), TextManager.Get($"tagcategory.{category}"), font: GUIStyle.SubHeadingFont);
var arrowImage = new GUIImage(new RectTransform(new Vector2(1f, 0.5f), categoryLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonVerticalArrowFreeScale");
var arrowPadding = new GUIFrame(new RectTransform(new Vector2(0.025f, 1f), categoryLayout.RectTransform), style: null);
bool hasHiddenCategories = false;
foreach (var categoryTag in categoryTags.OrderBy(t => t.Value))
{ {
if (!(userData is Identifier)) { return true; } var found = itemsByTag.FirstOrNull(kvp => kvp.Key.Identifier == categoryTag);
AddTag((Identifier)userData); if (found is null)
textBox.Text = Tags; {
msgBox.Close(); DebugConsole.ThrowError($"Failed to find tag with identifier {categoryTag} in itemsByTag");
continue;
}
var (tag, prefabsAndProbabilities) = found.Value;
bool isCorrectSubType = tag.IsRecommendedForSub(Submarine);
var tagLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), list.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
UserData = category,
Visible = isCorrectSubType
};
if (!isCorrectSubType)
{
hasHiddenCategories = true;
}
var checkBoxLayout = new GUILayoutGroup(new RectTransform(new Vector2(NameSize, 1f), tagLayout.RectTransform), childAnchor: Anchor.Center);
var enabledCheckBox = new GUITickBox(new RectTransform(Vector2.One, checkBoxLayout.RectTransform, Anchor.Center), tag.Name, font: GUIStyle.SmallFont)
{
Selected = tags.Contains(tag.Identifier),
ToolTip = tag.Description
};
var tickBoxText = enabledCheckBox.TextBlock;
tickBoxText.Text = ToolBox.LimitString(tickBoxText.Text, tickBoxText.Font, tickBoxText.Rect.Width);
var itemLayout = new GUILayoutGroup(new RectTransform(new Vector2(ItemSize, 1f), tagLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
var itemLayoutScissor = new GUIScissorComponent(new RectTransform(new Vector2(0.8f, 1f), itemLayout.RectTransform)) { CanBeFocused = false };
var itemLayoutButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1), itemLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.Center);
var itemLayoutButton = new GUIButton(new RectTransform(new Vector2(0.8f), itemLayoutButtonLayout.RectTransform), text: "...", style: "GUICharacterInfoButton")
{
UserData = tag,
ToolTip = TextManager.Get("containertagui.viewprobabilities")
};
itemLayoutButtonLayout.Recalculate();
float scroll = 0f;
float localScroll = 0f;
int lastSkippedItems = 0;
int skippedItems = 0;
var itemLayoutDraw = new GUICustomComponent(new RectTransform(new Vector2(1f, 0.9f), itemLayoutScissor.Content.RectTransform, Anchor.CenterLeft), onDraw: (spriteBatch, component) =>
{
component.ToolTip = string.Empty;
const float padding = 8f;
float offset = 0f;
float size = component.Rect.Height;
int start = (int)Math.Floor(scroll);
int amountToDraw = (int)Math.Ceiling(component.Rect.Width / size) + 1; // +1 just to be on the safe side
bool shouldIncrementOnSkip = true;
float toDrawWidth = prefabsAndProbabilities.Length * (size + padding);
// if the width is less than the component width we need to limit how many items we draw or it looks weird
if (toDrawWidth < component.Rect.Width)
{
shouldIncrementOnSkip = false;
amountToDraw = prefabsAndProbabilities.Length;
}
for (int i = start; i < start + amountToDraw; i++)
{
var (ip, probability, _) = prefabsAndProbabilities[i % prefabsAndProbabilities.Length];
var sprite = ip.InventoryIcon ?? ip.Sprite;
if (sprite is null)
{
// I don't think this should happen but just in case
if (shouldIncrementOnSkip)
{
amountToDraw++;
skippedItems++;
}
continue;
}
if (ShouldHideItemPrefab(ip, probability))
{
if (shouldIncrementOnSkip)
{
skippedItems++;
amountToDraw++;
}
continue;
}
float partialScroll = localScroll * (size + padding);
var drawRect = new RectangleF(itemLayoutScissor.Rect.X + offset - partialScroll, component.Rect.Y, size, size);
var isMouseOver = drawRect.Contains(PlayerInput.MousePosition);
if (isMouseOver)
{
component.ToolTip = ip.CreateTooltipText();
}
var slotSprite = Inventory.SlotSpriteSmall;
slotSprite?.Draw(spriteBatch, drawRect.Location, Color.White, origin: Vector2.Zero, rotate: 0f, scale: size / slotSprite.size.X * Inventory.SlotSpriteSmallScale);
float iconScale = Math.Min(drawRect.Width / sprite.size.X, drawRect.Height / sprite.size.Y) * 0.9f;
Color drawColor = ip.InventoryIconColor;
sprite.Draw(spriteBatch, drawRect.Center, drawColor, origin: sprite.Origin, scale: iconScale);
offset += size + padding;
}
// we need to compensate for the skipped items so that the scroll doesn't jump around
if (skippedItems < lastSkippedItems)
{
scroll += lastSkippedItems - skippedItems;
}
lastSkippedItems = skippedItems;
skippedItems = 0;
}, onUpdate: (deltaTime, component) =>
{
if (GUI.MouseOn != component && MathUtils.NearlyEqual(localScroll, 0, deltaTime * 2))
{
localScroll = 0f;
return;
}
float totalWidth = prefabsAndProbabilities.Length * (component.Rect.Height + 8f);
if (totalWidth < component.Rect.Width) { return; }
scroll += deltaTime;
localScroll = scroll % 1f;
})
{
HoverCursor = CursorState.Default,
AlwaysOverrideCursor = true
};
var tooltip = TextManager.Get(tag.WarnIfLess ? "ContainerTagUI.RecommendedAmount" : "ContainerTagUI.SuggestedAmount");
var countBlock = new GUITextBlock(new RectTransform(new Vector2(CountSize, 1f), tagLayout.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
ToolTip = tooltip
};
UpdateCountBlock(countBlock, tag);
enabledCheckBox.OnSelected += tickBox =>
{
if (tickBox.Selected)
{
AddTag(tag.Identifier);
}
else
{
RemoveTag(tag.Identifier);
}
if (tagTextBox is not null)
{
tagTextBox.Text = string.Join(',', tags.Where(t => !Prefab.Tags.Contains(t)));
}
UpdateCountBlock(countBlock, tag);
return true;
};
itemLayoutButton.OnClicked = (button, _) =>
{
CreateContainerTagItemListPopup(tag, button.Rect.Center, layout, prefabsAndProbabilities);
return true;
};
void UpdateCountBlock(GUITextBlock textBlock, ContainerTagPrefab containerTag)
{
if (textBlock is null) { return; }
var tagCount = Submarine.GetItems(alsoFromConnectedSubs: true).Count(i => i.HasTag(containerTag.Identifier));
textBlock.Text = $"{tagCount} ({containerTag.RecommendedAmount})";
if (!isCorrectSubType || !containerTag.WarnIfLess || containerTag.RecommendedAmount <= 0) { return; }
if (tagCount < containerTag.RecommendedAmount)
{
textBlock.TextColor = GUIStyle.Red;
textBlock.Text += "*";
textBlock.ToolTip = RichString.Rich($"{tooltip}\n\n‖color:gui.red‖{TextManager.Get("ContainerTagUI.RecommendedAmountWarning")}‖color:end‖");
}
else if (tagCount >= containerTag.RecommendedAmount)
{
textBlock.TextColor = GUIStyle.Green;
textBlock.ToolTip = tooltip;
}
}
}
arrowImage.SpriteEffects = hasHiddenCategories ? SpriteEffects.None : SpriteEffects.FlipVertically;
categoryButton.OnClicked = (_, _) =>
{
arrowImage.SpriteEffects ^= SpriteEffects.FlipVertically;
foreach (var child in list.Content.Children)
{
if (child.UserData is Identifier id && id == category)
{
child.Visible = !child.Visible;
}
}
return true;
};
}
}
private static void CreateContainerTagItemListPopup(ContainerTagPrefab tag, Point location, GUIComponent popupParent, ImmutableArray<ContainerTagPrefab.ItemAndProbability> prefabAndProbabilities)
{
const string TooltipUserData = "tooltip";
const string ProbabilityUserData = "probability";
if (popupParent.GetChildByUserData(TooltipUserData) is { } existingTooltip)
{
popupParent.RemoveChild(existingTooltip);
}
var tooltip = new GUIFrame(new RectTransform(new Point(popupParent.Rect.Height), popupParent.RectTransform)
{
AbsoluteOffset = location - popupParent.Rect.Location
})
{
UserData = TooltipUserData,
IgnoreLayoutGroups = true
};
if (tooltip.Rect.Bottom > GameMain.GraphicsHeight)
{
int diffY = tooltip.Rect.Bottom - GameMain.GraphicsHeight;
tooltip.RectTransform.AbsoluteOffset -= new Point(0, diffY);
}
if (tooltip.Rect.Right > GameMain.GraphicsWidth)
{
int diffX = tooltip.Rect.Right - GameMain.GraphicsWidth;
tooltip.RectTransform.AbsoluteOffset -= new Point(diffX, 0);
}
var tooltipLayout = new GUILayoutGroup(new RectTransform(ToolBox.PaddingSizeParentRelative(tooltip.RectTransform, 0.9f), tooltip.RectTransform, Anchor.Center));
var tooltipHeader = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), tooltipLayout.RectTransform), tag.Name, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
var tooltipList = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), tooltipLayout.RectTransform));
var tooltipHeaderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), tooltipList.Content.RectTransform), isHorizontal: true);
new GUIButton(new RectTransform(new Vector2(0.66f, 1f), tooltipHeaderLayout.RectTransform), TextManager.Get("tagheader.item"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes, CanBeFocused = false };
new GUIButton(new RectTransform(new Vector2(0.33f, 1f), tooltipHeaderLayout.RectTransform), TextManager.Get("tagheader.probability"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes, CanBeFocused = false };
foreach (var itemAndProbability in prefabAndProbabilities.OrderByDescending(p => p.Probability))
{
var (ip, probability, campaignOnlyProbability) = itemAndProbability;
if (ShouldHideItemPrefab(ip, probability)) { continue; }
var itemLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), tooltipList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
UserData = itemAndProbability
};
var itemNameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.66f, 1f), itemLayout.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
Stretch = true
};
var itemIcon = new GUIImage(new RectTransform(Vector2.One, itemNameLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), ip.InventoryIcon ?? ip.Sprite, scaleToFit: true)
{
Color = ip.InventoryIconColor
};
var itemName = new GUITextBlock(new RectTransform(Vector2.One, itemNameLayout.RectTransform), ip.Name);
itemName.Text = ToolBox.LimitString(ip.Name, itemName.Font, itemName.Rect.Width);
var toolTipContainer = new GUIFrame(new RectTransform(Vector2.One, itemNameLayout.RectTransform), style: null)
{
IgnoreLayoutGroups = true,
ToolTip = ip.CreateTooltipText()
};
var probabilityText = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1f), itemLayout.RectTransform), ProbabilityToPercentage(campaignOnlyProbability), textAlignment: Alignment.Right)
{
UserData = ProbabilityUserData
};
if (MathUtils.NearlyEqual(campaignOnlyProbability, 0f)) { probabilityText.TextColor = GUIStyle.Red; }
}
var campaignCheckbox = new GUITickBox(new RectTransform(new Vector2(1f, 0.1f), tooltipLayout.RectTransform), label: TextManager.Get("containertagui.campaignonly"))
{
ToolTip = TextManager.Get("containertagui.campaignonlytooltip"),
Selected = true,
OnSelected = box =>
{
foreach (var child in tooltipList.Content.Children)
{
if (child.UserData is not ContainerTagPrefab.ItemAndProbability data) { continue; }
if (child.GetChildByUserData(ProbabilityUserData) is not GUITextBlock text) { continue; }
float probability = box.Selected
? data.CampaignProbability
: data.Probability;
text.Text = ProbabilityToPercentage(probability);
text.TextColor = MathUtils.NearlyEqual(probability, 0f)
? GUIStyle.Red
: GUIStyle.TextColorNormal;
}
return true; return true;
} }
}; };
foreach (var availableTag in availableTags.ToList().OrderBy(t => t)) var tooltipClose = new GUIButton(new RectTransform(new Vector2(1f, 0.1f), tooltipLayout.RectTransform), TextManager.Get("Close"))
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textList.Content.RectTransform) { MinSize = new Point(0, 20) }, OnClicked = (_, _) =>
ToolBox.LimitString(availableTag.Value, GUIStyle.Font, textList.Content.Rect.Width))
{ {
UserData = availableTag popupParent.RemoveChild(tooltip);
}; return true;
} }
};
static LocalizedString ProbabilityToPercentage(float probability)
=> TextManager.GetWithVariable("percentageformat", "[value]", MathF.Round((probability * 100f), 1).ToString(CultureInfo.InvariantCulture));
} }
private static bool ShouldHideItemPrefab(ItemPrefab ip, float probability)
=> ip.HideInMenus && MathUtils.NearlyEqual(probability, 0f);
/// <summary> /// <summary>
/// Reposition currently active item interfaces to make sure they don't overlap with each other /// Reposition currently active item interfaces to make sure they don't overlap with each other
/// </summary> /// </summary>
@@ -1213,10 +1602,21 @@ namespace Barotrauma
} }
activeHUDs.Clear(); activeHUDs.Clear();
maxPriorityHUDs.Clear();
bool DrawHud(ItemComponent ic)
{
if (!ic.ShouldDrawHUD(character)) { return false; }
if (character.HasEquippedItem(this))
{
return ic.DrawHudWhenEquipped;
}
else
{
return ic.CanBeSelected && ic.HasRequiredItems(character, addMessage: false);
}
}
//the HUD of the component with the highest priority will be drawn //the HUD of the component with the highest priority will be drawn
//if all components have a priority of 0, all of them are drawn //if all components have a priority of 0, all of them are drawn
maxPriorityHUDs.Clear();
bool DrawHud(ItemComponent ic) => ic.ShouldDrawHUD(character) && (ic.CanBeSelected && ic.HasRequiredItems(character, addMessage: false) || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped));
foreach (ItemComponent ic in activeComponents) foreach (ItemComponent ic in activeComponents)
{ {
if (ic.HudPriority > 0 && DrawHud(ic) && (maxPriorityHUDs.Count == 0 || ic.HudPriority >= maxPriorityHUDs[0].HudPriority)) if (ic.HudPriority > 0 && DrawHud(ic) && (maxPriorityHUDs.Count == 0 || ic.HudPriority >= maxPriorityHUDs[0].HudPriority))
@@ -1317,7 +1717,7 @@ namespace Barotrauma
} }
} }
readonly List<ColoredText> texts = new List<ColoredText>(); readonly List<ColoredText> texts = new();
public List<ColoredText> GetHUDTexts(Character character, bool recreateHudTexts = true) public List<ColoredText> GetHUDTexts(Character character, bool recreateHudTexts = true)
{ {
// Always create the texts if they have not yet been created // Always create the texts if they have not yet been created
@@ -1348,42 +1748,91 @@ namespace Barotrauma
nameText += $" x{DroppedStack.Count()}"; nameText += $" x{DroppedStack.Count()}";
} }
texts.Add(new ColoredText(nameText, GUIStyle.TextColorNormal, false, false)); texts.Add(new ColoredText(nameText, GUIStyle.TextColorNormal, isCommand: false, isError: false));
if (CampaignMode.BlocksInteraction(CampaignInteractionType)) if (CampaignMode.BlocksInteraction(CampaignInteractionType))
{ {
texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)).Value, Color.Cyan, false, false)); texts.Add(new ColoredText(TextManager.GetWithVariable($"CampaignInteraction.{CampaignInteractionType}", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)).Value, Color.Cyan, isCommand: false, isError: false));
} }
else else
{ {
foreach (ItemComponent ic in components) foreach (ItemComponent itemComponent in components)
{ {
if (!ic.CanBePicked && !ic.CanBeSelected) { continue; } var interactionVisibility = GetComponentInteractionVisibility(character, itemComponent);
if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; } if (interactionVisibility == InteractionVisibility.None) { continue; }
if (ic is ConnectionPanel connectionPanel && !connectionPanel.CanRewire()) { continue; } if (itemComponent.DisplayMsg.IsNullOrEmpty()) { continue; }
Color color = Color.Gray;
if (ic.HasRequiredItems(character, false)) Color color = interactionVisibility == InteractionVisibility.MissingRequirement ? Color.Gray : Color.Cyan;
{ texts.Add(new ColoredText(itemComponent.DisplayMsg.Value, color, isCommand: false, isError: false));
if (ic is Repairable r)
{
if (r.IsBelowRepairThreshold) { color = Color.Cyan; }
}
else
{
color = Color.Cyan;
}
}
if (ic.DisplayMsg.IsNullOrEmpty()) { continue; }
texts.Add(new ColoredText(ic.DisplayMsg.Value, color, false, false));
} }
} }
if (PlayerInput.IsShiftDown() && CrewManager.DoesItemHaveContextualOrders(this)) if (PlayerInput.KeyDown(InputType.ContextualCommand))
{ {
texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")).Value, Color.Cyan, false, false)); texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")).Value, Color.Cyan, isCommand: false, isError: false));
}
else
{
texts.Add(new ColoredText(TextManager.Get("itemmsg.morreoptionsavailable").Value, Color.LightGray * 0.7f, isCommand: false, isError: false));
} }
return texts; return texts;
} }
private enum InteractionVisibility
{
None,
MissingRequirement,
Visible
}
/// <summary>
/// Determine, for UI display purposes, the type of interaction visibility for an item component.
///
/// Example:
/// Visible -> Display cyan "click to interact" type text on item hover.
/// MissingRequirement -> Display gray "need tool" type text on item hover.
/// None -> Hide from item hover texts.
/// </summary>
/// <param name="character">Character, for tool requirement purposes.</param>
/// <param name="itemComponent">The item component to inspect.</param>
/// <returns>The interaction visibility state for this component.</returns>
private static InteractionVisibility GetComponentInteractionVisibility(Character character, ItemComponent itemComponent)
{
if (!itemComponent.CanBePicked && !itemComponent.CanBeSelected) { return InteractionVisibility.None; }
if (itemComponent is Holdable holdable && !holdable.CanBeDeattached()) { return InteractionVisibility.None; }
if (itemComponent is ConnectionPanel connectionPanel && !connectionPanel.CanRewire()) { return InteractionVisibility.None; }
InteractionVisibility interactionVisibility = InteractionVisibility.MissingRequirement;
if (itemComponent.HasRequiredItems(character, addMessage: false))
{
if (itemComponent is Repairable repairable)
{
if (repairable.IsBelowRepairThreshold)
{
interactionVisibility = InteractionVisibility.Visible;
}
}
else
{
interactionVisibility = InteractionVisibility.Visible;
}
}
return interactionVisibility;
}
public bool HasVisibleInteraction(Character character)
{
foreach (var component in components)
{
if (GetComponentInteractionVisibility(character, component) == InteractionVisibility.Visible)
{
return true;
}
}
return false;
}
public void ForceHUDLayoutUpdate(bool ignoreLocking = false) public void ForceHUDLayoutUpdate(bool ignoreLocking = false)
{ {
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
@@ -331,7 +331,6 @@ namespace Barotrauma
} }
private GUIComponent CreateEditingHUD(bool inGame = false) private GUIComponent CreateEditingHUD(bool inGame = false)
{ {
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.15f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.15f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) })
{ {
UserData = this UserData = this
@@ -170,16 +170,18 @@ namespace Barotrauma
{ {
if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any()) if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any())
{ {
GameMain.NetworkMember?.CreateEntityEvent(this, new StatusEventData()); //these are used to modify the amount water/fire in the hull with console commands
//they should be usable even when not controlling a character
GameMain.Client?.CreateEntityEvent(this, new StatusEventData(), requireControlledCharacter: false);
} }
foreach (Decal decal in pendingDecalUpdates) foreach (Decal decal in pendingDecalUpdates)
{ {
GameMain.NetworkMember?.CreateEntityEvent(this, new DecalEventData(decal)); GameMain.Client?.CreateEntityEvent(this, new DecalEventData(decal));
} }
pendingDecalUpdates.Clear(); pendingDecalUpdates.Clear();
foreach (int pendingSectionUpdate in pendingSectionUpdates) foreach (int pendingSectionUpdate in pendingSectionUpdates)
{ {
GameMain.NetworkMember?.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate)); GameMain.Client?.CreateEntityEvent(this, new BackgroundSectionsEventData(pendingSectionUpdate));
} }
pendingSectionUpdates.Clear(); pendingSectionUpdates.Clear();
networkUpdatePending = false; networkUpdatePending = false;
@@ -16,27 +16,34 @@ namespace Barotrauma
float scale = Math.Min(drawArea.Width / (float)Bounds.Width, drawArea.Height / (float)Bounds.Height) * 0.9f; float scale = Math.Min(drawArea.Width / (float)Bounds.Width, drawArea.Height / (float)Bounds.Height) * 0.9f;
foreach ((Identifier identifier, Rectangle rect) in DisplayEntities) foreach (var displayEntity in DisplayEntities)
{ {
var entityPrefab = FindByIdentifier(identifier); var entityPrefab = FindByIdentifier(displayEntity.Identifier);
if (entityPrefab is CoreEntityPrefab || entityPrefab == null) { continue; } if (entityPrefab is CoreEntityPrefab || entityPrefab == null) { continue; }
var drawRect = new Rectangle( var drawRect = new Rectangle(
(int)(rect.X * scale) + drawArea.Center.X, (int)((rect.Y) * scale) - drawArea.Center.Y, (int)(displayEntity.Rect.X * scale) + drawArea.Center.X, (int)((displayEntity.Rect.Y) * scale) - drawArea.Center.Y,
(int)(rect.Width * scale), (int)(rect.Height * scale)); (int)(displayEntity.Rect.Width * scale), (int)(displayEntity.Rect.Height * scale));
entityPrefab.DrawPlacing(spriteBatch, drawRect, entityPrefab.Scale * scale); entityPrefab.DrawPlacing(spriteBatch, drawRect, entityPrefab.Scale * scale, rotation: displayEntity.RotationRad);
} }
} }
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam) public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{ {
base.DrawPlacing(spriteBatch, cam); base.DrawPlacing(spriteBatch, cam);
foreach ((Identifier identifier, Rectangle rect) in DisplayEntities) Draw(
spriteBatch,
placePosition != Vector2.Zero ? placePosition : Submarine.MouseToWorldGrid(cam, Submarine.MainSub));
}
public void Draw(SpriteBatch spriteBatch, Vector2 pos)
{
foreach (var displayEntity in DisplayEntities)
{ {
var entityPrefab = FindByIdentifier(identifier); var entityPrefab = FindByIdentifier(displayEntity.Identifier);
if (entityPrefab == null) { continue; } if (entityPrefab == null) { continue; }
Rectangle drawRect = rect; Rectangle drawRect = displayEntity.Rect;
drawRect.Location += placePosition != Vector2.Zero ? placePosition.ToPoint() : Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint(); drawRect.Location += pos.ToPoint();
entityPrefab.DrawPlacing(spriteBatch, drawRect, entityPrefab.Scale); entityPrefab.DrawPlacing(spriteBatch, drawRect, entityPrefab.Scale, rotation: displayEntity.RotationRad);
} }
} }
@@ -47,9 +54,12 @@ namespace Barotrauma
new XAttribute("description", description), new XAttribute("description", description),
new XAttribute("hideinmenus", hideInMenus)); new XAttribute("hideinmenus", hideInMenus));
//move the entities so that their "center of mass" is at {0,0} //move the entities so that their "center of mass" is at {0,0}
var assemblyEntities = MapEntity.CopyEntities(entities); var assemblyEntities = MapEntity.CopyEntities(entities);
for (int i = 0; i < assemblyEntities.Count && i < entities.Count; i++)
{
assemblyEntities[i].Layer = entities[i].Layer;
}
//find wires and items that are contained inside another item //find wires and items that are contained inside another item
//place them at {0,0} to prevent them from messing up the origin of the prefab and to hide them in preview //place them at {0,0} to prevent them from messing up the origin of the prefab and to hide them in preview
@@ -86,7 +96,7 @@ namespace Barotrauma
MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2); MathUtils.RoundTowardsClosest(center.Y, Submarine.GridSize.Y) - center.Y - Submarine.GridSize.Y / 2);
MapEntity.SelectedList.Clear(); MapEntity.SelectedList.Clear();
entities.ForEach(e => MapEntity.AddSelection(e)); assemblyEntities.ForEach(e => MapEntity.AddSelection(e));
foreach (MapEntity mapEntity in assemblyEntities) foreach (MapEntity mapEntity in assemblyEntities)
{ {
@@ -20,6 +20,8 @@ namespace Barotrauma
{ {
if (damage <= 0.0f) { return; } if (damage <= 0.0f) { return; }
Vector2 particlePos = worldPosition; Vector2 particlePos = worldPosition;
Vector2 particleDir = particlePos - WorldPosition;
if (particleDir.LengthSquared() > 0.0001f) { particleDir = Vector2.Normalize(particleDir); }
if (!Cells.Any(c => c.IsPointInside(particlePos))) if (!Cells.Any(c => c.IsPointInside(particlePos)))
{ {
bool intersectionFound = false; bool intersectionFound = false;
@@ -31,6 +33,7 @@ namespace Barotrauma
{ {
intersectionFound = true; intersectionFound = true;
particlePos = intersection; particlePos = intersection;
particleDir = edge.GetNormal(cell);
break; break;
} }
} }
@@ -38,14 +41,15 @@ namespace Barotrauma
} }
} }
Vector2 particleDir = particlePos - WorldPosition;
if (particleDir.LengthSquared() > 0.0001f) { particleDir = Vector2.Normalize(particleDir); }
int particleAmount = MathHelper.Clamp((int)damage, 1, 10); int particleAmount = MathHelper.Clamp((int)damage, 1, 10);
for (int i = 0; i < particleAmount; i++) for (int i = 0; i < particleAmount; i++)
{ {
var particle = GameMain.ParticleManager.CreateParticle("iceshards", var particle = GameMain.ParticleManager.CreateParticle("iceexplosionsmall",
particlePos + Rand.Vector(5.0f), particlePos + Rand.Vector(5.0f),
particleDir * Rand.Range(200.0f, 500.0f) + Rand.Vector(100.0f)); particleDir * Rand.Range(30.0f, 500.0f) + Rand.Vector(20.0f));
GameMain.ParticleManager.CreateParticle("iceshards",
particlePos + Rand.Vector(5.0f),
particleDir * Rand.Range(100.0f, 500.0f) + Rand.Vector(100.0f));
} }
} }
@@ -95,6 +95,7 @@ namespace Barotrauma
} }
} }
} }
foreach (var rects in blockedRects.Values) foreach (var rects in blockedRects.Values)
{ {
foreach (var rect in rects) foreach (var rect in rects)
@@ -18,7 +18,7 @@ namespace Barotrauma
//Maximum number of visible objects drawn at once. Should be large enough to not have an effect during normal gameplay, //Maximum number of visible objects drawn at once. Should be large enough to not have an effect during normal gameplay,
//but small enough to prevent wrecking performance when zooming out very far //but small enough to prevent wrecking performance when zooming out very far
const int MaxVisibleObjects = 500; const int MaxVisibleObjects = 600;
private Rectangle currentGridIndices; private Rectangle currentGridIndices;
@@ -362,6 +362,13 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, new Vector2(edge.Point1.X + cell.Translation.X, -(edge.Point1.Y + cell.Translation.Y)), GUI.DrawLine(spriteBatch, new Vector2(edge.Point1.X + cell.Translation.X, -(edge.Point1.Y + cell.Translation.Y)),
new Vector2(edge.Point2.X + cell.Translation.X, -(edge.Point2.Y + cell.Translation.Y)), edge.NextToCave ? Color.Red : (cell.Body == null ? Color.Cyan * 0.5f : (edge.IsSolid ? Color.White : Color.Gray)), new Vector2(edge.Point2.X + cell.Translation.X, -(edge.Point2.Y + cell.Translation.Y)), edge.NextToCave ? Color.Red : (cell.Body == null ? Color.Cyan * 0.5f : (edge.IsSolid ? Color.White : Color.Gray)),
width: edge.NextToCave ? 8 : 1); width: edge.NextToCave ? 8 : 1);
Vector2 normal = edge.GetNormal(cell);
GUI.DrawLine(spriteBatch,
(edge.Center + cell.Translation).FlipY(),
(edge.Center + cell.Translation + normal * 32).FlipY(),
Color.Red * 0.5f,
width: 3);
} }
foreach (Vector2 point in cell.BodyVertices) foreach (Vector2 point in cell.BodyVertices)
@@ -156,17 +156,7 @@ namespace Barotrauma.Lights
BoundingBox = rect; BoundingBox = rect;
this.isHorizontal = isHorizontal; this.isHorizontal = isHorizontal;
if (ParentEntity is Structure structure) Debug.Assert(!ParentEntity.Removed);
{
Debug.Assert(!structure.Removed);
isHorizontal = structure.IsHorizontal;
}
else if (ParentEntity is Item item)
{
Debug.Assert(!item.Removed);
var door = item.GetComponent<Door>();
if (door != null) { isHorizontal = door.IsHorizontal; }
}
Vector2[] verts = new Vector2[] Vector2[] verts = new Vector2[]
{ {
@@ -268,8 +268,7 @@ namespace Barotrauma.Lights
{ {
if (!light.Enabled) { continue; } if (!light.Enabled) { continue; }
if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; } if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
//above the top boundary of the level (in an inactive respawn shuttle?)
if (Level.Loaded != null && light.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (light.ParentBody != null) if (light.ParentBody != null)
{ {
light.ParentBody.UpdateDrawPosition(); light.ParentBody.UpdateDrawPosition();
@@ -696,12 +695,24 @@ namespace Barotrauma.Lights
if (diff.LengthSquared() > 20.0f * 20.0f) { losOffset = diff; } if (diff.LengthSquared() > 20.0f * 20.0f) { losOffset = diff; }
float rotation = MathUtils.VectorToAngle(losOffset); float rotation = MathUtils.VectorToAngle(losOffset);
//the visible area stretches to the maximum when the cursor is this far from the character
const float MaxOffset = 256.0f;
const float MinHorizontalScale = 2.2f;
const float MaxHorizontalScale = 2.8f;
const float VerticalScale = 2.5f;
//Starting point and scale-based modifier that moves the point of origin closer to the edge of the texture if the player moves their mouse further away, or vice versa.
float relativeOriginStartPosition = 0.22f; //Increasing this value moves the origin further behind the character
float originStartPosition = visionCircle.Width * relativeOriginStartPosition;
float relativeOriginLookAtPosModifier = -0.055f; //Increase this value increases how much the vision changes by moving the mouse
float originLookAtPosModifier = visionCircle.Width * relativeOriginLookAtPosModifier;
Vector2 scale = new Vector2( Vector2 scale = new Vector2(
MathHelper.Clamp(losOffset.Length() / 256.0f, 4.0f, 5.0f), 3.0f); MathHelper.Clamp(losOffset.Length() / MaxOffset, MinHorizontalScale, MaxHorizontalScale), VerticalScale);
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameSettings.CurrentConfig.Graphics.LightMapScale, GameSettings.CurrentConfig.Graphics.LightMapScale, 1.0f))); spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameSettings.CurrentConfig.Graphics.LightMapScale, GameSettings.CurrentConfig.Graphics.LightMapScale, 1.0f)));
spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation, spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
new Vector2(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f); new Vector2(originStartPosition + (scale.X * originLookAtPosModifier), visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
spriteBatch.End(); spriteBatch.End();
} }
else else
@@ -461,7 +461,9 @@ namespace Barotrauma
new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), repBarHolder.RectTransform), onDraw: (sb, component) => new GUICustomComponent(new RectTransform(new Vector2(0.6f, 1.0f), repBarHolder.RectTransform), onDraw: (sb, component) =>
{ {
if (location.Reputation == null) { return; } if (location.Reputation == null) { return; }
RoundSummary.DrawReputationBar(sb, component.Rect, location.Reputation.NormalizedValue); RoundSummary.DrawReputationBar(sb, component.Rect,
location.Reputation.NormalizedValue,
location.Reputation.MinReputation, location.Reputation.MaxReputation);
}); });
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1.0f), repBarHolder.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.4f, 1.0f), repBarHolder.RectTransform),
@@ -1128,8 +1130,11 @@ namespace Barotrauma
if (connection.LevelData.HasBeaconStation) if (connection.LevelData.HasBeaconStation)
{ {
var beaconStationIconStyle = connection.LevelData.IsBeaconActive ? "BeaconStationActive" : "BeaconStationInactive"; bool beaconActive =
DrawIcon(beaconStationIconStyle, (int)(28 * zoom), connection.LevelData.IsBeaconActive ? beaconStationActiveText : beaconStationInactiveText); connection.LevelData.IsBeaconActive ||
(Level.Loaded?.LevelData == connection.LevelData && Level.Loaded.CheckBeaconActive());
var beaconStationIconStyle = beaconActive ? "BeaconStationActive" : "BeaconStationInactive";
DrawIcon(beaconStationIconStyle, (int)(28 * zoom), beaconActive ? beaconStationActiveText : beaconStationInactiveText);
} }
if (connection.Locked) if (connection.Locked)
@@ -808,8 +808,16 @@ namespace Barotrauma
{ {
if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; } if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) { spriteEffects ^= SpriteEffects.FlipHorizontally; }
if (structure.FlippedY && structure.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; } if (structure.FlippedY && structure.Prefab.CanSpriteFlipY) { spriteEffects ^= SpriteEffects.FlipVertically; }
spriteRotation = MathHelper.ToRadians(structure.Rotation); rectangleRotation = MathHelper.ToRadians(structure.Rotation);
rectangleRotation = spriteRotation;
spriteRotation = rectangleRotation;
bool spriteIsFlippedHorizontally = structure.Sprite.effects.HasFlag(SpriteEffects.FlipHorizontally);
bool spriteIsFlippedVertically = structure.Sprite.effects.HasFlag(SpriteEffects.FlipVertically);
if (spriteIsFlippedHorizontally != spriteIsFlippedVertically)
{
spriteRotation = -spriteRotation;
}
if (structure.FlippedX != structure.FlippedY) { rectangleRotation = -rectangleRotation; } if (structure.FlippedX != structure.FlippedY) { rectangleRotation = -rectangleRotation; }
break; break;
} }
@@ -978,6 +986,11 @@ namespace Barotrauma
} }
} }
public static void ResetEditingHUD()
{
editingHUD = null;
}
public static void DrawEditor(SpriteBatch spriteBatch, Camera cam) public static void DrawEditor(SpriteBatch spriteBatch, Camera cam)
{ {
if (SelectedList.Count == 1) if (SelectedList.Count == 1)
@@ -1,12 +1,32 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
abstract partial class MapEntityPrefab : PrefabWithUintIdentifier abstract partial class MapEntityPrefab : PrefabWithUintIdentifier
{ {
public RichString CreateTooltipText()
{
LocalizedString name = Category.HasFlag(MapEntityCategory.Legacy) ? TextManager.GetWithVariable("legacyitemformat", "[name]", Name) : Name;
LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{name}‖color:end‖";
if (!Description.IsNullOrEmpty())
{
tooltip += '\n' + Description;
}
if (IsModded)
{
tooltip = $"{tooltip}\n‖color:{Color.MediumPurple.ToStringHex()}‖{ContentPackage?.Name}‖color:end‖";
}
return RichString.Rich(tooltip);
}
public bool IsModded
=> ContentPackage != GameMain.VanillaContent && ContentPackage != null;
public virtual void UpdatePlacing(Camera cam) public virtual void UpdatePlacing(Camera cam)
{ {
if (PlayerInput.SecondaryMouseButtonClicked()) if (PlayerInput.SecondaryMouseButtonClicked())
@@ -25,8 +25,10 @@ namespace Barotrauma
Stream = sound.Stream; Stream = sound.Stream;
Range = element.GetAttributeFloat("range", 1000.0f); Range = element.GetAttributeFloat("range", 1000.0f);
Volume = element.GetAttributeFloat("volume", 1.0f); Volume = element.GetAttributeFloat("volume", 1.0f);
IgnoreMuffling = element.GetAttributeBool("dontmuffle", false);
FrequencyMultiplierRange = new Vector2(1.0f); FrequencyMultiplierRange = new Vector2(1.0f);
string freqMultAttr = element.GetAttributeString("frequencymultiplier", element.GetAttributeString("frequency", "1.0"))!; string freqMultAttr = element.GetAttributeString("frequencymultiplier", element.GetAttributeString("frequency", "1.0"));
if (!freqMultAttr.Contains(',')) if (!freqMultAttr.Contains(','))
{ {
if (float.TryParse(freqMultAttr, NumberStyles.Any, CultureInfo.InvariantCulture, out float freqMult)) if (float.TryParse(freqMultAttr, NumberStyles.Any, CultureInfo.InvariantCulture, out float freqMult))
@@ -47,7 +49,6 @@ namespace Barotrauma
DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")", DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")",
contentPackage: element.ContentPackage); contentPackage: element.ContentPackage);
} }
IgnoreMuffling = element.GetAttributeBool("dontmuffle", false);
} }
public float GetRandomFrequencyMultiplier() public float GetRandomFrequencyMultiplier()
@@ -221,6 +221,12 @@ namespace Barotrauma
editor.AddCustomContent(tickBox, 1); editor.AddCustomContent(tickBox, 1);
} }
if (!Layer.IsNullOrEmpty())
{
var layerText = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)) { MinSize = new Point(0, heightScaled) }, TextManager.AddPunctuation(':', TextManager.Get("editor.layer"), Layer));
editor.AddCustomContent(layerText, 1);
}
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -435,13 +441,12 @@ namespace Barotrauma
dropShadowOffset.Y = -dropShadowOffset.Y; dropShadowOffset.Y = -dropShadowOffset.Y;
} }
SpriteEffects oldEffects = Prefab.BackgroundSprite.effects;
Prefab.BackgroundSprite.effects ^= SpriteEffects;
Vector2 backGroundOffset = new Vector2( Vector2 backGroundOffset = new Vector2(
MathUtils.PositiveModulo(-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width * TextureScale.X * Scale), MathUtils.PositiveModulo(-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width * TextureScale.X * Scale),
MathUtils.PositiveModulo(-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height * TextureScale.Y * Scale)); MathUtils.PositiveModulo(-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height * TextureScale.Y * Scale));
float rotationRad = rotationForSprite(this.rotationRad, Prefab.BackgroundSprite);
Prefab.BackgroundSprite.DrawTiled( Prefab.BackgroundSprite.DrawTiled(
spriteBatch, spriteBatch,
new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)), new Vector2(rect.X + rect.Width / 2 + drawOffset.X, -(rect.Y - rect.Height / 2 + drawOffset.Y)),
@@ -451,7 +456,8 @@ namespace Barotrauma
color: Prefab.BackgroundSpriteColor, color: Prefab.BackgroundSpriteColor,
textureScale: TextureScale * Scale, textureScale: TextureScale * Scale,
startOffset: backGroundOffset, startOffset: backGroundOffset,
depth: Math.Max(GetDrawDepth(Prefab.BackgroundSprite.Depth, Prefab.BackgroundSprite), depth + 0.000001f)); depth: Math.Max(GetDrawDepth(Prefab.BackgroundSprite.Depth, Prefab.BackgroundSprite), depth + 0.000001f),
spriteEffects: Prefab.BackgroundSprite.effects ^ SpriteEffects);
if (UseDropShadow) if (UseDropShadow)
{ {
@@ -464,18 +470,14 @@ namespace Barotrauma
color: Color.Black * 0.5f, color: Color.Black * 0.5f,
textureScale: TextureScale * Scale, textureScale: TextureScale * Scale,
startOffset: backGroundOffset, startOffset: backGroundOffset,
depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f); depth: (depth + Prefab.BackgroundSprite.Depth) / 2.0f,
spriteEffects: Prefab.BackgroundSprite.effects ^ SpriteEffects);
} }
Prefab.BackgroundSprite.effects = oldEffects;
} }
} }
if (back == GetRealDepth() > 0.5f) if (back == GetRealDepth() > 0.5f)
{ {
SpriteEffects oldEffects = Prefab.Sprite.effects;
Prefab.Sprite.effects ^= SpriteEffects;
Vector2 advanceX = MathUtils.RotatedUnitXRadians(this.rotationRad).FlipY(); Vector2 advanceX = MathUtils.RotatedUnitXRadians(this.rotationRad).FlipY();
Vector2 advanceY = advanceX.YX().FlipX(); Vector2 advanceY = advanceX.YX().FlipX();
if (FlippedX != FlippedY) if (FlippedX != FlippedY)
@@ -483,6 +485,9 @@ namespace Barotrauma
advanceX = advanceX.FlipY(); advanceX = advanceX.FlipY();
advanceY = advanceY.FlipX(); advanceY = advanceY.FlipX();
} }
float sectionSpriteRotationRad = rotationForSprite(this.rotationRad, Prefab.Sprite);
for (int i = 0; i < Sections.Length; i++) for (int i = 0; i < Sections.Length; i++)
{ {
Rectangle drawSection = Sections[i].rect; Rectangle drawSection = Sections[i].rect;
@@ -528,22 +533,17 @@ namespace Barotrauma
pos += rect.Location.ToVector2(); pos += rect.Location.ToVector2();
pos = new Vector2(pos.X + rect.Width / 2 + drawOffset.X, -(pos.Y - rect.Height / 2 + drawOffset.Y)); pos = new Vector2(pos.X + rect.Width / 2 + drawOffset.X, -(pos.Y - rect.Height / 2 + drawOffset.Y));
Vector2 pos = new Vector2(drawSection.X, drawSection.Y);
pos -= rect.Location.ToVector2();
pos = advanceX * pos.X + advanceY * pos.Y;
pos += rect.Location.ToVector2();
pos = new Vector2(pos.X + rect.Width / 2 + drawOffset.X, -(pos.Y - rect.Height / 2 + drawOffset.Y));
Prefab.Sprite.DrawTiled( Prefab.Sprite.DrawTiled(
spriteBatch, spriteBatch,
pos, pos,
new Vector2(drawSection.Width, drawSection.Height), new Vector2(drawSection.Width, drawSection.Height),
rotation: rotationRad, rotation: sectionSpriteRotationRad,
origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f), origin: rect.Size.ToVector2() * new Vector2(0.5f, 0.5f),
color: color, color: color,
startOffset: sectionOffset, startOffset: sectionOffset,
depth: depth, depth: depth,
textureScale: TextureScale * Scale); textureScale: TextureScale * Scale,
spriteEffects: Prefab.Sprite.effects ^ SpriteEffects);
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
@@ -551,27 +551,42 @@ namespace Barotrauma
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + this.rotationRad; float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState, spriteAnimState[decorativeSprite].RandomRotationFactor) + this.rotationRad;
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, Vector2 drawPos = DrawPosition + MathUtils.RotatePoint(offset, -this.rotationRad);
rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, Prefab.Sprite.effects, decorativeSprite.Sprite.Draw(
spriteBatch: spriteBatch,
pos: drawPos.FlipY(),
color: color,
rotate: rotation,
scale: decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale,
spriteEffect: Prefab.Sprite.effects ^ SpriteEffects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - Prefab.Sprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - Prefab.Sprite.Depth), 0.999f));
} }
Prefab.Sprite.effects = oldEffects; }
static float rotationForSprite(float rotationRad, Sprite sprite)
{
if (sprite.effects.HasFlag(SpriteEffects.FlipHorizontally) != sprite.effects.HasFlag(SpriteEffects.FlipVertically))
{
rotationRad = -rotationRad;
}
return rotationRad;
} }
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.5f) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.5f)
{ {
if (Bodies != null) if (Bodies != null)
{ {
for (int i = 0; i < Bodies.Count; i++) foreach (var body in Bodies)
{ {
Vector2 pos = FarseerPhysics.ConvertUnits.ToDisplayUnits(Bodies[i].Position); Vector2 pos = ConvertUnits.ToDisplayUnits(body.Position);
if (Submarine != null) { pos += Submarine.DrawPosition; } if (Submarine != null) { pos += Submarine.DrawPosition; }
pos.Y = -pos.Y; pos.Y = -pos.Y;
var dimensions = bodyDimensions[body];
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
pos, pos,
FarseerPhysics.ConvertUnits.ToDisplayUnits(bodyDebugDimensions[i].X), ConvertUnits.ToDisplayUnits(dimensions.X),
FarseerPhysics.ConvertUnits.ToDisplayUnits(bodyDebugDimensions[i].Y), ConvertUnits.ToDisplayUnits(dimensions.Y),
-Bodies[i].Rotation, Color.White); -body.Rotation, Color.White);
} }
} }
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text;
namespace Barotrauma namespace Barotrauma
{ {
@@ -506,6 +507,47 @@ namespace Barotrauma
Hull.ShowHulls = true; Hull.ShowHulls = true;
} }
if (!IsWarningSuppressed(SubEditorScreen.WarningType.NotEnoughContainers))
{
HashSet<ContainerTagPrefab> missingContainerTags = new();
foreach (var prefab in ContainerTagPrefab.Prefabs)
{
if (!prefab.IsRecommendedForSub(this) || !prefab.WarnIfLess) { continue; }
int count = Item.ItemList.Count(i => i.HasTag(prefab.Identifier));
if (count < prefab.RecommendedAmount)
{
missingContainerTags.Add(prefab);
}
}
if (missingContainerTags.Any())
{
StringBuilder sb = new();
int count = 0;
foreach (var tag in missingContainerTags)
{
sb.AppendLine($"- {tag.Name}");
count++;
if (missingContainerTags.Count > count && count >= 3)
{
var moreIndicator = TextManager.GetWithVariable(
"upgradeuitooltip.moreindicator",
"[amount]",
(missingContainerTags.Count - count).ToString()).Value;
sb.AppendLine(moreIndicator);
break;
}
}
errorMsgs.Add(TextManager.GetWithVariable(
"ContainerTagUI.CountWarning",
"[tags]",
sb.ToString()).Value);
warnings.Add(SubEditorScreen.WarningType.NotEnoughContainers);
}
}
if (Info.Type == SubmarineType.Player) if (Info.Type == SubmarineType.Player)
{ {
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
@@ -521,7 +563,40 @@ namespace Barotrauma
break; break;
} }
} }
foreach (Item item in Item.ItemList)
{
if (item.GetComponent<OxygenGenerator>() is not OxygenGenerator oxygenGenerator) { continue; }
Dictionary<Hull, float> hullOxygenFlow = new Dictionary<Hull, float>();
foreach (var linkedTo in item.linkedTo)
{
if (linkedTo is not Item linkedItem || linkedItem.GetComponent<Vent>() is not Vent vent) { continue; }
if (vent.Item.CurrentHull == null)
{
vent.Item.FindHull();
if (vent.Item.CurrentHull == null) { continue; }
}
float oxygenFlow = oxygenGenerator.GetVentOxygenFlow(vent);
if (!hullOxygenFlow.ContainsKey(vent.Item.CurrentHull))
{
hullOxygenFlow[vent.Item.CurrentHull] = oxygenFlow;
}
else
{
hullOxygenFlow[vent.Item.CurrentHull] += oxygenFlow;
}
}
foreach ((Hull hull, float oxygenFlow) in hullOxygenFlow)
{
if (oxygenFlow < Hull.OxygenConsumptionSpeed)
{
errorMsgs.Add(TextManager.GetWithVariable("LowOxygenOutputWarning", "[roomname]",
hull.DisplayName).Value);
warnings.Add(SubEditorScreen.WarningType.LowOxygenOutputWarning);
}
}
}
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human)) if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human))
{ {
if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoHumanSpawnpoints)) if (!IsWarningSuppressed(SubEditorScreen.WarningType.NoHumanSpawnpoints))
@@ -554,6 +629,15 @@ namespace Barotrauma
warnings.Add(SubEditorScreen.WarningType.NoHiddenContainers); warnings.Add(SubEditorScreen.WarningType.NoHiddenContainers);
} }
} }
if (Info.Dimensions.X * Physics.DisplayToRealWorldRatio > 80 ||
Info.Dimensions.Y * Physics.DisplayToRealWorldRatio > 32)
{
if (!IsWarningSuppressed(SubEditorScreen.WarningType.TooLargeForEndGame))
{
errorMsgs.Add(TextManager.Get("TooLargeForEndGameWarning").Value);
warnings.Add(SubEditorScreen.WarningType.TooLargeForEndGame);
}
}
} }
else if (Info.Type == SubmarineType.OutpostModule) else if (Info.Type == SubmarineType.OutpostModule)
{ {
@@ -749,7 +833,9 @@ namespace Barotrauma
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
{ {
throw new Exception($"Error while reading a network event for the submarine \"{Info.Name} ({ID})\". Submarines are not even supposed to receive events!"); Identifier layerIdentifier = msg.ReadIdentifier();
bool enabled = msg.ReadBoolean();
SetLayerEnabled(layerIdentifier, enabled);
} }
} }
} }
@@ -1,12 +1,52 @@
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Voronoi2;
namespace Barotrauma namespace Barotrauma
{ {
partial class SubmarineBody partial class SubmarineBody
{ {
partial void HandleLevelCollisionProjSpecific(Impact impact)
{
float wallImpact = Vector2.Dot(impact.Velocity, -impact.Normal);
int particleAmount = (int)Math.Min(wallImpact, 10);
const float BurstParticleThreshold = 5.0f;
float velocityFactor = MathHelper.Clamp(wallImpact / 10.0f, 0.0f, 1.0f);
for (int i = 0; i < particleAmount * 5; i++)
{
GameMain.ParticleManager.CreateParticle("iceshards",
ConvertUnits.ToDisplayUnits(impact.ImpactPos) + Rand.Vector(Rand.Range(1.0f, 50.0f)),
(Rand.Vector(0.9f) + impact.Normal) * Rand.Range(100.0f, 10000) * velocityFactor);
}
for (int i = 0; i < particleAmount; i++)
{
float particleVelocityMultiplier = Rand.Range(0.0f, 1);
var p = GameMain.ParticleManager.CreateParticle("iceexplosion",
ConvertUnits.ToDisplayUnits(impact.ImpactPos) + Rand.Vector(Rand.Range(1.0f, 50.0f)),
(Rand.Vector(0.5f) + impact.Normal) * particleVelocityMultiplier * 500 * velocityFactor);
if (p != null)
{
p.VelocityChangeMultiplier = particleVelocityMultiplier * Rand.Range(0.0f, 1.0f);
p.Size *= Math.Max(particleVelocityMultiplier, 0);
}
}
if (wallImpact > BurstParticleThreshold)
{
for (int i = 0; i < particleAmount; i++)
{
GameMain.ParticleManager.CreateParticle("iceburst",
ConvertUnits.ToDisplayUnits(impact.ImpactPos) + Rand.Vector(Rand.Range(1.0f, 50.0f)),
angle: MathUtils.VectorToAngle(impact.Normal.FlipY() + Rand.Vector(0.25f)), speed: 0.0f);
}
}
}
partial void ClientUpdatePosition(float deltaTime) partial void ClientUpdatePosition(float deltaTime)
{ {
if (GameMain.Client == null) { return; } if (GameMain.Client == null) { return; }
@@ -168,7 +168,7 @@ namespace Barotrauma
private bool IsHidden() private bool IsHidden()
{ {
if (!SubEditorScreen.IsLayerVisible(this)) { return false; } if (!SubEditorScreen.IsLayerVisible(this)) { return true; }
if (spawnType == SpawnType.Path) if (spawnType == SpawnType.Path)
{ {
return (!GameMain.DebugDraw && !ShowWayPoints); return (!GameMain.DebugDraw && !ShowWayPoints);
@@ -316,6 +316,11 @@ namespace Barotrauma
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("Spawnpoint"), font: GUIStyle.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("Spawnpoint"), font: GUIStyle.LargeFont);
if (!Layer.IsNullOrEmpty())
{
var layerText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("editor.layer"), Layer));
}
var spawnTypeContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true) var spawnTypeContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -46,7 +46,7 @@ namespace Barotrauma.Networking
{ {
if (localRemovedBans.Contains(bannedPlayer.UniqueIdentifier)) { continue; } if (localRemovedBans.Contains(bannedPlayer.UniqueIdentifier)) { continue; }
var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)BanFrame).Content.RectTransform) { MinSize = new Point(0, 70) }) var playerFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), ((GUIListBox)BanFrame).Content.RectTransform) { MinSize = new Point(0, 70) }, style: "InnerFrame")
{ {
UserData = BanFrame UserData = BanFrame
}; };
@@ -80,18 +80,22 @@ namespace Barotrauma.Networking
{ {
CanBeFocused = true CanBeFocused = true
}; };
textBlock.RectTransform.MinSize = new Point( textBlock.RectTransform.MinSize = new Point(0, (int)textBlock.Font.MeasureString(textBlock.Text.SanitizedValue).Y);
(int)textBlock.Font.MeasureString(textBlock.Text.SanitizedValue).X, 0);
var removeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.4f), topArea.RectTransform), var removeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.4f), topArea.RectTransform, Anchor.CenterRight),
TextManager.Get("BanListRemove"), style: "GUIButtonSmall") TextManager.Get("BanListRemove"), style: "GUIButtonSmall")
{ {
IgnoreLayoutGroups = true,
UserData = bannedPlayer, UserData = bannedPlayer,
OnClicked = RemoveBan OnClicked = RemoveBan,
Enabled = false
}; };
topArea.RectTransform.MinSize = new Point(0, (int)(removeButton.Rect.Height * 1.25f)); removeButton.OnAddedToGUIUpdateList += (component) =>
{
topArea.ForceLayoutRecalculation(); component.Enabled = GameMain.Client?.HasPermission(ClientPermissions.Unban) ?? false;
};
topArea.RectTransform.MinSize = new Point(0, Math.Max(textBlock.RectTransform.MinSize.Y, removeButton.RectTransform.MinSize.Y));
topArea.RectTransform.IsFixedSize = true;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedPlayerFrame.RectTransform),
bannedPlayer.ExpirationTime.TryUnwrap(out var expirationTime) bannedPlayer.ExpirationTime.TryUnwrap(out var expirationTime)
@@ -14,6 +14,17 @@ namespace Barotrauma.Networking
set; set;
} }
// Players can boost per-user volume by 200%
public const float MaxVoiceChatBoost = 2.0f;
private float voiceVolume = 1f;
public float VoiceVolume
{
get => voiceVolume;
set => voiceVolume = Math.Clamp(value, 0f, MaxVoiceChatBoost);
}
private SoundChannel radioNoiseChannel; private SoundChannel radioNoiseChannel;
private float radioNoise; private float radioNoise;
@@ -23,7 +34,6 @@ namespace Barotrauma.Networking
set { radioNoise = MathHelper.Clamp(value, 0.0f, 1.0f); } set { radioNoise = MathHelper.Clamp(value, 0.0f, 1.0f); }
} }
private bool mutedLocally; private bool mutedLocally;
public bool MutedLocally public bool MutedLocally
{ {
@@ -86,6 +96,17 @@ namespace Barotrauma.Networking
float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition); float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition);
gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist); gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
} }
if (!VoipSound.UsingRadio)
{
//emulate the "garbling" of the text chat
//this in a sense means the volume diminishes exponentially when close to the maximum range of the sound
//(diminished by both the garbling and the distance attenuation)
//which is good, because we want the voice chat to become unintelligible close to the max range,
//and we need to heavily reduce the volume to do that (otherwise it's just quiet, but still intelligible)
float garbleAmount = ChatMessage.GetGarbleAmount(Character.Controlled, character, ChatMessage.SpeakRangeVOIP);
gain *= 1.0f - garbleAmount;
}
if (RadioNoise > 0.0f) if (RadioNoise > 0.0f)
{ {
noiseGain = gain * RadioNoise; noiseGain = gain * RadioNoise;
@@ -21,7 +21,12 @@ namespace Barotrauma
DebugConsole.Log($"Received entity removal message for \"{entity}\"."); DebugConsole.Log($"Received entity removal message for \"{entity}\".");
if (entity is Item item && item.Container?.GetComponent<Deconstructor>() != null) if (entity is Item item && item.Container?.GetComponent<Deconstructor>() != null)
{ {
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + item.Prefab.Identifier); if (item.Prefab.ContentPackage == ContentPackageManager.VanillaCorePackage &&
/* we don't need info of every deconstructed item, we can get a good sample size just by logging 5% */
Rand.Range(0.0f, 1.0f) < 0.05f)
{
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + item.Prefab.Identifier);
}
} }
entity.Remove(); entity.Remove();
} }
@@ -45,7 +50,12 @@ namespace Barotrauma
{ {
if (newItem.Container?.GetComponent<Fabricator>() != null) if (newItem.Container?.GetComponent<Fabricator>() != null)
{ {
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier); if (newItem.Prefab.ContentPackage == ContentPackageManager.VanillaCorePackage &&
/* we don't need info of every fabricated item, we can get a good sample size just by logging 5% */
Rand.Range(0.0f, 1.0f) < 0.05f)
{
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier);
}
} }
receivedEvents.Add((newItem, false)); receivedEvents.Add((newItem, false));
} }
@@ -89,7 +89,7 @@ namespace Barotrauma.Networking
public readonly List<SubmarineInfo> ServerSubmarines = new List<SubmarineInfo>(); public readonly List<SubmarineInfo> ServerSubmarines = new List<SubmarineInfo>();
public string ServerName { get; private set; } public string ServerName => ServerSettings.ServerName;
private bool canStart; private bool canStart;
@@ -270,11 +270,11 @@ namespace Barotrauma.Networking
otherClients = new List<Client>(); otherClients = new List<Client>();
ServerSettings = new ServerSettings(this, "Server", 0, 0, 0, false, false); ServerSettings = new ServerSettings(this, serverName, 0, 0, 0, false, false);
Voting = new Voting(); Voting = new Voting();
serverEndpoints = endpoints; serverEndpoints = endpoints;
InitiateServerJoin(serverName); InitiateServerJoin();
//ServerLog = new ServerLog(""); //ServerLog = new ServerLog("");
@@ -289,7 +289,7 @@ namespace Barotrauma.Networking
return serverInfo; return serverInfo;
} }
private void InitiateServerJoin(string hostName) private void InitiateServerJoin()
{ {
LastClientListUpdateID = 0; LastClientListUpdateID = 0;
@@ -306,8 +306,6 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.ChatInput.Enabled = false; GameMain.NetLobbyScreen.ChatInput.Enabled = false;
} }
ServerName = hostName;
myCharacter = Character.Controlled; myCharacter = Character.Controlled;
ChatMessage.LastID = 0; ChatMessage.LastID = 0;
@@ -318,9 +316,8 @@ namespace Barotrauma.Networking
CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo"); CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo");
} }
public void SetLobbyPublic(bool isPublic) public static void SetLobbyPublic(bool isPublic)
{ {
GameMain.NetLobbyScreen.SetPublic(isPublic);
SteamManager.SetLobbyPublic(isPublic); SteamManager.SetLobbyPublic(isPublic);
} }
@@ -723,7 +720,7 @@ namespace Barotrauma.Networking
if (readyToStart && !CoroutineManager.IsCoroutineRunning("WaitForStartRound")) if (readyToStart && !CoroutineManager.IsCoroutineRunning("WaitForStartRound"))
{ {
CoroutineManager.StartCoroutine(GameMain.NetLobbyScreen.WaitForStartRound(startButton: null), "WaitForStartRound"); CoroutineManager.StartCoroutine(NetLobbyScreen.WaitForStartRound(startButton: null), "WaitForStartRound");
} }
break; break;
case ServerPacketHeader.STARTGAME: case ServerPacketHeader.STARTGAME:
@@ -1096,7 +1093,7 @@ namespace Barotrauma.Networking
var prevContentPackages = ClientPeer.ServerContentPackages; var prevContentPackages = ClientPeer.ServerContentPackages;
//decrement lobby update ID to make sure we update the lobby when we reconnect //decrement lobby update ID to make sure we update the lobby when we reconnect
GameMain.NetLobbyScreen.LastUpdateID--; GameMain.NetLobbyScreen.LastUpdateID--;
InitiateServerJoin(ServerName); InitiateServerJoin();
if (ClientPeer != null) if (ClientPeer != null)
{ {
//restore the previous list of content packages so we can reconnect immediately without having to recheck that the packages match //restore the previous list of content packages so we can reconnect immediately without having to recheck that the packages match
@@ -1189,7 +1186,7 @@ namespace Barotrauma.Networking
{ {
if (!CoroutineManager.IsCoroutineRunning("WaitForStartingInfo")) if (!CoroutineManager.IsCoroutineRunning("WaitForStartingInfo"))
{ {
InitiateServerJoin(ServerName); InitiateServerJoin();
yield return new WaitForSeconds(5.0f); yield return new WaitForSeconds(5.0f);
} }
yield return new WaitForSeconds(0.5f); yield return new WaitForSeconds(0.5f);
@@ -1253,7 +1250,7 @@ namespace Barotrauma.Networking
if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) || if (!(this.permittedConsoleCommands.Any(c => !permittedConsoleCommands.Contains(c)) ||
permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c)))) permittedConsoleCommands.Any(c => !this.permittedConsoleCommands.Contains(c))))
{ {
if (newPermissions == permissions) return; if (newPermissions == permissions) { return; }
} }
bool refreshCampaignUI = permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) || bool refreshCampaignUI = permissions.HasFlag(ClientPermissions.ManageCampaign) != newPermissions.HasFlag(ClientPermissions.ManageCampaign) ||
@@ -1335,6 +1332,8 @@ namespace Barotrauma.Networking
} }
GameMain.NetLobbyScreen.RefreshEnabledElements(); GameMain.NetLobbyScreen.RefreshEnabledElements();
//close settings menu in case it was open
ServerSettings.Close();
OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, this.permittedConsoleCommands)); OnPermissionChanged.Invoke(new PermissionChangedEvent(permissions, this.permittedConsoleCommands));
} }
@@ -2025,18 +2024,15 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.LastUpdateID = updateID; GameMain.NetLobbyScreen.LastUpdateID = updateID;
ServerSettings.ServerLog.ServerName = ServerSettings.ServerName; ServerSettings.ServerLog.ServerName = ServerSettings.ServerName;
if (!GameMain.NetLobbyScreen.ServerName.Selected) { GameMain.NetLobbyScreen.ServerName.Text = ServerSettings.ServerName; }
if (!GameMain.NetLobbyScreen.ServerMessage.Selected) { GameMain.NetLobbyScreen.ServerMessage.Text = ServerSettings.ServerMessageText; }
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle; GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
if (!allowSubVoting) { GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, GameMain.NetLobbyScreen.SubList); } if (!allowSubVoting || GameMain.NetLobbyScreen.SelectedSub == null) { GameMain.NetLobbyScreen.TrySelectSub(selectSubName, selectSubHash, GameMain.NetLobbyScreen.SubList); }
GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox); GameMain.NetLobbyScreen.TrySelectSub(selectShuttleName, selectShuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
GameMain.NetLobbyScreen.SetTraitorProbability(traitorProbability); GameMain.NetLobbyScreen.SetTraitorProbability(traitorProbability);
GameMain.NetLobbyScreen.SetTraitorDangerLevel(traitorDangerLevel); GameMain.NetLobbyScreen.SetTraitorDangerLevel(traitorDangerLevel);
GameMain.NetLobbyScreen.SetMissionType(missionType); GameMain.NetLobbyScreen.SetMissionType(missionType);
GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SelectMode(modeIndex); GameMain.NetLobbyScreen.SelectMode(modeIndex);
if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign) if (isInitialUpdate && GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
@@ -2053,7 +2049,6 @@ namespace Barotrauma.Networking
} }
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount); GameMain.NetLobbyScreen.SetBotCount(botCount);
@@ -2574,12 +2569,17 @@ namespace Barotrauma.Networking
} }
public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null) public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null)
{
CreateEntityEvent(entity, extraData, requireControlledCharacter: true);
}
public void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData, bool requireControlledCharacter)
{ {
if (entity is not IClientSerializable clientSerializable) if (entity is not IClientSerializable clientSerializable)
{ {
throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}"); throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}");
} }
EntityEventManager.CreateEvent(clientSerializable, extraData); EntityEventManager.CreateEvent(clientSerializable, extraData, requireControlledCharacter);
} }
public bool HasPermission(ClientPermissions permission) public bool HasPermission(ClientPermissions permission)
@@ -2703,9 +2703,19 @@ namespace Barotrauma.Networking
public override void AddChatMessage(ChatMessage message) public override void AddChatMessage(ChatMessage message)
{ {
base.AddChatMessage(message);
if (string.IsNullOrEmpty(message.Text)) { return; } if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null && !message.Sender.IsDead)
{
if (message.Text.IsNullOrEmpty())
{
message.Sender.ShowTextlessSpeechBubble(2.0f, message.Color);
}
else
{
message.Sender.ShowSpeechBubble(message.Color, message.Text);
}
}
GameMain.NetLobbyScreen.NewChatMessage(message); GameMain.NetLobbyScreen.NewChatMessage(message);
chatBox.AddMessage(message); chatBox.AddMessage(message);
} }
@@ -2894,7 +2904,7 @@ namespace Barotrauma.Networking
ClientPeer.Send(msg, DeliveryMethod.Reliable); ClientPeer.Send(msg, DeliveryMethod.Reliable);
} }
public bool SpectateClicked(GUIButton button, object _) public bool JoinOnGoingClicked(GUIButton button, object _)
{ {
MultiPlayerCampaign campaign = MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ? GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
@@ -3283,17 +3293,36 @@ namespace Barotrauma.Networking
private void CreateSelectionRelatedButtons(Client client, GUIComponent frame) private void CreateSelectionRelatedButtons(Client client, GUIComponent frame)
{ {
var content = new GUIFrame(new RectTransform(new Vector2(1f, 1.0f - frame.RectTransform.RelativeSize.Y), frame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter), var content = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1.0f - frame.RectTransform.RelativeSize.Y), frame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter), childAnchor: Anchor.TopCenter);
style: null);
var mute = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform, Anchor.TopCenter), var mute = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform, Anchor.TopCenter),
TextManager.Get("Mute")) TextManager.Get("Mute"))
{ {
Selected = client.MutedLocally, Selected = client.MutedLocally,
OnSelected = (tickBox) => { client.MutedLocally = tickBox.Selected; return true; } OnSelected = (tickBox) => { client.MutedLocally = tickBox.Selected; return true; }
}; };
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), content.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomLeft) var volumeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.45f), content.RectTransform, Anchor.TopCenter), isHorizontal: false);
var volumeTextLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), volumeLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
var label = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), volumeTextLayout.RectTransform), TextManager.Get("VoiceChatVolume"));
var percentageText = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), volumeTextLayout.RectTransform), ToolBox.GetFormattedPercentage(client.VoiceVolume), textAlignment: Alignment.Right);
var volumeSlider = new GUIScrollBar(new RectTransform(new Vector2(1f, 0.5f), volumeLayout.RectTransform), barSize: 0.1f, style: "GUISlider")
{
Range = new Vector2(0f, 1f),
BarScroll = client.VoiceVolume / Client.MaxVoiceChatBoost,
OnMoved = (_, barScroll) =>
{
float newVolume = barScroll * Client.MaxVoiceChatBoost;
client.VoiceVolume = newVolume;
percentageText.Text = ToolBox.GetFormattedPercentage(newVolume);
return true;
}
};
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), content.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomLeft)
{ {
RelativeSpacing = 0.05f, RelativeSpacing = 0.05f,
Stretch = true Stretch = true
@@ -3327,7 +3356,7 @@ namespace Barotrauma.Networking
TextManager.Get("Ban"), style: "GUIButtonSmall") TextManager.Get("Ban"), style: "GUIButtonSmall")
{ {
UserData = client, UserData = client,
OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.BanPlayer(client); return false; } OnClicked = (btn, userdata) => { NetLobbyScreen.BanPlayer(client); return false; }
}; };
} }
if (HasPermission(ClientPermissions.Kick) && client.AllowKicking) if (HasPermission(ClientPermissions.Kick) && client.AllowKicking)
@@ -3336,7 +3365,7 @@ namespace Barotrauma.Networking
TextManager.Get("Kick"), style: "GUIButtonSmall") TextManager.Get("Kick"), style: "GUIButtonSmall")
{ {
UserData = client, UserData = client,
OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.KickPlayer(client); return false; } OnClicked = (btn, userdata) => { NetLobbyScreen.KickPlayer(client); return false; }
}; };
} }
else if (ServerSettings.AllowVoteKick && client.AllowKicking) else if (ServerSettings.AllowVoteKick && client.AllowKicking)
@@ -42,16 +42,17 @@ namespace Barotrauma.Networking
thisClient = client; thisClient = client;
} }
public void CreateEvent(IClientSerializable entity, NetEntityEvent.IData extraData = null) public void CreateEvent(IClientSerializable entity, NetEntityEvent.IData extraData = null, bool requireControlledCharacter = true)
{ {
if (GameMain.Client?.Character == null) { return; } if (GameMain.Client == null) { return; }
if (requireControlledCharacter && GameMain.Client.Character == null) { return; }
if (!ValidateEntity(entity)) { return; } if (!ValidateEntity(entity)) { return; }
var newEvent = new ClientEntityEvent( var newEvent = new ClientEntityEvent(
entity, entity,
eventId: (UInt16)(ID + 1), eventId: (UInt16)(ID + 1),
characterStateId: GameMain.Client.Character.LastNetworkUpdateID); characterStateId: GameMain.Client.Character?.LastNetworkUpdateID ?? Entity.NullEntityID);
if (extraData != null) { newEvent.SetData(extraData); } if (extraData != null) { newEvent.SetData(extraData); }
for (int i = events.Count - 1; i >= 0; i--) for (int i = events.Count - 1; i >= 0; i--)
@@ -18,6 +18,8 @@ namespace Barotrauma.Networking
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } = public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
ImmutableArray<ServerContentPackage>.Empty; ImmutableArray<ServerContentPackage>.Empty;
public bool AllowModDownloads { get; private set; } = true;
public readonly record struct Callbacks( public readonly record struct Callbacks(
Callbacks.MessageCallback OnMessageReceived, Callbacks.MessageCallback OnMessageReceived,
Callbacks.DisconnectCallback OnDisconnect, Callbacks.DisconnectCallback OnDisconnect,
@@ -151,6 +153,7 @@ namespace Barotrauma.Networking
if (!ContentPackageOrderReceived) if (!ContentPackageOrderReceived)
{ {
ServerContentPackages = orderPacket.ContentPackages; ServerContentPackages = orderPacket.ContentPackages;
AllowModDownloads = orderPacket.AllowModDownloads;
if (ServerContentPackages.Length == 0) if (ServerContentPackages.Length == 0)
{ {
string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty."; string errorMsg = "Error in ContentPackageOrder message: list of content packages enabled on the server was empty.";
@@ -51,20 +51,38 @@ namespace Barotrauma.Networking
{ {
if (Character.Controlled != null || (GameMain.GameSession is not { IsRunning: true })) { return; } if (Character.Controlled != null || (GameMain.GameSession is not { IsRunning: true })) { return; }
LocalizedString text = TextManager.Get("respawnquestionprompt"); LocalizedString text;
GUIMessageBox respawnPrompt;
if (SkillLossPercentageOnImmediateRespawn > 0)
{
// Respawn asap with extra skill loss?
text = TextManager.GetWithVariable("respawnquestionprompt", "[percentage]", ((int)Math.Round(SkillLossPercentageOnImmediateRespawn)).ToString());
respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), text,
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
{
UserData = "respawnquestionprompt"
};
}
else
{
// Respawn asap?
text = TextManager.Get("respawnquestionpromptnoloss");
respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), text,
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawnnoloss"), TextManager.Get("respawnquestionpromptwait") })
{
UserData = "respawnquestionprompt"
};
}
if (SkillLossPercentageOnDeath > 0) if (SkillLossPercentageOnDeath > 0)
{ {
// You have died... etc added BEFORE the above text
text = text =
TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)SkillLossPercentageOnDeath).ToString()) + TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)SkillLossPercentageOnDeath).ToString()) +
"\n\n" + text; "\n\n" + text;
}; };
var respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), text,
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
{
UserData = "respawnquestionprompt"
};
respawnPrompt.Buttons[0].OnClicked += (btn, userdata) => respawnPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{ {
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false); GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,924 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Networking
{
partial class ServerSettings : ISerializableEntity
{
//GUI stuff
private GUIFrame settingsFrame;
private readonly Dictionary<SettingsTab, GUIComponent> settingsTabs = new Dictionary<SettingsTab, GUIComponent>();
private readonly Dictionary<SettingsTab, GUIButton> tabButtons = new Dictionary<SettingsTab, GUIButton>();
private SettingsTab selectedTab;
//UI elements relating to karma, hidden when karma is disabled
private readonly List<GUIComponent> karmaElements = new List<GUIComponent>();
private GUIDropDown karmaPresetDD;
private GUIListBox karmaSettingsList;
private GUIComponent extraCargoPanel, monstersEnabledPanel;
private GUIButton extraCargoButton, monstersEnabledButton;
enum SettingsTab
{
ServerIdentity,
General,
Antigriefing,
Banlist
}
public void AssignGUIComponent(string propertyName, GUIComponent component)
{
GetPropertyData(propertyName).AssignGUIComponent(component);
}
public void AddToGUIUpdateList()
{
if (GUI.DisableHUD) { return; }
settingsFrame?.AddToGUIUpdateList();
}
private void CreateSettingsFrame()
{
foreach (NetPropertyData prop in netProperties.Values)
{
prop.TempValue = prop.Value;
}
//background frame
settingsFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, settingsFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
new GUIButton(new RectTransform(Vector2.One, settingsFrame.RectTransform), "", style: null).OnClicked += (btn, userData) =>
{
if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) { ToggleSettingsFrame(btn, userData); }
return true;
};
new GUIButton(new RectTransform(Vector2.One, settingsFrame.RectTransform), "", style: null)
{
OnClicked = ToggleSettingsFrame
};
//center frames
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.85f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) });
GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(innerFrame.Rect.Size - new Point(GUI.IntScale(20)), innerFrame.RectTransform, Anchor.Center),
childAnchor: Anchor.TopCenter)
{
Stretch = true,
RelativeSpacing = 0.02f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), TextManager.Get("serversettingsbutton"), font: GUIStyle.LargeFont);
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), paddedFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.01f
};
var tabContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), paddedFrame.RectTransform), style: "InnerFrame");
//tabs
var settingsTabTypes = Enum.GetValues(typeof(SettingsTab)).Cast<SettingsTab>();
foreach (var settingsTab in settingsTabTypes)
{
settingsTabs[settingsTab] = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tabContent.RectTransform, Anchor.Center));
tabButtons[settingsTab] = new GUIButton(new RectTransform(new Vector2(0.2f, 1.2f), buttonArea.RectTransform), TextManager.Get($"ServerSettings{settingsTab}Tab"), style: "GUITabButton")
{
UserData = settingsTab,
OnClicked = SelectSettingsTab
};
}
GUITextBlock.AutoScaleAndNormalize(tabButtons.Values.Select(b => b.TextBlock));
SelectSettingsTab(tabButtons[0], 0);
tabButtons[SettingsTab.Banlist].Enabled =
GameMain.Client.HasPermission(Networking.ClientPermissions.Ban) ||
GameMain.Client.HasPermission(Networking.ClientPermissions.Unban);
//"Close"
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.05f), paddedFrame.RectTransform), style: null);
var closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Close"))
{
OnClicked = ToggleSettingsFrame
};
CreateServerIdentityTab(settingsTabs[SettingsTab.ServerIdentity]);
CreateGeneralTab(settingsTabs[SettingsTab.General]);
CreateAntigriefingTab(settingsTabs[SettingsTab.Antigriefing]);
CreateBanlistTab(settingsTabs[SettingsTab.Banlist]);
if (GameMain.Client == null ||
!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings))
{
//block all settings if the client doesn't have permission to edit them
foreach (var settingsTab in settingsTabs)
{
SetElementInteractability(settingsTab.Value, false);
}
}
//keep these enabled, so clients can open the panels and see what's enabled even if they can't edit them
extraCargoButton.Enabled = monstersEnabledButton.Enabled = true;
}
private void SetElementInteractability(GUIComponent parent, bool interactable)
{
foreach (var child in parent.GetAllChildren<GUIComponent>())
{
child.Enabled = interactable;
//make the disabled color slightly less dim (these should be readable, despite being non-interactable)
child.DisabledColor = new Color(child.Color, child.Color.A / 255.0f * 0.8f);
if (child is GUITextBlock textBlock)
{
textBlock.DisabledTextColor = new Color(textBlock.TextColor, textBlock.TextColor.A / 255.0f * 0.8f);
}
}
}
private void CreateServerIdentityTab(GUIComponent parent)
{
//changing server visibility on the fly is not supported in dedicated servers
if (GameMain.Client?.ClientPeer is not LidgrenClientPeer)
{
var isPublic = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform),
TextManager.Get("publicserver"))
{
ToolTip = TextManager.Get("publicservertooltip")
};
AssignGUIComponent(nameof(IsPublic), isPublic);
}
var serverNameLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), TextManager.Get("ServerName"));
var serverNameBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), serverNameLabel.RectTransform, Anchor.CenterRight),
GameMain.Client.ServerSettings.ServerName)
{
OverflowClip = true,
MaxTextLength = NetConfig.ServerNameMaxLength
};
serverNameBox.OnDeselected += (textBox, key) =>
{
if (textBox.Text.IsNullOrWhiteSpace())
{
textBox.Flash(GUIStyle.Red);
if (GameMain.Client != null)
{
textBox.Text = GameMain.Client.ServerSettings.ServerName;
}
}
GameMain.Client?.ServerSettings.ClientAdminWrite(NetFlags.Properties);
};
AssignGUIComponent(nameof(ServerName), serverNameBox);
// server message *************************************************************************
var motdHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), TextManager.Get("ServerMOTD"));
var motdCharacterCount = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), motdHeader.RectTransform, Anchor.CenterRight), string.Empty, textAlignment: Alignment.CenterRight);
var serverMessageContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.2f), parent.RectTransform))
{
Visible = true
};
var serverMessageBox = new GUITextBox(new RectTransform(Vector2.One, serverMessageContainer.Content.RectTransform),
style: "GUITextBoxNoBorder", wrap: true, textAlignment: Alignment.TopLeft)
{
MaxTextLength = NetConfig.ServerMessageMaxLength
};
var serverMessageHint = new GUITextBlock(new RectTransform(Vector2.One, serverMessageBox.RectTransform),
textColor: Color.DarkGray * 0.6f, textAlignment: Alignment.TopLeft, font: GUIStyle.Font, text: TextManager.Get("ClickToWriteServerMessage"));
AssignGUIComponent(nameof(ServerMessageText), serverMessageBox);
void updateServerMessageScrollBasedOnCaret()
{
float caretY = serverMessageBox.CaretScreenPos.Y;
float bottomCaretExtent = serverMessageBox.Font.LineHeight * 1.5f;
float topCaretExtent = -serverMessageBox.Font.LineHeight * 0.5f;
if (caretY + bottomCaretExtent > serverMessageContainer.Rect.Bottom)
{
serverMessageContainer.ScrollBar.BarScroll
= (caretY - serverMessageBox.Rect.Top - serverMessageContainer.Rect.Height + bottomCaretExtent)
/ (serverMessageBox.Rect.Height - serverMessageContainer.Rect.Height);
}
else if (caretY + topCaretExtent < serverMessageContainer.Rect.Top)
{
serverMessageContainer.ScrollBar.BarScroll
= (caretY - serverMessageBox.Rect.Top + topCaretExtent)
/ (serverMessageBox.Rect.Height - serverMessageContainer.Rect.Height);
}
}
serverMessageBox.OnSelected += (textBox, key) =>
{
serverMessageHint.Visible = false;
updateServerMessageScrollBasedOnCaret();
};
serverMessageBox.OnTextChanged += (textBox, text) =>
{
serverMessageHint.Visible = !textBox.Selected && !textBox.Readonly && string.IsNullOrWhiteSpace(textBox.Text);
RefreshServerInfoSize();
return true;
};
serverMessageBox.RectTransform.SizeChanged += RefreshServerInfoSize;
motdCharacterCount.TextGetter += () => { return serverMessageBox.Text.Length + " / " + NetConfig.ServerMessageMaxLength; };
void RefreshServerInfoSize()
{
serverMessageHint.Visible = !serverMessageBox.Selected && !serverMessageBox.Readonly && string.IsNullOrWhiteSpace(serverMessageBox.Text);
Vector2 textSize = serverMessageBox.Font.MeasureString(serverMessageBox.WrappedText);
serverMessageBox.RectTransform.NonScaledSize = new Point(serverMessageBox.RectTransform.NonScaledSize.X, Math.Max(serverMessageContainer.Content.Rect.Height, (int)textSize.Y + 10));
serverMessageContainer.UpdateScrollBarSize();
}
serverMessageBox.OnEnterPressed += (textBox, text) =>
{
string str = textBox.Text;
int caretIndex = textBox.CaretIndex;
textBox.Text = $"{str[..caretIndex]}\n{str[caretIndex..]}";
textBox.CaretIndex = caretIndex + 1;
return true;
};
serverMessageBox.OnDeselected += (textBox, key) =>
{
if (!textBox.Readonly)
{
GameMain.Client?.ServerSettings?.ClientAdminWrite(NetFlags.Properties);
}
serverMessageHint.Visible = !textBox.Readonly && string.IsNullOrWhiteSpace(textBox.Text);
};
serverMessageBox.OnKeyHit += (sender, key) => updateServerMessageScrollBasedOnCaret();
// *************************************************************************
var playStyleLayoutLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform),
TextManager.Get("ServerSettingsPlayStyle"));
var playStyleSelection = new GUISelectionCarousel<PlayStyle>(new RectTransform(new Vector2(0.5f, 1.0f), playStyleLayoutLabel.RectTransform, Anchor.CenterRight));
foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
{
playStyleSelection.AddElement(playStyle, TextManager.Get("servertag." + playStyle), TextManager.Get("servertagdescription." + playStyle));
}
AssignGUIComponent(nameof(PlayStyle), playStyleSelection);
var passwordLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), TextManager.Get("Password"));
new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), passwordLabel.RectTransform, Anchor.CenterRight),
TextManager.Get("ServerSettingsSetPassword"), style: "GUIButtonSmall")
{
OnClicked = (btn, userdata) => { CreateChangePasswordPrompt(); return true; }
};
var wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), parent.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"));
AssignGUIComponent(nameof(BanAfterWrongPassword), wrongPasswordBanBox);
var allowedPasswordRetries = NetLobbyScreen.CreateLabeledNumberInput(parent, "ServerSettingsPasswordRetriesBeforeBan", 0, 10);
AssignGUIComponent(nameof(MaxPasswordRetriesBeforeBan), allowedPasswordRetries);
var maxPlayers = NetLobbyScreen.CreateLabeledNumberInput(parent, "MaxPlayers", 0, NetConfig.MaxPlayers);
AssignGUIComponent(nameof(MaxPlayers), maxPlayers);
// Language
var languageLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform), TextManager.Get("Language"));
var languageDD = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), languageLabel.RectTransform, Anchor.CenterRight));
foreach (var language in ServerLanguageOptions.Options)
{
languageDD.AddItem(language.Label, language.Identifier);
}
languageLabel.InheritTotalChildrenMinHeight();
AssignGUIComponent(nameof(Language), languageDD);
}
private static void CreateChangePasswordPrompt()
{
var passwordMsgBox = new GUIMessageBox(
TextManager.Get("ServerSettingsSetPassword"),
"", new LocalizedString[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), passwordMsgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform))
{
Censor = true
};
passwordMsgBox.Content.Recalculate();
passwordMsgBox.Content.InheritTotalChildrenHeight();
passwordMsgBox.Content.Parent.RectTransform.MinSize = new Point(0, (int)(passwordMsgBox.Content.RectTransform.MinSize.Y / passwordMsgBox.Content.RectTransform.RelativeSize.Y));
var okButton = passwordMsgBox.Buttons[0];
okButton.OnClicked += (_, __) =>
{
DebugConsole.ExecuteCommand($"setpassword \"{passwordBox.Text}\"");
return true;
};
okButton.OnClicked += passwordMsgBox.Close;
var cancelButton = passwordMsgBox.Buttons[1];
cancelButton.OnClicked = (_, __) =>
{
passwordMsgBox?.Close();
passwordMsgBox = null;
return true;
};
passwordBox.OnEnterPressed += (_, __) =>
{
okButton.OnClicked.Invoke(okButton, okButton.UserData);
return true;
};
passwordBox.Select();
}
private void CreateGeneralTab(GUIComponent parent)
{
var listBox = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform), style: "GUIListBoxNoBorder")
{
AutoHideScrollBar = true,
CurrentSelectMode = GUIListBox.SelectMode.None
};
NetLobbyScreen.CreateSubHeader("serversettingscategory.roundmanagement", listBox.Content);
var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ServerSettingsEndRoundVoting"));
AssignGUIComponent(nameof(AllowEndVoting), endVoteBox);
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: string.Empty, valueLabelTag: "ServerSettingsEndRoundVotesRequired", tooltipTag: string.Empty, out var slider, out var sliderLabel);
LocalizedString endRoundLabel = sliderLabel.Text;
slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f);
AssignGUIComponent(nameof(EndVoteRequiredRatio), slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + " " + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
return true;
};
slider.OnMoved(slider, slider.BarScroll);
//***********************************************
// Sub Selection
var subSelectionLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ServerSettingsSubSelection"));
var subSelection = new GUISelectionCarousel<SelectionMode>(new RectTransform(new Vector2(0.5f, 1.0f), subSelectionLabel.RectTransform, Anchor.CenterRight));
foreach (SelectionMode selectionMode in Enum.GetValues(typeof(SelectionMode)))
{
subSelection.AddElement(selectionMode, TextManager.Get(selectionMode.ToString()));
}
AssignGUIComponent(nameof(SubSelectionMode), subSelection);
// Mode Selection
var gameModeSelectionLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ServerSettingsModeSelection"));
var gameModeSelection = new GUISelectionCarousel<SelectionMode>(new RectTransform(new Vector2(0.5f, 1.0f), gameModeSelectionLabel.RectTransform, Anchor.CenterRight));
foreach (SelectionMode selectionMode in Enum.GetValues(typeof(SelectionMode)))
{
gameModeSelection.AddElement(selectionMode, TextManager.Get(selectionMode.ToString()));
}
AssignGUIComponent(nameof(ModeSelectionMode), gameModeSelection);
//***********************************************
LocalizedString autoRestartDelayLabel = TextManager.Get("ServerSettingsAutoRestartDelay") + " ";
var autorestartBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("AutoRestart"));
AssignGUIComponent(nameof(AutoRestart), autorestartBox);
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: string.Empty, valueLabelTag: string.Empty, tooltipTag: string.Empty,
out var startIntervalSlider, out var startIntervalSliderLabel, range: new Vector2(10.0f, 300.0f));
startIntervalSlider.StepValue = 10.0f;
startIntervalSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
GUITextBlock text = scrollBar.UserData as GUITextBlock;
text.Text = autoRestartDelayLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
return true;
};
AssignGUIComponent(nameof(AutoRestartInterval), startIntervalSlider);
startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll);
//***********************************************
var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ServerSettingsStartWhenClientsReady"));
AssignGUIComponent(nameof(StartWhenClientsReady), startWhenClientsReady);
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: string.Empty, valueLabelTag: "ServerSettingsStartWhenClientsReadyRatio", tooltipTag: string.Empty,
out slider, out sliderLabel);
LocalizedString clientsReadyRequiredLabel = sliderLabel.Text;
slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString());
return true;
};
AssignGUIComponent(nameof(StartWhenClientsReadyRatio), slider);
slider.OnMoved(slider, slider.BarScroll);
var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed"));
AssignGUIComponent(nameof(RandomizeSeed), randomizeLevelBox);
//***********************************************
NetLobbyScreen.CreateSubHeader("serversettingsroundstab", listBox.Content);
var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ServerSettingsVoiceChatEnabled"));
AssignGUIComponent(nameof(VoiceChatEnabled), voiceChatEnabled);
var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowSpectating"));
AssignGUIComponent(nameof(AllowSpectating), allowSpecBox);
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("LosEffect"));
var losModeSelection = new GUISelectionCarousel<LosMode>(new RectTransform(new Vector2(0.5f, 0.6f), losModeLabel.RectTransform, Anchor.CenterRight));
foreach (var losMode in Enum.GetValues(typeof(LosMode)).Cast<LosMode>())
{
losModeSelection.AddElement(losMode, TextManager.Get($"LosMode{losMode}"), TextManager.Get($"LosMode{losMode}.tooltip"));
}
AssignGUIComponent(nameof(LosMode), losModeSelection);
var healthBarModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ShowEnemyHealthBars"));
var healthBarModeSelection = new GUISelectionCarousel<EnemyHealthBarMode>(new RectTransform(new Vector2(0.5f, 0.6f), healthBarModeLabel.RectTransform, Anchor.CenterRight));
foreach (var healthBarMode in Enum.GetValues(typeof(EnemyHealthBarMode)).Cast<EnemyHealthBarMode>())
{
healthBarModeSelection.AddElement(healthBarMode, TextManager.Get($"ShowEnemyHealthBars.{healthBarMode}"));
}
AssignGUIComponent(nameof(ShowEnemyHealthBars), healthBarModeSelection);
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
AssignGUIComponent(nameof(DisableBotConversations), disableBotConversationsBox);
//***********************************************
NetLobbyScreen.CreateSubHeader("serversettingscategory.misc", listBox.Content);
var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsShareSubFiles"));
AssignGUIComponent(nameof(AllowFileTransfers), shareSubsBox);
var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsSaveLogs"))
{
OnSelected = (GUITickBox) =>
{
//TODO: fix?
//showLogButton.Visible = SaveServerLogs;
return true;
}
};
AssignGUIComponent(nameof(SaveServerLogs), saveLogsBox);
//--------------------------------------------------------------------------------
// game settings
//--------------------------------------------------------------------------------
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
const string MonstersEnabledUserdata = "monstersenabled";
const string ExtraCargoUserdata = "extracargo";
monstersEnabledButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonHolder.RectTransform),
TextManager.Get("ServerSettingsMonsterSpawns"), style: "GUIButtonSmall")
{
Enabled = !GameMain.NetworkMember.GameStarted
};
monstersEnabledPanel = CreateMonstersEnabledPanel();
monstersEnabledButton.UserData = MonstersEnabledUserdata;
monstersEnabledButton.OnClicked = ExtraSettingsButtonClicked;
extraCargoButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonHolder.RectTransform),
TextManager.Get("ServerSettingsAdditionalCargo"), style: "GUIButtonSmall")
{
Enabled = !GameMain.NetworkMember.GameStarted
};
extraCargoPanel = CreateExtraCargoPanel();
extraCargoButton.UserData = ExtraCargoUserdata;
extraCargoButton.OnClicked = ExtraSettingsButtonClicked;
GUITextBlock.AutoScaleAndNormalize(buttonHolder.Children.Select(c => ((GUIButton)c).TextBlock));
bool ExtraSettingsButtonClicked(GUIButton button, object obj)
{
//the extra settings buttons (monsters enabled, cargo) hold a reference to the panel they're supposed to toggle
GUIComponent panel;
switch (obj as string)
{
case MonstersEnabledUserdata:
panel = monstersEnabledPanel;
break;
case ExtraCargoUserdata:
panel = extraCargoPanel;
break;
default:
throw new Exception("Unrecognized extra settings button");
}
if (GameMain.NetworkMember.GameStarted)
{
panel.Visible = false;
button.Enabled = false;
return true;
}
panel.Visible = !panel.Visible;
return true;
}
}
private GUIComponent CreateMonstersEnabledPanel()
{
var monsterFrame = new GUIListBox(new RectTransform(new Vector2(0.5f, 0.7f), settingsTabs[SettingsTab.General].RectTransform, Anchor.BottomLeft, Pivot.BottomRight))
{
Visible = false,
IgnoreLayoutGroups = true
};
InitMonstersEnabled();
List<Identifier> monsterNames = MonsterEnabled.Keys.ToList();
tempMonsterEnabled = new Dictionary<Identifier, bool>(MonsterEnabled);
foreach (Identifier s in monsterNames)
{
LocalizedString translatedLabel = TextManager.Get($"Character.{s}").Fallback(s.Value);
var monsterEnabledBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), monsterFrame.Content.RectTransform) { MinSize = new Point(0, 25) },
label: translatedLabel)
{
Selected = tempMonsterEnabled[s],
OnSelected = (GUITickBox tb) =>
{
tempMonsterEnabled[s] = tb.Selected;
return true;
}
};
}
monsterFrame.Content.RectTransform.SortChildren((c1, c2) =>
{
var name1 = (c1.GUIComponent as GUITickBox)?.Text ?? string.Empty;
var name2 = (c2.GUIComponent as GUITickBox)?.Text ?? string.Empty;
return name1.CompareTo(name2);
});
if (GameMain.Client == null ||
!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings))
{
SetElementInteractability(monsterFrame.Content, false);
}
return monsterFrame;
}
private GUIComponent CreateExtraCargoPanel()
{
var cargoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), settingsTabs[SettingsTab.General].RectTransform, Anchor.BottomRight, Pivot.BottomLeft))
{
Visible = false,
IgnoreLayoutGroups = true
};
var cargoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), cargoFrame.RectTransform, Anchor.Center))
{
Stretch = true
};
var filterText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), cargoContent.RectTransform), TextManager.Get("serverlog.filter"), font: GUIStyle.SubHeadingFont);
var entityFilterBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), filterText.RectTransform, Anchor.CenterRight), font: GUIStyle.Font, createClearButton: true);
filterText.RectTransform.MinSize = new Point(0, entityFilterBox.RectTransform.MinSize.Y);
var cargoList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), cargoContent.RectTransform));
entityFilterBox.OnTextChanged += (textBox, text) =>
{
foreach (var child in cargoList.Content.Children)
{
if (child.UserData is not ItemPrefab itemPrefab) { continue; }
child.Visible = string.IsNullOrEmpty(text) || itemPrefab.Name.Contains(text, StringComparison.OrdinalIgnoreCase);
}
return true;
};
foreach (ItemPrefab ip in ItemPrefab.Prefabs.OrderBy(ip => ip.Name))
{
if (ip.AllowAsExtraCargo.HasValue)
{
if (!ip.AllowAsExtraCargo.Value) { continue; }
}
else
{
if (!ip.CanBeBought) { continue; }
}
var itemFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), cargoList.Content.RectTransform) { MinSize = new Point(0, 30) }, isHorizontal: true)
{
Stretch = true,
UserData = ip,
RelativeSpacing = 0.05f
};
if (ip.InventoryIcon != null || ip.Sprite != null)
{
GUIImage img = new GUIImage(new RectTransform(new Point(itemFrame.Rect.Height), itemFrame.RectTransform),
ip.InventoryIcon ?? ip.Sprite, scaleToFit: true)
{
CanBeFocused = false
};
img.Color = img.Sprite == ip.InventoryIcon ? ip.InventoryIconColor : ip.SpriteColor;
}
new GUITextBlock(new RectTransform(new Vector2(0.75f, 1.0f), itemFrame.RectTransform),
ip.Name, font: GUIStyle.SmallFont)
{
Wrap = true,
CanBeFocused = false
};
ExtraCargo.TryGetValue(ip, out int cargoVal);
var amountInput = new GUINumberInput(new RectTransform(new Vector2(0.35f, 1.0f), itemFrame.RectTransform),
NumberType.Int, textAlignment: Alignment.CenterLeft)
{
MinValueInt = 0,
MaxValueInt = MaxExtraCargoItemsOfType,
IntValue = cargoVal
};
amountInput.OnValueChanged += (numberInput) =>
{
if (ExtraCargo.ContainsKey(ip))
{
ExtraCargo[ip] = numberInput.IntValue;
if (numberInput.IntValue <= 0) { ExtraCargo.Remove(ip); }
}
else if (ExtraCargo.Keys.Count < MaxExtraCargoItemTypes)
{
ExtraCargo.Add(ip, numberInput.IntValue);
}
numberInput.IntValue = ExtraCargo.ContainsKey(ip) ? ExtraCargo[ip] : 0;
CoroutineManager.Invoke(() =>
{
foreach (var child in cargoList.Content.GetAllChildren())
{
if (child.GetChild<GUINumberInput>() is GUINumberInput otherNumberInput)
{
otherNumberInput.PlusButton.Enabled = ExtraCargo.Keys.Count < MaxExtraCargoItemTypes && otherNumberInput.IntValue < otherNumberInput.MaxValueInt;
}
}
}, 0.0f);
};
}
if (GameMain.Client == null ||
!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings))
{
SetElementInteractability(cargoList.Content, false);
}
return cargoFrame;
}
private void CreateAntigriefingTab(GUIComponent parent)
{
var listBox = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform), style: "GUIListBoxNoBorder")
{
AutoHideScrollBar = true,
CurrentSelectMode = GUIListBox.SelectMode.None
};
//--------------------------------------------------------------------------------
// antigriefing
//--------------------------------------------------------------------------------
var tickBoxContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), listBox.Content.RectTransform))
{
AutoHideScrollBar = true,
UseGridLayout = true
};
tickBoxContainer.Padding *= 2.0f;
var allowFriendlyFire = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowFriendlyFire"));
AssignGUIComponent(nameof(AllowFriendlyFire), allowFriendlyFire);
var killableNPCs = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsKillableNPCs"));
AssignGUIComponent(nameof(KillableNPCs), killableNPCs);
var destructibleOutposts = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsDestructibleOutposts"));
AssignGUIComponent(nameof(DestructibleOutposts), destructibleOutposts);
var lockAllDefaultWires = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsLockAllDefaultWires"));
AssignGUIComponent(nameof(LockAllDefaultWires), lockAllDefaultWires);
var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowRewiring"));
AssignGUIComponent(nameof(AllowRewiring), allowRewiring);
var allowWifiChatter = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowWifiChat"));
AssignGUIComponent(nameof(AllowLinkingWifiToChat), allowWifiChatter);
var allowDisguises = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowDisguises"));
AssignGUIComponent(nameof(AllowDisguises), allowDisguises);
var allowImmediateItemDeliveryBox = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsImmediateItemDelivery"));
AssignGUIComponent(nameof(AllowImmediateItemDelivery), allowImmediateItemDeliveryBox);
GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock));
tickBoxContainer.RectTransform.MinSize = new Point(0, (int)(tickBoxContainer.Content.Children.First().Rect.Height * 2.0f + tickBoxContainer.Padding.Y + tickBoxContainer.Padding.W));
tickBoxContainer.RectTransform.MinSize = new Point(0, (int)(tickBoxContainer.Content.Children.First().Rect.Height * 2.0f + tickBoxContainer.Padding.Y + tickBoxContainer.Padding.W));
var voteKickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("ServerSettingsAllowVoteKick"));
AssignGUIComponent(nameof(AllowVoteKick), voteKickBox);
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: string.Empty, valueLabelTag: "ServerSettingsKickVotesRequired", tooltipTag: string.Empty, out var slider, out var sliderLabel);
LocalizedString votesRequiredLabel = sliderLabel.Text + " ";
slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = votesRequiredLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
return true;
};
AssignGUIComponent(nameof(KickVoteRequiredRatio), slider);
slider.OnMoved(slider, slider.BarScroll);
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: string.Empty, valueLabelTag: "ServerSettingsAutobanTime", tooltipTag: "ServerSettingsAutobanTime.Tooltip", out slider, out sliderLabel);
LocalizedString autobanLabel = sliderLabel.Text + " ";
slider.Range = new Vector2(0.0f, MaxAutoBanTime);
slider.StepValue = 60.0f * 15.0f; //15 minutes
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
((GUITextBlock)scrollBar.UserData).Text = autobanLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
return true;
};
AssignGUIComponent(nameof(AutoBanTime), slider);
slider.OnMoved(slider, slider.BarScroll);
var maximumTransferAmount = NetLobbyScreen.CreateLabeledNumberInput(listBox.Content, "serversettingsmaximumtransferrequest", 0, CampaignMode.MaxMoney, "serversettingsmaximumtransferrequesttooltip");
AssignGUIComponent(nameof(MaximumMoneyTransferRequest), maximumTransferAmount);
var lootedMoneyDestination = NetLobbyScreen.CreateLabeledDropdown(listBox.Content, "serversettingslootedmoneydestination", numElements: 2, "serversettingslootedmoneydestinationtooltip");
lootedMoneyDestination.AddItem(TextManager.Get("lootedmoneydestination.bank"), LootedMoneyDestination.Bank);
lootedMoneyDestination.AddItem(TextManager.Get("lootedmoneydestination.wallet"), LootedMoneyDestination.Wallet);
AssignGUIComponent(nameof(LootedMoneyDestination), lootedMoneyDestination);
var enableDosProtection = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.0f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsEnableDoSProtection"))
{
ToolTip = TextManager.Get("ServerSettingsEnableDoSProtectionTooltip")
};
AssignGUIComponent(nameof(EnableDoSProtection), enableDosProtection);
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: string.Empty, valueLabelTag: "ServerSettingsMaxPacketAmount", tooltipTag: string.Empty, out GUIScrollBar maxPacketSlider, out GUITextBlock maxPacketSliderLabel);
LocalizedString maxPacketCountLabel = maxPacketSliderLabel.Text;
maxPacketSlider.Step = 0.001f;
maxPacketSlider.Range = new Vector2(PacketLimitMin, PacketLimitMax);
maxPacketSlider.ToolTip = packetAmountTooltip;
maxPacketSlider.OnMoved = (scrollBar, _) =>
{
GUITextBlock textBlock = (GUITextBlock)scrollBar.UserData;
int value = (int)MathF.Floor(scrollBar.BarScrollValue);
LocalizedString valueText = value > PacketLimitMin
? value.ToString()
: TextManager.Get("ServerSettingsNoLimit");
switch (value)
{
case <= PacketLimitMin:
textBlock.TextColor = GUIStyle.Green;
scrollBar.ToolTip = packetAmountTooltip;
break;
case < PacketLimitWarning:
textBlock.TextColor = GUIStyle.Red;
scrollBar.ToolTip = packetAmountTooltipWarning;
break;
default:
textBlock.TextColor = GUIStyle.TextColorNormal;
scrollBar.ToolTip = packetAmountTooltip;
break;
}
textBlock.Text = $"{maxPacketCountLabel} {valueText}";
return true;
};
AssignGUIComponent(nameof(MaxPacketAmount), maxPacketSlider);
maxPacketSlider.OnMoved(maxPacketSlider, maxPacketSlider.BarScroll);
// karma --------------------------------------------------------------------------
NetLobbyScreen.CreateSubHeader("Karma", listBox.Content, toolTipTag: "KarmaExplanation");
var karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsUseKarma"))
{
ToolTip = TextManager.Get("KarmaExplanation")
};
AssignGUIComponent(nameof(KarmaEnabled), karmaBox);
karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform));
foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys)
{
karmaPresetDD.AddItem(TextManager.Get("KarmaPreset." + karmaPreset), karmaPreset);
}
karmaElements.Add(karmaPresetDD);
var karmaSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), listBox.Content.RectTransform), style: null);
karmaElements.Add(karmaSettingsContainer);
karmaSettingsList = new GUIListBox(new RectTransform(Vector2.One, karmaSettingsContainer.RectTransform))
{
Spacing = (int)(8 * GUI.Scale)
};
karmaSettingsList.Padding *= 2.0f;
karmaPresetDD.SelectItem(KarmaPreset);
SetElementInteractability(karmaSettingsList.Content, !karmaBox.Selected || KarmaPreset != "custom");
GameMain.NetworkMember.KarmaManager.CreateSettingsFrame(karmaSettingsList.Content);
karmaPresetDD.OnSelected = (selected, obj) =>
{
string newKarmaPreset = obj as string;
if (newKarmaPreset == KarmaPreset) { return true; }
List<NetPropertyData> properties = netProperties.Values.ToList();
List<object> prevValues = new List<object>();
foreach (NetPropertyData prop in netProperties.Values)
{
prevValues.Add(prop.TempValue);
if (prop.GUIComponent != null) { prop.Value = prop.GUIComponentValue; }
}
if (KarmaPreset == "custom")
{
GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset();
GameMain.NetworkMember?.KarmaManager?.Save();
}
KarmaPreset = newKarmaPreset;
GameMain.NetworkMember.KarmaManager.SelectPreset(KarmaPreset);
karmaSettingsList.Content.ClearChildren();
GameMain.NetworkMember.KarmaManager.CreateSettingsFrame(karmaSettingsList.Content);
SetElementInteractability(karmaSettingsList.Content, !karmaBox.Selected || KarmaPreset != "custom");
for (int i = 0; i < netProperties.Count; i++)
{
properties[i].TempValue = prevValues[i];
}
return true;
};
AssignGUIComponent(nameof(KarmaPreset), karmaPresetDD);
karmaBox.OnSelected = (tb) =>
{
SetElementInteractability(karmaSettingsList.Content, !karmaBox.Selected || KarmaPreset != "custom");
karmaElements.ForEach(e => e.Visible = tb.Selected);
return true;
};
karmaElements.ForEach(e => e.Visible = KarmaEnabled);
listBox.Content.InheritTotalChildrenMinHeight();
}
private void CreateBanlistTab(GUIComponent parent)
{
BanList.CreateBanFrame(parent);
}
private bool SelectSettingsTab(GUIButton button, object obj)
{
selectedTab = (SettingsTab)obj;
foreach (var key in settingsTabs.Keys)
{
settingsTabs[key].Visible = key == selectedTab;
tabButtons[key].Selected = key == selectedTab;
}
return true;
}
public void Close()
{
if (KarmaPreset == "custom")
{
GameMain.NetworkMember?.KarmaManager?.SaveCustomPreset();
GameMain.NetworkMember?.KarmaManager?.Save();
}
ClientAdminWrite(NetFlags.Properties);
foreach (NetPropertyData prop in netProperties.Values)
{
prop.GUIComponent = null;
}
settingsFrame = null;
//give control of server settings back to elements in the lobby
GameMain.NetLobbyScreen.AssignComponentsToServerSettings();
}
public bool ToggleSettingsFrame(GUIButton button, object obj)
{
if (GameMain.NetworkMember == null) { return false; }
if (settingsFrame == null)
{
CreateSettingsFrame();
}
else
{
Close();
}
return false;
}
}
}
@@ -275,7 +275,7 @@ namespace Barotrauma.Networking
var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default; var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default;
if (GameMain.Client.Character.IsDead) { messageType = ChatMessageType.Dead; } if (GameMain.Client.Character.IsDead) { messageType = ChatMessageType.Dead; }
GameMain.Client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); GameMain.Client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
} }
//encode audio and enqueue it //encode audio and enqueue it
lock (buffers) lock (buffers)
@@ -106,7 +106,7 @@ namespace Barotrauma.Networking
if (client.VoipSound == null) if (client.VoipSound == null)
{ {
DebugConsole.Log("Recreating voipsound " + queueId); DebugConsole.Log("Recreating voipsound " + queueId);
client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue); client.VoipSound = new VoipSound(client, GameMain.SoundManager, client.VoipQueue);
} }
GameMain.SoundManager.ForceStreamUpdate(); GameMain.SoundManager.ForceStreamUpdate();
client.RadioNoise = 0.0f; client.RadioNoise = 0.0f;
@@ -122,12 +122,13 @@ namespace Barotrauma.Networking
ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) &&
senderRadio.CanReceive(recipientRadio) ? senderRadio.CanReceive(recipientRadio) ?
ChatMessageType.Radio : ChatMessageType.Default; ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters; client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
client.RadioNoise = 0.0f; client.RadioNoise = 0.0f;
if (messageType == ChatMessageType.Radio) if (messageType == ChatMessageType.Radio)
{ {
client.VoipSound.UsingRadio = true;
client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier); client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
if (distanceFactor > RangeNear && !spectating) if (distanceFactor > RangeNear && !spectating)
{ {
@@ -137,11 +138,12 @@ namespace Barotrauma.Networking
} }
else else
{ {
client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier); client.VoipSound.UsingRadio = false;
client.VoipSound.SetRange(ChatMessage.SpeakRangeVOIP * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRangeVOIP * speechImpedimentMultiplier * rangeMultiplier);
} }
client.VoipSound.UseMuffleFilter = client.VoipSound.UseMuffleFilter =
messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters && messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters &&
SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull); SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRangeVOIP, client.Character.CurrentHull);
} }
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client); GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
@@ -70,6 +70,8 @@ namespace Barotrauma.Particles
public Vector4 ColorMultiplier; public Vector4 ColorMultiplier;
public float VelocityChangeMultiplier;
public bool DrawOnTop { get; private set; } public bool DrawOnTop { get; private set; }
public ParticlePrefab.DrawTargetType DrawTarget public ParticlePrefab.DrawTargetType DrawTarget
@@ -122,7 +124,7 @@ namespace Barotrauma.Particles
animState = 0; animState = 0;
animFrame = 0; animFrame = 0;
currentHull = Hull.FindHull(position, hullGuess); currentHull = prefab.CanEnterSubs ? Hull.FindHull(position, hullGuess) : null;
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f); size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
@@ -178,6 +180,8 @@ namespace Barotrauma.Particles
HighQualityCollisionDetection = false; HighQualityCollisionDetection = false;
VelocityChangeMultiplier = 1.0f;
OnChangeHull = null; OnChangeHull = null;
OnCollision = null; OnCollision = null;
@@ -247,8 +251,8 @@ namespace Barotrauma.Particles
bool inWater = (currentHull == null || (currentHull.Submarine != null && position.Y - currentHull.Submarine.DrawPosition.Y < currentHull.Surface)); bool inWater = (currentHull == null || (currentHull.Submarine != null && position.Y - currentHull.Submarine.DrawPosition.Y < currentHull.Surface));
if (inWater) if (inWater)
{ {
velocity.X += velocityChangeWater.X * deltaTime; velocity.X += velocityChangeWater.X * VelocityChangeMultiplier * deltaTime;
velocity.Y += velocityChangeWater.Y * deltaTime; velocity.Y += velocityChangeWater.Y * VelocityChangeMultiplier * deltaTime;
if (prefab.WaterDrag > 0.0f) if (prefab.WaterDrag > 0.0f)
{ {
ApplyDrag(prefab.WaterDrag, deltaTime); ApplyDrag(prefab.WaterDrag, deltaTime);
@@ -256,8 +260,8 @@ namespace Barotrauma.Particles
} }
else else
{ {
velocity.X += velocityChange.X * deltaTime; velocity.X += velocityChange.X * VelocityChangeMultiplier * deltaTime;
velocity.Y += velocityChange.Y * deltaTime; velocity.Y += velocityChange.Y * VelocityChangeMultiplier * deltaTime;
if (prefab.Drag > 0.0f) if (prefab.Drag > 0.0f)
{ {
ApplyDrag(prefab.Drag, deltaTime); ApplyDrag(prefab.Drag, deltaTime);
@@ -132,6 +132,9 @@ namespace Barotrauma.Particles
} }
} }
[Editable, Serialize(true, IsPropertySaveable.No, description: "Is the particle considered to be inside a submarine if it spawns at a position inside a hull (causing it to move with the sub)?")]
public bool CanEnterSubs { get; private set; }
[Editable(0.0f, 10000.0f), Serialize(0.0f, IsPropertySaveable.No, description: "Radius of the particle's collider. Only has an effect if UseCollision is set to true.")] [Editable(0.0f, 10000.0f), Serialize(0.0f, IsPropertySaveable.No, description: "Radius of the particle's collider. Only has an effect if UseCollision is set to true.")]
public float CollisionRadius { get; private set; } public float CollisionRadius { get; private set; }
@@ -153,10 +156,10 @@ namespace Barotrauma.Particles
//size ----------------------------------------- //size -----------------------------------------
[Editable, Serialize("1.0,1.0", IsPropertySaveable.No, description: "The minimum initial size of the particle.")] [Editable(DecimalCount = 3), Serialize("1.0,1.0", IsPropertySaveable.No, description: "The minimum initial size of the particle.")]
public Vector2 StartSizeMin { get; private set; } public Vector2 StartSizeMin { get; private set; }
[Editable, Serialize("1.0,1.0", IsPropertySaveable.No, description: "The maximum initial size of the particle.")] [Editable(DecimalCount = 3), Serialize("1.0,1.0", IsPropertySaveable.No, description: "The maximum initial size of the particle.")]
public Vector2 StartSizeMax { get; private set; } public Vector2 StartSizeMax { get; private set; }
[Editable, Serialize("0.0,0.0", IsPropertySaveable.No, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")] [Editable, Serialize("0.0,0.0", IsPropertySaveable.No, description: "How much the size of the particle changes per second. The rate of growth for each particle is randomize between SizeChangeMin and SizeChangeMax.")]

Some files were not shown because too many files have changed in this diff Show More