v0.13.0.11

This commit is contained in:
Joonas Rikkonen
2021-04-22 17:33:08 +03:00
parent 0697d7fc64
commit 8bb31f2893
391 changed files with 17271 additions and 5949 deletions
@@ -37,7 +37,7 @@ namespace Barotrauma
public float MinZoom public float MinZoom
{ {
get { return minZoom;} get { return minZoom;}
set { minZoom = MathHelper.Clamp(value, 0.01f, 10.0f); } set { minZoom = MathHelper.Clamp(value, 0.001f, 10.0f); }
} }
private float maxZoom = 2.0f; private float maxZoom = 2.0f;
@@ -51,8 +51,6 @@ namespace Barotrauma
private float zoom; private float zoom;
private float offsetAmount;
private Matrix transform, shaderTransform, viewMatrix; private Matrix transform, shaderTransform, viewMatrix;
private Vector2 position; private Vector2 position;
private float rotation; private float rotation;
@@ -68,15 +66,8 @@ namespace Barotrauma
private Vector2 shakePosition; private Vector2 shakePosition;
private float shakeTimer; private float shakeTimer;
//the area of the world inside the camera view
private Rectangle worldView;
private float globalZoomScale = 1.0f; private float globalZoomScale = 1.0f;
private Point resolution;
private Vector2 targetPos;
//used to smooth out the movement when in freecam //used to smooth out the movement when in freecam
private float targetZoom; private float targetZoom;
private Vector2 velocity; private Vector2 velocity;
@@ -89,10 +80,10 @@ namespace Barotrauma
zoom = MathHelper.Clamp(value, GameMain.DebugDraw ? 0.01f : MinZoom, MaxZoom); zoom = MathHelper.Clamp(value, GameMain.DebugDraw ? 0.01f : MinZoom, MaxZoom);
Vector2 center = WorldViewCenter; Vector2 center = WorldViewCenter;
float newWidth = resolution.X / zoom; float newWidth = Resolution.X / zoom;
float newHeight = resolution.Y / zoom; float newHeight = Resolution.Y / zoom;
worldView = new Rectangle( WorldView = new Rectangle(
(int)(center.X - newWidth / 2.0f), (int)(center.X - newWidth / 2.0f),
(int)(center.Y + newHeight / 2.0f), (int)(center.Y + newHeight / 2.0f),
(int)newWidth, (int)newWidth,
@@ -122,29 +113,20 @@ namespace Barotrauma
} }
} }
public float OffsetAmount public float OffsetAmount { get; set; }
{
get { return offsetAmount; }
set { offsetAmount = value; }
}
public Point Resolution public Point Resolution { get; private set; }
{
get { return resolution; }
}
public Rectangle WorldView //the area of the world inside the camera view
{ public Rectangle WorldView { get; private set; }
get { return worldView; }
}
public Vector2 WorldViewCenter public Vector2 WorldViewCenter
{ {
get get
{ {
return new Vector2( return new Vector2(
worldView.X + worldView.Width / 2.0f, WorldView.X + WorldView.Width / 2.0f,
worldView.Y - worldView.Height / 2.0f); WorldView.Y - WorldView.Height / 2.0f);
} }
} }
@@ -171,12 +153,13 @@ namespace Barotrauma
UpdateTransform(false); UpdateTransform(false);
} }
public Vector2 TargetPos ~Camera()
{ {
get { return targetPos; } GameMain.Instance.ResolutionChanged -= CreateMatrices;
set { targetPos = value; }
} }
public Vector2 TargetPos { get; set; }
public Vector2 GetPosition() public Vector2 GetPosition()
{ {
return position; return position;
@@ -204,21 +187,29 @@ namespace Barotrauma
public void SetResolution(Point res) public void SetResolution(Point res)
{ {
resolution = res; Resolution = res;
worldView = new Rectangle(0, 0, res.X, res.Y); WorldView = new Rectangle(0, 0, res.X, res.Y);
viewMatrix = Matrix.CreateTranslation(new Vector3(res.X / 2.0f, res.Y / 2.0f, 0)); viewMatrix = Matrix.CreateTranslation(new Vector3(res.X / 2.0f, res.Y / 2.0f, 0));
globalZoomScale = (float)Math.Pow(new Vector2(GUI.UIWidth, resolution.Y).Length() / GUI.ReferenceResolution.Length(), 2); float newGlobalZoomScale = (float)new Vector2(GUI.UIWidth, Resolution.Y).Length() / GUI.ReferenceResolution.Length();
if (globalZoomScale > 0.0f)
{
Zoom *= newGlobalZoomScale / globalZoomScale;
targetZoom *= newGlobalZoomScale / globalZoomScale;
prevZoom *= newGlobalZoomScale / globalZoomScale;
}
globalZoomScale = newGlobalZoomScale;
} }
public void UpdateTransform(bool interpolate = true) public void UpdateTransform(bool interpolate = true, bool updateListener = true)
{ {
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position; Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;
float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom; float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom;
worldView.X = (int)(interpolatedPosition.X - worldView.Width / 2.0); WorldView = new Rectangle((int)(interpolatedPosition.X - WorldView.Width / 2.0),
worldView.Y = (int)(interpolatedPosition.Y + worldView.Height / 2.0); (int)(interpolatedPosition.Y + WorldView.Height / 2.0),
WorldView.Width, WorldView.Height);
transform = Matrix.CreateTranslation( transform = Matrix.CreateTranslation(
new Vector3(-interpolatedPosition.X, interpolatedPosition.Y, 0)) * new Vector3(-interpolatedPosition.X, interpolatedPosition.Y, 0)) *
@@ -227,19 +218,22 @@ namespace Barotrauma
shaderTransform = Matrix.CreateTranslation( shaderTransform = Matrix.CreateTranslation(
new Vector3( new Vector3(
-interpolatedPosition.X - resolution.X / interpolatedZoom / 2.0f, -interpolatedPosition.X - Resolution.X / interpolatedZoom / 2.0f,
-interpolatedPosition.Y - resolution.Y / interpolatedZoom / 2.0f, 0)) * -interpolatedPosition.Y - Resolution.Y / interpolatedZoom / 2.0f, 0)) *
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) * Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
viewMatrix * Matrix.CreateRotationZ(-rotation); viewMatrix * Matrix.CreateRotationZ(-rotation);
if (Character.Controlled == null) if (updateListener)
{ {
GameMain.SoundManager.ListenerPosition = new Vector3(WorldViewCenter.X, WorldViewCenter.Y, -(100.0f / zoom)); if (Character.Controlled == null)
} {
else GameMain.SoundManager.ListenerPosition = new Vector3(WorldViewCenter.X, WorldViewCenter.Y, -(100.0f / zoom));
{ }
GameMain.SoundManager.ListenerPosition = new Vector3(Character.Controlled.WorldPosition.X, Character.Controlled.WorldPosition.Y, -(100.0f / zoom)); else
{
GameMain.SoundManager.ListenerPosition = new Vector3(Character.Controlled.WorldPosition.X, Character.Controlled.WorldPosition.Y, -(100.0f / zoom));
}
} }
@@ -257,7 +251,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public bool Freeze { get; set; } public bool Freeze { get; set; }
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true) public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true, bool? followSub = null)
{ {
prevPosition = position; prevPosition = position;
prevZoom = zoom; prevZoom = zoom;
@@ -265,7 +259,7 @@ namespace Barotrauma
float moveSpeed = 20.0f / zoom; float moveSpeed = 20.0f / zoom;
Vector2 moveCam = Vector2.Zero; Vector2 moveCam = Vector2.Zero;
if (targetPos == Vector2.Zero) if (TargetPos == Vector2.Zero)
{ {
Vector2 moveInput = Vector2.Zero; Vector2 moveInput = Vector2.Zero;
if (allowMove && !Freeze) if (allowMove && !Freeze)
@@ -284,7 +278,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) if (Screen.Selected == GameMain.GameScreen && (followSub ?? FollowSub))
{ {
var closestSub = Submarine.FindClosest(WorldViewCenter); var closestSub = Submarine.FindClosest(WorldViewCenter);
if (closestSub != null) if (closestSub != null)
@@ -294,7 +288,7 @@ namespace Barotrauma
} }
} }
if (allowZoom && GUI.MouseOn == null) if (allowZoom)
{ {
Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition); Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition);
Vector2 diffViewCenter; Vector2 diffViewCenter;
@@ -318,14 +312,15 @@ namespace Barotrauma
else if (allowMove) else if (allowMove)
{ {
Vector2 mousePos = PlayerInput.MousePosition; Vector2 mousePos = PlayerInput.MousePosition;
Vector2 offset = mousePos - resolution.ToVector2() / 2; Vector2 offset = mousePos - Resolution.ToVector2() / 2;
offset.X = offset.X / (resolution.X * 0.4f); offset.X = offset.X / (Resolution.X * 0.4f);
offset.Y = -offset.Y / (resolution.Y * 0.3f); offset.Y = -offset.Y / (Resolution.Y * 0.3f);
if (offset.LengthSquared() > 1.0f) offset.Normalize(); if (offset.LengthSquared() > 1.0f) offset.Normalize();
offset *= offsetAmount; float offsetUnscaledLen = offset.Length();
offset *= OffsetAmount;
// Freeze the camera movement by default, when the cursor is on top of an ui element. // Freeze the camera movement by default, when the cursor is on top of an ui element.
// Setting a positive value to the OffsetAmount, will override this behaviour. // Setting a positive value to the OffsetAmount, will override this behaviour.
if (GUI.MouseOn != null && offsetAmount > 0) if (GUI.MouseOn != null && OffsetAmount > 0)
{ {
Freeze = true; Freeze = true;
} }
@@ -336,7 +331,7 @@ namespace Barotrauma
} }
if (Freeze) if (Freeze)
{ {
offset = previousOffset; if (offset.LengthSquared() > 0.001f) { offset = previousOffset; }
} }
else else
{ {
@@ -345,23 +340,18 @@ namespace Barotrauma
//how much to zoom out (zoom completely out when offset is 1000) //how much to zoom out (zoom completely out when offset is 1000)
float zoomOutAmount = GetZoomAmount(offset); float zoomOutAmount = GetZoomAmount(offset);
//zoom amount when resolution is not taken into account //scaled zoom amount
float unscaledZoom = MathHelper.Lerp(DefaultZoom, MinZoom, zoomOutAmount); float scaledZoom = MathHelper.Lerp(DefaultZoom, MinZoom, zoomOutAmount) * globalZoomScale;
//zoom with resolution taken into account (zoom further out on smaller resolutions) //zoom in further if zoomOutAmount is low and resolution is lower than reference
float scaledZoom = unscaledZoom * globalZoomScale; float newZoom = scaledZoom * (MathHelper.Lerp(0.3f * (1f - Math.Min(globalZoomScale, 1f)), 0f,
(GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(offsetUnscaledLen) : 0.3f) + 1f);
//an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution,
//while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling
//on lower resolutions, which doesn't look that good)
float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom,
(GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(zoomOutAmount) : 0.3f);
Zoom += (newZoom - zoom) / ZoomSmoothness; Zoom += (newZoom - zoom) / ZoomSmoothness;
//force targetzoom to the current zoom value, so the camera stays at the same zoom when switching to freecam //force targetzoom to the current zoom value, so the camera stays at the same zoom when switching to freecam
targetZoom = Zoom; targetZoom = Zoom;
Vector2 diff = (targetPos + offset) - position; Vector2 diff = (TargetPos + offset) - position;
moveCam = diff / MoveSmoothness; moveCam = diff / MoveSmoothness;
} }
@@ -11,6 +11,9 @@ namespace Barotrauma
{ {
if (!ShowAITargets) { return; } if (!ShowAITargets) { return; }
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y); var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
float thickness = 1 / Screen.Selected.Cam.Zoom;
float offset = MathUtils.VectorToAngle(new Vector2(sectorDir.X, -sectorDir.Y)) - (sectorRad / 2f);
if (soundRange > 0.0f) if (soundRange > 0.0f)
{ {
Color color; Color color;
@@ -26,8 +29,16 @@ namespace Barotrauma
{ {
color = Color.OrangeRed; color = Color.OrangeRed;
} }
ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
ShapeExtensions.DrawCircle(spriteBatch, pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom); if (sectorRad < MathHelper.TwoPi)
{
spriteBatch.DrawSector(pos, SoundRange, sectorRad, 100, color, offset: offset, thickness: thickness);
}
else
{
spriteBatch.DrawCircle(pos, SoundRange, 100, color, thickness: thickness);
}
spriteBatch.DrawCircle(pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SoundRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1); GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SoundRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1);
} }
if (sightRange > 0.0f) if (sightRange > 0.0f)
@@ -47,7 +58,14 @@ namespace Barotrauma
// disable the indicators for structures and hulls, because they clutter the debug view // disable the indicators for structures and hulls, because they clutter the debug view
return; return;
} }
ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); if (sectorRad < MathHelper.TwoPi)
{
spriteBatch.DrawSector(pos, SightRange, sectorRad, 100, color, offset: offset, thickness: thickness);
}
else
{
spriteBatch.DrawCircle(pos, SightRange, 100, color, thickness: thickness);
}
ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom); ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SightRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1); GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SightRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1);
} }
@@ -1,20 +1,12 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using FarseerPhysics; using FarseerPhysics;
using System;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
partial class HumanAIController : AIController partial class HumanAIController : AIController
{ {
partial void InitProjSpecific()
{
/*if (GameMain.GameSession != null && GameMain.GameSession.CrewManager != null)
{
CurrentOrder = Order.GetPrefab("dismissed");
objectiveManager.SetOrder(CurrentOrder, "", null);
GameMain.GameSession.CrewManager.SetCharacterOrder(Character, CurrentOrder, null, null);
}*/
}
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{ {
if (Character == Character.Controlled) { return; } if (Character == Character.Controlled) { return; }
@@ -22,6 +14,7 @@ namespace Barotrauma
Vector2 pos = Character.WorldPosition; Vector2 pos = Character.WorldPosition;
pos.Y = -pos.Y; pos.Y = -pos.Y;
Vector2 textOffset = new Vector2(-40, -160); Vector2 textOffset = new Vector2(-40, -160);
textOffset.Y -= Math.Max(ObjectiveManager.CurrentOrders.Count - 1, 0) * 20;
if (SelectedAiTarget?.Entity != null) if (SelectedAiTarget?.Entity != null)
{ {
@@ -29,56 +22,55 @@ namespace Barotrauma
//GUI.DrawString(spriteBatch, pos + textOffset, $"AI TARGET: {SelectedAiTarget.Entity.ToString()}", Color.White, Color.Black); //GUI.DrawString(spriteBatch, pos + textOffset, $"AI TARGET: {SelectedAiTarget.Entity.ToString()}", Color.White, Color.Black);
} }
GUI.DrawString(spriteBatch, pos + textOffset, Character.Name, Color.White, Color.Black); Vector2 stringDrawPos = pos + textOffset;
GUI.DrawString(spriteBatch, stringDrawPos, Character.Name, Color.White, Color.Black);
var currentOrder = ObjectiveManager.CurrentOrder; var currentOrder = ObjectiveManager.CurrentOrder;
if (currentOrder != null) if (ObjectiveManager.CurrentOrders.Any())
{ {
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.Priority.FormatZeroDecimal()})", Color.White, Color.Black); var currentOrders = ObjectiveManager.CurrentOrders;
currentOrders.Sort((x, y) => y.ManualPriority.CompareTo(x.ManualPriority));
for (int i = 0; i < currentOrders.Count; i++)
{
stringDrawPos += new Vector2(0, 20);
var order = currentOrders[i];
GUI.DrawString(spriteBatch, stringDrawPos, $"ORDER {i + 1}: {order.Objective.DebugTag} ({order.Objective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
} }
else if (ObjectiveManager.WaitTimer > 0) else if (ObjectiveManager.WaitTimer > 0)
{ {
GUI.DrawString(spriteBatch, pos + new Vector2(0, 20), $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black); stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos - textOffset, $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black);
} }
var currentObjective = ObjectiveManager.CurrentObjective; var currentObjective = ObjectiveManager.CurrentObjective;
if (currentObjective != null) if (currentObjective != null)
{ {
int offset = currentOrder != null ? 20 : 0; int offset = currentOrder != null ? 20 + ((ObjectiveManager.CurrentOrders.Count - 1) * 20) : 0;
if (currentOrder == null || currentOrder.Priority <= 0) if (currentOrder == null || currentOrder.Priority <= 0)
{ {
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20 + offset), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos, $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
} }
var subObjective = currentObjective.CurrentSubObjective; var subObjective = currentObjective.CurrentSubObjective;
if (subObjective != null) if (subObjective != null)
{ {
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40 + offset), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos, $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
} }
var activeObjective = ObjectiveManager.GetActiveObjective(); var activeObjective = ObjectiveManager.GetActiveObjective();
if (activeObjective != null) if (activeObjective != null)
{ {
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60 + offset), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos, $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
} }
} }
Vector2 objectiveStringDrawPos = stringDrawPos + new Vector2(120, 40);
for (int i = 0; i < ObjectiveManager.Objectives.Count; i++) for (int i = 0; i < ObjectiveManager.Objectives.Count; i++)
{ {
var objective = ObjectiveManager.Objectives[i]; var objective = ObjectiveManager.Objectives[i];
int offsetMultiplier; GUI.DrawString(spriteBatch, objectiveStringDrawPos, $"{objective.DebugTag} ({objective.Priority.FormatZeroDecimal()})", Color.White, Color.Black * 0.5f);
if (ObjectiveManager.CurrentOrder == null) objectiveStringDrawPos += new Vector2(0, 18);
{
if (i == 0)
{
continue;
}
else
{
offsetMultiplier = i - 1;
}
}
else
{
offsetMultiplier = i + 1;
}
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(120, offsetMultiplier * 18 + 100), $"{objective.DebugTag} ({objective.Priority.FormatZeroDecimal()})", Color.White, Color.Black * 0.5f);
} }
if (steeringManager is IndoorsSteeringManager pathSteering) if (steeringManager is IndoorsSteeringManager pathSteering)
@@ -106,7 +98,7 @@ namespace Barotrauma
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y), new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
Color.BlueViolet, 0, 3); Color.BlueViolet, 0, 3);
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); GUI.DrawString(spriteBatch, stringDrawPos + new Vector2(0, 40), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f);
} }
} }
} }
@@ -55,6 +55,11 @@ namespace Barotrauma
set set
{ {
if (controlled == value) return; if (controlled == value) return;
if ((!(controlled is null)) && (!(Screen.Selected?.Cam is null)) && value is null)
{
Screen.Selected.Cam.TargetPos = Vector2.Zero;
Lights.LightManager.ViewTarget = null;
}
controlled = value; controlled = value;
if (controlled != null) controlled.Enabled = true; if (controlled != null) controlled.Enabled = true;
CharacterHealth.OpenHealthWindow = null; CharacterHealth.OpenHealthWindow = null;
@@ -97,6 +102,13 @@ namespace Barotrauma
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
} }
private float grainStrength;
public float GrainStrength
{
get => grainStrength;
set => grainStrength = MathHelper.Clamp(value, 0.0f, 1.0f);
}
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> BloodEmitters public IEnumerable<ParticleEmitter> BloodEmitters
{ {
@@ -115,6 +127,9 @@ namespace Barotrauma
get { return gibEmitters; } get { return gibEmitters; }
} }
public static bool IsMouseOnUI => GUI.MouseOn != null ||
(CharacterInventory.IsMouseOnInventory() && !CharacterInventory.DraggingItemToWorld);
public class ObjectiveEntity public class ObjectiveEntity
{ {
public Entity Entity; public Entity Entity;
@@ -217,8 +232,7 @@ namespace Barotrauma
float targetOffsetAmount = 0.0f; float targetOffsetAmount = 0.0f;
if (moveCam) if (moveCam)
{ {
if (NeedsAir && if (NeedsAir && !IsProtectedFromPressure() &&
pressureProtection < 80.0f &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f)) (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
{ {
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure; float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
@@ -285,6 +299,10 @@ namespace Barotrauma
cam.OffsetAmount = targetOffsetAmount = 0.0f; cam.OffsetAmount = targetOffsetAmount = 0.0f;
} }
} }
else if (IsMouseOnUI)
{
targetOffsetAmount = cam.OffsetAmount;
}
else if (Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f) else if (Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f)
{ {
Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos); Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos);
@@ -375,17 +393,47 @@ namespace Barotrauma
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log) partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log)
{ {
HintManager.OnCharacterKilled(this);
if (GameMain.NetworkMember != null && controlled == this) if (GameMain.NetworkMember != null && controlled == this)
{ {
string chatMessage = CauseOfDeath.Type == CauseOfDeathType.Affliction ? string chatMessage = CauseOfDeath.Type == CauseOfDeathType.Affliction ?
CauseOfDeath.Affliction.SelfCauseOfDeathDescription : CauseOfDeath.Affliction.SelfCauseOfDeathDescription :
TextManager.Get("Self_CauseOfDeathDescription." + CauseOfDeath.Type.ToString(), fallBackTag: "Self_CauseOfDeathDescription.Damage"); TextManager.Get("Self_CauseOfDeathDescription." + CauseOfDeath.Type.ToString(), fallBackTag: "Self_CauseOfDeathDescription.Damage");
if (GameMain.Client != null) chatMessage += " " + TextManager.Get("DeathChatNotification"); if (GameMain.Client != null) { chatMessage += " " + TextManager.Get("DeathChatNotification"); }
if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false)
{
CoroutineManager.InvokeAfter(() =>
{
if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
var respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"),
new string[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") });
respawnPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false);
respawnPrompt.Close();
return true;
};
respawnPrompt.Buttons[1].OnClicked += (btn, userdata) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: true);
respawnPrompt.Close();
return true;
};
}, delay: 5.0f);
}
GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead); GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead);
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
controlled = null; controlled = null;
if (!(Screen.Selected?.Cam is null))
{
Screen.Selected.Cam.TargetPos = Vector2.Zero;
Lights.LightManager.ViewTarget = null;
}
} }
PlaySound(CharacterSound.SoundType.Die); PlaySound(CharacterSound.SoundType.Die);
@@ -393,7 +441,15 @@ namespace Barotrauma
partial void DisposeProjSpecific() partial void DisposeProjSpecific()
{ {
if (controlled == this) controlled = null; if (controlled == this)
{
controlled = null;
if (!(Screen.Selected?.Cam is null))
{
Screen.Selected.Cam.TargetPos = Vector2.Zero;
Lights.LightManager.ViewTarget = null;
}
}
if (GameMain.GameSession?.CrewManager != null && if (GameMain.GameSession?.CrewManager != null &&
GameMain.GameSession.CrewManager.GetCharacters().Contains(this)) GameMain.GameSession.CrewManager.GetCharacters().Contains(this))
@@ -634,9 +690,9 @@ namespace Barotrauma
} }
} }
partial void SetOrderProjSpecific(Order order, string orderOption) partial void SetOrderProjSpecific(Order order, string orderOption, int priority)
{ {
GameMain.GameSession?.CrewManager?.AddCurrentOrderIcon(this, order, orderOption); GameMain.GameSession?.CrewManager?.AddCurrentOrderIcon(this, order, orderOption, priority);
} }
public static void AddAllToGUIUpdateList() public static void AddAllToGUIUpdateList()
@@ -815,7 +871,7 @@ namespace Barotrauma
iconPos.Y = -iconPos.Y; iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color; 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; 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); icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -11,6 +12,56 @@ namespace Barotrauma
{ {
class CharacterHUD class CharacterHUD
{ {
const float BossHealthBarDuration = 120.0f;
class BossHealthBar
{
public readonly Character Character;
public float FadeTimer;
public readonly GUIComponent TopContainer;
public readonly GUIComponent SideContainer;
public readonly GUIProgressBar TopHealthBar;
public readonly GUIProgressBar SideHealthBar;
public BossHealthBar(Character character)
{
Character = character;
FadeTimer = BossHealthBarDuration;
TopContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 0.03f), HUDFrame.RectTransform, Anchor.TopCenter)
{
MinSize = new Point(100, 50),
RelativeOffset = new Vector2(0.0f, 0.01f)
}, isHorizontal: false, childAnchor: Anchor.TopCenter);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), TopContainer.RectTransform), character.DisplayName, textAlignment: Alignment.Center, textColor: GUI.Style.Red);
TopHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.6f), TopContainer.RectTransform)
{
MinSize = new Point(100, HUDLayoutSettings.HealthBarArea.Size.Y)
}, barSize: 0.0f, style: "CharacterHealthBarCentered")
{
Color = GUI.Style.Red
};
SideContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), bossHealthContainer.RectTransform)
{
MinSize = new Point(80, 60)
}, isHorizontal: false, childAnchor: Anchor.TopRight);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), SideContainer.RectTransform), character.DisplayName, textAlignment: Alignment.CenterRight, textColor: GUI.Style.Red);
SideHealthBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.7f), SideContainer.RectTransform), barSize: 0.0f, style: "CharacterHealthBar")
{
Color = GUI.Style.Red
};
TopContainer.Visible = SideContainer.Visible = false;
TopContainer.CanBeFocused = false;
TopContainer.Children.ForEach(c => c.CanBeFocused = false);
SideContainer.CanBeFocused = false;
SideContainer.Children.ForEach(c => c.CanBeFocused = false);
}
}
private static readonly Dictionary<ISpatialEntity, int> orderIndicatorCount = new Dictionary<ISpatialEntity, int>(); private static readonly Dictionary<ISpatialEntity, int> orderIndicatorCount = new Dictionary<ISpatialEntity, int>();
const float ItemOverlayDelay = 1.0f; const float ItemOverlayDelay = 1.0f;
private static Item focusedItem; private static Item focusedItem;
@@ -19,8 +70,12 @@ namespace Barotrauma
private static readonly List<Item> brokenItems = new List<Item>(); private static readonly List<Item> brokenItems = new List<Item>();
private static float brokenItemsCheckTimer; private static float brokenItemsCheckTimer;
private static readonly List<BossHealthBar> bossHealthBars = new List<BossHealthBar>();
private static readonly Dictionary<string, string> cachedHudTexts = new Dictionary<string, string>(); private static readonly Dictionary<string, string> cachedHudTexts = new Dictionary<string, string>();
private static GUILayoutGroup bossHealthContainer;
private static GUIFrame hudFrame; private static GUIFrame hudFrame;
public static GUIFrame HUDFrame public static GUIFrame HUDFrame
{ {
@@ -33,6 +88,13 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
bossHealthContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 0.5f), hudFrame.RectTransform, Anchor.CenterRight)
{
RelativeOffset = new Vector2(0.005f, 0.0f)
})
{
AbsoluteSpacing = GUI.IntScale(10)
};
} }
return hudFrame; return hudFrame;
} }
@@ -70,7 +132,7 @@ namespace Barotrauma
public static void AddToGUIUpdateList(Character character) public static void AddToGUIUpdateList(Character character)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
{ {
@@ -83,7 +145,7 @@ namespace Barotrauma
foreach (ItemComponent ic in item.Components) foreach (ItemComponent ic in item.Components)
{ {
if (ic.DrawHudWhenEquipped) ic.AddToGUIUpdateList(); if (ic.DrawHudWhenEquipped) { ic.AddToGUIUpdateList(); }
} }
} }
} }
@@ -99,13 +161,14 @@ namespace Barotrauma
public static void Update(float deltaTime, Character character, Camera cam) public static void Update(float deltaTime, Character character, Camera cam)
{ {
UpdateBossHealthBars(deltaTime);
if (GUI.DisableHUD) if (GUI.DisableHUD)
{ {
if (character.Inventory != null && !LockInventory(character)) if (character.Inventory != null && !LockInventory(character))
{ {
character.Inventory.UpdateSlotInput(); character.Inventory.UpdateSlotInput();
} }
return; return;
} }
@@ -130,17 +193,6 @@ namespace Barotrauma
{ {
character.Inventory.ClearSubInventories(); character.Inventory.ClearSubInventories();
} }
for (int i = 0; i < character.Inventory.Capacity; i++)
{
var item = character.Inventory.GetItemAt(i);
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
foreach (ItemComponent ic in item.Components)
{
if (ic.DrawHudWhenEquipped) ic.UpdateHUD(character, deltaTime, cam);
}
}
} }
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
@@ -221,9 +273,9 @@ namespace Barotrauma
} }
} }
if (DrawIcon(character.CurrentOrder)) if (character.GetCurrentOrderWithTopPriority()?.Order is Order currentOrder && DrawIcon(currentOrder))
{ {
DrawOrderIndicator(spriteBatch, cam, character, character.CurrentOrder, 1.0f); DrawOrderIndicator(spriteBatch, cam, character, currentOrder, 1.0f);
} }
static bool DrawIcon(Order o) => static bool DrawIcon(Order o) =>
@@ -253,7 +305,7 @@ namespace Barotrauma
return Math.Min((maxDistance - dist) / maxDistance * 2.0f, 1.0f); return Math.Min((maxDistance - dist) / maxDistance * 2.0f, 1.0f);
} }
if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen && (!character.IsKeyDown(InputType.Aim) || character.HeldItems.Any(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.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
{ {
@@ -499,6 +551,86 @@ namespace Barotrauma
} }
} }
public static void ShowBossHealthBar(Character character)
{
if (character == null || character.IsDead || character.Removed) { return; }
var existingBar = bossHealthBars.Find(b => b.Character == character);
if (existingBar != null)
{
existingBar.FadeTimer = BossHealthBarDuration;
return;
}
if (bossHealthBars.Count > 5)
{
BossHealthBar oldestHealthBar = bossHealthBars.First();
foreach (var bar in bossHealthBars)
{
if (bar.TopHealthBar.BarSize < oldestHealthBar.TopHealthBar.BarSize)
{
oldestHealthBar = bar;
}
}
oldestHealthBar.FadeTimer = Math.Min(oldestHealthBar.FadeTimer, 1.0f);
}
bossHealthBars.Add(new BossHealthBar(character));
}
public static void UpdateBossHealthBars(float deltaTime)
{
for (int i = 0; i < bossHealthBars.Count; i++)
{
var bossHealthBar = bossHealthBars[i];
bool showTopBar = i == 0;
if (showTopBar != bossHealthBar.TopContainer.Visible)
{
bossHealthContainer.Recalculate();
}
bossHealthBar.TopContainer.Visible = showTopBar;
bossHealthBar.SideContainer.Visible = !bossHealthBar.TopContainer.Visible;
float health = bossHealthBar.Character.Vitality / bossHealthBar.Character.MaxVitality;
float alpha = Math.Min(bossHealthBar.FadeTimer, 1.0f);
foreach (var c in bossHealthBar.SideContainer.GetAllChildren().Concat(bossHealthBar.TopContainer.GetAllChildren()))
{
c.Color = new Color(c.Color, (byte)(alpha * 255));
if (c is GUITextBlock textBlock)
{
textBlock.TextColor = new Color(bossHealthBar.Character.IsDead ? Color.Gray : textBlock.TextColor, (byte)(alpha * 255));
}
}
bossHealthBar.TopHealthBar.BarSize = bossHealthBar.SideHealthBar.BarSize = health;
if (bossHealthBar.Character.Removed || !bossHealthBar.Character.Enabled)
{
bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 1.0f);
}
else if (bossHealthBar.Character.IsDead)
{
bossHealthBar.FadeTimer = Math.Min(bossHealthBar.FadeTimer, 5.0f);
}
bossHealthBar.FadeTimer -= deltaTime;
}
for (int i = bossHealthBars.Count - 1; i >= 0 ; i--)
{
var bossHealthBar = bossHealthBars[i];
if (bossHealthBar.FadeTimer <= 0)
{
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
bossHealthBars.RemoveAt(i);
bossHealthContainer.Recalculate();
}
}
}
private static bool LockInventory(Character character) private static bool LockInventory(Character character)
{ {
if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; } if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; }
@@ -100,7 +100,22 @@ namespace Barotrauma
Color textColor = Color.White * (0.5f + skill.Level / 200.0f); Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font) { Padding = Vector4.Zero }; var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font) { Padding = Vector4.Zero };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), ((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
float modifiedSkillLevel = skill.Level;
if (Character != null)
{
modifiedSkillLevel = Character.GetSkillLevel(skill.Identifier);
}
if (!MathUtils.NearlyEqual(MathF.Round(modifiedSkillLevel), MathF.Round(skill.Level)))
{
int skillChange = (int)MathF.Round(modifiedSkillLevel - skill.Level);
string changeText = $"{(skillChange > 0 ? "+" : "") + skillChange}";
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), $"{(int)skill.Level} ({changeText})", textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), ((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
}
} }
} }
else if (Character != null && Character.IsDead) else if (Character != null && Character.IsDead)
@@ -529,6 +544,7 @@ namespace Barotrauma
{ {
ushort infoID = inc.ReadUInt16(); ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString(); string newName = inc.ReadString();
string originalName = inc.ReadString();
int gender = inc.ReadByte(); int gender = inc.ReadByte();
int race = inc.ReadByte(); int race = inc.ReadByte();
int headSpriteID = inc.ReadByte(); int headSpriteID = inc.ReadByte();
@@ -556,7 +572,7 @@ namespace Barotrauma
} }
// TODO: animations // TODO: animations
CharacterInfo ch = new CharacterInfo(speciesName, newName, jobPrefab, ragdollFile, variant) CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant)
{ {
ID = infoID, ID = infoID,
}; };
@@ -292,7 +292,7 @@ namespace Barotrauma
break; break;
case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 5); int eventType = msg.ReadRangedInteger(0, 6);
switch (eventType) switch (eventType)
{ {
case 0: //NetEntityEvent.Type.InventoryState case 0: //NetEntityEvent.Type.InventoryState
@@ -330,6 +330,8 @@ namespace Barotrauma
GameMain.Client.HasSpawned = true; GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this; GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
GameMain.Client.WaitForNextRoundRespawn = null;
} }
else else
{ {
@@ -390,6 +392,19 @@ namespace Barotrauma
byte campaignInteractionType = msg.ReadByte(); byte campaignInteractionType = msg.ReadByte();
(GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType); (GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
break; break;
case 6: //NetEntityEvent.Type.ObjectiveManagerOrderState
bool properData = msg.ReadBoolean();
if (!properData) { break; }
int orderIndex = msg.ReadRangedInteger(0, Order.PrefabList.Count);
var orderPrefab = Order.PrefabList[orderIndex];
string option = null;
if (orderPrefab.HasOptions)
{
int optionIndex = msg.ReadRangedInteger(0, orderPrefab.Options.Length);
option = orderPrefab.Options[optionIndex];
}
GameMain.GameSession.CrewManager.SetHighlightedOrderIcon(this, orderPrefab.Identifier, option);
break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
break; break;
@@ -441,13 +456,15 @@ namespace Barotrauma
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(character, character.CampaignInteractionType); (GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(character, character.CampaignInteractionType);
} }
// Check if the character has a current order // Check if the character has current orders
if (inc.ReadBoolean()) int orderCount = inc.ReadByte();
for (int i = 0; i < orderCount; i++)
{ {
int orderPrefabIndex = inc.ReadByte(); int orderPrefabIndex = inc.ReadByte();
Entity targetEntity = FindEntityByID(inc.ReadUInt16()); Entity targetEntity = FindEntityByID(inc.ReadUInt16());
Character orderGiver = inc.ReadBoolean() ? FindEntityByID(inc.ReadUInt16()) as Character : null; Character orderGiver = inc.ReadBoolean() ? FindEntityByID(inc.ReadUInt16()) as Character : null;
int orderOptionIndex = inc.ReadByte(); int orderOptionIndex = inc.ReadByte();
int orderPriority = inc.ReadByte();
OrderTarget targetPosition = null; OrderTarget targetPosition = null;
if (inc.ReadBoolean()) if (inc.ReadBoolean())
{ {
@@ -468,7 +485,7 @@ namespace Barotrauma
new Order(orderPrefab, targetPosition, orderGiver: orderGiver); new Order(orderPrefab, targetPosition, orderGiver: orderGiver);
character.SetOrder(order, character.SetOrder(order,
orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null, orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null,
orderGiver, speak: false); orderPriority, orderGiver, speak: false);
} }
else else
{ {
@@ -487,7 +504,7 @@ namespace Barotrauma
character.ReadStatus(inc); character.ReadStatus(inc);
} }
if (character.IsHuman && character.TeamID != CharacterTeamType.FriendlyNPC && !character.IsDead) if (character.IsHuman && character.TeamID != CharacterTeamType.FriendlyNPC && character.TeamID != CharacterTeamType.None && !character.IsDead)
{ {
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID); CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo); GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
@@ -501,6 +518,7 @@ namespace Barotrauma
if (!character.IsDead) { Controlled = character; } if (!character.IsDead) { Controlled = character; }
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
character.memInput.Clear(); character.memInput.Clear();
character.memState.Clear(); character.memState.Clear();
@@ -240,6 +240,8 @@ namespace Barotrauma
Character.Controlled.SelectedConstruction = null; Character.Controlled.SelectedConstruction = null;
} }
} }
HintManager.OnShowHealthInterface();
} }
} }
@@ -295,6 +297,7 @@ namespace Barotrauma
barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical") barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical")
{ {
HoverCursor = CursorState.Hand, HoverCursor = CursorState.Hand,
ToolTip = TextManager.GetWithVariable("hudbutton.healthinterface", "[key]", GameMain.Config.KeyBindText(InputType.Health)),
Enabled = true, Enabled = true,
IsHorizontal = horizontal IsHorizontal = horizontal
}; };
@@ -668,12 +671,17 @@ namespace Barotrauma
bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f); bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
if (bloodParticleTimer <= 0.0f) if (bloodParticleTimer <= 0.0f)
{ {
var emitter = Character.BloodEmitters.FirstOrDefault();
float particleMinScale = emitter != null ? emitter.Prefab.ScaleMin : 0.5f;
float particleMaxScale = emitter != null ? emitter.Prefab.ScaleMax : 1;
float severity = Math.Min(affliction.Strength / affliction.Prefab.MaxStrength * Character.Params.BleedParticleMultiplier, 1);
float bloodParticleSize = MathHelper.Lerp(particleMinScale, particleMaxScale, severity);
bool inWater = Character.AnimController.InWater; bool inWater = Character.AnimController.InWater;
float bloodParticleSize = MathHelper.Lerp(0.5f, 1.0f, affliction.Strength / 100.0f);
if (!inWater) if (!inWater)
{ {
bloodParticleSize *= 2.0f; bloodParticleSize *= 2.0f;
} }
var blood = GameMain.ParticleManager.CreateParticle( var blood = GameMain.ParticleManager.CreateParticle(
inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir, inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull); targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull);
@@ -682,7 +690,7 @@ namespace Barotrauma
{ {
blood.Size *= bloodParticleSize; blood.Size *= bloodParticleSize;
} }
bloodParticleTimer = 1.0f; bloodParticleTimer = MathHelper.Lerp(2, 0.5f, severity);
} }
} }
@@ -713,6 +721,7 @@ namespace Barotrauma
int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond); int dmgPerSecond = Math.Sign(a2.DamagePerSecond - a1.DamagePerSecond);
return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength); return dmgPerSecond != 0 ? dmgPerSecond : Math.Sign(a1.Strength - a1.Strength);
}); });
HintManager.OnAfflictionDisplayed(Character, currentDisplayedAfflictions);
updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval; updateDisplayedAfflictionsTimer = UpdateDisplayedAfflictionsInterval;
} }
@@ -732,6 +741,7 @@ namespace Barotrauma
float distortSpeed = 0.0f; float distortSpeed = 0.0f;
float radialDistortStrength = 0.0f; float radialDistortStrength = 0.0f;
float chromaticAberrationStrength = 0.0f; float chromaticAberrationStrength = 0.0f;
float grainStrength = 0.0f;
if (Character.IsUnconscious) if (Character.IsUnconscious)
{ {
@@ -752,6 +762,7 @@ namespace Barotrauma
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength()); blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength()); radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength()); chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength());
} }
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
@@ -766,6 +777,7 @@ namespace Barotrauma
Character.RadialDistortStrength = radialDistortStrength; Character.RadialDistortStrength = radialDistortStrength;
Character.ChromaticAberrationStrength = chromaticAberrationStrength; Character.ChromaticAberrationStrength = chromaticAberrationStrength;
Character.GrainStrength = grainStrength;
if (blurStrength > 0.0f) if (blurStrength > 0.0f)
{ {
distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi; distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi;
@@ -986,8 +998,8 @@ namespace Barotrauma
cprButton.Visible = cprButton.Visible =
Character == Character.Controlled?.SelectedCharacter Character == Character.Controlled?.SelectedCharacter
&& (Character.IsUnconscious || Character.Stun > 0.0f)
&& !Character.IsDead && !Character.IsDead
&& Character.IsKnockedDown
&& openHealthWindow == this; && openHealthWindow == this;
cprButton.IgnoreLayoutGroups = !cprButton.Visible; cprButton.IgnoreLayoutGroups = !cprButton.Visible;
cprButton.Selected = cprButton.Selected =
@@ -1183,7 +1195,7 @@ namespace Barotrauma
} }
} }
private Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction) public static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction)
{ {
// No specific colors, use generic // No specific colors, use generic
if (prefab.IconColors == null) if (prefab.IconColors == null)
@@ -1203,6 +1215,8 @@ namespace Barotrauma
} }
} }
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
private void UpdateAfflictionContainer(LimbHealth selectedLimb) private void UpdateAfflictionContainer(LimbHealth selectedLimb)
{ {
selectedLimbText.Text = selectedLimb == null ? "" : selectedLimb.Name; selectedLimbText.Text = selectedLimb == null ? "" : selectedLimb.Name;
@@ -1270,7 +1284,7 @@ namespace Barotrauma
var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true) var afflictionIcon = new GUIImage(new RectTransform(Vector2.One * 0.8f, button.RectTransform, Anchor.Center), affliction.Prefab.Icon, scaleToFit: true)
{ {
Color = GetAfflictionIconColor(affliction.Prefab, affliction), Color = GetAfflictionIconColor(affliction),
CanBeFocused = false CanBeFocused = false
}; };
afflictionIcon.PressedColor = afflictionIcon.Color; afflictionIcon.PressedColor = afflictionIcon.Color;
@@ -1906,7 +1920,7 @@ namespace Barotrauma
float alpha = MathHelper.Lerp(0.3f, 1.0f, float alpha = MathHelper.Lerp(0.3f, 1.0f,
(affliction.Strength - showIconThreshold) / Math.Min(affliction.Prefab.MaxStrength - showIconThreshold, 10.0f)); (affliction.Strength - showIconThreshold) / Math.Min(affliction.Prefab.MaxStrength - showIconThreshold, 10.0f));
affliction.Prefab.Icon.Draw(spriteBatch, iconPos - iconSize / 2.0f, GetAfflictionIconColor(affliction.Prefab, affliction) * alpha, 0, iconScale); affliction.Prefab.Icon.Draw(spriteBatch, iconPos - iconSize / 2.0f, GetAfflictionIconColor(affliction) * alpha, 0, iconScale);
iconPos += new Vector2(10.0f, 20.0f) * iconScale; iconPos += new Vector2(10.0f, 20.0f) * iconScale;
} }
@@ -299,7 +299,12 @@ namespace Barotrauma
{ {
var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White) var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White)
{ {
CanBeFocused = false CanBeFocused = true,
OnSecondaryClicked = (component, data) =>
{
GUIContextMenu.CreateContextMenu(new ContextMenuOption("editor.copytoclipboard", true, () => { Clipboard.SetText(msg.Text); }));
return true;
}
}; };
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) }, var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
msg.Text, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true) msg.Text, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true)
@@ -480,7 +485,7 @@ namespace Barotrauma
var subInfo = new SubmarineInfo(string.Join(" ", args)); var subInfo = new SubmarineInfo(string.Join(" ", args));
Submarine.MainSub = Submarine.Load(subInfo, true); Submarine.MainSub = Submarine.Load(subInfo, true);
} }
GameMain.SubEditorScreen.Select(); GameMain.SubEditorScreen.Select(enableAutoSave: Screen.Selected != GameMain.GameScreen);
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) => commands.Add(new Command("editparticles|particleeditor", "editparticles/particleeditor: Switch to the Particle Editor to edit particle effects.", (string[] args) =>
@@ -620,6 +625,11 @@ namespace Barotrauma
NewMessage(SubEditorScreen.ShouldDrawGrid ? "Enabled submarine grid." : "Disabled submarine grid.", GUI.Style.Green); NewMessage(SubEditorScreen.ShouldDrawGrid ? "Enabled submarine grid." : "Disabled submarine grid.", GUI.Style.Green);
})); }));
commands.Add(new Command("spreadsheetexport", "Export items in format recognized by the spreadsheet importer.", (string[] args) =>
{
SpreadsheetExport.Export();
}));
commands.Add(new Command("wikiimage_character", "Save an image of the currently controlled character with a transparent background.", (string[] args) => commands.Add(new Command("wikiimage_character", "Save an image of the currently controlled character with a transparent background.", (string[] args) =>
{ {
if (Character.Controlled == null) { return; } if (Character.Controlled == null) { return; }
@@ -649,6 +659,7 @@ namespace Barotrauma
AssignRelayToServer("bindkey", false); AssignRelayToServer("bindkey", false);
AssignRelayToServer("unbindkey", false); AssignRelayToServer("unbindkey", false);
AssignRelayToServer("savebinds", false); AssignRelayToServer("savebinds", false);
AssignRelayToServer("spreadsheetexport", false);
#if DEBUG #if DEBUG
AssignRelayToServer("crash", false); AssignRelayToServer("crash", false);
AssignRelayToServer("showballastflorasprite", false); AssignRelayToServer("showballastflorasprite", false);
@@ -1236,6 +1247,7 @@ namespace Barotrauma
GameMain.DebugDraw = false; GameMain.DebugDraw = false;
GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
} }
NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White); NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White);
}); });
@@ -1523,7 +1535,9 @@ namespace Barotrauma
List<string> lines = missingTags.Select(t => "\"" + t.Key + "\"\n missing from " + string.Join(", ", t.Value)).ToList(); List<string> lines = missingTags.Select(t => "\"" + t.Key + "\"\n missing from " + string.Join(", ", t.Value)).ToList();
string filePath = "missingloca.txt"; string filePath = "missingloca.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
TextManager.Language = "English"; TextManager.Language = "English";
})); }));
@@ -1532,7 +1546,9 @@ namespace Barotrauma
{ {
var debugLines = EventSet.GetDebugStatistics(); var debugLines = EventSet.GetDebugStatistics();
string filePath = "eventstats.txt"; string filePath = "eventstats.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, debugLines); File.WriteAllLines(filePath, debugLines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
@@ -1705,6 +1721,15 @@ namespace Barotrauma
{ {
GameMain.Client?.ForceTimeOut(); GameMain.Client?.ForceTimeOut();
}, isCheat: false)); }, isCheat: false));
commands.Add(new Command("bumpitem", "", (string[] args) =>
{
float vel = 10.0f;
if (args.Length > 0)
{
float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out vel);
}
Character.Controlled?.FocusedItem?.body?.ApplyLinearImpulse(Rand.Vector(vel));
}, isCheat: false));
#endif #endif
@@ -1813,7 +1838,9 @@ namespace Barotrauma
lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</EntityName." + me.Identifier + ">"); lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</EntityName." + me.Identifier + ">");
lines.Add("<EntityDescription." + me.Identifier + ">" + me.Description + "</EntityDescription." + me.Identifier + ">"); lines.Add("<EntityDescription." + me.Identifier + ">" + me.Description + "</EntityDescription." + me.Identifier + ">");
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
})); }));
commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) => commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) =>
@@ -1832,8 +1859,8 @@ namespace Barotrauma
docs.Add(eventPrefab.ConfigElement.Document); docs.Add(eventPrefab.ConfigElement.Document);
getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier); getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier);
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
@@ -1841,7 +1868,6 @@ namespace Barotrauma
Indent = true, Indent = true,
NewLineOnAttributes = false NewLineOnAttributes = false
}; };
foreach (XDocument doc in docs) foreach (XDocument doc in docs)
{ {
using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings)) using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings))
@@ -1850,6 +1876,7 @@ namespace Barotrauma
writer.Flush(); writer.Flush();
} }
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
void getTextsFromElement(XElement element, List<string> list, string parentName) void getTextsFromElement(XElement element, List<string> list, string parentName)
{ {
@@ -1996,10 +2023,17 @@ namespace Barotrauma
lines.Add("[/table]"); lines.Add("[/table]");
lines.Add(""); lines.Add("");
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
#if DEBUG #if DEBUG
commands.Add(new Command("playovervc", "Plays a sound over voice chat.", (args) =>
{
VoipCapture.Instance?.SetOverrideSound(args.Length > 0 ? args[0] : null);
}));
commands.Add(new Command("querylobbies", "Queries all SteamP2P lobbies", (args) => commands.Add(new Command("querylobbies", "Queries all SteamP2P lobbies", (args) =>
{ {
TaskPool.Add("DebugQueryLobbies", TaskPool.Add("DebugQueryLobbies",
@@ -2033,7 +2067,9 @@ namespace Barotrauma
commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) => commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) =>
{ {
string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\propertylocalization.txt"; string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\propertylocalization.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(path, SerializableEntityEditor.MissingLocalizations); File.WriteAllLines(path, SerializableEntityEditor.MissingLocalizations);
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
})); }));
commands.Add(new Command("getproperties", "Goes through the MapEntity prefabs and checks their serializable properties for localization issues.", (string[] args) => commands.Add(new Command("getproperties", "Goes through the MapEntity prefabs and checks their serializable properties for localization issues.", (string[] args) =>
@@ -2248,12 +2284,15 @@ namespace Barotrauma
"revokeperm", "revokeperm",
(string[] args) => (string[] args) =>
{ {
if (args.Length < 1) return; if (args.Length < 1) { return; }
NewMessage("Valid permissions are:", Color.White); if (args.Length < 2)
foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions)))
{ {
NewMessage(" - " + permission.ToString(), Color.White); NewMessage("Valid permissions are:", Color.White);
foreach (ClientPermissions permission in Enum.GetValues(typeof(ClientPermissions)))
{
NewMessage(" - " + permission.ToString(), Color.White);
}
} }
ShowQuestionPrompt("Permission to revoke from client " + args[0] + "?", (perm) => ShowQuestionPrompt("Permission to revoke from client " + args[0] + "?", (perm) =>
@@ -560,6 +560,22 @@ namespace Barotrauma
}; };
} }
break; break;
case NetworkEventType.UNLOCKPATH:
UInt16 connectionIndex = msg.ReadUInt16();
if (GameMain.GameSession?.Map?.Connections != null)
{
if (connectionIndex >= GameMain.GameSession.Map.Connections.Count)
{
DebugConsole.ThrowError($"Failed to unlock a path on the campaign map. Connection index out of bounds (index: {connectionIndex}, number of connections: {GameMain.GameSession.Map.Connections.Count})");
}
else
{
GameMain.GameSession.Map.Connections[connectionIndex].Locked = false;
new GUIMessageBox(string.Empty, TextManager.Get("pathunlockedgeneric"),
new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "UnlockPathIcon", relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128));
}
}
break;
} }
} }
} }
@@ -0,0 +1,64 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class AbandonedOutpostMission : Mission
{
public override int State
{
get { return base.State; }
protected set
{
if (state != value)
{
base.State = value;
if (state == HostagesKilledState && !string.IsNullOrEmpty(hostagesKilledMessage))
{
CreateMessageBox(string.Empty, hostagesKilledMessage);
}
}
}
}
public override void ClientReadInitial(IReadMessage msg)
{
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)
{
Character character = Character.ReadSpawnData(msg);
characters.Add(character);
if (msg.ReadBoolean()) { requireKill.Add(character); }
if (msg.ReadBoolean())
{
requireRescue.Add(character);
#if CLIENT
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
#endif
}
ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++)
{
Item.ReadSpawnData(msg);
}
if (character.Submarine != null && character.AIController is EnemyAIController enemyAi)
{
enemyAi.UnattackableSubmarines.Add(character.Submarine);
enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
foreach (Submarine sub in Submarine.MainSub.DockedTo)
{
enemyAi.UnattackableSubmarines.Add(sub);
}
}
}
if (characters.Contains(null))
{
throw new System.Exception("Error in AbandonedOutpostMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
}
if (characters.Count != characterCount)
{
throw new System.Exception("Error in AbandonedOutpostMission.ClientReadInitial: character count does not match the server count (" + characters + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
}
}
}
}
@@ -1,10 +1,66 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
namespace Barotrauma namespace Barotrauma
{ {
abstract partial class Mission abstract partial class Mission
{ {
private readonly List<string> shownMessages = new List<string>();
public IEnumerable<string> ShownMessages
{
get { return shownMessages; }
}
public Color GetDifficultyColor()
{
int v = Difficulty ?? MissionPrefab.MinDifficulty;
float t = MathUtils.InverseLerp(MissionPrefab.MinDifficulty, MissionPrefab.MaxDifficulty, v);
return ToolBox.GradientLerp(t, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red);
}
public string GetMissionRewardText()
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward));
return TextManager.GetWithVariable("missionreward", "[reward]", $"‖color:gui.orange‖{rewardText}‖end‖");
}
public string GetReputationRewardText(Location currLocation)
{
List<string> reputationRewardTexts = new List<string>();
foreach (var reputationReward in ReputationRewards)
{
string name = "";
if (reputationReward.Key.Equals("location", StringComparison.OrdinalIgnoreCase))
{
name = $"‖color:gui.orange‖{currLocation.Name}‖end‖";
}
else
{
var faction = FactionPrefab.Prefabs.Find(f => f.Identifier.Equals(reputationReward.Key, StringComparison.OrdinalIgnoreCase));
if (faction != null)
{
name = $"‖color:{XMLExtensions.ColorToString(faction.IconColor)}‖{faction.Name}‖end‖";
}
else
{
name = TextManager.Get(reputationReward.Key);
}
}
float normalizedValue = MathUtils.InverseLerp(-100.0f, 100.0f, reputationReward.Value);
string formattedValue = ((int)reputationReward.Value).ToString("+#;-#;0"); //force plus sign for positive numbers
string rewardText = TextManager.GetWithVariables(
"reputationformat",
new string[] { "[reputationname]", "[reputationvalue]" },
new string[] { name, $"‖color:{XMLExtensions.ColorToString(Reputation.GetReputationColor(normalizedValue))}‖{formattedValue}‖end‖" });
reputationRewardTexts.Add(rewardText);
}
return TextManager.AddPunctuation(':', TextManager.Get("reputation"), string.Join(", ", reputationRewardTexts));
}
partial void ShowMessageProjSpecific(int missionState) partial void ShowMessageProjSpecific(int missionState)
{ {
int messageIndex = missionState - 1; int messageIndex = missionState - 1;
@@ -23,11 +79,17 @@ namespace Barotrauma
{ {
yield return new WaitForSeconds(1.0f); yield return new WaitForSeconds(1.0f);
} }
new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon) CreateMessageBox(header, message);
yield return CoroutineStatus.Success;
}
protected void CreateMessageBox(string header, string message)
{
shownMessages.Add(message);
new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon, parseRichText: true)
{ {
IconColor = Prefab.IconColor IconColor = Prefab.IconColor
}; };
yield return CoroutineStatus.Success;
} }
public void ClientRead(IReadMessage msg) public void ClientRead(IReadMessage msg)
@@ -1,19 +1,17 @@
using Microsoft.Xna.Framework; namespace Barotrauma
using System;
namespace Barotrauma
{ {
abstract partial class MissionMode : GameMode abstract partial class MissionMode : GameMode
{ {
public override void ShowStartMessage() public override void ShowStartMessage()
{ {
if (mission == null) return; foreach (Mission mission in missions)
new GUIMessageBox(mission.Name, mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon)
{ {
IconColor = mission.Prefab.IconColor, new GUIMessageBox(mission.Name, mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon, parseRichText: true)
UserData = "missionstartmessage" {
}; IconColor = mission.Prefab.IconColor,
UserData = "missionstartmessage"
};
}
} }
} }
} }
@@ -1,7 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
@@ -0,0 +1,18 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class OutpostDestroyMission : AbandonedOutpostMission
{
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
var item = Item.ReadSpawnData(msg);
items.Add(item);
}
}
}
}
@@ -422,12 +422,12 @@ namespace Barotrauma
} }
} }
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData, int rtdOffset = 0)
{ {
DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, richTextData); DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, richTextData, rtdOffset);
} }
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData, int rtdOffset = 0)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
@@ -457,15 +457,15 @@ namespace Barotrauma
Color currentTextColor; Color currentTextColor;
if (currentRichTextData != null && i > currentRichTextData.EndIndex + lineNum) while (currentRichTextData != null && i + rtdOffset > currentRichTextData.EndIndex + lineNum)
{ {
richTextDataIndex++; richTextDataIndex++;
currentRichTextData = richTextDataIndex < richTextData.Count ? richTextData[richTextDataIndex] : null; currentRichTextData = richTextDataIndex < richTextData.Count ? richTextData[richTextDataIndex] : null;
} }
if (currentRichTextData != null && currentRichTextData.StartIndex + lineNum <= i && i <= currentRichTextData.EndIndex + lineNum) if (currentRichTextData != null && currentRichTextData.StartIndex + lineNum <= i + rtdOffset && i + rtdOffset <= currentRichTextData.EndIndex + lineNum)
{ {
currentTextColor = currentRichTextData.Color ?? color; currentTextColor = currentRichTextData.Color * currentRichTextData.Alpha ?? color;
if (!string.IsNullOrEmpty(currentRichTextData.Metadata)) if (!string.IsNullOrEmpty(currentRichTextData.Metadata))
{ {
currentTextColor = Color.Lerp(currentTextColor, Color.White, 0.5f); currentTextColor = Color.Lerp(currentTextColor, Color.White, 0.5f);
@@ -321,6 +321,7 @@ namespace Barotrauma
float prevSize = chatBox.BarSize; float prevSize = chatBox.BarSize;
string displayedText = message.TranslatedText; string displayedText = message.TranslatedText;
string senderName = ""; string senderName = "";
Color senderColor = Color.White; Color senderColor = Color.White;
if (!string.IsNullOrWhiteSpace(message.SenderName)) if (!string.IsNullOrWhiteSpace(message.SenderName))
@@ -377,13 +378,29 @@ namespace Barotrauma
} }
var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform) var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameTimestamp == null ? 0 : senderNameTimestamp.Rect.Height) }, { AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameTimestamp == null ? 0 : senderNameTimestamp.Rect.Height) },
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true, displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f) color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f, parseRichText: true)
{ {
UserData = message.SenderName, UserData = message.SenderName,
CanBeFocused = true CanBeFocused = false
}; };
msgText.CalculateHeightFromText();
if (msgText.RichTextData != null)
{
foreach (var data in msgText.RichTextData)
{
var clickableArea = new GUITextBlock.ClickableArea()
{
Data = data
};
if (GameMain.NetLobbyScreen != null && GameMain.NetworkMember != null)
{
clickableArea.OnClick = GameMain.NetLobbyScreen.SelectPlayer;
}
msgText.ClickableAreas.Add(clickableArea);
}
}
if (message is OrderChatMessage orderChatMsg && if (message is OrderChatMessage orderChatMsg &&
Character.Controlled != null && Character.Controlled != null &&
@@ -444,7 +461,7 @@ namespace Barotrauma
senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height); senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
} }
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true) displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true, parseRichText: true)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -523,6 +540,7 @@ namespace Barotrauma
if (ToggleButton != null) if (ToggleButton != null)
{ {
ToggleButton.Selected = ToggleOpen;
ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height); ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
} }
@@ -566,6 +584,7 @@ namespace Barotrauma
if (ToggleOpen) if (ToggleOpen)
{ {
GUIFrame.CanBeFocused = true;
openState += deltaTime * 5.0f; openState += deltaTime * 5.0f;
//delete all popup messages when the chatbox is open //delete all popup messages when the chatbox is open
foreach (var popupMsg in popupMessages) foreach (var popupMsg in popupMessages)
@@ -576,6 +595,7 @@ namespace Barotrauma
} }
else else
{ {
GUIFrame.CanBeFocused = false;
openState -= deltaTime * 5.0f; openState -= deltaTime * 5.0f;
int yOffset = 0; int yOffset = 0;
@@ -1,10 +1,10 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -91,9 +91,10 @@ namespace Barotrauma
UserData = "container" UserData = "container"
}; };
int panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560));
var availableMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform) var availableMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform)
{ {
MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height) MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
}) })
{ {
Stretch = true, Stretch = true,
@@ -149,7 +150,7 @@ namespace Barotrauma
var pendingAndCrewMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform, anchor: Anchor.TopRight) var pendingAndCrewMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform, anchor: Anchor.TopRight)
{ {
MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height) MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
}) })
{ {
Stretch = true, Stretch = true,
@@ -177,7 +178,7 @@ namespace Barotrauma
var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center, var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center,
parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform) parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform)
{ {
MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height) MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
}).RectTransform)); }).RectTransform));
float height = 0.05f; float height = 0.05f;
@@ -188,7 +189,7 @@ namespace Barotrauma
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUI.SubHeadingFont);
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 +208,7 @@ namespace Barotrauma
{ {
ClickSound = GUISoundType.HireRepairClick, ClickSound = GUISoundType.HireRepairClick,
ForceUpperCase = true, ForceUpperCase = true,
OnClicked = (b, o) => ValidatePendingHires(true) OnClicked = (b, o) => ValidateHires(PendingHires, true)
}; };
clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall")) clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall"))
{ {
@@ -335,9 +336,9 @@ namespace Barotrauma
jobColor = characterInfo.Job.Prefab.UIColor; jobColor = characterInfo.Job.Prefab.UIColor;
} }
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, 55), parent: listBox.Content.RectTransform), "ListBoxElement") GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, (int)(GUI.yScale * 55)), parent: listBox.Content.RectTransform), "ListBoxElement")
{ {
UserData = new Tuple<CharacterInfo, float>(characterInfo, skill != null ? skill.Level : 0.0f) UserData = new Tuple<CharacterInfo, float>(characterInfo, skill?.Level ?? 0.0f)
}; };
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
@@ -345,19 +346,22 @@ namespace Barotrauma
}; };
float portraitWidth = (0.8f * mainGroup.Rect.Height) / mainGroup.Rect.Width; float portraitWidth = (0.8f * mainGroup.Rect.Height) / mainGroup.Rect.Width;
new GUICustomComponent(new RectTransform(new Vector2(portraitWidth, 0.8f), mainGroup.RectTransform), var icon = new GUICustomComponent(new RectTransform(new Vector2(portraitWidth, 0.8f), mainGroup.RectTransform),
onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())) onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
{ {
CanBeFocused = false CanBeFocused = false
}; };
GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)); GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)) { CanBeFocused = false };
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), GUILayoutGroup nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { CanBeFocused = false };
characterInfo.Name, textColor: jobColor, textAlignment: Alignment.BottomLeft) GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameGroup.RectTransform),
listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name,
textColor: jobColor, textAlignment: Alignment.BottomLeft)
{ {
CanBeFocused = false CanBeFocused = false
}; };
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width); nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Job.Name, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft) characterInfo.Job.Name, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft)
{ {
@@ -366,7 +370,7 @@ namespace Barotrauma
jobBlock.Text = ToolBox.LimitString(jobBlock.Text, jobBlock.Font, jobBlock.Rect.Width); jobBlock.Text = ToolBox.LimitString(jobBlock.Text, jobBlock.Font, jobBlock.Rect.Width);
float width = 0.6f / 3; float width = 0.6f / 3;
if (characterInfo.Job != null) if (characterInfo.Job != null && skill != null)
{ {
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true); GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true);
float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width; float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width;
@@ -383,11 +387,18 @@ namespace Barotrauma
if (listBox != crewList) if (listBox != crewList)
{ {
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), FormatCurrency(characterInfo.Salary), textAlignment: Alignment.Center) new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
FormatCurrency(characterInfo.Salary),
textAlignment: Alignment.Center)
{ {
CanBeFocused = false CanBeFocused = false
}; };
} }
else
{
// Just a bit of padding to make list layouts similar
new GUIFrame(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
}
if (listBox == hireableList) if (listBox == hireableList)
{ {
@@ -446,6 +457,23 @@ namespace Barotrauma
} }
}; };
} }
if (listBox == pendingList || listBox == crewList)
{
nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height));
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height));
Point size = new Point((int)(0.7f * nameBlock.Rect.Height));
new GUIImage(new RectTransform(size, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false };
size = new Point(3 * mainGroup.AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.Rect.Width, mainGroup.Rect.Height);
new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null)
{
Enabled = HasPermission,
ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", TextManager.Get($"input.{(PlayerInput.MouseButtonsSwapped() ? "rightmouse" : "leftmouse")}")),
UserData = characterInfo,
OnClicked = CreateRenamingComponent
};
}
} }
private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo) private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
@@ -478,7 +506,10 @@ namespace Barotrauma
GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true }; GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
float blockHeight = 1.0f / 4; float blockHeight = 1.0f / 4;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name")); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), characterInfo.Name); GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), "");
string name = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
nameBlock.Text = ToolBox.LimitString(name, nameBlock.Font, nameBlock.Rect.Width);
if (characterInfo.HasGenders) if (characterInfo.HasGenders)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("gender")); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("gender"));
@@ -553,9 +584,18 @@ namespace Barotrauma
if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); } if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); }
pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo)); pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo));
pendingList.UpdateScrollBarSize(); pendingList.UpdateScrollBarSize();
CreateCharacterFrame(characterInfo, hireableList);
SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData); // Server will reset the names to originals in multiplayer
hireableList.UpdateScrollBarSize(); if (!GameMain.IsMultiplayer) { characterInfo?.ResetName(); }
if (campaign.Map.CurrentLocation.HireManager.AvailableCharacters.Any(info => info.GetIdentifierUsingOriginalName() == characterInfo.GetIdentifierUsingOriginalName()) &&
hireableList.Content.Children.None(c => c.UserData is Tuple<CharacterInfo, float> userData && userData.Item1.GetIdentifierUsingOriginalName() == characterInfo.GetIdentifierUsingOriginalName()))
{
CreateCharacterFrame(characterInfo, hireableList);
SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData);
hireableList.UpdateScrollBarSize();
}
if (setTotalHireCost) { SetTotalHireCost(); } if (setTotalHireCost) { SetTotalHireCost(); }
if (createNetworkMessage) { SendCrewState(true); } if (createNetworkMessage) { SendCrewState(true); }
return true; return true;
@@ -572,36 +612,41 @@ namespace Barotrauma
{ {
if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; } if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; }
int total = 0; int total = 0;
pendingList.Content.Children.ForEach(c => total += (c.UserData as Tuple<CharacterInfo, float>).Item1.Salary); pendingList.Content.Children.ForEach(c =>
{
total += (c.UserData as Tuple<CharacterInfo, float>).Item1.Salary;
});
totalBlock.Text = FormatCurrency(total); totalBlock.Text = FormatCurrency(total);
bool enoughMoney = campaign != null ? total <= campaign.Money : true; bool enoughMoney = campaign != null ? total <= campaign.Money : true;
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red; totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any(); validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any();
} }
public bool ValidatePendingHires(bool createNetworkEvent = false) public bool ValidateHires(List<CharacterInfo> hires, bool createNetworkEvent = false)
{ {
List<CharacterInfo> hires = new List<CharacterInfo>(); if (hires == null || hires.None()) { return false; }
int total = 0;
foreach (GUIComponent c in pendingList.Content.Children.ToList())
{
if (c.UserData is Tuple<CharacterInfo, float> info)
{
hires.Add(info.Item1);
total += info.Item1.Salary;
}
}
if (hires.None() || total > campaign.Money) { return false; } List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>();
hires.ForEach(hireInfo =>
{
if(campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
{
nonDuplicateHires.Add(hireInfo);
}
});
if (nonDuplicateHires.None()) { return false; }
int total = nonDuplicateHires.Aggregate(0, (total, info) => total + info.Salary);
if (total > campaign.Money) { return false; }
bool atLeastOneHired = false; bool atLeastOneHired = false;
foreach (CharacterInfo ci in hires) foreach (CharacterInfo ci in nonDuplicateHires)
{ {
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci)) if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci))
{ {
atLeastOneHired = true; atLeastOneHired = true;
PendingHires.Remove(ci);
pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == ci));
} }
else else
{ {
@@ -628,6 +673,93 @@ namespace Barotrauma
return false; return false;
} }
private bool CreateRenamingComponent(GUIButton button, object userData)
{
if (!HasPermission || !(userData is CharacterInfo characterInfo)) { return false; }
var outerGlowFrame = new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black * 0.7f);
var frame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor: Anchor.Center)
{
MaxSize = new Point(400, 300).Multiply(GUI.Scale)
});
var layoutGroup = new GUILayoutGroup(new RectTransform((frame.Rect.Size - GUIStyle.ItemFrameMargin).Multiply(new Vector2(0.75f, 1.0f)), frame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
RelativeSpacing = 0.02f,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), layoutGroup.RectTransform), TextManager.Get("campaigncrew.givenickname"), font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
var groupElementSize = new Vector2(1.0f, 0.25f);
var nameBox = new GUITextBox(new RectTransform(groupElementSize, layoutGroup.RectTransform))
{
MaxTextLength = Client.MaxNameLength
};
new GUIButton(new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get("confirm"))
{
OnClicked = (button, userData) =>
{
if (RenameCharacter(characterInfo, nameBox.Text?.Trim()))
{
parentComponent.RemoveChild(outerGlowFrame);
return true;
}
else
{
nameBox.Flash(color: Color.Red);
return false;
}
}
};
new GUIButton(new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get("cancel"))
{
OnClicked = (button, userData) =>
{
parentComponent.RemoveChild(outerGlowFrame);
return true;
}
};
layoutGroup.Recalculate();
return true;
}
public bool RenameCharacter(CharacterInfo characterInfo, string newName)
{
if (characterInfo == null || string.IsNullOrEmpty(newName)) { return false; }
if (newName == characterInfo.Name) { return false; }
if (GameMain.IsMultiplayer)
{
SendCrewState(false, renameCharacter: (characterInfo, newName));
}
else
{
var crewComponent = crewList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo);
if (crewComponent != null)
{
crewList.Content.RemoveChild(crewComponent);
campaign.CrewManager.RenameCharacter(characterInfo, newName);
CreateCharacterFrame(characterInfo, crewList);
SortCharacters(crewList, SortingMethod.JobAsc);
}
else
{
var pendingComponent = pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo);
if (pendingComponent != null)
{
pendingList.Content.RemoveChild(pendingComponent);
campaign.Map.CurrentLocation.HireManager.RenameCharacter(characterInfo, newName);
CreateCharacterFrame(characterInfo, pendingList);
SortCharacters(pendingList, SortingMethod.JobAsc);
SetTotalHireCost();
}
else
{
return false;
}
}
}
return true;
}
private bool FireCharacter(GUIButton button, object selection) private bool FireCharacter(GUIButton button, object selection)
{ {
if (!(selection is CharacterInfo characterInfo)) { return false; } if (!(selection is CharacterInfo characterInfo)) { return false; }
@@ -648,9 +780,10 @@ namespace Barotrauma
UpdateLocationView(campaign.Map.CurrentLocation, false); UpdateLocationView(campaign.Map.CurrentLocation, false);
} }
if ((GUI.MouseOn?.UserData as Tuple<CharacterInfo, float>)?.Item1 is CharacterInfo characterInfo) (GUIComponent highlightedFrame, CharacterInfo highlightedInfo) = FindHighlightedCharacter(GUI.MouseOn);
if (highlightedFrame != null && highlightedInfo != null)
{ {
if (characterPreviewFrame == null || characterInfo != characterPreviewFrame.UserData) if (characterPreviewFrame == null || highlightedInfo != characterPreviewFrame.UserData)
{ {
GUIComponent component = GUI.MouseOn; GUIComponent component = GUI.MouseOn;
GUIListBox listBox = null; GUIListBox listBox = null;
@@ -673,7 +806,7 @@ namespace Barotrauma
if (listBox != null) if (listBox != null)
{ {
SelectCharacter(listBox, GUI.MouseOn as GUIFrame, characterInfo); SelectCharacter(listBox, highlightedFrame as GUIFrame, highlightedInfo);
} }
} }
else else
@@ -687,6 +820,27 @@ namespace Barotrauma
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame); characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null; characterPreviewFrame = null;
} }
static (GUIComponent, CharacterInfo) FindHighlightedCharacter(GUIComponent c)
{
if (c == null)
{
return default;
}
if (c.UserData is Tuple<CharacterInfo, float> highlightedData)
{
return (c, highlightedData.Item1);
}
if (c.Parent != null)
{
if (c.Parent is GUIListBox)
{
return default;
}
return FindHighlightedCharacter(c.Parent);
}
return default;
}
} }
public void SetPendingHires(List<int> characterInfos, Location location) public void SetPendingHires(List<int> characterInfos, Location location)
@@ -699,7 +853,7 @@ namespace Barotrauma
PendingHires.Clear(); PendingHires.Clear();
foreach (int identifier in characterInfos) foreach (int identifier in characterInfos)
{ {
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifier() == identifier); CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifierUsingOriginalName() == identifier);
if (match != null) if (match != null)
{ {
PendingHires.Add(match); PendingHires.Add(match);
@@ -716,9 +870,10 @@ namespace Barotrauma
/// Notify the server of crew changes /// Notify the server of crew changes
/// </summary> /// </summary>
/// <param name="updatePending">When set to true will tell the server to update the pending hires</param> /// <param name="updatePending">When set to true will tell the server to update the pending hires</param>
/// <param name="renameCharacter">When not null tell the server to rename this character. Item1 is the character to rename, Item2 is the new name, Item3 indicates whether the renamed character is already a part of the crew.</param>
/// <param name="firedCharacter">When not null tell the server to fire this character</param> /// <param name="firedCharacter">When not null tell the server to fire this character</param>
/// <param name="validateHires">When set to true will tell the server to validate pending hires</param> /// <param name="validateHires">When set to true will tell the server to validate pending hires</param>
public void SendCrewState(bool updatePending, CharacterInfo firedCharacter = null, bool validateHires = false) public void SendCrewState(bool updatePending, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
{ {
if (campaign is MultiPlayerCampaign) if (campaign is MultiPlayerCampaign)
{ {
@@ -731,12 +886,23 @@ namespace Barotrauma
msg.Write((ushort)PendingHires.Count); msg.Write((ushort)PendingHires.Count);
foreach (CharacterInfo pendingHire in PendingHires) foreach (CharacterInfo pendingHire in PendingHires)
{ {
msg.Write(pendingHire.GetIdentifier()); msg.Write(pendingHire.GetIdentifierUsingOriginalName());
} }
} }
msg.Write(validateHires); msg.Write(validateHires);
bool validRenaming = renameCharacter.info != null && !string.IsNullOrEmpty(renameCharacter.newName);
msg.Write(validRenaming);
if (validRenaming)
{
int identifier = renameCharacter.info.GetIdentifierUsingOriginalName();
msg.Write(identifier);
msg.Write(renameCharacter.newName);
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false;
msg.Write(existingCrewMember);
}
msg.Write(firedCharacter != null); msg.Write(firedCharacter != null);
if (firedCharacter != null) if (firedCharacter != null)
{ {
@@ -218,6 +218,21 @@ namespace Barotrauma
public static bool DisableHUD, DisableUpperHUD, DisableItemHighlights, DisableCharacterNames; public static bool DisableHUD, DisableUpperHUD, DisableItemHighlights, DisableCharacterNames;
private static bool isSavingIndicatorEnabled;
private static Color savingIndicatorColor = Color.Transparent;
private static bool IsSavingIndicatorVisible => savingIndicatorColor.A > 0;
private static float savingIndicatorSpriteIndex;
private static float savingIndicatorColorLerpAmount;
private static SavingIndicatorState savingIndicatorState = SavingIndicatorState.None;
private static float? timeUntilSavingIndicatorDisabled;
private enum SavingIndicatorState
{
None,
FadingIn,
FadingOut
}
public static void Init(GameWindow window, IEnumerable<ContentPackage> selectedContentPackages, GraphicsDevice graphicsDevice) public static void Init(GameWindow window, IEnumerable<ContentPackage> selectedContentPackages, GraphicsDevice graphicsDevice)
{ {
GraphicsDevice = graphicsDevice; GraphicsDevice = graphicsDevice;
@@ -345,7 +360,11 @@ namespace Barotrauma
} }
#endif #endif
if (DisableHUD) { return; } if (DisableHUD)
{
DrawSavingIndicator(spriteBatch);
return;
}
if (GameMain.ShowFPS || GameMain.DebugDraw) if (GameMain.ShowFPS || GameMain.DebugDraw)
{ {
@@ -539,60 +558,42 @@ namespace Barotrauma
} }
} }
IEnumerable<string> strings;
if (MouseOn != null) if (MouseOn != null)
{ {
RectTransform mouseOnRect = MouseOn.RectTransform; RectTransform mouseOnRect = MouseOn.RectTransform;
bool isAbsoluteOffsetInUse = mouseOnRect.AbsoluteOffset != Point.Zero || mouseOnRect.RelativeOffset == Vector2.Zero; bool isAbsoluteOffsetInUse = mouseOnRect.AbsoluteOffset != Point.Zero || mouseOnRect.RelativeOffset == Vector2.Zero;
string selectedString = $"Selected UI Element: {MouseOn.GetType().Name} ({ MouseOn.Style?.Element.Name.LocalName ?? "no style" }, {MouseOn.Rect}"; strings = new string[]
string offsetString = $"Relative Offset: {mouseOnRect.RelativeOffset} | Absolute Offset: {(isAbsoluteOffsetInUse ? mouseOnRect.AbsoluteOffset : mouseOnRect.ParentRect.MultiplySize(mouseOnRect.RelativeOffset))}{(isAbsoluteOffsetInUse ? "" : " (Calculated from RelativeOffset)")}"; {
string anchorPivotString = $"Anchor: {mouseOnRect.Anchor} | Pivot: {mouseOnRect.Pivot}"; $"Selected UI Element: {MouseOn.GetType().Name} ({ MouseOn.Style?.Element.Name.LocalName ?? "no style" }, {MouseOn.Rect}",
Vector2 selectedStringSize = SmallFont.MeasureString(selectedString); $"Relative Offset: {mouseOnRect.RelativeOffset} | Absolute Offset: {(isAbsoluteOffsetInUse ? mouseOnRect.AbsoluteOffset : mouseOnRect.ParentRect.MultiplySize(mouseOnRect.RelativeOffset))}{(isAbsoluteOffsetInUse ? "" : " (Calculated from RelativeOffset)")}",
Vector2 offsetStringSize = SmallFont.MeasureString(offsetString); $"Anchor: {mouseOnRect.Anchor} | Pivot: {mouseOnRect.Pivot}"
Vector2 anchorPivotStringSize = SmallFont.MeasureString(anchorPivotString); };
int padding = IntScale(10);
int yPos = padding;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)selectedStringSize.X - padding, yPos), selectedString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)selectedStringSize.Y + padding / 2;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)offsetStringSize.X - padding, yPos), offsetString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)offsetStringSize.Y + padding / 2;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)anchorPivotStringSize.X - padding, yPos), anchorPivotString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)anchorPivotStringSize.Y + padding / 2;
} }
else else
{ {
string guiScaleString = $"GUI.Scale: {Scale}"; strings = new string[]
string guixScaleString = $"GUI.xScale: {xScale}"; {
string guiyScaleString = $"GUI.yScale: {yScale}"; $"GUI.Scale: {Scale}",
string relativeHorizontalAspectRatioString = $"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}"; $"GUI.xScale: {xScale}",
string relativeVerticalAspectRatioString = $"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}"; $"GUI.yScale: {yScale}",
Vector2 guiScaleStringSize = SmallFont.MeasureString(guiScaleString); $"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}",
Vector2 guixScaleStringSize = SmallFont.MeasureString(guixScaleString); $"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}",
Vector2 guiyScaleStringSize = SmallFont.MeasureString(guiyScaleString); };
Vector2 relativeHorizontalAspectRatioStringSize = SmallFont.MeasureString(relativeHorizontalAspectRatioString); }
Vector2 relativeVerticalAspectRatioStringSize = SmallFont.MeasureString(relativeVerticalAspectRatioString);
int padding = IntScale(10); strings = strings.Concat(new string[] { $"Cam.Zoom: {Screen.Selected.Cam?.Zoom ?? 0f}" });
int yPos = padding;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiScaleStringSize.X - padding, yPos), guiScaleString, Color.LightGreen, Color.Black, 0, SmallFont); int padding = IntScale(10);
yPos += (int)guiScaleStringSize.Y + padding / 2; int yPos = padding;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guixScaleStringSize.X - padding, yPos), guixScaleString, Color.LightGreen, Color.Black, 0, SmallFont); foreach (string str in strings)
yPos += (int)guixScaleStringSize.Y + padding / 2; {
Vector2 stringSize = SmallFont.MeasureString(str);
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiyScaleStringSize.X - padding, yPos), guiyScaleString, Color.LightGreen, Color.Black, 0, SmallFont); DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)stringSize.X - padding, yPos), str, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)guiyScaleStringSize.Y + padding / 2; yPos += (int)stringSize.Y + padding / 2;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)relativeHorizontalAspectRatioStringSize.X - padding, yPos), relativeHorizontalAspectRatioString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)relativeHorizontalAspectRatioStringSize.Y + padding / 2;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)relativeVerticalAspectRatioStringSize.X - padding, yPos), relativeVerticalAspectRatioString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)relativeVerticalAspectRatioStringSize.Y + padding / 2;
} }
} }
@@ -645,6 +646,8 @@ namespace Barotrauma
} }
} }
DrawSavingIndicator(spriteBatch);
if (GameMain.WindowActive && !HideCursor) if (GameMain.WindowActive && !HideCursor)
{ {
spriteBatch.End(); spriteBatch.End();
@@ -855,6 +858,7 @@ namespace Barotrauma
lock (mutex) lock (mutex)
{ {
GUIMessageBox.AddActiveToGUIUpdateList(); GUIMessageBox.AddActiveToGUIUpdateList();
GUIContextMenu.AddActiveToGUIUpdateList();
if (pauseMenuOpen) if (pauseMenuOpen)
{ {
@@ -921,6 +925,7 @@ namespace Barotrauma
if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn) if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || c == prevMouseOn)
{ {
MouseOn = c; MouseOn = c;
var sakdjfnsjkd = c.MouseRect;
} }
break; break;
} }
@@ -1043,7 +1048,7 @@ namespace Barotrauma
} }
} }
if (parent != null) if (parent != null && parent.CanBeFocused)
{ {
if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; } if (!parent.Rect.Equals(monitorRect)) { return parent.HoverCursor; }
} }
@@ -1222,6 +1227,7 @@ namespace Barotrauma
Debug.Assert(updateList.Count == updateListSet.Count); Debug.Assert(updateList.Count == updateListSet.Count);
updateList.ForEach(c => c.UpdateAuto(deltaTime)); updateList.ForEach(c => c.UpdateAuto(deltaTime));
UpdateMessages(deltaTime); UpdateMessages(deltaTime);
UpdateSavingIndicator(deltaTime);
} }
} }
@@ -1266,6 +1272,58 @@ namespace Barotrauma
} }
private static void UpdateSavingIndicator(float deltaTime)
{
lock (mutex)
{
if (timeUntilSavingIndicatorDisabled.HasValue)
{
timeUntilSavingIndicatorDisabled -= deltaTime;
if (timeUntilSavingIndicatorDisabled <= 0.0f)
{
isSavingIndicatorEnabled = false;
timeUntilSavingIndicatorDisabled = null;
}
}
if (isSavingIndicatorEnabled)
{
if (savingIndicatorColor == Color.Transparent)
{
savingIndicatorState = SavingIndicatorState.FadingIn;
savingIndicatorColorLerpAmount = 0.0f;
}
else if (savingIndicatorColor == Color.White)
{
savingIndicatorState = SavingIndicatorState.None;
}
}
else
{
if (savingIndicatorColor == Color.White)
{
savingIndicatorState = SavingIndicatorState.FadingOut;
savingIndicatorColorLerpAmount = 0.0f;
}
else if (savingIndicatorColor == Color.Transparent)
{
savingIndicatorState = SavingIndicatorState.None;
}
}
if (savingIndicatorState != SavingIndicatorState.None)
{
bool isFadingIn = savingIndicatorState == SavingIndicatorState.FadingIn;
Color lerpStartColor = isFadingIn ? Color.Transparent : Color.White;
Color lerpTargetColor = isFadingIn ? Color.White : Color.Transparent;
savingIndicatorColorLerpAmount += (isFadingIn ? 2.0f : 0.5f) * deltaTime;
savingIndicatorColor = Color.Lerp(lerpStartColor, lerpTargetColor, savingIndicatorColorLerpAmount);
}
if (IsSavingIndicatorVisible)
{
savingIndicatorSpriteIndex = (savingIndicatorSpriteIndex + 15.0f * deltaTime) % (Style.SavingIndicator.FrameCount + 1);
}
}
}
#region Element drawing #region Element drawing
private static List<float> usedIndicatorAngles = new List<float>(); private static List<float> usedIndicatorAngles = new List<float>();
@@ -1278,7 +1336,7 @@ namespace Barotrauma
Vector2 diff = worldPosition - cam.WorldViewCenter; Vector2 diff = worldPosition - cam.WorldViewCenter;
float dist = diff.Length(); float dist = diff.Length();
float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier; float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale;
if (overrideAlpha.HasValue || dist > hideDist) if (overrideAlpha.HasValue || dist > hideDist)
{ {
@@ -1336,9 +1394,9 @@ namespace Barotrauma
} }
} }
public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1) public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, float width = 1)
{ {
DrawLine(sb, t, start, end, clr, depth, width); DrawLine(sb, t, start, end, clr, depth, (int)width);
} }
public static void DrawLine(SpriteBatch sb, Sprite sprite, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1) public static void DrawLine(SpriteBatch sb, Sprite sprite, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1)
@@ -1405,7 +1463,7 @@ namespace Barotrauma
font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, richTextData); font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, richTextData);
} }
public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, float thickness = 1)
{ {
if (size.X < 0) if (size.X < 0)
{ {
@@ -1420,7 +1478,7 @@ namespace Barotrauma
DrawRectangle(sb, new Rectangle((int)start.X, (int)start.Y, (int)size.X, (int)size.Y), clr, isFilled, depth, thickness); DrawRectangle(sb, new Rectangle((int)start.X, (int)start.Y, (int)size.X, (int)size.Y), clr, isFilled, depth, thickness);
} }
public static void DrawRectangle(SpriteBatch sb, Rectangle rect, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Rectangle rect, Color clr, bool isFilled = false, float depth = 0.0f, float thickness = 1)
{ {
if (isFilled) if (isFilled)
{ {
@@ -1428,15 +1486,15 @@ namespace Barotrauma
} }
else else
{ {
sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); Rectangle srcRect = new Rectangle(0, 0, 1, 1);
sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y + rect.Height - thickness, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); sb.Draw(t, new Vector2(rect.X, rect.Y), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(thickness, rect.Height), SpriteEffects.None, depth);
sb.Draw(t, new Vector2(rect.X + thickness, rect.Y), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(rect.Width - thickness, thickness), SpriteEffects.None, depth);
sb.Draw(t, new Rectangle(rect.X, rect.Y, thickness, rect.Height), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); sb.Draw(t, new Vector2(rect.X + thickness, rect.Bottom - thickness), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(rect.Width - thickness, thickness), SpriteEffects.None, depth);
sb.Draw(t, new Rectangle(rect.X + rect.Width - thickness, rect.Y, thickness, rect.Height), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth); sb.Draw(t, new Vector2(rect.Right - thickness, rect.Y + thickness), srcRect, clr, 0.0f, Vector2.Zero, new Vector2(thickness, rect.Height - thickness * 2f), SpriteEffects.None, depth);
} }
} }
public static void DrawRectangle(SpriteBatch sb, Vector2 center, float width, float height, float rotation, Color clr, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2 center, float width, float height, float rotation, Color clr, float depth = 0.0f, float thickness = 1)
{ {
Matrix rotate = Matrix.CreateRotationZ(rotation); Matrix rotate = Matrix.CreateRotationZ(rotation);
@@ -1453,7 +1511,7 @@ namespace Barotrauma
DrawLine(sb, bottomLeft, topLeft, clr, depth, thickness); DrawLine(sb, bottomLeft, topLeft, clr, depth, thickness);
} }
public static void DrawRectangle(SpriteBatch sb, Vector2[] corners, Color clr, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2[] corners, Color clr, float depth = 0.0f, float thickness = 1)
{ {
if (corners.Length != 4) if (corners.Length != 4)
{ {
@@ -1590,6 +1648,14 @@ namespace Barotrauma
ShapeExtensions.DrawPoint(spriteBatch, pos, color, dotSize); ShapeExtensions.DrawPoint(spriteBatch, pos, color, dotSize);
} }
} }
private static void DrawSavingIndicator(SpriteBatch spriteBatch)
{
if (!IsSavingIndicatorVisible) { return; }
var sheet = Style.SavingIndicator;
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
}
#endregion #endregion
#region Element creation #region Element creation
@@ -2246,7 +2312,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; } if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; }
if (GameMain.GameSession.GameMode is CampaignMode || (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition)) if (GameMain.GameSession.GameMode is CampaignMode || (!Submarine.MainSub.AtStartExit && !Submarine.MainSub.AtEndExit))
{ {
var msgBox = new GUIMessageBox("", var msgBox = new GUIMessageBox("",
TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"), TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"),
@@ -2343,6 +2409,21 @@ namespace Barotrauma
float aspectRatio = HorizontalAspectRatio; float aspectRatio = HorizontalAspectRatio;
return aspectRatio > 1.3f && aspectRatio < 1.4f; return aspectRatio > 1.3f && aspectRatio < 1.4f;
} }
public static void SetSavingIndicatorState(bool enabled)
{
if (enabled)
{
timeUntilSavingIndicatorDisabled = null;
}
isSavingIndicatorEnabled = enabled;
}
public static void DisableSavingIndicatorDelayed(float delay = 3.0f)
{
if (!isSavingIndicatorEnabled) { return; }
timeUntilSavingIndicatorDisabled = delay;
}
#endregion #endregion
} }
} }
@@ -77,6 +77,12 @@ namespace Barotrauma
return RectTransform.IsParentOf(component.RectTransform, recursive); return RectTransform.IsParentOf(component.RectTransform, recursive);
} }
public bool IsChildOf(GUIComponent component, bool recursive = true)
{
if (component == null) { return false; }
return RectTransform.IsChildOf(component.RectTransform, recursive);
}
public virtual void RemoveChild(GUIComponent child) public virtual void RemoveChild(GUIComponent child)
{ {
if (child == null) { return; } if (child == null) { return; }
@@ -706,6 +712,29 @@ namespace Barotrauma
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData); DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData);
} }
public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Vector2 pos, List<RichTextData> richTextData = null)
{
if (Tutorials.Tutorial.ContentRunning) { return; }
int width = (int)(400 * GUI.Scale);
int height = (int)(18 * GUI.Scale);
Point padding = new Point((int)(10 * GUI.Scale));
if (toolTipBlock == null || (string)toolTipBlock.userData != toolTip)
{
toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), richTextData, toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip");
toolTipBlock.RectTransform.NonScaledSize = new Point(
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z),
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W));
toolTipBlock.userData = toolTip;
}
toolTipBlock.RectTransform.AbsoluteOffset = pos.ToPoint();
toolTipBlock.SetTextPos();
toolTipBlock.DrawManually(spriteBatch);
}
public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement, List<RichTextData> richTextData = null) public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement, List<RichTextData> richTextData = null)
{ {
if (Tutorials.Tutorial.ContentRunning) { return; } if (Tutorials.Tutorial.ContentRunning) { return; }
@@ -0,0 +1,292 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
struct ContextMenuOption
{
public string Label;
public Action OnSelected;
public ContextMenuOption[]? SubOptions;
public bool IsEnabled;
public string Tooltip;
// Creates a regular context menu
public ContextMenuOption(string label, bool isEnabled, Action onSelected)
{
Label = TextManager.Get(label, returnNull: true) ?? label;
OnSelected = onSelected;
IsEnabled = isEnabled;
SubOptions = null;
Tooltip = string.Empty;
}
// Creates a option with a sub context menu
public ContextMenuOption(string label, bool isEnabled, params ContextMenuOption[] options): this(label, isEnabled, () => { })
{
SubOptions = options;
}
}
internal class GUIContextMenu : GUIComponent
{
public static GUIContextMenu? CurrentContextMenu;
private readonly Dictionary<ContextMenuOption, GUITextBlock> Options = new Dictionary<ContextMenuOption, GUITextBlock>();
private GUIContextMenu? SubMenu;
public readonly GUITextBlock? HeaderLabel;
public GUITextBlock? ParentOption;
/// <summary>
/// Creates a context menu. This constructor does not make the context menu active.
/// Use <see cref="CreateContextMenu(Barotrauma.ContextMenuOption[])"/> to make right click context menus.
/// </summary>
/// <param name="position">Position at which to create the context menu</param>
/// <param name="header">Header text</param>
/// <param name="style">Background style</param>
/// <param name="options">list of context menu options</param>
public GUIContextMenu(Vector2? position, string header, string style, params ContextMenuOption[] options) : base(style, new RectTransform(Point.Zero, GUI.Canvas))
{
Vector2 pos = position ?? PlayerInput.MousePosition;
ScalableFont headerFont = GUI.SubHeadingFont;
ScalableFont font = GUI.SmallFont; // font the context menu options use
Vector4 padding = new Vector4(4), headerPadding = new Vector4(8);
int horizontalPadding = (int) (padding.X + padding.Z), verticalPadding = (int) (padding.Y + padding.W);
bool hasHeader = !string.IsNullOrWhiteSpace(header);
//----------------------------------------------------------------------------------
// Estimate the size of the context menu
//----------------------------------------------------------------------------------
Dictionary<ContextMenuOption, Vector2> optionsAndSizes = new Dictionary<ContextMenuOption, Vector2>();
// estimate how big the context menu needs to be
Point estimatedSize = new Point(horizontalPadding, verticalPadding);
if (hasHeader)
{
InflateSize(ref estimatedSize, header, headerFont);
}
foreach (ContextMenuOption option in options)
{
Vector2 optionSize = InflateSize(ref estimatedSize, option.Label, font);
optionsAndSizes.Add(option, optionSize);
}
// it's better to overestimate the size since it's going to be cropped anyways
estimatedSize = estimatedSize.Multiply(1.2f);
RectTransform.NonScaledSize = estimatedSize;
RectTransform.AbsoluteOffset = pos.ToPoint();
//----------------------------------------------------------------------------------
// Construct the GUI elements
//----------------------------------------------------------------------------------
GUILayoutGroup background = new GUILayoutGroup(new RectTransform(Vector2.One, RectTransform, Anchor.Center));
if (hasHeader)
{
HeaderLabel = new GUITextBlock(new RectTransform(new Vector2(1f, 0.2f), background.RectTransform), header, font: headerFont) { Padding = headerPadding };
}
GUIListBox optionList = new GUIListBox(new RectTransform(new Vector2(1f, hasHeader ? 0.8f : 1f), background.RectTransform), style: null)
{
AutoHideScrollBar = false,
ScrollBarVisible = false,
Padding = hasHeader ? new Vector4(4, 0, 4, 4) : padding
};
foreach (var (option, size) in optionsAndSizes)
{
GUITextBlock optionElement = new GUITextBlock(new RectTransform(size.ToPoint(), optionList.Content.RectTransform), option.Label, font: font)
{
UserData = option,
Enabled = option.IsEnabled
};
Options.Add(option, optionElement);
if (!string.IsNullOrWhiteSpace(option.Tooltip) && optionElement.Enabled)
{
optionElement.ToolTip = option.Tooltip;
}
if (!option.IsEnabled)
{
optionElement.TextColor *= 0.5f;
}
}
//----------------------------------------------------------------------------------
// Positioning and cropping the context menu
//----------------------------------------------------------------------------------
List<GUIComponent> children = optionList.Content.Children.ToList();
// Resize all children to the size of their text
foreach (GUITextBlock block in children.Where(c => c is GUITextBlock).Cast<GUITextBlock>())
{
block.RectTransform.NonScaledSize = new Point((int) (block.TextSize.X + (block.Padding.X + block.Padding.Z)), (int) (18 * GUI.Scale));
}
int largestWidth = children.Max(c => c.Rect.Width + horizontalPadding);
// if the header is bigger than any of the options then overwrite
if (HeaderLabel != null)
{
RectTransform headerTransform = HeaderLabel.RectTransform;
headerTransform.MinSize = new Point((int) (HeaderLabel.TextSize.X + (headerPadding.X + headerPadding.Z)), headerTransform.NonScaledSize.Y);
if (largestWidth < headerTransform.MinSize.X)
{
largestWidth = headerTransform.MinSize.X;
}
}
// resize all children to the size of the longest element
foreach (GUIComponent c in children)
{
c.RectTransform.MinSize = new Point(largestWidth, c.Rect.Height);
}
// the cropped size of the option list
Point newSize = new Point(largestWidth, children.Sum(c => c.Rect.Height) + verticalPadding);
// resize the menu itself taking into account the option menus relative Y size
RectTransform.NonScaledSize = new Point(newSize.X, (int) (newSize.Y / optionList.RectTransform.RelativeSize.Y));
optionList.RectTransform.NonScaledSize = newSize;
// move the context menu if it would go outside of screen
if (RectTransform.Rect.Bottom > GameMain.GraphicsHeight)
{
Rectangle rect = RectTransform.Rect;
RectTransform.AbsoluteOffset = new Point(rect.X, rect.Y - rect.Height);
}
if (RectTransform.Rect.Right > GameMain.GraphicsWidth)
{
Rectangle rect = RectTransform.Rect;
RectTransform.AbsoluteOffset = new Point(rect.X - rect.Width, rect.Y);
}
background.Recalculate();
optionList.OnSelected = OnSelected;
}
public static GUIContextMenu CreateContextMenu(params ContextMenuOption[] options) => CreateContextMenu(PlayerInput.MousePosition, string.Empty, null, options);
public static GUIContextMenu CreateContextMenu(Vector2? pos, string header, Color? headerColor, params ContextMenuOption[] options)
{
GUIContextMenu menu = new GUIContextMenu(pos,header, "GUIToolTip", options);
if (headerColor != null)
{
menu.HeaderLabel?.OverrideTextColor(headerColor.Value);
}
CurrentContextMenu = menu;
return menu;
}
private bool OnSelected(GUIComponent _, object data)
{
if (data is ContextMenuOption option && option.IsEnabled)
{
CurrentContextMenu = null;
option.OnSelected();
return true;
}
return false;
}
/// <summary>
/// Inflates a point by the size of the text
/// </summary>
/// <param name="size">Pint to resize</param>
/// <param name="label">String whose size to inflate by</param>
/// <param name="font">What font to use</param>
/// <returns>The size of the text</returns>
private Vector2 InflateSize(ref Point size, string label, ScalableFont font)
{
Vector2 textSize = font.MeasureString(label);
size.X = Math.Max((int) Math.Ceiling(textSize.X), size.X);
size.Y += (int) Math.Ceiling(textSize.Y);
return textSize;
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
// keep the parent highlighted
if (ParentOption != null)
{
ParentOption.State = ComponentState.Hover;
}
if (SubMenu != null && !SubMenu.IsMouseOver())
{
SubMenu = null;
return;
}
foreach (var (option, textBlock) in Options)
{
// Create a new sub context menu if hovering over an option with sub options
if (GUI.MouseOn != textBlock) { continue; }
if (option.IsEnabled && option.SubOptions is { } subOptions && subOptions.Any())
{
Vector2 subMenuPos = new Vector2(textBlock.MouseRect.Right + 4, textBlock.MouseRect.Y);
SubMenu = new GUIContextMenu(subMenuPos, "", "GUIToolTip", subOptions)
{
ParentOption = textBlock
};
}
}
}
/// <summary>
/// Checks if the mouse cursor is over this context menu or any of its sub menus
/// </summary>
/// <returns></returns>
private bool IsMouseOver()
{
Rectangle expandedRect = Rect;
expandedRect.Inflate(20, 20);
bool isMouseOn = expandedRect.Contains(PlayerInput.MousePosition);
if (ParentOption != null)
{
isMouseOn |= GUI.MouseOn == ParentOption;
}
// Recursively check sub context menus
if (!isMouseOn && SubMenu != null)
{
isMouseOn = SubMenu.IsMouseOver();
}
return isMouseOn;
}
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
base.AddToGUIUpdateList(ignoreChildren, order);
SubMenu?.AddToGUIUpdateList();
}
public static void AddActiveToGUIUpdateList()
{
if (CurrentContextMenu != null && !CurrentContextMenu.IsMouseOver())
{
CurrentContextMenu = null;
}
CurrentContextMenu?.AddToGUIUpdateList();
}
}
}
@@ -194,6 +194,8 @@ namespace Barotrauma
public bool ScrollBarEnabled { get; set; } = true; public bool ScrollBarEnabled { get; set; } = true;
public bool KeepSpaceForScrollBar { get; set; } public bool KeepSpaceForScrollBar { get; set; }
public bool CanTakeKeyBoardFocus { get; set; } = true;
public bool ScrollBarVisible public bool ScrollBarVisible
{ {
get get
@@ -214,7 +216,22 @@ namespace Barotrauma
public bool AutoHideScrollBar { get; set; } = true; public bool AutoHideScrollBar { get; set; } = true;
private bool IsScrollBarOnDefaultSide { get; set; } private bool IsScrollBarOnDefaultSide { get; set; }
public bool CanDragElements { get; set; } = false; public bool CanDragElements
{
get
{
return canDragElements;
}
set
{
if (value == false && canDragElements && draggedElement != null)
{
draggedElement = null;
}
canDragElements = value;
}
}
private bool canDragElements = false;
private GUIComponent draggedElement; private GUIComponent draggedElement;
private Rectangle draggedReferenceRectangle; private Rectangle draggedReferenceRectangle;
private Point draggedReferenceOffset; private Point draggedReferenceOffset;
@@ -223,9 +240,12 @@ namespace Barotrauma
private bool scheduledScroll = false; private bool scheduledScroll = false;
private readonly bool isHorizontal;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param> /// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT)
{ {
this.isHorizontal = isHorizontal;
HoverCursor = CursorState.Hand; HoverCursor = CursorState.Hand;
CanBeFocused = true; CanBeFocused = true;
selected = new List<GUIComponent>(); selected = new List<GUIComponent>();
@@ -454,22 +474,42 @@ namespace Barotrauma
} }
else else
{ {
draggedElement.RectTransform.AbsoluteOffset = draggedReferenceOffset + new Point(0, (int)PlayerInput.MousePosition.Y - draggedReferenceRectangle.Center.Y); draggedElement.RectTransform.AbsoluteOffset = isHorizontal ?
draggedReferenceOffset + new Point((int)PlayerInput.MousePosition.X - draggedReferenceRectangle.Center.X, 0) :
draggedReferenceOffset + new Point(0, (int)PlayerInput.MousePosition.Y - draggedReferenceRectangle.Center.Y);
int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform); int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform);
int currIndex = index; int currIndex = index;
while (currIndex > 0 && PlayerInput.MousePosition.Y < draggedReferenceRectangle.Top) if (isHorizontal)
{ {
currIndex--; while (currIndex > 0 && PlayerInput.MousePosition.X < draggedReferenceRectangle.Left)
draggedReferenceRectangle.Y -= draggedReferenceRectangle.Height; {
draggedReferenceOffset.Y -= draggedReferenceRectangle.Height; currIndex--;
draggedReferenceRectangle.X -= draggedReferenceRectangle.Width;
draggedReferenceOffset.X -= draggedReferenceRectangle.Width;
}
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.X > draggedReferenceRectangle.Right)
{
currIndex++;
draggedReferenceRectangle.X += draggedReferenceRectangle.Width;
draggedReferenceOffset.X += draggedReferenceRectangle.Width;
}
} }
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.Y > draggedReferenceRectangle.Bottom) else
{ {
currIndex++; while (currIndex > 0 && PlayerInput.MousePosition.Y < draggedReferenceRectangle.Top)
draggedReferenceRectangle.Y += draggedReferenceRectangle.Height; {
draggedReferenceOffset.Y += draggedReferenceRectangle.Height; currIndex--;
draggedReferenceRectangle.Y -= draggedReferenceRectangle.Height;
draggedReferenceOffset.Y -= draggedReferenceRectangle.Height;
}
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.Y > draggedReferenceRectangle.Bottom)
{
currIndex++;
draggedReferenceRectangle.Y += draggedReferenceRectangle.Height;
draggedReferenceOffset.Y += draggedReferenceRectangle.Height;
}
} }
if (currIndex != index) if (currIndex != index)
@@ -853,7 +893,7 @@ namespace Barotrauma
} }
// If one of the children is the subscriber, we don't want to register, because it will unregister the child. // If one of the children is the subscriber, we don't want to register, because it will unregister the child.
if (takeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) if (takeKeyBoardFocus && CanTakeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber))
{ {
Selected = true; Selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
@@ -945,7 +985,8 @@ namespace Barotrauma
{ {
if (child == null) { return; } if (child == null) { return; }
child.RectTransform.Parent = null; child.RectTransform.Parent = null;
if (selected.Contains(child)) selected.Remove(child); if (selected.Contains(child)) { selected.Remove(child); }
if (draggedElement == child) { draggedElement = null; }
UpdateScrollBarSize(); UpdateScrollBarSize();
} }
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -22,11 +23,11 @@ namespace Barotrauma
{ {
Default, Default,
InGame, InGame,
Vote Vote,
Hint
} }
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>(); public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
//public GUIFrame BackgroundFrame { get; private set; }
public GUILayoutGroup Content { get; private set; } public GUILayoutGroup Content { get; private set; }
public GUIFrame InnerFrame { get; private set; } public GUIFrame InnerFrame { get; private set; }
public GUITextBlock Header { get; private set; } public GUITextBlock Header { get; private set; }
@@ -58,8 +59,6 @@ namespace Barotrauma
public bool AutoClose; public bool AutoClose;
private readonly bool alwaysVisible;
private float openState; private float openState;
private float iconState; private float iconState;
private bool iconSwitching; private bool iconSwitching;
@@ -77,10 +76,17 @@ namespace Barotrauma
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
} }
public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null) public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null, bool parseRichText = false)
: base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox") : base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
{ {
int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0; int width = (int)(DefaultWidth * type switch
{
Type.Default => 1.0f,
Type.Hint => 1.25f,
_ => 1.5f
});
int height = 0;
if (relativeSize.HasValue) if (relativeSize.HasValue)
{ {
width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X); width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X);
@@ -107,6 +113,7 @@ namespace Barotrauma
Anchor anchor = type switch Anchor anchor = type switch
{ {
Type.InGame => Anchor.TopCenter, Type.InGame => Anchor.TopCenter,
Type.Hint => Anchor.TopRight,
Type.Vote => Anchor.TopRight, Type.Vote => Anchor.TopRight,
_ => Anchor.Center _ => Anchor.Center
}; };
@@ -127,13 +134,13 @@ namespace Barotrauma
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 }; Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true); headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Header, "", this); GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text)) if (!string.IsNullOrWhiteSpace(text))
{ {
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true); Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Text, "", this); GUI.Style.Apply(Text, "", this);
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height); new Point(Text.Rect.Width, Text.Rect.Height);
@@ -180,7 +187,6 @@ namespace Barotrauma
else if (type == Type.InGame) else if (type == Type.InGame)
{ {
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
alwaysVisible = true;
CanBeFocused = false; CanBeFocused = false;
AutoClose = true; AutoClose = true;
GUI.Style.Apply(InnerFrame, "", this); GUI.Style.Apply(InnerFrame, "", this);
@@ -235,13 +241,13 @@ namespace Barotrauma
}; };
} }
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true); Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Header, "", this); GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text)) if (!string.IsNullOrWhiteSpace(text))
{ {
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true); Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true, parseRichText: parseRichText);
GUI.Style.Apply(Text, "", this); GUI.Style.Apply(Text, "", this);
Content.Recalculate(); Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
@@ -266,30 +272,184 @@ namespace Barotrauma
} }
Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y); Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y);
} }
else if (type == Type.Hint)
{
CanBeFocused = false;
GUI.Style.Apply(InnerFrame, "", this);
Point absoluteSpacing = GUIStyle.ItemFrameMargin.Multiply(1.0f / 5.0f);
var verticalLayoutGroup = new GUILayoutGroup(new RectTransform(GetVerticalLayoutGroupSize(), parent: InnerFrame.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = absoluteSpacing.Y,
Stretch = true
};
var topHorizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.7f), verticalLayoutGroup.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
int iconMaxHeight = 0;
if (icon != null)
{
Icon = new GUIImage(new RectTransform(new Vector2(0.15f, 0.95f), topHorizontalLayoutGroup.RectTransform), icon, scaleToFit: true);
iconMaxHeight = (int)Icon.Sprite.size.Y;
}
else
{
bool iconStyleDefined = !string.IsNullOrEmpty(iconStyle);
Icon = new GUIImage(new RectTransform(new Vector2(0.15f, 0.95f), topHorizontalLayoutGroup.RectTransform),
iconStyleDefined ? iconStyle : "GUIButtonInfo", scaleToFit: true);
if (!iconStyleDefined)
{
Icon.Color = Color.Orange;
}
iconMaxHeight = (int)(Icon.Style.GetDefaultSprite()?.size.Y ?? GUI.yScale * 40);
}
iconMaxHeight = Math.Min((int)(GUI.yScale * 40), iconMaxHeight);
int iconMinHeight = Math.Min((int)(GUI.yScale * 40), iconMaxHeight);
Icon.RectTransform.MinSize = new Point(Icon.Rect.Width, iconMinHeight);
Icon.RectTransform.MaxSize = new Point(Icon.Rect.Width, iconMaxHeight);
Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.85f : 1.0f, 1.0f), topHorizontalLayoutGroup.RectTransform))
{
AbsoluteSpacing = absoluteSpacing.Y,
};
var bottomContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.3f), verticalLayoutGroup.RectTransform), style: null);
var tickBoxLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.67f, 1.0f), bottomContainer.RectTransform, anchor: Anchor.CenterLeft),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
var dontShowAgainTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickBoxLayoutGroup.RectTransform),
TextManager.Get("hintmessagebox.dontshowagain"))
{
ToolTip = TextManager.Get("hintmessagebox.dontshowagaintooltip"),
UserData = "dontshowagain"
};
//var disableHintsTickBox = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), tickBoxLayoutGroup.RectTransform),
// TextManager.Get("hintmessagebox.disablehints"))
//{
// ToolTip = TextManager.Get("hintmessagebox.disablehintstooltip"),
// UserData = "disablehints"
//};
Buttons = new List<GUIButton>(1)
{
new GUIButton(new RectTransform(new Vector2(0.33f, 1.0f), bottomContainer.RectTransform, Anchor.CenterRight),
text: TextManager.Get("hintmessagebox.dismiss"), style: "GUIButtonSmall")
{
OnClicked = Close
}
};
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true);
GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text))
{
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
Text.RectTransform.IsFixedSize = true;
if (string.IsNullOrWhiteSpace(headerText))
{
Header.RectTransform.Parent = null;
Content.ChildAnchor = Anchor.Center;
}
}
if (height == 0)
{
height = absoluteSpacing.Y;
int upperContainerHeight = absoluteSpacing.Y;
if (Header.Rect.Height > 0) { upperContainerHeight += Header.Rect.Height + Content.AbsoluteSpacing; }
if (Text != null) { upperContainerHeight += Text.Rect.Height + Content.AbsoluteSpacing; }
upperContainerHeight = Math.Max(upperContainerHeight, Icon.Rect.Height);
height += upperContainerHeight;
height += absoluteSpacing.Y;
height += (int)((bottomContainer.RectTransform.RelativeSize.Y / topHorizontalLayoutGroup.RectTransform.RelativeSize.Y) * upperContainerHeight);
height += absoluteSpacing.Y;
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, height);
verticalLayoutGroup.RectTransform.NonScaledSize = GetVerticalLayoutGroupSize();
verticalLayoutGroup.Recalculate();
topHorizontalLayoutGroup.Recalculate();
Content.Recalculate();
tickBoxLayoutGroup.Recalculate();
}
InnerFrame.RectTransform.AbsoluteOffset = new Point(GUI.IntScale(64), -InnerFrame.Rect.Height);
Point GetVerticalLayoutGroupSize()
{
return InnerFrame.Rect.Size - absoluteSpacing.Multiply(2);
}
}
MessageBoxes.Add(this); MessageBoxes.Add(this);
} }
/// <summary>
/// Use to create a message box of Hint type
/// </summary>
public GUIMessageBox(string hintIdentifier, string text, Sprite icon) : this("", text, new string[0], textAlignment: Alignment.CenterLeft, type: Type.Hint, icon: icon)
{
if (InnerFrame.FindChild("dontshowagain", recursive: true) is GUITickBox dontShowAgainTickBox)
{
dontShowAgainTickBox.OnSelected = HintManager.OnDontShowAgain;
dontShowAgainTickBox.UserData = hintIdentifier;
}
if (InnerFrame.FindChild("disablehints", recursive: true) is GUITickBox disableHintsTickBox)
{
disableHintsTickBox.OnSelected = HintManager.OnDisableHints;
disableHintsTickBox.UserData = hintIdentifier;
}
}
private static Type[] messageBoxTypes;
public static void AddActiveToGUIUpdateList() public static void AddActiveToGUIUpdateList()
{ {
for (int i = 0; i < MessageBoxes.Count; i++) messageBoxTypes ??= (Type[])Enum.GetValues(typeof(Type));
foreach (var type in messageBoxTypes)
{ {
if (MessageBoxes[i] is GUIMessageBox alwaysVisibleMsgBox && alwaysVisibleMsgBox.alwaysVisible) // Don't display hints when HUD is disabled
if (type == Type.Hint && GUI.DisableHUD) { continue; }
for (int i = 0; i < MessageBoxes.Count; i++)
{ {
alwaysVisibleMsgBox.AddToGUIUpdateList(); if (MessageBoxes[i] == null) { continue; }
break; if (!(MessageBoxes[i] is GUIMessageBox messageBox))
} {
} if (type == Type.Default)
for (int i = MessageBoxes.Count - 1; i >= 0; i--) {
{ // Message box not of type GUIMessageBox is likely the round summary
if (MessageBoxes[i].UserData as string == "verificationprompt" || MessageBoxes[i].AddToGUIUpdateList();
MessageBoxes[i].UserData as string == "bugreporter") break;
{ }
continue; continue;
} }
if (!(MessageBoxes[i] is GUIMessageBox msgBox) || !msgBox.alwaysVisible) if (messageBox.type != type) { continue; }
{
MessageBoxes[i].AddToGUIUpdateList(); // These are handled separately in GUI.HandlePersistingElements()
if (MessageBoxes[i].UserData as string == "verificationprompt") { continue; }
if (MessageBoxes[i].UserData as string == "bugreporter") { continue; }
messageBox.AddToGUIUpdateList();
break; break;
} }
} }
@@ -337,11 +497,21 @@ namespace Barotrauma
} }
} }
if (type == Type.InGame) if (type == Type.InGame || type == Type.Hint)
{ {
Vector2 initialPos = new Vector2(0.0f, GameMain.GraphicsHeight); Vector2 initialPos, defaultPos, endPos;
Vector2 defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale); if (type == Type.InGame)
Vector2 endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y); {
initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
}
else
{
initialPos = new Vector2(GUI.IntScale(64), -InnerFrame.Rect.Height);
defaultPos = new Vector2(initialPos.X, HUDLayoutSettings.ButtonAreaTop.Height + GUI.IntScale(64));
endPos = new Vector2(-InnerFrame.Rect.Width, defaultPos.Y);
}
if (!closing) if (!closing)
{ {
@@ -428,7 +598,7 @@ namespace Barotrauma
public void Close() public void Close()
{ {
if (type == Type.InGame) if (type == Type.InGame || type == Type.Hint)
{ {
closing = true; closing = true;
} }
@@ -34,6 +34,11 @@ namespace Barotrauma
public readonly Sprite[] CursorSprite = new Sprite[7]; public readonly Sprite[] CursorSprite = new Sprite[7];
public UISprite RadiationSprite { get; private set; }
public SpriteSheet RadiationAnimSpriteSheet { get; private set; }
public SpriteSheet SavingIndicator { get; private set; }
public UISprite UIGlow { get; private set; } public UISprite UIGlow { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
@@ -77,6 +82,12 @@ namespace Barotrauma
public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f; public Color TextColorDim { get; private set; } = Color.White * 0.6f;
public Color ColorReputationVeryLow { get; private set; } = Color.Red;
public Color ColorReputationLow { get; private set; } = Color.Orange;
public Color ColorReputationNeutral { get; private set; } = Color.White * 0.8f;
public Color ColorReputationHigh { get; private set; } = Color.LightBlue;
public Color ColorReputationVeryHigh { get; private set; } = Color.Blue;
// Inventory // Inventory
public Color EquipmentSlotIconColor { get; private set; } = new Color(99, 70, 64); public Color EquipmentSlotIconColor { get; private set; } = new Color(99, 70, 64);
@@ -167,6 +178,21 @@ namespace Barotrauma
case "textcolor": case "textcolor":
TextColor = subElement.GetAttributeColor("color", TextColor); TextColor = subElement.GetAttributeColor("color", TextColor);
break; break;
case "colorreputationverylow":
ColorReputationVeryLow = subElement.GetAttributeColor("color", TextColor);
break;
case "colorreputationlow":
ColorReputationLow = subElement.GetAttributeColor("color", TextColor);
break;
case "colorreputationneutral":
ColorReputationNeutral = subElement.GetAttributeColor("color", TextColor);
break;
case "colorreputationhigh":
ColorReputationHigh = subElement.GetAttributeColor("color", TextColor);
break;
case "colorreputationveryhigh":
ColorReputationVeryHigh = subElement.GetAttributeColor("color", TextColor);
break;
case "equipmentsloticoncolor": case "equipmentsloticoncolor":
EquipmentSlotIconColor = subElement.GetAttributeColor("color", EquipmentSlotIconColor); EquipmentSlotIconColor = subElement.GetAttributeColor("color", EquipmentSlotIconColor);
break; break;
@@ -209,6 +235,12 @@ namespace Barotrauma
case "uiglow": case "uiglow":
UIGlow = new UISprite(subElement); UIGlow = new UISprite(subElement);
break; break;
case "radiation":
RadiationSprite = new UISprite(subElement);
break;
case "radiationanimspritesheet":
RadiationAnimSpriteSheet = new SpriteSheet(subElement);
break;
case "uiglowcircular": case "uiglowcircular":
UIGlowCircular = new UISprite(subElement); UIGlowCircular = new UISprite(subElement);
break; break;
@@ -218,6 +250,9 @@ namespace Barotrauma
case "focusindicator": case "focusindicator":
FocusIndicator = new SpriteSheet(subElement); FocusIndicator = new SpriteSheet(subElement);
break; break;
case "savingindicator":
SavingIndicator = new SpriteSheet(subElement);
break;
case "font": case "font":
Font = LoadFont(subElement, graphicsDevice); Font = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[Font] = subElement.GetAttributeBool("forceuppercase", false); ForceFontUpperCase[Font] = subElement.GetAttributeBool("forceuppercase", false);
@@ -259,9 +259,11 @@ namespace Barotrauma
public StrikethroughSettings Strikethrough = null; public StrikethroughSettings Strikethrough = null;
private readonly List<RichTextData> richTextData = null; public readonly List<RichTextData> RichTextData = null;
private readonly bool hasColorHighlight = false; public bool HasColorHighlight => RichTextData != null;
public bool OverrideRichTextDataAlpha = true;
public struct ClickableArea public struct ClickableArea
{ {
@@ -279,7 +281,8 @@ namespace Barotrauma
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
/// </summary> /// </summary>
public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null, public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false) Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null,
bool playerInput = false, bool parseRichText = false)
: base(style, rectT) : base(style, rectT)
{ {
if (color.HasValue) if (color.HasValue)
@@ -291,6 +294,11 @@ namespace Barotrauma
OverrideTextColor(textColor.Value); OverrideTextColor(textColor.Value);
} }
if (parseRichText)
{
RichTextData = Barotrauma.RichTextData.GetRichTextData(text, out text);
}
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font, //if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
//use the default CJK font as a fallback //use the default CJK font as a fallback
var selectedFont = originalFont = font ?? GUI.Font; var selectedFont = originalFont = font ?? GUI.Font;
@@ -318,8 +326,7 @@ namespace Barotrauma
public GUITextBlock(RectTransform rectT, List<RichTextData> richTextData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false) public GUITextBlock(RectTransform rectT, List<RichTextData> richTextData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
: this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput) : this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput)
{ {
this.richTextData = richTextData; this.RichTextData = richTextData;
hasColorHighlight = richTextData != null;
} }
public void CalculateHeightFromText(int padding = 0, bool removeExtraSpacing = false) public void CalculateHeightFromText(int padding = 0, bool removeExtraSpacing = false)
@@ -568,8 +575,9 @@ namespace Barotrauma
{ {
base.Update(deltaTime); base.Update(deltaTime);
if (ClickableAreas.Any() && (GUI.MouseOn?.IsParentOf(this) ?? true) && Rect.Contains(PlayerInput.MousePosition)) if (ClickableAreas.Any() && (GUI.MouseOn?.IsParentOf(this) ?? true))
{ {
if (!Rect.Contains(PlayerInput.MousePosition)) { return; }
int index = GetCaretIndexFromScreenPos(PlayerInput.MousePosition); int index = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
foreach (ClickableArea clickableArea in ClickableAreas) foreach (ClickableArea clickableArea in ClickableAreas)
{ {
@@ -627,7 +635,7 @@ namespace Barotrauma
currentTextColor = selectedTextColor; currentTextColor = selectedTextColor;
} }
if (!hasColorHighlight) if (!HasColorHighlight)
{ {
string textToShow = Censor ? censoredText : (Wrap ? wrappedText : text); string textToShow = Censor ? censoredText : (Wrap ? wrappedText : text);
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f); Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
@@ -639,12 +647,15 @@ namespace Barotrauma
} }
Font.DrawString(spriteBatch, textToShow, pos, colorToShow, 0.0f, origin, TextScale, SpriteEffects.None, textDepth); Font.DrawString(spriteBatch, textToShow, pos, colorToShow, 0.0f, origin, TextScale, SpriteEffects.None, textDepth);
} }
else else
{ {
if (OverrideRichTextDataAlpha)
{
RichTextData.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
}
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos, Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, richTextData); currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData);
} }
if (Strikethrough != null) if (Strikethrough != null)
@@ -626,6 +626,9 @@ namespace Barotrauma
{ {
if (Text == null) Text = ""; if (Text == null) Text = "";
// Prevent alt gr from triggering any of these as that combination is often needed for special characters
if (PlayerInput.IsAltDown()) return;
switch (command) switch (command)
{ {
case '\b' when !Readonly: //backspace case '\b' when !Readonly: //backspace
@@ -667,7 +670,10 @@ namespace Barotrauma
} }
break; break;
case (char)0x1: // ctrl-a case (char)0x1: // ctrl-a
SelectAll(); if (PlayerInput.IsCtrlDown())
{
SelectAll();
}
break; break;
case (char)0x1A when !Readonly && !SubEditorScreen.IsSubEditor(): // ctrl-z case (char)0x1A when !Readonly && !SubEditorScreen.IsSubEditor(): // ctrl-z
text = memento.Undo(); text = memento.Undo();
@@ -128,7 +128,7 @@ namespace Barotrauma
int messageAreaWidth = GameMain.GraphicsWidth / 3; int messageAreaWidth = GameMain.GraphicsWidth / 3;
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom, messageAreaWidth, ButtonAreaTop.Height); MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom + ButtonAreaTop.Height, messageAreaWidth, ButtonAreaTop.Height);
bool isFourByThree = GUI.IsFourByThree(); bool isFourByThree = GUI.IsFourByThree();
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale); int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
@@ -139,7 +139,9 @@ namespace Barotrauma
int objectiveAnchorOffsetY = (int)(150 * GUI.Scale); int objectiveAnchorOffsetY = (int)(150 * GUI.Scale);
ObjectiveAnchor = new Rectangle(Padding, ChatBoxArea.Y - objectiveAnchorOffsetY, objectiveAnchorWidth, 0); ObjectiveAnchor = new Rectangle(Padding, ChatBoxArea.Y - objectiveAnchorOffsetY, objectiveAnchorWidth, 0);
CrewArea = new Rectangle(Padding, Padding, (int)Math.Max(400 * GUI.Scale, 220), ObjectiveAnchor.Top - Padding * 2); int crewAreaY = ButtonAreaTop.Bottom + Padding;
int crewAreaHeight = ObjectiveAnchor.Top - Padding - crewAreaY;
CrewArea = new Rectangle(Padding, crewAreaY, (int)Math.Max(400 * GUI.Scale, 220), crewAreaHeight);
InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY); InventoryAreaLower = new Rectangle(ChatBoxArea.Right + Padding * 7, inventoryTopY, GameMain.GraphicsWidth - Padding * 9 - ChatBoxArea.Width, GameMain.GraphicsHeight - inventoryTopY);
@@ -70,6 +70,14 @@ namespace Barotrauma
} }
private string selectedTip; private string selectedTip;
private List<RichTextData> selectedTipRichTextData;
private bool selectedTipRichTextUnparsed;
private void SetSelectedTip(string tip)
{
selectedTip = tip;
selectedTipRichTextData = null;
selectedTipRichTextUnparsed = true;
}
private readonly object loadMutex = new object(); private readonly object loadMutex = new object();
private float? loadState; private float? loadState;
@@ -115,7 +123,7 @@ namespace Barotrauma
overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png"); overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png");
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero); noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true; DrawLoadingText = true;
selectedTip = TextManager.Get("LoadingScreenTip", true); SetSelectedTip(TextManager.Get("LoadingScreenTip", true));
} }
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime) public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
@@ -215,14 +223,34 @@ namespace Barotrauma
if (GUI.Font != null && selectedTip != null) if (GUI.Font != null && selectedTip != null)
{ {
if (selectedTipRichTextUnparsed)
{
selectedTipRichTextData = RichTextData.GetRichTextData(selectedTip, out selectedTip);
selectedTipRichTextUnparsed = false;
}
string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font); string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font);
string[] lines = wrappedTip.Split('\n'); string[] lines = wrappedTip.Split('\n');
float lineHeight = GUI.Font.MeasureString(selectedTip).Y; float lineHeight = GUI.Font.MeasureString(selectedTip).Y;
for (int i = 0; i < lines.Length; i++) if (selectedTipRichTextData != null)
{ {
GUI.Font.DrawString(spriteBatch, lines[i], int rtdOffset = 0;
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White); for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawStringWithColors(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White,
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTipRichTextData, rtdOffset);
rtdOffset += lines[i].Length;
}
}
else
{
for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawString(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White);
}
} }
} }
@@ -302,7 +330,7 @@ namespace Barotrauma
{ {
GameMain.Config.Language = language; GameMain.Config.Language = language;
//reload tip in the selected language //reload tip in the selected language
selectedTip = TextManager.Get("LoadingScreenTip", true); SetSelectedTip(TextManager.Get("LoadingScreenTip", true));
GameMain.Config.SetDefaultBindings(legacy: false); GameMain.Config.SetDefaultBindings(legacy: false);
GameMain.Config.CheckBindings(useDefaults: true); GameMain.Config.CheckBindings(useDefaults: true);
WaitForLanguageSelection = false; WaitForLanguageSelection = false;
@@ -364,7 +392,7 @@ namespace Barotrauma
{ {
drawn = false; drawn = false;
LoadState = null; LoadState = null;
selectedTip = TextManager.Get("LoadingScreenTip", true); SetSelectedTip(TextManager.Get("LoadingScreenTip", true));
currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture; currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
while (!drawn) while (!drawn)
@@ -94,7 +94,7 @@ namespace Barotrauma
} }
} }
private static Point maxPoint = new Point(int.MaxValue, int.MaxValue); public readonly static Point MaxPoint = new Point(int.MaxValue, int.MaxValue);
private Point? maxSize; private Point? maxSize;
/// <summary> /// <summary>
@@ -103,7 +103,7 @@ namespace Barotrauma
/// </summary> /// </summary>
public Point MaxSize public Point MaxSize
{ {
get { return maxSize ?? maxPoint; } get { return maxSize ?? MaxPoint; }
set set
{ {
if (maxSize == value) { return; } if (maxSize == value) { return; }
@@ -640,6 +640,12 @@ namespace Barotrauma
return children.Contains(rectT) || (recursive && children.Any(c => c.IsParentOf(rectT))); return children.Contains(rectT) || (recursive && children.Any(c => c.IsParentOf(rectT)));
} }
public bool IsChildOf(RectTransform rectT, bool recursive = true)
{
if (Parent == null) { return false; }
return Parent == rectT || (recursive && Parent.IsChildOf(rectT));
}
public void ClearChildren() public void ClearChildren()
{ {
children.ForEachMod(c => c.Parent = null); children.ForEachMod(c => c.Parent = null);
@@ -24,7 +24,6 @@ namespace Barotrauma
private int buyTotal, sellTotal; private int buyTotal, sellTotal;
private GUITextBlock merchantBalanceBlock; private GUITextBlock merchantBalanceBlock;
private GUILayoutGroup valueChangeGroup;
private GUITextBlock currentSellValueBlock, newSellValueBlock; private GUITextBlock currentSellValueBlock, newSellValueBlock;
private GUIImage sellValueChangeArrow; private GUIImage sellValueChangeArrow;
private GUIDropDown sortingDropDown; private GUIDropDown sortingDropDown;
@@ -158,7 +157,7 @@ namespace Barotrauma
}; };
// Store header ------------------------------------------------ // Store header ------------------------------------------------
var headerGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), storeContent.RectTransform), isHorizontal: true) var headerGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f / 14.0f), storeContent.RectTransform), isHorizontal: true)
{ {
RelativeSpacing = 0.005f RelativeSpacing = 0.005f
}; };
@@ -209,7 +208,7 @@ namespace Barotrauma
// Item sell value ------------------------------------------------ // Item sell value ------------------------------------------------
var sellValueContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform)) var sellValueContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), balanceAndValueGroup.RectTransform))
{ {
CanBeFocused = false, CanBeFocused = true,
RelativeSpacing = 0.005f RelativeSpacing = 0.005f
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform),
@@ -220,9 +219,9 @@ namespace Barotrauma
ForceUpperCase = true ForceUpperCase = true
}; };
valueChangeGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) var valueChangeGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), sellValueContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
CanBeFocused = true, CanBeFocused = false,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
float blockWidth = GUI.IsFourByThree() ? 0.32f : 0.28f; float blockWidth = GUI.IsFourByThree() ? 0.32f : 0.28f;
@@ -247,7 +246,7 @@ namespace Barotrauma
{ {
string tooltipTag = newStatus.SellPriceModifier > CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier ? string tooltipTag = newStatus.SellPriceModifier > CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier ?
"campaingstore.valueincreasetooltip" : "campaingstore.valuedecreasetooltip"; "campaingstore.valueincreasetooltip" : "campaingstore.valuedecreasetooltip";
valueChangeGroup.ToolTip = TextManager.Get(tooltipTag); sellValueContainer.ToolTip = TextManager.Get(tooltipTag);
currentSellValueBlock.TextColor = newStatus.Color; currentSellValueBlock.TextColor = newStatus.Color;
sellValueChangeArrow.Color = newStatus.Color; sellValueChangeArrow.Color = newStatus.Color;
sellValueChangeArrow.Visible = true; sellValueChangeArrow.Visible = true;
@@ -256,7 +255,7 @@ namespace Barotrauma
return $"{(CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %"; return $"{(CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier * 100).FormatZeroDecimal()} %";
} }
} }
valueChangeGroup.ToolTip = null; sellValueContainer.ToolTip = TextManager.Get("campaignstore.sellvaluetooltip");
currentSellValueBlock.TextColor = CurrentLocation.BalanceColor; currentSellValueBlock.TextColor = CurrentLocation.BalanceColor;
sellValueChangeArrow.Visible = false; sellValueChangeArrow.Visible = false;
newSellValueBlock.Text = null; newSellValueBlock.Text = null;
@@ -264,7 +263,7 @@ namespace Barotrauma
} }
else else
{ {
valueChangeGroup.ToolTip = null; sellValueContainer.ToolTip = null;
sellValueChangeArrow.Visible = false; sellValueChangeArrow.Visible = false;
newSellValueBlock.Text = null; newSellValueBlock.Text = null;
return null; return null;
@@ -293,7 +292,7 @@ namespace Barotrauma
newSellValueBlock.Padding = newPadding; newSellValueBlock.Padding = newPadding;
// Store mode buttons ------------------------------------------------ // Store mode buttons ------------------------------------------------
var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f / 14.0f), storeContent.RectTransform), style: null); var modeButtonFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.4f / 14.0f), storeContent.RectTransform), style: null);
var modeButtonContainer = new GUILayoutGroup(new RectTransform(Vector2.One, modeButtonFrame.RectTransform), isHorizontal: true); var modeButtonContainer = new GUILayoutGroup(new RectTransform(Vector2.One, modeButtonFrame.RectTransform), isHorizontal: true);
var tabs = Enum.GetValues(typeof(StoreTab)); var tabs = Enum.GetValues(typeof(StoreTab));
@@ -676,6 +675,7 @@ namespace Barotrauma
(itemFrame.UserData as PurchasedItem).Quantity = quantity; (itemFrame.UserData as PurchasedItem).Quantity = quantity;
SetQuantityLabelText(StoreTab.Buy, itemFrame); SetQuantityLabelText(StoreTab.Buy, itemFrame);
SetOwnedLabelText(itemFrame); SetOwnedLabelText(itemFrame);
SetPriceGetters(itemFrame, true);
} }
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0); SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0);
existingItemFrames.Add(itemFrame); existingItemFrames.Add(itemFrame);
@@ -750,6 +750,7 @@ namespace Barotrauma
(itemFrame.UserData as PurchasedItem).Quantity = itemQuantity; (itemFrame.UserData as PurchasedItem).Quantity = itemQuantity;
SetQuantityLabelText(StoreTab.Sell, itemFrame); SetQuantityLabelText(StoreTab.Sell, itemFrame);
SetOwnedLabelText(itemFrame); SetOwnedLabelText(itemFrame);
SetPriceGetters(itemFrame, false);
} }
SetItemFrameStatus(itemFrame, hasPermissions && itemQuantity > 0); SetItemFrameStatus(itemFrame, hasPermissions && itemQuantity > 0);
if (itemQuantity < 1 && !isRequestedGood) if (itemQuantity < 1 && !isRequestedGood)
@@ -772,6 +773,37 @@ namespace Barotrauma
shoppingCrateSellList.BarScroll = prevShoppingCrateScroll; shoppingCrateSellList.BarScroll = prevShoppingCrateScroll;
} }
private void SetPriceGetters(GUIComponent itemFrame, bool buying)
{
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; }
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
{
if (buying)
{
undiscountedPriceBlock.TextGetter = () => GetCurrencyFormatted(
CurrentLocation?.GetAdjustedItemBuyPrice(pi.ItemPrefab, considerDailySpecials: false) ?? 0);
}
else
{
undiscountedPriceBlock.TextGetter = () => GetCurrencyFormatted(
CurrentLocation?.GetAdjustedItemSellPrice(pi.ItemPrefab, considerRequestedGoods: false) ?? 0);
}
}
if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock)
{
if (buying)
{
priceBlock.TextGetter = () => GetCurrencyFormatted(CurrentLocation?.GetAdjustedItemBuyPrice(pi.ItemPrefab) ?? 0);
}
else
{
priceBlock.TextGetter = () => GetCurrencyFormatted(CurrentLocation?.GetAdjustedItemSellPrice(pi.ItemPrefab) ?? 0);
}
}
}
public void RefreshItemsToSell() public void RefreshItemsToSell()
{ {
itemsToSell.Clear(); itemsToSell.Clear();
@@ -1188,14 +1220,6 @@ namespace Barotrauma
}; };
priceBlock.Color *= (forceDisable ? 0.5f : 1.0f); priceBlock.Color *= (forceDisable ? 0.5f : 1.0f);
priceBlock.CalculateHeightFromText(); priceBlock.CalculateHeightFromText();
if (isSellingRelatedList)
{
priceBlock.TextGetter = () => GetCurrencyFormatted(CurrentLocation?.GetAdjustedItemSellPrice(pi.ItemPrefab, priceInfo: priceInfo) ?? 0);
}
else
{
priceBlock.TextGetter = () => GetCurrencyFormatted(CurrentLocation?.GetAdjustedItemBuyPrice(pi.ItemPrefab, priceInfo: priceInfo) ?? 0);
}
if (locationHasDealOnItem) if (locationHasDealOnItem)
{ {
var undiscounterPriceBlock = new GUITextBlock( var undiscounterPriceBlock = new GUITextBlock(
@@ -1209,17 +1233,8 @@ namespace Barotrauma
TextColor = priceBlock.TextColor, TextColor = priceBlock.TextColor,
UserData = "undiscountedprice" UserData = "undiscountedprice"
}; };
if (isSellingRelatedList)
{
undiscounterPriceBlock.TextGetter = () => GetCurrencyFormatted(
CurrentLocation?.GetAdjustedItemSellPrice(pi.ItemPrefab, priceInfo: priceInfo, considerRequestedGoods: false) ?? 0);
}
else
{
undiscounterPriceBlock.TextGetter = () => GetCurrencyFormatted(
CurrentLocation?.GetAdjustedItemBuyPrice(pi.ItemPrefab, priceInfo: priceInfo, considerDailySpecials: false) ?? 0);
}
} }
SetPriceGetters(frame, !isSellingRelatedList);
if (isParentOnLeftSideOfInterface) if (isParentOnLeftSideOfInterface)
{ {
@@ -1268,7 +1283,8 @@ namespace Barotrauma
// Add items on the sub(s) // Add items on the sub(s)
Submarine.MainSub?.GetItems(true) Submarine.MainSub?.GetItems(true)
.Where(i => i.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached) && .Where(i => i.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached) &&
i.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) i.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null)) &&
ItemAndAllContainersInteractable(i))
.ForEach(i => AddToOwnedItems(i.Prefab)); .ForEach(i => AddToOwnedItems(i.Prefab));
// Add items in character inventories // Add items in character inventories
@@ -1286,6 +1302,16 @@ namespace Barotrauma
ownedItemsUpdateTimer = 0.0f; ownedItemsUpdateTimer = 0.0f;
static bool ItemAndAllContainersInteractable(Item item)
{
do
{
if (!item.IsPlayerTeamInteractable) { return false; }
item = item.Container;
} while (item != null);
return true;
}
void AddToOwnedItems(ItemPrefab itemPrefab, int amount = 1) void AddToOwnedItems(ItemPrefab itemPrefab, int amount = 1)
{ {
if (OwnedItems.ContainsKey(itemPrefab)) if (OwnedItems.ContainsKey(itemPrefab))
@@ -60,6 +60,7 @@ namespace Barotrauma
public GUITextBlock submarineFee; public GUITextBlock submarineFee;
public GUIButton selectSubmarineButton; public GUIButton selectSubmarineButton;
public GUITextBlock middleTextBlock; public GUITextBlock middleTextBlock;
public GUIButton previewButton;
} }
public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent) public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent)
@@ -191,6 +192,12 @@ namespace Barotrauma
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUI.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Center); submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUI.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Center);
submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUI.SubHeadingFont); submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUI.SubHeadingFont);
submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null); submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null);
submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton")
{
Color = Color.White,
HoverColor = Color.White,
PressedColor = Color.White
};
submarineDisplays[i] = submarineDisplayElement; submarineDisplays[i] = submarineDisplayElement;
} }
@@ -299,6 +306,7 @@ namespace Barotrauma
submarineDisplays[i].selectSubmarineButton.OnClicked = null; submarineDisplays[i].selectSubmarineButton.OnClicked = null;
submarineDisplays[i].displayedSubmarine = null; submarineDisplays[i].displayedSubmarine = null;
submarineDisplays[i].middleTextBlock.AutoDraw = false; submarineDisplays[i].middleTextBlock.AutoDraw = false;
submarineDisplays[i].previewButton.Visible = false;
} }
else else
{ {
@@ -369,6 +377,13 @@ namespace Barotrauma
{ {
SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect); SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect);
} }
submarineDisplays[i].previewButton.Visible = true;
submarineDisplays[i].previewButton.OnClicked = (btn, obj) =>
{
SubmarinePreview.Create(subToDisplay);
return false;
};
} }
submarineIndex++; submarineIndex++;
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using System.Globalization; using System.Globalization;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -17,7 +18,7 @@ namespace Barotrauma
private static UISprite spectateIcon, disconnectedIcon; private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon; private static Sprite ownerIcon, moderatorIcon;
private enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor }; private enum InfoFrameTab { Crew, Mission, Reputation, MyCharacter, Traitor, Submarine };
private static InfoFrameTab selectedTab; private static InfoFrameTab selectedTab;
private GUIFrame infoFrame, contentFrame; private GUIFrame infoFrame, contentFrame;
@@ -40,11 +41,11 @@ namespace Barotrauma
private const ushort mediumPingThreshold = 200; private const ushort mediumPingThreshold = 200;
private ushort currentPing; private ushort currentPing;
private Client client; private readonly Client client;
private Character character; private readonly Character character;
private bool hasCharacter; private readonly bool hasCharacter;
private GUITextBlock textBlock; private readonly GUITextBlock textBlock;
private GUIFrame frame; private readonly GUIFrame frame;
public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock) public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock)
{ {
@@ -180,60 +181,79 @@ namespace Barotrauma
infoFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null); infoFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker"); new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, infoFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
//this used to be a switch expression but i changed it because it killed enc :(
Vector2 contentFrameSize;
switch (selectedTab) switch (selectedTab)
{ {
case InfoFrameTab.Crew:
case InfoFrameTab.Mission:
case InfoFrameTab.Traitor:
default:
contentFrame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.667f), infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { /*MinSize = new Point(width, height),*/ RelativeOffset = new Vector2(0.025f, 0.12f) });
break;
case InfoFrameTab.MyCharacter: case InfoFrameTab.MyCharacter:
contentFrame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.5f), infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { /*MinSize = new Point(width, height),*/ RelativeOffset = new Vector2(0.025f, 0.12f) }); contentFrameSize = new Vector2(0.45f, 0.5f);
break;
default:
contentFrameSize = new Vector2(0.45f, 0.667f);
break; break;
} }
contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.12f) });
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(17.5f)) }, style: null); var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(25f)) }, isHorizontal: true)
var buttonArea = new GUILayoutGroup(new RectTransform(new Point(innerFrame.Rect.Width, GUI.IntScale(25f)), innerFrame.RectTransform) { AbsoluteOffset = new Point(2, 0) }, isHorizontal: true)
{ {
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
infoFrameHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.926f), innerFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter), style: null); var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(0.07f, 1f), parent: horizontalLayoutGroup.RectTransform), isHorizontal: false)
var crewButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("Crew"), style: "GUITabButton")
{ {
UserData = InfoFrameTab.Crew, AbsoluteSpacing = GUI.IntScale(5f)
OnClicked = SelectInfoFrameTab
}; };
tabButtons.Add(crewButton); var innerLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.92f, 1f), horizontalLayoutGroup.RectTransform))
var missionButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("Mission"), style: "GUITabButton")
{ {
UserData = InfoFrameTab.Mission, RelativeSpacing = 0.01f,
OnClicked = SelectInfoFrameTab Stretch = true
}; };
tabButtons.Add(missionButton);
bool isTraitor = GameMain.Client?.Character?.IsTraitor ?? false; float absoluteSpacing = innerLayoutGroup.RelativeSpacing * innerLayoutGroup.Rect.Height;
if (isTraitor && GameMain.Client.TraitorMission != null) int multiplier = GameMain.GameSession?.GameMode is CampaignMode ? 2 : 1;
int infoFrameHolderHeight = Math.Min((int)(0.97f * innerLayoutGroup.Rect.Height), (int)(innerLayoutGroup.Rect.Height - multiplier * (GUI.IntScale(15f) + absoluteSpacing)));
infoFrameHolder = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: null);
GUIButton createTabButton(InfoFrameTab tab, string textTag)
{ {
var traitorButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("tabmenu.traitor"), style: "GUITabButton") var newButton = new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: $"InfoFrameTabButton.{tab}")
{ {
UserData = InfoFrameTab.Traitor, UserData = tab,
ToolTip = TextManager.Get(textTag),
OnClicked = SelectInfoFrameTab OnClicked = SelectInfoFrameTab
}; };
tabButtons.Add(traitorButton); tabButtons.Add(newButton);
return newButton;
} }
var crewButton = createTabButton(InfoFrameTab.Crew, "crew");
var missionButton = createTabButton(InfoFrameTab.Mission, "mission");
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{
var reputationButton = createTabButton(InfoFrameTab.Reputation, "reputation");
var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame");
new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), "", textAlignment: Alignment.Right, parseRichText: true)
{
TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money))
};
}
else
{
bool isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
if (isTraitor && GameMain.Client.TraitorMission != null)
{
var traitorButton = createTabButton(InfoFrameTab.Traitor, "tabmenu.traitor");
}
}
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
var myCharacterButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("tabmenu.character"), style: "GUITabButton") var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character");
{
UserData = InfoFrameTab.MyCharacter,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(myCharacterButton);
} }
} }
@@ -252,6 +272,14 @@ namespace Barotrauma
case InfoFrameTab.Mission: case InfoFrameTab.Mission:
CreateMissionInfo(infoFrameHolder); CreateMissionInfo(infoFrameHolder);
break; break;
case InfoFrameTab.Reputation:
if (GameMain.GameSession.RoundSummary != null && GameMain.GameSession.GameMode is CampaignMode campaignMode)
{
infoFrameHolder.ClearChildren();
GUIFrame reputationFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrameHolder.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
GameMain.GameSession.RoundSummary.CreateReputationInfoPanel(reputationFrame, campaignMode);
}
break;
case InfoFrameTab.Traitor: case InfoFrameTab.Traitor:
TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission; TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission;
Character traitor = GameMain.Client.Character; Character traitor = GameMain.Client.Character;
@@ -262,6 +290,9 @@ namespace Barotrauma
if (GameMain.NetworkMember == null) { return false; } if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder); GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder);
break; break;
case InfoFrameTab.Submarine:
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
} }
return true; return true;
@@ -356,6 +387,21 @@ namespace Barotrauma
crewFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(headerFrames[0].Rect.Height * headerFrames.Length) - (teamIDs.Count > 1 ? GUI.IntScale(10f) : 0)); crewFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(headerFrames[0].Rect.Height * headerFrames.Length) - (teamIDs.Count > 1 ? GUI.IntScale(10f) : 0));
float totalRelativeHeight = 0.0f;
if (teamIDs.Count > 1) { totalRelativeHeight += teamIDs.Count * nameHeight; }
headerFrames.ForEach(f => totalRelativeHeight += f.RectTransform.RelativeSize.Y);
crewListArray.ForEach(f => totalRelativeHeight += f.RectTransform.RelativeSize.Y);
if (totalRelativeHeight > 1.0f)
{
float heightOverflow = totalRelativeHeight - 1.0f;
float heightToReduce = heightOverflow / crewListArray.Length;
crewListArray.ForEach(l =>
{
l.RectTransform.Resize(l.RectTransform.RelativeSize - new Vector2(0.0f, heightToReduce));
l.UpdateDimensions();
});
}
if (GameMain.IsMultiplayer) if (GameMain.IsMultiplayer)
{ {
CreateMultiPlayerList(false); CreateMultiPlayerList(false);
@@ -676,7 +722,7 @@ namespace Barotrauma
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter"); GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) infoFrameHolder.RemoveChild(existingPreview); if (existingPreview != null) infoFrameHolder.RemoveChild(existingPreview);
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.061f, 0) }) GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.145f, 0) })
{ {
UserData = "SelectedCharacter" UserData = "SelectedCharacter"
}; };
@@ -831,12 +877,24 @@ namespace Barotrauma
if (logList != null) if (logList != null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), logList.Content.RectTransform), line, wrap: true, font: GUI.SmallFont) var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), logList.Content.RectTransform), line, wrap: true, font: GUI.SmallFont, parseRichText: true)
{ {
TextColor = textColor, TextColor = textColor,
CanBeFocused = false, CanBeFocused = false,
UserData = line UserData = line
}.CalculateHeightFromText(); };
textBlock.CalculateHeightFromText();
if (textBlock.HasColorHighlight)
{
foreach (var data in textBlock.RichTextData)
{
textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea()
{
Data = data,
OnClick = GameMain.NetLobbyScreen.SelectPlayer
});
}
}
} }
} }
@@ -857,54 +915,109 @@ namespace Barotrauma
int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2; int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2;
GUIFrame missionDescriptionHolder; GUIListBox missionList;
if (hasPortrait) if (hasPortrait)
{ {
GUIFrame missionImageHolder = new GUIFrame(new RectTransform(new Point(contentWidth, (int)(missionFrame.Rect.Height * 0.588f)), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }); GUIFrame portraitHolder = new GUIFrame(new RectTransform(new Point(contentWidth, (int)(missionFrame.Rect.Height * 0.588f)), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height; float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height;
GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(1.0f, 1f), missionImageHolder.RectTransform), portrait, scaleToFit: true); GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(1.0f, 1f), portraitHolder.RectTransform), portrait, scaleToFit: true);
missionImageHolder.RectTransform.NonScaledSize = new Point(portraitImage.Rect.Size.X, (int)(portraitImage.Rect.Size.X / portraitAspectRatio)); portraitHolder.RectTransform.NonScaledSize = new Point(portraitImage.Rect.Size.X, (int)(portraitImage.Rect.Size.X / portraitAspectRatio));
missionDescriptionHolder = new GUIFrame(new RectTransform(new Point(contentWidth, 0), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, missionImageHolder.RectTransform.AbsoluteOffset.Y + missionImageHolder.Rect.Height + padding) }, style: null);
missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrame.Rect.Bottom - portraitHolder.Rect.Bottom - padding), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, portraitHolder.RectTransform.AbsoluteOffset.Y + portraitHolder.Rect.Height + padding) });
} }
else else
{ {
missionDescriptionHolder = new GUIFrame(new RectTransform(new Point(contentWidth, 0), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }, style: null); missionList = new GUIListBox(new RectTransform(new Point(contentWidth, missionFrame.Rect.Height - locationInfoYOffset - padding), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
} }
missionList.ContentBackground.Color = Color.Transparent;
missionList.Spacing = GUI.IntScale(15);
Mission mission = GameMain.GameSession?.Mission; if (GameMain.GameSession?.Missions != null)
if (mission != null)
{ {
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft); foreach (Mission mission in GameMain.GameSession.Missions)
string missionNameString = ToolBox.WrapText(mission.Name, missionTextGroup.Rect.Width, GUI.LargeFont);
string missionDescriptionString = ToolBox.WrapText(mission.Description, missionTextGroup.Rect.Width, GUI.Font);
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward));
string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", rewardText), missionTextGroup.Rect.Width, GUI.Font);
Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
Vector2 missionRewardSize = GUI.Font.MeasureString(missionRewardString);
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y));
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
if (mission.Prefab.Icon != null)
{ {
float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height; GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null);
int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X); GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft)
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); {
Point iconSize = new Point(iconWidth, iconHeight); AbsoluteSpacing = GUI.IntScale(5)
};
string descriptionText = mission.Description;
foreach (string missionMessage in mission.ShownMessages)
{
descriptionText += "\n\n" + missionMessage;
}
string rewardText = mission.GetMissionRewardText();
string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor }; var missionNameRichTextData = RichTextData.GetRichTextData(mission.Name, out string missionNameString);
var missionRewardRichTextData = RichTextData.GetRichTextData(rewardText, out string missionRewardString);
var missionReputationRichTextData = RichTextData.GetRichTextData(reputationText, out string missionReputationString);
var missionDescriptionRichTextData = RichTextData.GetRichTextData(descriptionText, out string missionDescriptionString);
missionNameString = ToolBox.WrapText(missionNameString, missionTextGroup.Rect.Width, GUI.LargeFont);
missionRewardString = ToolBox.WrapText(missionRewardString, missionTextGroup.Rect.Width, GUI.Font);
missionReputationString = ToolBox.WrapText(missionReputationString, missionTextGroup.Rect.Width, GUI.Font);
missionDescriptionString = ToolBox.WrapText(missionDescriptionString, missionTextGroup.Rect.Width, GUI.Font);
Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
Vector2 missionRewardSize = GUI.Font.MeasureString(missionRewardString);
Vector2 missionReputationSize = GUI.Font.MeasureString(missionReputationString);
float ySize = missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y + missionTextGroup.AbsoluteSpacing * 4;
bool displayDifficulty = mission.Difficulty.HasValue;
if (displayDifficulty) { ySize += missionRewardSize.Y; }
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)ySize);
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
if (mission.Prefab.Icon != null)
{
float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height;
int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true)
{
Color = mission.Prefab.IconColor,
HoverColor = mission.Prefab.IconColor,
SelectedColor = mission.Prefab.IconColor,
CanBeFocused = false
};
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameRichTextData, missionNameString, font: GUI.LargeFont);
GUILayoutGroup difficultyIndicatorGroup = null;
if (displayDifficulty)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Point(missionTextGroup.Rect.Width, (int)missionRewardSize.Y), parent: missionTextGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
};
var difficultyColor = mission.GetDifficultyColor();
for (int i = 0; i < mission.Difficulty.Value; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), "DifficultyIndicator", scaleToFit: true)
{
CanBeFocused = false,
Color = difficultyColor
};
}
}
var rewardTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardRichTextData, missionRewardString);
if (difficultyIndicatorGroup != null)
{
difficultyIndicatorGroup.RectTransform.Resize(new Point((int)(difficultyIndicatorGroup.Rect.Width - rewardTextBlock.Padding.X - rewardTextBlock.Padding.Z), difficultyIndicatorGroup.Rect.Height));
difficultyIndicatorGroup.RectTransform.AbsoluteOffset = new Point((int)rewardTextBlock.Padding.X, 0);
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionReputationRichTextData, missionReputationString);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionRichTextData, missionDescriptionString);
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
} }
else else
{ {
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft), false, childAnchor: Anchor.TopLeft); GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0f), missionList.RectTransform, Anchor.CenterLeft), false, childAnchor: Anchor.TopLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), TextManager.Get("NoMission"), font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), TextManager.Get("NoMission"), font: GUI.LargeFont);
} }
} }
@@ -938,5 +1051,93 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
} }
private void CreateSubmarineInfo(GUIFrame infoFrame, Submarine sub)
{
GUIFrame subInfoFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
GUIFrame paddedFrame = new GUIFrame(new RectTransform(Vector2.One * 0.97f, subInfoFrame.RectTransform, Anchor.Center), style: null);
var previewButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.43f), paddedFrame.RectTransform), style: null)
{
OnClicked = (btn, obj) => { SubmarinePreview.Create(sub.Info); return false; },
};
var previewImage = sub.Info.PreviewImage ?? SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.Equals(sub.Info.Name, StringComparison.OrdinalIgnoreCase))?.PreviewImage;
if (previewImage == null)
{
new GUITextBlock(new RectTransform(Vector2.One, previewButton.RectTransform), TextManager.Get("SubPreviewImageNotFound"));
}
else
{
var submarinePreviewBackground = new GUIFrame(new RectTransform(Vector2.One, previewButton.RectTransform), style: null)
{
Color = Color.Black,
HoverColor = Color.Black,
SelectedColor = Color.Black,
PressedColor = Color.Black,
CanBeFocused = false,
};
new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), previewImage, scaleToFit: true) { CanBeFocused = false };
new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black) { CanBeFocused = false };
}
new GUIFrame(new RectTransform(Vector2.One * 0.12f, previewButton.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight)
{
AbsoluteOffset = new Point((int)(0.03f * previewButton.Rect.Height))
},
"ExpandButton", Color.White)
{
Color = Color.White,
HoverColor = Color.White,
PressedColor = Color.White
};
var subInfoTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedFrame.RectTransform));
string className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}") : TextManager.Get("shuttle");
int nameHeight = (int)GUI.LargeFont.MeasureString(sub.Info.DisplayName, true).Y;
int classHeight = (int)GUI.SubHeadingFont.MeasureString(className).Y;
var submarineNameText = new GUITextBlock(new RectTransform(new Point(subInfoTextLayout.Rect.Width, nameHeight + HUDLayoutSettings.Padding / 2), subInfoTextLayout.RectTransform), sub.Info.DisplayName, textAlignment: Alignment.CenterLeft, font: GUI.LargeFont) { CanBeFocused = false };
submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y);
var submarineClassText = new GUITextBlock(new RectTransform(new Point(subInfoTextLayout.Rect.Width, classHeight), subInfoTextLayout.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) { CanBeFocused = false };
submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y);
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
GUILayoutGroup headerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.09f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0f, 0.43f) }, isHorizontal: true) { Stretch = true };
GUIImage headerIcon = new GUIImage(new RectTransform(Vector2.One, headerLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "SubmarineIcon");
new GUITextBlock(new RectTransform(Vector2.One, headerLayout.RectTransform), TextManager.Get("uicategory.upgrades"), font: GUI.LargeFont);
var upgradeRootLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.48f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft), isHorizontal: true);
var upgradeCategoryPanel = UpgradeStore.CreateUpgradeCategoryList(new RectTransform(new Vector2(0.4f, 1f), upgradeRootLayout.RectTransform));
upgradeCategoryPanel.HideChildrenOutsideFrame = true;
UpgradeStore.UpdateCategoryList(upgradeCategoryPanel, campaign, sub, UpgradeStore.GetApplicableCategories(sub).ToArray());
GUIComponent[] toRemove = upgradeCategoryPanel.Content.FindChildren(c => !c.Enabled).ToArray();
toRemove.ForEach(c => upgradeCategoryPanel.RemoveChild(c));
var upgradePanel = new GUIListBox(new RectTransform(new Vector2(0.6f, 1f), upgradeRootLayout.RectTransform));
upgradeCategoryPanel.OnSelected = (component, userData) =>
{
upgradePanel.ClearChildren();
if (userData is UpgradeStore.CategoryData categoryData && Submarine.MainSub != null)
{
foreach (UpgradePrefab prefab in categoryData.Prefabs)
{
var frame = UpgradeStore.CreateUpgradeFrame(prefab, categoryData.Category, campaign, new RectTransform(new Vector2(1f, 0.3f), upgradePanel.Content.RectTransform), addBuyButton: false);
UpgradeStore.UpdateUpgradeEntry(frame, prefab, categoryData.Category, campaign);
}
}
return true;
};
}
else
{
var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft));
sub.Info.CreateSpecsWindow(specsListBox, GUI.Font, includeTitle: false, includeClass: false, includeDescription: true);
}
}
} }
} }
@@ -15,7 +15,7 @@ namespace Barotrauma
internal class UpgradeStore internal class UpgradeStore
{ {
private readonly struct CategoryData public readonly struct CategoryData
{ {
public readonly UpgradeCategory Category; public readonly UpgradeCategory Category;
public readonly List<UpgradePrefab> Prefabs; public readonly List<UpgradePrefab> Prefabs;
@@ -125,7 +125,7 @@ namespace Barotrauma
{ {
if (component.UserData is CategoryData data) if (component.UserData is CategoryData data)
{ {
UpdateUpgradeEntry(component, data.SinglePrefab, data.Category); UpdateUpgradeEntry(component, data.SinglePrefab, data.Category, Campaign);
} }
} }
} }
@@ -133,29 +133,35 @@ namespace Barotrauma
// update the small indicator icons on the list // update the small indicator icons on the list
if (currentStoreLayout?.Parent != null) if (currentStoreLayout?.Parent != null)
{ {
foreach (GUIComponent component in currentStoreLayout.Content.Children) UpdateCategoryList(currentStoreLayout, Campaign, drawnSubmarine, applicableCategories);
{ }
if (!(component.UserData is CategoryData data)) { continue; } }
if (component.FindChild("indicators", true) is { } indicators)
{
UpdateCategoryIndicators(indicators, component, data.Prefabs, data.Category);
}
}
// reset the order first //TODO: move this somewhere else
foreach (UpgradeCategory category in UpgradeCategory.Categories) public static void UpdateCategoryList(GUIListBox categoryList, CampaignMode campaign, Submarine drawnSubmarine, IEnumerable<UpgradeCategory> applicableCategories)
{
foreach (GUIComponent component in categoryList.Content.Children)
{
if (!(component.UserData is CategoryData data)) { continue; }
if (component.FindChild("indicators", true) is { } indicators)
{ {
GUIComponent component = currentStoreLayout.Content.FindChild(c => c.UserData is CategoryData categoryData && categoryData.Category == category); UpdateCategoryIndicators(indicators, component, data.Prefabs, data.Category, campaign, drawnSubmarine, applicableCategories);
component?.SetAsLastChild();
} }
}
// send the disabled components to the bottom // reset the order first
List<GUIComponent> lastChilds = currentStoreLayout.Content.Children.Where(component => !component.Enabled).ToList(); foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
GUIComponent component = categoryList.Content.FindChild(c => c.UserData is CategoryData categoryData && categoryData.Category == category);
component?.SetAsLastChild();
}
foreach (var lastChild in lastChilds) // send the disabled components to the bottom
{ List<GUIComponent> lastChilds = categoryList.Content.Children.Where(component => !component.Enabled).ToList();
lastChild.SetAsLastChild();
} foreach (var lastChild in lastChilds)
{
lastChild.SetAsLastChild();
} }
} }
@@ -511,9 +517,10 @@ namespace Barotrauma
} }
} }
private void CreateUpgradeTab() //TODO: put this somewhere else
public static GUIListBox CreateUpgradeCategoryList(RectTransform rectTransform)
{ {
currentStoreLayout = new GUIListBox(rectT(1.0f, 1.5f, storeLayout), style: null) var upgradeCategoryList = new GUIListBox(rectTransform, style: null)
{ {
AutoHideScrollBar = false, AutoHideScrollBar = false,
ScrollBarVisible = false, ScrollBarVisible = false,
@@ -548,7 +555,7 @@ namespace Barotrauma
foreach (var (category, prefabs) in upgrades) foreach (var (category, prefabs) in upgrades)
{ {
var frameChild = new GUIFrame(rectT(1, 0.15f, currentStoreLayout.Content), style: "UpgradeUIFrame") var frameChild = new GUIFrame(rectT(1, 0.15f, upgradeCategoryList.Content), style: "UpgradeUIFrame")
{ {
UserData = new CategoryData(category, prefabs), UserData = new CategoryData(category, prefabs),
GlowOnSelect = true GlowOnSelect = true
@@ -565,8 +572,8 @@ namespace Barotrauma
* |-----------------------------|--------------------------| * |-----------------------------|--------------------------|
*/ */
GUILayoutGroup contentLayout = new GUILayoutGroup(rectT(0.9f, 0.85f, frameChild, Anchor.Center)); GUILayoutGroup contentLayout = new GUILayoutGroup(rectT(0.9f, 0.85f, frameChild, Anchor.Center));
var itemCategoryLabel = new GUITextBlock(rectT(1, 1, contentLayout), category.Name, font: GUI.SubHeadingFont) { CanBeFocused = false }; var itemCategoryLabel = new GUITextBlock(rectT(1, 1, contentLayout), category.Name, font: GUI.SubHeadingFont) { CanBeFocused = false };
GUILayoutGroup indicatorLayout = new GUILayoutGroup(rectT(0.5f, 0.25f, contentLayout, Anchor.BottomRight), isHorizontal: true, childAnchor: Anchor.TopRight) { UserData = "indicators", IgnoreLayoutGroups = true, RelativeSpacing = 0.01f }; GUILayoutGroup indicatorLayout = new GUILayoutGroup(rectT(0.5f, 0.25f, contentLayout, Anchor.BottomRight), isHorizontal: true, childAnchor: Anchor.TopRight) { UserData = "indicators", IgnoreLayoutGroups = true, RelativeSpacing = 0.01f };
foreach (var prefab in prefabs) foreach (var prefab in prefabs)
{ {
@@ -582,6 +589,13 @@ namespace Barotrauma
indicatorLayout.Recalculate(); indicatorLayout.Recalculate();
} }
return upgradeCategoryList;
}
private void CreateUpgradeTab()
{
currentStoreLayout = CreateUpgradeCategoryList(rectT(1.0f, 1.5f, storeLayout));
selectedUpgradeCategoryLayout = new GUIFrame(rectT(GUI.IsFourByThree() ? 0.3f : 0.25f, 1, mainStoreLayout), style: null) { CanBeFocused = false }; selectedUpgradeCategoryLayout = new GUIFrame(rectT(GUI.IsFourByThree() ? 0.3f : 0.25f, 1, mainStoreLayout), style: null) { CanBeFocused = false };
RefreshUpgradeList(); RefreshUpgradeList();
@@ -637,7 +651,7 @@ namespace Barotrauma
} }
} }
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item> itemsOnSubmarine) public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{ {
/* UPGRADE PREFAB ENTRY /* UPGRADE PREFAB ENTRY
* |------------------------------------------------------------------| * |------------------------------------------------------------------|
@@ -648,8 +662,8 @@ namespace Barotrauma
* | | progress bar | x / y | | * | | progress bar | x / y | |
* |------------------------------------------------------------------| * |------------------------------------------------------------------|
*/ */
GUIFrame prefabFrame = new GUIFrame(rectT(1f, 0.25f, parent), style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = new CategoryData(category, prefab) }; GUIFrame prefabFrame = new GUIFrame(rectTransform, style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = new CategoryData(category, prefab) };
GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(0.98f,0.95f, prefabFrame, Anchor.Center), isHorizontal: true); GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(0.98f, 0.95f, prefabFrame, Anchor.Center), isHorizontal: true) { Stretch = true };
GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center); GUILayoutGroup imageLayout = new GUILayoutGroup(rectT(new Point(prefabLayout.Rect.Height, prefabLayout.Rect.Height), prefabLayout), childAnchor: Anchor.Center);
var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout), prefab.Sprite, scaleToFit: true) { CanBeFocused = false }; var icon = new GUIImage(rectT(0.9f, 0.9f, imageLayout), prefab.Sprite, scaleToFit: true) { CanBeFocused = false };
GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout)); GUILayoutGroup textLayout = new GUILayoutGroup(rectT(0.8f - imageLayout.RectTransform.RelativeSize.X, 1, prefabLayout));
@@ -659,9 +673,13 @@ namespace Barotrauma
GUILayoutGroup progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" }; GUILayoutGroup progressLayout = new GUILayoutGroup(rectT(1, 0.25f, textLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft) { UserData = "progressbar" };
new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUI.Style.Orange); new GUIProgressBar(rectT(0.8f, 0.75f, progressLayout), 0.0f, GUI.Style.Orange);
new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUI.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero }; new GUITextBlock(rectT(0.2f, 1, progressLayout), string.Empty, font: GUI.SmallFont, textAlignment: Alignment.Center) { Padding = Vector4.Zero };
GUILayoutGroup buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; GUILayoutGroup buyButtonLayout = null;
new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), FormatCurrency(prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation)), textAlignment: Alignment.Center) { Padding = Vector4.Zero }; if (addBuyButton)
{
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
new GUITextBlock(rectT(1, 0.4f, buyButtonLayout), FormatCurrency(prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation)), textAlignment: Alignment.Center) { Padding = Vector4.Zero };
var buyButton = new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "UpgradeBuyButton") { Enabled = false }; var buyButton = new GUIButton(rectT(0.7f, 0.5f, buyButtonLayout), string.Empty, style: "UpgradeBuyButton") { Enabled = false };
}
description.CalculateHeightFromText(); description.CalculateHeightFromText();
// cut the description if it overflows and add a tooltip to it // cut the description if it overflows and add a tooltip to it
@@ -677,14 +695,33 @@ namespace Barotrauma
} }
// Recalculate everything to prevent jumping // Recalculate everything to prevent jumping
if (parent is GUILayoutGroup group) { group.Recalculate(); } if (rectTransform.Parent.GUIComponent is GUILayoutGroup group) { group.Recalculate(); }
descriptionLayout.Recalculate(); descriptionLayout.Recalculate();
prefabLayout.Recalculate(); prefabLayout.Recalculate();
imageLayout.Recalculate(); imageLayout.Recalculate();
textLayout.Recalculate(); textLayout.Recalculate();
progressLayout.Recalculate(); progressLayout.Recalculate();
buyButtonLayout.Recalculate(); buyButtonLayout?.Recalculate();
return prefabFrame;
}
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item> itemsOnSubmarine)
{
GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent));
var prefabLayout = prefabFrame.GetChild<GUILayoutGroup>();
GUILayoutGroup[] childLayouts = prefabLayout.GetAllChildren<GUILayoutGroup>().ToArray();
var imageLayout = childLayouts[0];
var icon = imageLayout.GetChild<GUIImage>();
var textLayout = childLayouts[1];
var name = textLayout.GetChild<GUITextBlock>();
GUILayoutGroup[] textChildLayouts = textLayout.GetAllChildren<GUILayoutGroup>().ToArray();
var descriptionLayout = textChildLayouts[0];
var description = descriptionLayout.GetChild<GUITextBlock>();
var progressLayout = textChildLayouts[1];
var buyButtonLayout = childLayouts[2];
var buyButton = buyButtonLayout.GetChild<GUIButton>();
if (!HasPermission || itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))) if (!HasPermission || itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab)))
{ {
@@ -713,7 +750,7 @@ namespace Barotrauma
return true; return true;
}; };
UpdateUpgradeEntry(prefabFrame, prefab, category); UpdateUpgradeEntry(prefabFrame, prefab, category, Campaign);
} }
private void CreateItemTooltip(MapEntity entity) private void CreateItemTooltip(MapEntity entity)
@@ -778,6 +815,18 @@ namespace Barotrauma
static string CreateListEntry(string name, int level) => TextManager.GetWithVariables("upgradeuitooltip.upgradelistelement", new[] { "[upgradename]", "[level]" }, new[] { name, $"{level}" }); static string CreateListEntry(string name, int level) => TextManager.GetWithVariables("upgradeuitooltip.upgradelistelement", new[] { "[upgradename]", "[level]" }, new[] { name, $"{level}" });
} }
public static IEnumerable<UpgradeCategory> GetApplicableCategories(Submarine drawnSubmarine)
{
Item[] entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToArray();
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
if (entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
{
yield return category;
}
}
}
private void UpdateSubmarinePreview(float deltaTime, GUICustomComponent parent) private void UpdateSubmarinePreview(float deltaTime, GUICustomComponent parent)
{ {
if (!parent.Children.Any() || Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) if (!parent.Children.Any() || Submarine.MainSub != null && Submarine.MainSub != drawnSubmarine || GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y)
@@ -789,16 +838,8 @@ namespace Barotrauma
CreateSubmarinePreview(drawnSubmarine, parent); CreateSubmarinePreview(drawnSubmarine, parent);
CreateHullBorderVerticies(drawnSubmarine, parent); CreateHullBorderVerticies(drawnSubmarine, parent);
List<Item> entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToList();
applicableCategories.Clear(); applicableCategories.Clear();
applicableCategories.AddRange(GetApplicableCategories(drawnSubmarine));
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
if (entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
{
applicableCategories.Add(category);
}
}
} }
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
@@ -1002,9 +1043,9 @@ namespace Barotrauma
} }
} }
private void UpdateUpgradeEntry(GUIComponent prefabFrame, UpgradePrefab prefab, UpgradeCategory category) public static void UpdateUpgradeEntry(GUIComponent prefabFrame, UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign)
{ {
int currentLevel = Campaign.UpgradeManager.GetUpgradeLevel(prefab, category); int currentLevel = campaign.UpgradeManager.GetUpgradeLevel(prefab, category);
string progressText = TextManager.GetWithVariables("upgrades.progressformat", new[] { "[level]", "[maxlevel]" }, new[] { currentLevel.ToString(), prefab.MaxLevel.ToString() }); string progressText = TextManager.GetWithVariables("upgrades.progressformat", new[] { "[level]", "[maxlevel]" }, new[] { currentLevel.ToString(), prefab.MaxLevel.ToString() });
if (prefabFrame.FindChild("progressbar", true) is { } progressParent) if (prefabFrame.FindChild("progressbar", true) is { } progressParent)
@@ -1023,7 +1064,7 @@ namespace Barotrauma
if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) if (prefabFrame.FindChild("buybutton", true) is { } buttonParent)
{ {
GUITextBlock priceLabel = buttonParent.GetChild<GUITextBlock>(); GUITextBlock priceLabel = buttonParent.GetChild<GUITextBlock>();
int price = prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate) if (priceLabel != null && !WaitForServerUpdate)
{ {
@@ -1038,7 +1079,7 @@ namespace Barotrauma
if (button != null) if (button != null)
{ {
button.Enabled = currentLevel < prefab.MaxLevel; button.Enabled = currentLevel < prefab.MaxLevel;
if (WaitForServerUpdate || !HasPermission || price > Campaign.Money) if (WaitForServerUpdate || !campaign.AllowedToManageCampaign() || price > campaign.Money)
{ {
button.Enabled = false; button.Enabled = false;
} }
@@ -1046,7 +1087,14 @@ namespace Barotrauma
} }
} }
private void UpdateCategoryIndicators(GUIComponent indicators, GUIComponent parent, List<UpgradePrefab> prefabs, UpgradeCategory category) private static void UpdateCategoryIndicators(
GUIComponent indicators,
GUIComponent parent,
List<UpgradePrefab> prefabs,
UpgradeCategory category,
CampaignMode campaign,
Submarine drawnSubmarine,
IEnumerable<UpgradeCategory> applicableCategories)
{ {
// Disables the parent and only re-enables if the submarine contains valid items // Disables the parent and only re-enables if the submarine contains valid items
if (!category.IsWallUpgrade && drawnSubmarine != null) if (!category.IsWallUpgrade && drawnSubmarine != null)
@@ -1078,13 +1126,13 @@ namespace Barotrauma
GUIComponentStyle dimStyle = styles["upgradeindicatordim"]; GUIComponentStyle dimStyle = styles["upgradeindicatordim"];
GUIComponentStyle offStyle = styles["upgradeindicatoroff"]; GUIComponentStyle offStyle = styles["upgradeindicatoroff"];
if (Campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= prefab.MaxLevel) if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= prefab.MaxLevel)
{ {
// we check this to avoid flickering from re-applying the same style // we check this to avoid flickering from re-applying the same style
if (image.Style == onStyle) { continue; } if (image.Style == onStyle) { continue; }
image.ApplyStyle(onStyle); image.ApplyStyle(onStyle);
} }
else if (Campaign.UpgradeManager.GetUpgradeLevel(prefab, category) > 0) else if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) > 0)
{ {
if (image.Style == dimStyle) { continue; } if (image.Style == dimStyle) { continue; }
image.ApplyStyle(dimStyle); image.ApplyStyle(dimStyle);
@@ -77,10 +77,6 @@ namespace Barotrauma
set set
{ {
if (gameSession == value) { return; } if (gameSession == value) { return; }
if (value == null && Screen.Selected == GameScreen && gameSession.GameMode is CampaignMode)
{
DebugConsole.AddWarning("GameSession set to null while in the game screen\n" + Environment.StackTrace.CleanupStackTrace());
}
if (gameSession?.GameMode != null && gameSession.GameMode != value?.GameMode) if (gameSession?.GameMode != null && gameSession.GameMode != value?.GameMode)
{ {
gameSession.GameMode.Remove(); gameSession.GameMode.Remove();
@@ -466,6 +462,27 @@ namespace Barotrauma
while (Config.WaitingForAutoUpdate) { yield return CoroutineStatus.Running; } while (Config.WaitingForAutoUpdate) { yield return CoroutineStatus.Running; }
} }
#if DEBUG
if (Config.ModBreakerMode)
{
Config.SelectCorePackage(ContentPackage.CorePackages.GetRandom());
foreach (var regularPackage in ContentPackage.RegularPackages)
{
if (Rand.Range(0.0, 1.0) <= 0.5)
{
Config.EnableRegularPackage(regularPackage);
}
else
{
Config.DisableRegularPackage(regularPackage);
}
}
ContentPackage.SortContentPackages(p =>
{
return Rand.Int(int.MaxValue);
});
}
#endif
if (Config.AllEnabledPackages.None()) if (Config.AllEnabledPackages.None())
{ {
@@ -535,6 +552,7 @@ namespace Barotrauma
Order.Init(); Order.Init();
EventManagerSettings.Init(); EventManagerSettings.Init();
BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature)); BallastFloraPrefab.LoadAll(GetFilesOfType(ContentType.MapCreature));
HintManager.Init();
TitleScreen.LoadState = 50.0f; TitleScreen.LoadState = 50.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -904,7 +922,9 @@ namespace Barotrauma
} }
#if !DEBUG #if !DEBUG
if (NetworkMember == null && !WindowActive && !Paused && true && Screen.Selected != MainMenuScreen && Config.PauseOnFocusLost) if (NetworkMember == null && !WindowActive && !Paused && true && Config.PauseOnFocusLost &&
Screen.Selected != MainMenuScreen && Screen.Selected != ServerListScreen && Screen.Selected != NetLobbyScreen &&
Screen.Selected != SubEditorScreen && Screen.Selected != LevelEditorScreen)
{ {
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
Paused = true; Paused = true;
@@ -918,6 +938,8 @@ namespace Barotrauma
Client.AddToGUIUpdateList(); Client.AddToGUIUpdateList();
} }
SubmarinePreview.AddToGUIUpdateList();
FileSelection.AddToGUIUpdateList(); FileSelection.AddToGUIUpdateList();
DebugConsole.AddToGUIUpdateList(); DebugConsole.AddToGUIUpdateList();
@@ -1044,6 +1066,8 @@ namespace Barotrauma
{ {
if (save) if (save)
{ {
GUI.SetSavingIndicatorState(true);
if (GameSession.Submarine != null && !GameSession.Submarine.Removed) if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
{ {
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine); GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
@@ -1074,13 +1098,6 @@ namespace Barotrauma
{ {
((TutorialMode)GameSession.GameMode).Tutorial?.Stop(); ((TutorialMode)GameSession.GameMode).Tutorial?.Stop();
} }
if (GameSettings.SendUserStatistics)
{
Mission mission = GameSession.Mission;
GameAnalyticsManager.AddDesignEvent("QuitRound:" + (save ? "Save" : "NoSave"));
GameAnalyticsManager.AddDesignEvent("EndRound:" + (mission == null ? "NoMission" : (mission.Completed ? "MissionCompleted" : "MissionFailed")));
}
} }
GUIMessageBox.CloseAll(); GUIMessageBox.CloseAll();
MainMenuScreen.Select(); MainMenuScreen.Select();
@@ -1214,13 +1231,19 @@ namespace Barotrauma
base.OnExiting(sender, args); base.OnExiting(sender, args);
} }
public void ShowOpenUrlInWebBrowserPrompt(string url) public void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
{ {
if (string.IsNullOrEmpty(url)) { return; } if (string.IsNullOrEmpty(url)) { return; }
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; } if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url), string text = TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url);
new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) string extensionText = TextManager.Get(promptExtensionTag, returnNull: true, useEnglishAsFallBack: false);
if (!string.IsNullOrEmpty(extensionText))
{
text += $"\n\n{extensionText}";
}
var msgBox = new GUIMessageBox("", text, new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
{ {
UserData = "verificationprompt" UserData = "verificationprompt"
}; };
File diff suppressed because it is too large Load Diff
@@ -18,6 +18,8 @@ namespace Barotrauma
protected Color overlayTextColor; protected Color overlayTextColor;
protected Sprite overlaySprite; protected Sprite overlaySprite;
private TransitionType prevCampaignUIAutoOpenType;
protected GUIButton endRoundButton; protected GUIButton endRoundButton;
public GUIButton ReadyCheckButton; public GUIButton ReadyCheckButton;
@@ -53,19 +55,26 @@ namespace Barotrauma
{ {
chatBox.ToggleOpen = wasChatBoxOpen; chatBox.ToggleOpen = wasChatBoxOpen;
} }
if (!value && CampaignUI?.SelectedTab == InteractionType.PurchaseSub)
{
SubmarinePreview.Close();
}
showCampaignUI = value; showCampaignUI = value;
} }
} }
public override void ShowStartMessage() public override void ShowStartMessage()
{ {
if (Mission == null) return; foreach (Mission mission in Missions)
new GUIMessageBox(Mission.Name, Mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: Mission.Prefab.Icon)
{ {
IconColor = Mission.Prefab.IconColor, new GUIMessageBox(
UserData = "missionstartmessage" mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name,
}; mission.Description, new string[0], type: GUIMessageBox.Type.InGame, icon: mission.Prefab.Icon, parseRichText: true)
{
IconColor = mission.Prefab.IconColor,
UserData = "missionstartmessage"
};
}
} }
/// <summary> /// <summary>
@@ -119,22 +128,22 @@ namespace Barotrauma
{ {
var backgroundSprite = GUI.Style.GetComponentStyle("CommandBackground").GetDefaultSprite(); var backgroundSprite = GUI.Style.GetComponentStyle("CommandBackground").GetDefaultSprite();
Vector2 centerPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2; Vector2 centerPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2;
string wrappedText = ToolBox.WrapText(overlayText, GameMain.GraphicsWidth / 3, GUI.Font);
Vector2 textSize = GUI.Font.MeasureString(wrappedText);
Vector2 textPos = centerPos - textSize / 2;
backgroundSprite.Draw(spriteBatch, backgroundSprite.Draw(spriteBatch,
centerPos, centerPos,
Color.White * (overlayTextColor.A / 255.0f), Color.White * (overlayTextColor.A / 255.0f),
origin: backgroundSprite.size / 2, origin: backgroundSprite.size / 2,
rotate: 0.0f, rotate: 0.0f,
scale: new Vector2(1.5f, 0.7f) * (GameMain.GraphicsWidth / 3 / backgroundSprite.size.X)); scale: new Vector2(GameMain.GraphicsWidth / 2 / backgroundSprite.size.X, textSize.Y / backgroundSprite.size.Y * 1.5f));
string wrappedText = ToolBox.WrapText(overlayText, GameMain.GraphicsWidth / 3, GUI.Font);
Vector2 textSize = GUI.Font.MeasureString(wrappedText);
Vector2 textPos = centerPos - textSize / 2;
GUI.DrawString(spriteBatch, textPos + Vector2.One, wrappedText, Color.Black * (overlayTextColor.A / 255.0f)); GUI.DrawString(spriteBatch, textPos + Vector2.One, wrappedText, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, textPos, wrappedText, overlayTextColor); GUI.DrawString(spriteBatch, textPos, wrappedText, overlayTextColor);
if (!string.IsNullOrEmpty(overlayTextBottom)) if (!string.IsNullOrEmpty(overlayTextBottom))
{ {
Vector2 bottomTextPos = centerPos + new Vector2(0.0f, textSize.Y + 30 * GUI.Scale) - GUI.Font.MeasureString(overlayTextBottom) / 2; Vector2 bottomTextPos = centerPos + new Vector2(0.0f, textSize.Y / 2 + 40 * GUI.Scale) - GUI.Font.MeasureString(overlayTextBottom) / 2;
GUI.DrawString(spriteBatch, bottomTextPos + Vector2.One, overlayTextBottom, Color.Black * (overlayTextColor.A / 255.0f)); GUI.DrawString(spriteBatch, bottomTextPos + Vector2.One, overlayTextBottom, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, bottomTextPos, overlayTextBottom, overlayTextColor); GUI.DrawString(spriteBatch, bottomTextPos, overlayTextBottom, overlayTextColor);
} }
@@ -147,7 +156,7 @@ namespace Barotrauma
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = false; } if (ReadyCheckButton != null) { ReadyCheckButton.Visible = false; }
return; return;
} }
if (Submarine.MainSub == null) { return; } if (Submarine.MainSub == null || Level.Loaded == null) { return; }
endRoundButton.Visible = false; endRoundButton.Visible = false;
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub); var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
@@ -158,7 +167,8 @@ namespace Barotrauma
case TransitionType.ProgressToNextEmptyLocation: case TransitionType.ProgressToNextEmptyLocation:
if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub)) if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
{ {
buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]"); string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
} }
break; break;
@@ -170,7 +180,8 @@ namespace Barotrauma
case TransitionType.ReturnToPreviousEmptyLocation: case TransitionType.ReturnToPreviousEmptyLocation:
if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub)) if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
{ {
buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
} }
@@ -194,7 +205,20 @@ namespace Barotrauma
if (endRoundButton.Visible) if (endRoundButton.Visible)
{ {
if (!AllowedToEndRound()) { buttonText = TextManager.Get("map"); } if (!AllowedToEndRound())
{
buttonText = TextManager.Get("map");
}
else if (prevCampaignUIAutoOpenType != availableTransition &&
(availableTransition == TransitionType.ProgressToNextEmptyLocation || availableTransition == TransitionType.ReturnToPreviousEmptyLocation))
{
HintManager.OnAvailableTransition(availableTransition);
//opening the campaign map pauses the game and prevents HintManager from running -> update it manually to get the hint to show up immediately
HintManager.Update();
Map.SelectLocation(-1);
endRoundButton.OnClicked(EndRoundButton, null);
prevCampaignUIAutoOpenType = availableTransition;
}
endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5); endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5);
if (endRoundButton.Text != buttonText) if (endRoundButton.Text != buttonText)
{ {
@@ -209,15 +233,20 @@ namespace Barotrauma
{ {
endRoundButton.RectTransform.ScreenSpaceOffset = new Point(0, Character.Controlled.CharacterHealth.SuicideButton.Rect.Height); endRoundButton.RectTransform.ScreenSpaceOffset = new Point(0, Character.Controlled.CharacterHealth.SuicideButton.Rect.Height);
} }
else if (GameMain.Client != null && GameMain.Client.IsFollowSubTickBoxVisible)
{
endRoundButton.RectTransform.ScreenSpaceOffset = new Point(0, HUDLayoutSettings.Padding + GameMain.Client.FollowSubTickBox.Rect.Height);
}
else else
{ {
endRoundButton.RectTransform.ScreenSpaceOffset = Point.Zero; endRoundButton.RectTransform.ScreenSpaceOffset = Point.Zero;
} }
} }
endRoundButton.DrawManually(spriteBatch); endRoundButton.DrawManually(spriteBatch);
if (this is MultiPlayerCampaign) if (this is MultiPlayerCampaign && ReadyCheckButton != null)
{ {
ReadyCheckButton?.DrawManually(spriteBatch); ReadyCheckButton.RectTransform.ScreenSpaceOffset = endRoundButton.RectTransform.ScreenSpaceOffset;
ReadyCheckButton.DrawManually(spriteBatch);
} }
} }
@@ -227,6 +227,11 @@ namespace Barotrauma
float timer = 0.0f; float timer = 0.0f;
while (timer < textDuration) while (timer < textDuration)
{ {
if (GameMain.GameSession == null || Screen.Selected != GameMain.GameScreen)
{
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
// Try to grab the controlled here to prevent inputs, assigned late on multiplayer // Try to grab the controlled here to prevent inputs, assigned late on multiplayer
if (Character.Controlled != null) if (Character.Controlled != null)
{ {
@@ -239,11 +244,20 @@ namespace Barotrauma
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
var outpost = GameMain.GameSession.Level.StartOutpost;
var borders = outpost.GetDockedBorders();
borders.Location += outpost.WorldPosition.ToPoint();
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
float startZoom = 0.8f /
((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X);
GameMain.GameScreen.Cam.MinZoom = Math.Min(startZoom, GameMain.GameScreen.Cam.MinZoom);
var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam, var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam,
null, null, null, null,
fadeOut: false, fadeOut: false,
duration: 5, losFadeIn: true,
startZoom: 1.5f, endZoom: 1.0f) waitDuration: 1,
panDuration: 5,
startZoom: startZoom, endZoom: 1.0f)
{ {
AllowInterrupt = true, AllowInterrupt = true,
RemoveControlFromCharacter = false RemoveControlFromCharacter = false
@@ -274,7 +288,8 @@ namespace Barotrauma
var transition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, var transition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam,
null, null, null, null,
fadeOut: false, fadeOut: false,
duration: 5, losFadeIn: true,
panDuration: 5,
startZoom: 0.5f, endZoom: 1.0f) startZoom: 0.5f, endZoom: 1.0f)
{ {
AllowInterrupt = true, AllowInterrupt = true,
@@ -311,6 +326,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);
GUI.SetSavingIndicatorState(success);
crewDead = false; crewDead = false;
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
@@ -327,7 +343,7 @@ namespace Barotrauma
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
Alignment.Center, Alignment.Center,
fadeOut: false, fadeOut: false,
duration: EndTransitionDuration); panDuration: EndTransitionDuration);
GameMain.Client.EndCinematic = endTransition; GameMain.Client.EndCinematic = endTransition;
Location portraitLocation = Map?.SelectedLocation ?? Map?.CurrentLocation ?? Level.Loaded?.StartLocation; Location portraitLocation = Map?.SelectedLocation ?? Map?.CurrentLocation ?? Level.Loaded?.StartLocation;
@@ -335,7 +351,7 @@ namespace Barotrauma
{ {
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
} }
float fadeOutDuration = endTransition.Duration; float fadeOutDuration = endTransition.PanDuration;
float t = 0.0f; float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running) while (t < fadeOutDuration || endTransition.Running)
{ {
@@ -368,6 +384,7 @@ namespace Barotrauma
} }
} }
GUI.SetSavingIndicatorState(false);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -420,7 +437,7 @@ namespace Barotrauma
{ {
//wasn't initially docked (sub doesn't have a docking port?) //wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost // -> choose a destination when the sub is far enough from the start outpost
if (!Submarine.MainSub.AtStartPosition) if (!Submarine.MainSub.AtStartExit)
{ {
ForceMapUI = true; ForceMapUI = true;
if (CampaignUI == null) { InitCampaignUI(); } if (CampaignUI == null) { InitCampaignUI(); }
@@ -437,8 +454,10 @@ namespace Barotrauma
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
} }
HintManager.OnAvailableTransition(transitionType);
} }
} }
public override void End(TransitionType transitionType = TransitionType.None) public override void End(TransitionType transitionType = TransitionType.None)
{ {
base.End(transitionType); base.End(transitionType);
@@ -496,7 +515,7 @@ namespace Barotrauma
var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam, var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam,
null, Alignment.Center, null, Alignment.Center,
fadeOut: true, fadeOut: true,
duration: 10, panDuration: 10,
startZoom: null, endZoom: 0.2f); startZoom: null, endZoom: 0.2f);
while (transition.Running) while (transition.Running)
@@ -649,7 +668,7 @@ namespace Barotrauma
{ {
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, mapSeed); GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Unsure, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -711,13 +730,20 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error when receiving campaign data from the server: mission prefab \"{availableMission.First}\" not found."); DebugConsole.ThrowError($"Error when receiving campaign data from the server: mission prefab \"{availableMission.First}\" not found.");
continue; continue;
} }
if (availableMission.Second < 0 || availableMission.Second >= campaign.Map.CurrentLocation.Connections.Count) if (availableMission.Second == 255)
{ {
DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.First}\" out of range (index: {availableMission.Second}, current location: {campaign.Map.CurrentLocation.Name}, connections: {campaign.Map.CurrentLocation.Connections.Count})."); campaign.Map.CurrentLocation.UnlockMission(missionPrefab);
continue; }
else
{
if (availableMission.Second < 0 || availableMission.Second >= campaign.Map.CurrentLocation.Connections.Count)
{
DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.First}\" out of range (index: {availableMission.Second}, current location: {campaign.Map.CurrentLocation.Name}, connections: {campaign.Map.CurrentLocation.Connections.Count}).");
continue;
}
LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.Second];
campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection);
} }
LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.Second];
campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection);
} }
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -771,15 +797,28 @@ namespace Barotrauma
pendingHires.Add(msg.ReadInt32()); pendingHires.Add(msg.ReadInt32());
} }
bool validateHires = msg.ReadBoolean(); ushort hiredLength = msg.ReadUInt16();
List<CharacterInfo> hiredCharacters = new List<CharacterInfo>();
for (int i = 0; i < hiredLength; i++)
{
CharacterInfo hired = CharacterInfo.ClientRead("human", msg);
hired.Salary = msg.ReadInt32();
hiredCharacters.Add(hired);
}
bool renameCrewMember = msg.ReadBoolean();
if (renameCrewMember)
{
int renamedIdentifier = msg.ReadInt32();
string newName = msg.ReadString();
CharacterInfo renamedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
if (renamedCharacter != null) { CrewManager.RenameCharacter(renamedCharacter, newName); }
}
bool fireCharacter = msg.ReadBoolean(); bool fireCharacter = msg.ReadBoolean();
int firedIdentifier = -1;
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
if (fireCharacter) if (fireCharacter)
{ {
int firedIdentifier = msg.ReadInt32();
CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier); CharacterInfo firedCharacter = CrewManager.CharacterInfos.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); }
@@ -787,10 +826,10 @@ namespace Barotrauma
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null) if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null)
{ {
CampaignUI?.CrewManagement?.SetHireables(map.CurrentLocation, availableHires); CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
if (validateHires) { CampaignUI?.CrewManagement.ValidatePendingHires(); } if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); }
CampaignUI?.CrewManagement?.SetPendingHires(pendingHires, map?.CurrentLocation); CampaignUI.CrewManagement.SetPendingHires(pendingHires, map.CurrentLocation);
if (fireCharacter) { CampaignUI?.CrewManagement.UpdateCrew(); } if (renameCrewMember || fireCharacter) { CampaignUI.CrewManagement.UpdateCrew(); }
} }
} }
@@ -22,7 +22,7 @@ namespace Barotrauma
{ {
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; } if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; }
if (PlayerInput.RightButtonClicked() || if (PlayerInput.SecondaryMouseButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
@@ -57,11 +57,12 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Instantiates a new single player campaign /// Instantiates a new single player campaign
/// </summary> /// </summary>
private SinglePlayerCampaign(string mapSeed) : base(GameModePreset.SinglePlayerCampaign) private SinglePlayerCampaign(string mapSeed, CampaignSettings settings) : base(GameModePreset.SinglePlayerCampaign)
{ {
CampaignMetadata = new CampaignMetadata(this); CampaignMetadata = new CampaignMetadata(this);
UpgradeManager = new UpgradeManager(this); UpgradeManager = new UpgradeManager(this);
map = new Map(this, mapSeed); map = new Map(this, mapSeed, settings);
Settings = settings;
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{ {
for (int i = 0; i < jobPrefab.InitialCount; i++) for (int i = 0; i < jobPrefab.InitialCount; i++)
@@ -85,11 +86,14 @@ namespace Barotrauma
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
case "campaignsettings":
Settings = new CampaignSettings(subElement);
break;
case "crew": case "crew":
GameMain.GameSession.CrewManager = new CrewManager(subElement, true); GameMain.GameSession.CrewManager = new CrewManager(subElement, true);
break; break;
case "map": case "map":
map = Map.Load(this, subElement); map = Map.Load(this, subElement, Settings);
break; break;
case "metadata": case "metadata":
CampaignMetadata = new CampaignMetadata(this, subElement); CampaignMetadata = new CampaignMetadata(this, subElement);
@@ -141,9 +145,9 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Start a completely new single player campaign /// Start a completely new single player campaign
/// </summary> /// </summary>
public static SinglePlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub) public static SinglePlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub, CampaignSettings settings)
{ {
var campaign = new SinglePlayerCampaign(mapSeed); var campaign = new SinglePlayerCampaign(mapSeed, settings);
return campaign; return campaign;
} }
@@ -213,6 +217,7 @@ namespace Barotrauma
if (!savedOnStart) if (!savedOnStart)
{ {
GUI.SetSavingIndicatorState(true);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
savedOnStart = true; savedOnStart = true;
} }
@@ -224,6 +229,8 @@ namespace Barotrauma
{ {
PetBehavior.LoadPets(petsElement); PetBehavior.LoadPets(petsElement);
} }
GUI.DisableSavingIndicatorDelayed();
} }
protected override void LoadInitialLevel() protected override void LoadInitialLevel()
@@ -290,15 +297,29 @@ namespace Barotrauma
break; break;
} }
} }
if (GameMain.GameSession == null)
{
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
var outpost = GameMain.GameSession.Level.StartOutpost;
var borders = outpost.GetDockedBorders();
borders.Location += outpost.WorldPosition.ToPoint();
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
float startZoom = 0.8f /
((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X);
GameMain.GameScreen.Cam.MinZoom = Math.Min(startZoom, GameMain.GameScreen.Cam.MinZoom);
var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam, var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam,
null, null, null, null,
fadeOut: false, fadeOut: false,
duration: 5, losFadeIn: true,
startZoom: 1.5f, endZoom: 1.0f) waitDuration: 1,
panDuration: 5,
startZoom: startZoom, endZoom: 1.0f)
{ {
AllowInterrupt = true, AllowInterrupt = true,
RemoveControlFromCharacter = false RemoveControlFromCharacter = false
@@ -323,19 +344,13 @@ namespace Barotrauma
else else
{ {
ISpatialEntity transitionTarget; ISpatialEntity transitionTarget;
if (prevControlled != null) transitionTarget = (ISpatialEntity)prevControlled ?? Submarine.MainSub;
{
transitionTarget = prevControlled;
}
else
{
transitionTarget = Submarine.MainSub;
}
var transition = new CameraTransition(transitionTarget, GameMain.GameScreen.Cam, var transition = new CameraTransition(transitionTarget, GameMain.GameScreen.Cam,
null, null, null, null,
fadeOut: false, fadeOut: false,
duration: 5, losFadeIn: prevControlled != null,
panDuration: 5,
startZoom: 0.5f, endZoom: 1.0f) startZoom: 0.5f, endZoom: 1.0f)
{ {
AllowInterrupt = true, AllowInterrupt = true,
@@ -370,11 +385,9 @@ namespace Barotrauma
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
SoundPlayer.OverrideMusicType = success ? "endround" : "crewdead"; SoundPlayer.OverrideMusicType = success ? "endround" : "crewdead";
SoundPlayer.OverrideMusicDuration = 18.0f; SoundPlayer.OverrideMusicDuration = 18.0f;
GUI.SetSavingIndicatorState(success);
crewDead = false; crewDead = false;
LevelData lvlData = GameMain.GameSession.LevelData;
bool beaconActive = GameMain.GameSession.Level.CheckBeaconActive();
GameMain.GameSession.EndRound("", traitorResults, transitionType); GameMain.GameSession.EndRound("", traitorResults, transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton; var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
RoundSummary roundSummary = null; RoundSummary roundSummary = null;
@@ -408,13 +421,13 @@ namespace Barotrauma
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center, transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
fadeOut: false, fadeOut: false,
duration: EndTransitionDuration); panDuration: EndTransitionDuration);
GUI.ClearMessages(); GUI.ClearMessages();
Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation; Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation;
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
float fadeOutDuration = endTransition.Duration; float fadeOutDuration = endTransition.PanDuration;
float t = 0.0f; float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running) while (t < fadeOutDuration || endTransition.Running)
{ {
@@ -459,8 +472,6 @@ namespace Barotrauma
} }
} }
lvlData.IsBeaconActive = beaconActive;
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
} }
else else
@@ -484,6 +495,7 @@ namespace Barotrauma
overlayColor = Color.Transparent; overlayColor = Color.Transparent;
}); });
GUI.SetSavingIndicatorState(false);
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -510,7 +522,7 @@ namespace Barotrauma
var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam, var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam,
null, Alignment.Center, null, Alignment.Center,
fadeOut: true, fadeOut: true,
duration: 10, panDuration: 10,
startZoom: null, endZoom: 0.2f); startZoom: null, endZoom: 0.2f);
while (transition.Running) while (transition.Running)
@@ -531,6 +543,8 @@ namespace Barotrauma
base.Update(deltaTime); base.Update(deltaTime);
Map?.Radiation?.UpdateRadiation(deltaTime);
if (PlayerInput.SecondaryMouseButtonClicked() || if (PlayerInput.SecondaryMouseButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
@@ -583,7 +597,7 @@ namespace Barotrauma
{ {
//wasn't initially docked (sub doesn't have a docking port?) //wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost // -> choose a destination when the sub is far enough from the start outpost
if (!Submarine.MainSub.AtStartPosition) if (!Submarine.MainSub.AtStartExit)
{ {
ForceMapUI = true; ForceMapUI = true;
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
@@ -611,6 +625,7 @@ namespace Barotrauma
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
} }
HintManager.OnAvailableTransition(transitionType);
} }
if (!crewDead) if (!crewDead)
@@ -632,9 +647,9 @@ namespace Barotrauma
if (nextLevel == null) if (nextLevel == null)
{ {
//no level selected -> force the player to select one //no level selected -> force the player to select one
ForceMapUI = true;
CampaignUI.SelectTab(InteractionType.Map); CampaignUI.SelectTab(InteractionType.Map);
map.SelectLocation(-1); map.SelectLocation(-1);
ForceMapUI = true;
return false; return false;
} }
else if (transitionType == TransitionType.ProgressToNextEmptyLocation) else if (transitionType == TransitionType.ProgressToNextEmptyLocation)
@@ -707,6 +722,7 @@ namespace Barotrauma
new XAttribute("purchasedhullrepairs", PurchasedHullRepairs), new XAttribute("purchasedhullrepairs", PurchasedHullRepairs),
new XAttribute("purchaseditemrepairs", PurchasedItemRepairs), new XAttribute("purchaseditemrepairs", PurchasedItemRepairs),
new XAttribute("cheatsenabled", CheatsEnabled)); new XAttribute("cheatsenabled", CheatsEnabled));
modeElement.Add(Settings.Save());
//save and remove all items that are in someone's inventory so they don't get included in the sub file as well //save and remove all items that are in someone's inventory so they don't get included in the sub file as well
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
@@ -614,7 +614,7 @@ namespace Barotrauma.Tutorials
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
var cinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight, duration: 5.0f); var cinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight, panDuration: 5.0f);
while (cinematic.Running) while (cinematic.Running)
{ {
@@ -99,7 +99,7 @@ namespace Barotrauma.Tutorials
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition; captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>(); tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>(); tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor")); var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("medicaldoctor"));
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor"); captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
captain_medic.TeamID = CharacterTeamType.Team1; captain_medic.TeamID = CharacterTeamType.Team1;
captain_medic.GiveJobItems(null); captain_medic.GiveJobItems(null);
@@ -122,17 +122,17 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_lockedDoor_1, null, false); SetDoorAccess(tutorial_lockedDoor_1, null, false);
SetDoorAccess(tutorial_lockedDoor_2, null, false); SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic")); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.TeamID = CharacterTeamType.Team1; captain_mechanic.TeamID = CharacterTeamType.Team1;
captain_mechanic.GiveJobItems(); captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.TeamID = CharacterTeamType.Team1; captain_security.TeamID = CharacterTeamType.Team1;
captain_security.GiveJobItems(); captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer"); captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.TeamID = CharacterTeamType.Team1; captain_engineer.TeamID = CharacterTeamType.Team1;
captain_engineer.GiveJobItems(); captain_engineer.GiveJobItems();
@@ -249,7 +249,7 @@ namespace Barotrauma.Tutorials
{ {
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false); yield return new WaitForSeconds(1.0f, false);
} while (!Submarine.MainSub.AtEndPosition || !Submarine.MainSub.DockedTo.Any()); } while (!Submarine.MainSub.AtEndExit || !Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[6]); RemoveCompletedObjective(segments[6]);
yield return new WaitForSeconds(3f, false); yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
@@ -78,7 +78,7 @@ namespace Barotrauma.Tutorials
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull; var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull; medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull;
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("assistant"));
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
patient1.TeamID = CharacterTeamType.Team1; patient1.TeamID = CharacterTeamType.Team1;
patient1.GiveJobItems(null); patient1.GiveJobItems(null);
@@ -86,26 +86,26 @@ namespace Barotrauma.Tutorials
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false); patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false);
patient1.AIController.Enabled = false; patient1.AIController.Enabled = false;
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("assistant"));
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
patient2.TeamID = CharacterTeamType.Team1; patient2.TeamID = CharacterTeamType.Team1;
patient2.GiveJobItems(null); patient2.GiveJobItems(null);
patient2.CanSpeak = false; patient2.CanSpeak = false;
patient2.AIController.Enabled = false; patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient1.TeamID = CharacterTeamType.Team1; subPatient1.TeamID = CharacterTeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1); subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = CharacterTeamType.Team1; subPatient2.TeamID = CharacterTeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2); subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient3.TeamID = CharacterTeamType.Team1; subPatient3.TeamID = CharacterTeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
@@ -283,7 +283,7 @@ namespace Barotrauma.Tutorials
doctor.RemoveActiveObjectiveEntity(patient1); doctor.RemoveActiveObjectiveEntity(patient1);
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command)); // Get the patient to medbay TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command)); // Get the patient to medbay
while (patient1.CurrentOrder == null || patient1.CurrentOrder.Identifier != "follow") while (patient1.GetCurrentOrderWithTopPriority()?.Order?.Identifier != "follow")
{ {
// TODO: Rework order highlighting for new command UI // TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5)); // GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5));
@@ -317,7 +317,8 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos); officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
((EnemyAIController)officer_hammerhead.AIController).StayInsideLevel = false; officer_hammerhead.Params.AI.AvoidAbyss = false;
officer_hammerhead.Params.AI.StayInAbyss = false;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget); officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
SetHighlight(officer_coilgunPeriscope, true); SetHighlight(officer_coilgunPeriscope, true);
float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos); float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos);
@@ -62,7 +62,7 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefab: null); GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams != null) if (generationParams != null)
@@ -110,7 +110,7 @@ namespace Barotrauma.Tutorials
} }
CharacterInfo charInfo = configElement.Element("Character") == null ? CharacterInfo charInfo = configElement.Element("Character") == null ?
new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) : new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: JobPrefab.Get("engineer")) :
new CharacterInfo(configElement.Element("Character")); new CharacterInfo(configElement.Element("Character"));
WayPoint wayPoint = GetSpawnPoint(charInfo); WayPoint wayPoint = GetSpawnPoint(charInfo);
@@ -182,7 +182,8 @@ namespace Barotrauma.Tutorials
protected bool HasOrder(Character character, string identifier, string option = null) protected bool HasOrder(Character character, string identifier, string option = null)
{ {
if (character.CurrentOrder?.Identifier == identifier) var currentOrderInfo = character.GetCurrentOrderWithTopPriority();
if (currentOrderInfo?.Order?.Identifier == identifier)
{ {
if (option == null) if (option == null)
{ {
@@ -190,8 +191,7 @@ namespace Barotrauma.Tutorials
} }
else else
{ {
HumanAIController humanAI = character.AIController as HumanAIController; return currentOrderInfo?.OrderOption == option;
return humanAI.CurrentOrderOption == option;
} }
} }
@@ -288,7 +288,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(waitBeforeFade); yield return new WaitForSeconds(waitBeforeFade);
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, duration: fadeOutTime); var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: fadeOutTime);
currentTutorialCompleted = Completed = true; currentTutorialCompleted = Completed = true;
while (endCinematic.Running) yield return null; while (endCinematic.Running) yield return null;
Stop(); Stop();
@@ -535,15 +535,15 @@ namespace Barotrauma.Tutorials
titleBlock.RectTransform.IsFixedSize = true; titleBlock.RectTransform.IsFixedSize = true;
} }
List<RichTextData> richTextData = RichTextData.GetRichTextData(text, out text); List<RichTextData> richTextData = RichTextData.GetRichTextData(" " + text, out text);
GUITextBlock textBlock; GUITextBlock textBlock;
if (richTextData == null) if (richTextData == null)
{ {
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true); textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
} }
else else
{ {
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), richTextData, " " + text, wrap: true); textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), richTextData, text, wrap: true);
} }
textBlock.RectTransform.IsFixedSize = true; textBlock.RectTransform.IsFixedSize = true;
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
{ {
@@ -26,6 +27,7 @@ namespace Barotrauma
if (tabMenu == null && GameMode is TutorialMode == false) if (tabMenu == null && GameMode is TutorialMode == false)
{ {
tabMenu = new TabMenu(); tabMenu = new TabMenu();
HintManager.OnShowTabMenu();
} }
else else
{ {
@@ -36,12 +38,106 @@ namespace Barotrauma
return true; return true;
} }
private GUILayoutGroup topLeftButtonGroup;
private GUIButton crewListButton, commandButton, tabMenuButton;
private GUIComponent respawnInfoFrame, respawnButtonContainer;
private GUITextBlock respawnInfoText;
private GUITickBox respawnTickBox;
private GUILayoutGroup TopLeftButtonGroup;
private void CreateTopLeftButtons()
{
if (topLeftButtonGroup != null)
{
topLeftButtonGroup.RectTransform.Parent = null;
topLeftButtonGroup = null;
crewListButton = commandButton = tabMenuButton = null;
}
topLeftButtonGroup = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUI.Canvas), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = HUDLayoutSettings.Padding,
CanBeFocused = false
};
topLeftButtonGroup.RectTransform.ParentChanged += (_) =>
{
GameMain.Instance.ResolutionChanged -= CreateTopLeftButtons;
};
int buttonHeight = GUI.IntScale(40);
Vector2 buttonSpriteSize = GUI.Style.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size;
int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X);
Point buttonSize = new Point(buttonWidth, buttonHeight);
crewListButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "CrewListToggleButton")
{
ToolTip = TextManager.GetWithVariable("hudbutton.crewlist", "[key]", GameMain.Config.KeyBindText(InputType.CrewOrders)),
OnClicked = (GUIButton btn, object userdata) =>
{
if (CrewManager == null) { return false; }
CrewManager.IsCrewMenuOpen = !CrewManager.IsCrewMenuOpen;
return true;
}
};
commandButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "CommandButton")
{
ToolTip = TextManager.GetWithVariable("hudbutton.commandinterface", "[key]", GameMain.Config.KeyBindText(InputType.Command)),
OnClicked = (button, userData) =>
{
if (CrewManager == null) { return false; }
CrewManager.ToggleCommandUI();
return true;
}
};
tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton")
{
ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)),
OnClicked = (button, userData) =>
{
return ToggleTabMenu();
}
};
GameMain.Instance.ResolutionChanged += CreateTopLeftButtons;
respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
{ MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null)
{
Visible = false
};
respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform), "", wrap: true);
respawnButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = HUDLayoutSettings.Padding,
Stretch = true
};
respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn"))
{
ToolTip = TextManager.Get("respawnquestionprompt"),
OnSelected = (tickbox) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: !tickbox.Selected);
return true;
}
};
}
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
GameMode?.AddToGUIUpdateList(); GameMode?.AddToGUIUpdateList();
tabMenu?.AddToGUIUpdateList(); tabMenu?.AddToGUIUpdateList();
if ((!(GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
{
if (topLeftButtonGroup == null)
{
CreateTopLeftButtons();
}
crewListButton.Selected = CrewManager != null && CrewManager.IsCrewMenuOpen;
commandButton.Selected = CrewManager.IsCommandInterfaceOpen;
commandButton.Enabled = CrewManager.CanIssueOrders;
tabMenuButton.Selected = IsTabMenuOpen;
topLeftButtonGroup.AddToGUIUpdateList();
}
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList(); GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList();
@@ -55,7 +151,7 @@ namespace Barotrauma
if (tabMenu == null) if (tabMenu == null)
{ {
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) if (PlayerInput.KeyHit(InputType.InfoTab) && !(GUI.KeyboardDispatcher.Subscriber is GUITextBox))
{ {
ToggleTabMenu(); ToggleTabMenu();
} }
@@ -63,8 +159,8 @@ namespace Barotrauma
else else
{ {
tabMenu.Update(); tabMenu.Update();
if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) &&
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) !(GUI.KeyboardDispatcher.Subscriber is GUITextBox))
{ {
ToggleTabMenu(); ToggleTabMenu();
} }
@@ -88,6 +184,19 @@ namespace Barotrauma
} }
} }
} }
HintManager.Update();
}
public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn)
{
if (topLeftButtonGroup == null) { return; }
respawnInfoFrame.Visible = visible;
if (!visible) { return; }
respawnInfoText.Text = text;
respawnInfoText.TextColor = textColor;
respawnButtonContainer.Visible = buttonsVisible;
respawnTickBox.Selected = !waitForNextRoundRespawn;
} }
public void Draw(SpriteBatch spriteBatch) public void Draw(SpriteBatch spriteBatch)
@@ -0,0 +1,731 @@
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
static class HintManager
{
private const string HintManagerFile = "hintmanager.xml";
private static HashSet<string> HintIdentifiers { get; set; }
private static Dictionary<string, HashSet<string>> HintTags { get; } = new Dictionary<string, HashSet<string>>();
private static Dictionary<string, (string identifier, string option)> HintOrders { get; } = new Dictionary<string, (string orderIdentifier, string orderOption)>();
/// <summary>
/// Hints that have already been shown this round and shouldn't be shown shown again until the next round
/// </summary>
private static HashSet<string> HintsIgnoredThisRound { get; } = new HashSet<string>();
private static GUIMessageBox ActiveHintMessageBox { get; set; }
private static Action OnUpdate { get; set; }
private static double TimeStoppedInteracting { get; set; }
private static double TimeRoundStarted { get; set; }
/// <summary>
/// Seconds before any reminders can be shown
/// </summary>
private static int TimeBeforeReminders { get; set; }
/// <summary>
/// Seconds before another reminder can be shown
/// </summary>
private static int ReminderCooldown { get; set; }
private static double TimeReminderLastDisplayed { get; set; }
private static HashSet<Hull> BallastHulls { get; } = new HashSet<Hull>();
public static void Init()
{
if (File.Exists(HintManagerFile))
{
var doc = XMLExtensions.TryLoadXml(HintManagerFile);
if (doc?.Root != null)
{
HintIdentifiers = new HashSet<string>();
foreach (var element in doc.Root.Elements())
{
GetHintsRecursive(element, element.Name.ToString());
}
}
else
{
DebugConsole.ThrowError($"File \"{HintManagerFile}\" is empty - cannot initialize the HintManager!");
}
}
else
{
DebugConsole.ThrowError($"File \"{HintManagerFile}\" is missing - cannot initialize the HintManager!");
}
static void GetHintsRecursive(XElement element, string identifier)
{
if (!element.HasElements)
{
HintIdentifiers.Add(identifier);
if (element.GetAttributeStringArray("tags", null, convertToLowerInvariant: true) is string[] tags)
{
HintTags.TryAdd(identifier, tags.ToHashSet());
}
if (element.GetAttributeString("order", null) is string orderIdentifier && !string.IsNullOrEmpty(orderIdentifier))
{
string orderOption = element.GetAttributeString("orderoption", "");
HintOrders.Add(identifier, (orderIdentifier, orderOption));
}
return;
}
else if (element.Name.ToString().Equals("reminder"))
{
TimeBeforeReminders = element.GetAttributeInt("timebeforereminders", TimeBeforeReminders);
ReminderCooldown = element.GetAttributeInt("remindercooldown", ReminderCooldown);
}
foreach (var childElement in element.Elements())
{
GetHintsRecursive(childElement, $"{identifier}.{childElement.Name}");
}
}
}
public static void Update()
{
if (HintIdentifiers == null || GameMain.Config.DisableInGameHints) { return; }
if (GameMain.GameSession == null || !GameMain.GameSession.IsRunning) { return; }
if (ActiveHintMessageBox != null)
{
if (ActiveHintMessageBox.Closed)
{
ActiveHintMessageBox = null;
OnUpdate = null;
}
else
{
OnUpdate?.Invoke();
return;
}
}
CheckIsInteracting();
CheckIfDivingGearOutOfOxygen();
CheckHulls();
CheckReminders();
}
public static void OnSetSelectedConstruction(Character character, Item oldConstruction, Item newConstruction)
{
if (oldConstruction == newConstruction) { return; }
if (Character.Controlled != null && Character.Controlled == character && oldConstruction != null && oldConstruction.GetComponent<Ladder>() == null)
{
TimeStoppedInteracting = Timing.TotalTime;
}
if (newConstruction == null) { return; }
if (newConstruction.GetComponent<Ladder>() != null) { return; }
if (newConstruction.GetComponent<ConnectionPanel>() is ConnectionPanel cp && cp.User == character) { return; }
OnStartedInteracting(character, newConstruction);
}
private static void OnStartedInteracting(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled || item == null) { return; }
string hintIdentifierBase = "onstartedinteracting";
// onstartedinteracting.brokenitem
if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold))
{
if (DisplayHint($"{hintIdentifierBase}.brokenitem")) { return; }
}
// Don't display other item-related hints if the repair interface is displayed
if (item.Repairables.Any(r => r.ShouldDrawHUD(character))) { return; }
// onstartedinteracting.lootingisstealing
if (item.Submarine?.Info?.Type == SubmarineType.Outpost &&
item.ContainedItems.Any(i => !i.AllowStealing))
{
if (DisplayHint($"{hintIdentifierBase}.lootingisstealing")) { return; }
}
// onstartedinteracting.turretperiscope
if (item.HasTag("periscope") &&
item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret)
{
if (DisplayHint($"{hintIdentifierBase}.turretperiscope",
variableTags: new string[] { "[shootkey]", "[deselectkey]", },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect) }))
{ return; }
}
// onstartedinteracting.item...
hintIdentifierBase += ".item";
foreach (string hintIdentifier in HintIdentifiers)
{
if (!hintIdentifier.StartsWith(hintIdentifierBase)) { continue; }
if (!HintTags.TryGetValue(hintIdentifier, out var hintTags)) { continue; }
if (!item.HasTag(hintTags)) { continue; }
if (DisplayHint(hintIdentifier)) { return; }
}
}
private static void CheckIsInteracting()
{
if (!CanDisplayHints()) { return; }
if (Character.Controlled?.SelectedConstruction == null) { return; }
if (Character.Controlled.SelectedConstruction.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedConstruction.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag("reactorfuel")) > 1)
{
if (DisplayHint("onisinteracting.reactorwithextrarods")) { return; }
}
}
public static void OnRoundStarted()
{
// Make sure everything's been reset properly, OnRoundEnded() isn't always called when exiting a game
Reset();
TimeRoundStarted = GameMain.GameScreen.GameTime;
var initRoundHandle = CoroutineManager.StartCoroutine(InitRound(), "HintManager.InitRound");
if (!CanDisplayHints(requireGameScreen: false, requireControllingCharacter: false)) { return; }
CoroutineManager.StartCoroutine(DisplayRoundStartedHints(initRoundHandle), "HintManager.DisplayRoundStartedHints");
static IEnumerable<object> InitRound()
{
while (Character.Controlled == null) { yield return CoroutineStatus.Running; }
// Get the ballast hulls on round start not to find them again and again later
BallastHulls.Clear();
var sub = Submarine.MainSubs.FirstOrDefault(s => s != null && s.TeamID == Character.Controlled.TeamID);
if (sub != null)
{
foreach (var item in sub.GetItems(true))
{
if (item.CurrentHull == null) { continue; }
if (item.GetComponent<Pump>() == null) { continue; }
if (!item.HasTag("ballast")) { continue; }
BallastHulls.Add(item.CurrentHull);
}
}
yield return CoroutineStatus.Success;
}
static IEnumerable<object> DisplayRoundStartedHints(CoroutineHandle initRoundHandle)
{
while (GameMain.Instance.LoadingScreenOpen || Screen.Selected != GameMain.GameScreen ||
CoroutineManager.IsCoroutineRunning(initRoundHandle) ||
CoroutineManager.IsCoroutineRunning("LevelTransition") ||
CoroutineManager.IsCoroutineRunning("SinglePlayerCampaign.DoInitialCameraTransition") ||
CoroutineManager.IsCoroutineRunning("MultiPlayerCampaign.DoInitialCameraTransition") ||
GUIMessageBox.VisibleBox != null || Character.Controlled == null)
{
yield return CoroutineStatus.Running;
}
OnStartedControlling();
while (ActiveHintMessageBox != null)
{
yield return CoroutineStatus.Running;
}
if (!GameMain.GameSession.GameMode.IsSinglePlayer &&
GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled)
{
DisplayHint("onroundstarted.voipdisabled", onUpdate: () =>
{
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) { return; }
ActiveHintMessageBox.Close();
});
}
yield return CoroutineStatus.Success;
}
}
public static void OnRoundEnded()
{
Reset();
}
private static void Reset()
{
CoroutineManager.StopCoroutines("HintManager.InitRound");
CoroutineManager.StopCoroutines("HintManager.DisplayRoundStartedHints");
if (ActiveHintMessageBox != null)
{
GUIMessageBox.MessageBoxes.Remove(ActiveHintMessageBox);
ActiveHintMessageBox = null;
}
OnUpdate = null;
HintsIgnoredThisRound.Clear();
}
public static void OnSonarSpottedCharacter(Item sonar, Character spottedCharacter)
{
if (!CanDisplayHints()) { return; }
if (sonar == null || sonar.Removed) { return; }
if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; }
if (Character.Controlled.SelectedConstruction != sonar) { return; }
if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; }
DisplayHint("onsonarspottedenemy");
}
public static void OnAfflictionDisplayed(Character character, List<Affliction> displayedAfflictions)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled || displayedAfflictions == null) { return; }
foreach (var affliction in displayedAfflictions)
{
if (affliction?.Prefab == null) { continue; }
if (affliction.Prefab.IsBuff) { continue; }
if (affliction.Prefab == AfflictionPrefab.OxygenLow) { continue; }
if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.IsEntityRadiated(character) ?? false)) { continue; }
if (affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
DisplayHint("onafflictiondisplayed",
variableTags: new string[1] { "[key]" },
variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Health) },
icon: affliction.Prefab.Icon,
iconColor: CharacterHealth.GetAfflictionIconColor(affliction),
onUpdate: () =>
{
if (CharacterHealth.OpenHealthWindow == null) { return; }
ActiveHintMessageBox.Close();
});
return;
}
}
public static void OnShootWithoutAiming(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
if (character.SelectedConstruction != null || character.FocusedItem != null) { return; }
if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; }
if (TimeStoppedInteracting + 1 > Timing.TotalTime) { return; }
if (GUI.MouseOn != null) { return; }
if (Character.Controlled.Inventory?.visualSlots != null && Character.Controlled.Inventory.visualSlots.Any(s => s.InteractRect.Contains(PlayerInput.MousePosition))) { return; }
string hintIdentifier = "onshootwithoutaiming";
if (!HintTags.TryGetValue(hintIdentifier, out var tags)) { return; }
if (!item.HasTag(tags)) { return; }
DisplayHint(hintIdentifier,
variableTags: new string[1] { "[key]" },
variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Aim) },
onUpdate: () =>
{
if (character.SelectedConstruction == null && GUI.MouseOn == null && PlayerInput.KeyDown(InputType.Aim))
{
ActiveHintMessageBox.Close();
}
});
}
public static void OnWeldingDoor(Character character, Door door)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
if (door == null || door.Stuck < 20.0f) { return; }
DisplayHint("onweldingdoor");
}
public static void OnTryOpenStuckDoor(Character character)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
DisplayHint("ontryopenstuckdoor");
}
public static void OnShowCampaignInterface(CampaignMode.InteractionType interactionType)
{
if (!CanDisplayHints()) { return; }
if (interactionType == CampaignMode.InteractionType.None) { return; }
string hintIdentifier = $"onshowcampaigninterface.{interactionType.ToString().ToLowerInvariant()}";
DisplayHint(hintIdentifier, onUpdate: () =>
{
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign) ||
(!campaign.ShowCampaignUI && !campaign.ForceMapUI) ||
campaign.CampaignUI?.SelectedTab != CampaignMode.InteractionType.Map)
{
ActiveHintMessageBox.Close();
}
});
}
public static void OnShowCommandInterface()
{
IgnoreReminder("commandinterface");
if (!CanDisplayHints()) { return; }
DisplayHint("onshowcommandinterface", onUpdate: () =>
{
if (CrewManager.IsCommandInterfaceOpen) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnShowHealthInterface()
{
if (!CanDisplayHints()) { return; }
if (CharacterHealth.OpenHealthWindow == null) { return; }
DisplayHint("onshowhealthinterface", onUpdate: () =>
{
if (CharacterHealth.OpenHealthWindow != null) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnShowTabMenu()
{
IgnoreReminder("tabmenu");
}
public static void OnStoleItem(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
if (item == null || item.AllowStealing || !item.StolenDuringRound) { return; }
DisplayHint("onstoleitem", onUpdate: () =>
{
if (item == null || item.Removed || item.GetRootInventoryOwner() != character)
{
ActiveHintMessageBox.Close();
}
});
}
public static void OnHandcuffed(Character character)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled || !character.LockHands) { return; }
DisplayHint("onhandcuffed", onUpdate: () =>
{
if (character != null && !character.Removed && character.LockHands) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnReactorOutOfFuel(Reactor reactor)
{
if (!CanDisplayHints()) { return; }
if (reactor == null) { return; }
if (reactor.Item.Submarine?.Info?.Type != SubmarineType.Player || reactor.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; }
if (!HasValidJob("engineer")) { return; }
DisplayHint("onreactoroutoffuel", onUpdate: () =>
{
if (reactor?.Item != null && !reactor.Item.Removed && reactor.AvailableFuel < 1) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnAvailableTransition(CampaignMode.TransitionType transitionType)
{
if (!CanDisplayHints()) { return; }
if (transitionType == CampaignMode.TransitionType.None) { return; }
DisplayHint($"onavailabletransition.{transitionType.ToString().ToLowerInvariant()}");
}
public static void OnShowSubInventory(Item item)
{
if (item?.Prefab == null) { return; }
if (item.Prefab.Identifier.Equals("toolbelt", StringComparison.OrdinalIgnoreCase))
{
IgnoreReminder("toolbelt");
}
}
public static void OnChangeCharacter()
{
IgnoreReminder("characterchange");
}
public static void OnCharacterUnconscious(Character character)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
if (character.IsDead) { return; }
if (character.CharacterHealth != null && character.Vitality < character.CharacterHealth.MinVitality) { return; }
DisplayHint("oncharacterunconscious");
}
public static void OnCharacterKilled(Character character)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
if (GameMain.IsMultiplayer) { return; }
if (GameMain.GameSession?.CrewManager == null) { return; }
if (GameMain.GameSession.CrewManager.GetCharacters().None(c => !c.IsDead)) { return; }
DisplayHint("oncharacterkilled");
}
private static void OnStartedControlling()
{
if (Level.IsLoadedOutpost) { return; }
if (Character.Controlled?.Info?.Job?.Prefab == null) { return; }
string hintIdentifier = $"onstartedcontrolling.job.{Character.Controlled.Info.Job.Prefab.Identifier}";
DisplayHint(hintIdentifier,
icon: Character.Controlled.Info.Job.Prefab.Icon,
iconColor: Character.Controlled.Info.Job.Prefab.UIColor,
onDisplay: () =>
{
if (!HintOrders.TryGetValue(hintIdentifier, out var orderInfo)) { return; }
var orderPrefab = Order.GetPrefab(orderInfo.identifier);
if (orderPrefab == null) { return; }
Item targetEntity = null;
ItemComponent targetItem = null;
if (orderPrefab.MustSetTarget)
{
targetEntity = orderPrefab.GetMatchingItems(true, interactableFor: Character.Controlled).FirstOrDefault();
if (targetEntity == null) { return; }
targetItem = orderPrefab.GetTargetItemComponent(targetEntity);
}
var order = new Order(orderPrefab, targetEntity as Entity, targetItem, orderGiver: Character.Controlled);
GameMain.GameSession.CrewManager.SetCharacterOrder(Character.Controlled, order, orderInfo.option, CharacterInfo.HighestManualOrderPriority, Character.Controlled);
});
}
public static void OnAutoPilotPathUpdated(Steering steering)
{
if (!CanDisplayHints()) { return; }
if (!HasValidJob("captain")) { return; }
if (steering?.Item?.Submarine?.Info == null) { return; }
if (steering.Item.Submarine.Info.Type != SubmarineType.Player) { return; }
if (steering.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; }
if (!steering.AutoPilot || steering.MaintainPos) { return; }
if (steering.SteeringPath?.CurrentNode?.Tunnel?.Type != Level.TunnelType.MainPath) { return; }
if (!steering.SteeringPath.Finished && steering.SteeringPath.NextNode != null) { return; }
if (steering.LevelStartSelected && (Level.Loaded.StartOutpost == null || !steering.Item.Submarine.AtStartExit)) { return; }
if (steering.LevelEndSelected && (Level.Loaded.EndOutpost == null || !steering.Item.Submarine.AtEndExit)) { return; }
DisplayHint("onautopilotreachedoutpost");
}
public static void OnStatusEffectApplied(ItemComponent component, ActionType actionType, Character character)
{
if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; }
// Could make this more generic if there will ever be any other status effect related hints
if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; }
DisplayHint("onrepairfailed");
}
public static void OnActiveOrderAdded(Order order)
{
if (!CanDisplayHints()) { return; }
if (order == null) { return; }
if (order.Identifier == "reportballastflora" &&
order.TargetEntity is Hull h &&
h.Submarine?.TeamID == Character.Controlled.TeamID)
{
DisplayHint("onballastflorainfected");
}
}
private static void CheckIfDivingGearOutOfOxygen()
{
if (!CanDisplayHints()) { return; }
var divingGear = Character.Controlled.GetEquippedItem("diving");
if (divingGear?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; }
DisplayHint("ondivinggearoutofoxygen", onUpdate: () =>
{
if (divingGear == null || divingGear.Removed ||
Character.Controlled == null || !Character.Controlled.HasEquippedItem(divingGear) ||
divingGear.GetContainedItemConditionPercentage() > 0.0f)
{
ActiveHintMessageBox.Close();
}
});
}
private static void CheckHulls()
{
if (!CanDisplayHints()) { return; }
if (Character.Controlled.CurrentHull == null) { return; }
if (HumanAIController.IsBallastFloraNoticeable(Character.Controlled, Character.Controlled.CurrentHull))
{
if (DisplayHint("onballastflorainfected")) { return; }
}
foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps)
{
if (gap.ConnectedDoor == null || gap.ConnectedDoor.Impassable) { continue; }
if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; }
if (!gap.IsRoomToRoom)
{
if (!(Character.Controlled.GetEquippedItem("deepdiving") is Item)) { continue; }
if (Character.Controlled.IsProtectedFromPressure()) { continue; }
if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; }
continue;
}
foreach (var me in gap.linkedTo)
{
if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; }
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; }
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; }
}
}
}
private static void CheckReminders()
{
if (!CanDisplayHints()) { return; }
if (Level.Loaded == null) { return; }
if (GameMain.GameScreen.GameTime < TimeRoundStarted + TimeBeforeReminders) { return; }
if (GameMain.GameScreen.GameTime < TimeReminderLastDisplayed + ReminderCooldown) { return; }
string hintIdentifierBase = "reminder";
if (GameMain.GameSession.GameMode.IsSinglePlayer)
{
if (DisplayHint($"{hintIdentifierBase}.characterchange"))
{
TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return;
}
}
if (Level.Loaded.Type != LevelData.LevelType.Outpost)
{
if (DisplayHint($"{hintIdentifierBase}.commandinterface",
variableTags: new string[] { "[commandkey]" },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Command) },
onUpdate: () =>
{
if (!CrewManager.IsCommandInterfaceOpen) { return; }
ActiveHintMessageBox.Close();
}))
{
TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return;
}
}
if (DisplayHint($"{hintIdentifierBase}.tabmenu",
variableTags: new string[] { "[infotabkey]" },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.InfoTab) },
onUpdate: () =>
{
if (!GameSession.IsTabMenuOpen) { return; }
ActiveHintMessageBox.Close();
}))
{
TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return;
}
if (Character.Controlled.Inventory?.GetItemInLimbSlot(InvSlotType.Bag)?.Prefab?.Identifier == "toolbelt")
{
if (DisplayHint($"{hintIdentifierBase}.toolbelt"))
{
TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return;
}
}
}
private static bool DisplayHint(string hintIdentifier, bool extendTextTag = true, string[] variableTags = null, string[] variableValues = null, Sprite icon = null, Color? iconColor = null, Action onDisplay = null, Action onUpdate = null)
{
if (string.IsNullOrEmpty(hintIdentifier)) { return false; }
if (!HintIdentifiers.Contains(hintIdentifier)) { return false; }
if (GameMain.Config.IgnoredHints.Contains(hintIdentifier)) { return false; }
if (HintsIgnoredThisRound.Contains(hintIdentifier)) { return false; }
string text;
string textTag = extendTextTag ? $"hint.{hintIdentifier}" : hintIdentifier;
if (variableTags != null && variableTags != null && variableTags.Length > 0 && variableTags.Length == variableValues.Length)
{
text = TextManager.GetWithVariables(textTag, variableTags, variableValues, returnNull: true);
}
else
{
text = TextManager.Get(textTag, returnNull: true);
}
if (string.IsNullOrEmpty(text))
{
#if DEBUG
DebugConsole.ThrowError($"No hint text found for text tag \"{textTag}\"");
#endif
return false;
}
HintsIgnoredThisRound.Add(hintIdentifier);
ActiveHintMessageBox = new GUIMessageBox(hintIdentifier, text, icon);
if (iconColor.HasValue) { ActiveHintMessageBox.IconColor = iconColor.Value; }
OnUpdate = onUpdate;
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
ActiveHintMessageBox.InnerFrame.Flash(color: iconColor ?? Color.Orange, flashDuration: 0.75f);
onDisplay?.Invoke();
return true;
}
public static bool OnDontShowAgain(GUITickBox tickBox)
{
IgnoreHint((string)tickBox.UserData, ignore: tickBox.Selected);
return true;
}
private static void IgnoreHint(string hintIdentifier, bool ignore = true)
{
if (string.IsNullOrEmpty(hintIdentifier)) { return; }
if (!HintIdentifiers.Contains(hintIdentifier))
{
#if DEBUG
DebugConsole.ThrowError($"Tried to ignore a hint not defined in {HintManagerFile}: {hintIdentifier}");
#endif
return;
}
if (ignore)
{
GameMain.Config.IgnoredHints.Add(hintIdentifier);
}
else
{
GameMain.Config.IgnoredHints.Remove(hintIdentifier);
}
}
private static void IgnoreReminder(string reminderIdentifier)
{
HintsIgnoredThisRound.Add($"reminder.{reminderIdentifier}");
}
public static bool OnDisableHints(GUITickBox tickBox)
{
GameMain.Config.DisableInGameHints = tickBox.Selected;
return GameMain.Config.SaveNewPlayerConfig();
}
private static bool CanDisplayHints(bool requireGameScreen = true, bool requireControllingCharacter = true)
{
if (HintIdentifiers == null) { return false; }
if (GameMain.Config.DisableInGameHints) { return false; }
if (ActiveHintMessageBox != null) { return false; }
if (requireControllingCharacter && Character.Controlled == null) { return false; }
var gameMode = GameMain.GameSession?.GameMode;
if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; }
if (requireGameScreen && Screen.Selected != GameMain.GameScreen) { return false; }
return true;
}
private static bool HasValidJob(string jobIdentifier)
{
// In singleplayer, we can control all character so we don't care about job restrictions
if (GameMain.GameSession.GameMode.IsSinglePlayer) { return true; }
if (Character.Controlled.HasJob(jobIdentifier)) { return true; }
// In multiplayer, if there are players with the job, display the hint to all players
foreach (var c in GameMain.GameSession.CrewManager.GetCharacters())
{
if (c == null || !c.IsRemotePlayer) { continue; }
if (c.IsUnconscious || c.IsDead || c.Removed) { continue; }
if (!c.HasJob(jobIdentifier)) { continue; }
return false;
}
return true;
}
}
}
@@ -48,6 +48,7 @@ namespace Barotrauma
msgBox.Buttons[0].OnClicked = delegate msgBox.Buttons[0].OnClicked = delegate
{ {
msgBox.Close(); msgBox.Close();
if (GameMain.Client == null) { return true; }
SendState(ReadyStatus.Yes); SendState(ReadyStatus.Yes);
CreateResultsMessage(); CreateResultsMessage();
return true; return true;
@@ -57,6 +58,7 @@ namespace Barotrauma
msgBox.Buttons[1].OnClicked = delegate msgBox.Buttons[1].OnClicked = delegate
{ {
msgBox.Close(); msgBox.Close();
if (GameMain.Client == null) { return true; }
SendState(ReadyStatus.No); SendState(ReadyStatus.No);
CreateResultsMessage(); CreateResultsMessage();
return true; return true;
@@ -65,6 +67,8 @@ namespace Barotrauma
private void CreateResultsMessage() private void CreateResultsMessage()
{ {
if (GameMain.Client == null) { return; }
Vector2 relativeSize = new Vector2(0.2f, 0.3f); Vector2 relativeSize = new Vector2(0.2f, 0.3f);
Point minSize = new Point(300, 400); Point minSize = new Point(300, 400);
resultsBox = new GUIMessageBox(readyCheckHeader, string.Empty, new[] { closeButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = ResultData, Draggable = true }; resultsBox = new GUIMessageBox(readyCheckHeader, string.Empty, new[] { closeButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = ResultData, Draggable = true };
@@ -16,7 +16,7 @@ namespace Barotrauma
private int jobColumnWidth, characterColumnWidth, statusColumnWidth; private int jobColumnWidth, characterColumnWidth, statusColumnWidth;
private readonly SubmarineInfo sub; private readonly SubmarineInfo sub;
private readonly Mission selectedMission; private readonly List<Mission> selectedMissions;
private readonly Location startLocation, endLocation; private readonly Location startLocation, endLocation;
private readonly GameMode gameMode; private readonly GameMode gameMode;
@@ -32,11 +32,11 @@ namespace Barotrauma
public RoundSummary(SubmarineInfo sub, GameMode gameMode, Mission selectedMission, Location startLocation, Location endLocation) public RoundSummary(SubmarineInfo sub, GameMode gameMode, IEnumerable<Mission> selectedMissions, Location startLocation, Location endLocation)
{ {
this.sub = sub; this.sub = sub;
this.gameMode = gameMode; this.gameMode = gameMode;
this.selectedMission = selectedMission; this.selectedMissions = selectedMissions.ToList();
this.startLocation = startLocation; this.startLocation = startLocation;
this.endLocation = endLocation; this.endLocation = endLocation;
initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f; initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f;
@@ -75,7 +75,7 @@ namespace Barotrauma
//crew panel ------------------------------------------------------------------------------- //crew panel -------------------------------------------------------------------------------
GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.45f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
GUIFrame crewFrameInner = new GUIFrame(new RectTransform(new Point(crewFrame.Rect.Width - padding * 2, crewFrame.Rect.Height - padding * 2), crewFrame.RectTransform, Anchor.Center), style: "InnerFrame"); GUIFrame crewFrameInner = new GUIFrame(new RectTransform(new Point(crewFrame.Rect.Width - padding * 2, crewFrame.Rect.Height - padding * 2), crewFrame.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner.RectTransform, Anchor.Center)) var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner.RectTransform, Anchor.Center))
@@ -90,10 +90,10 @@ namespace Barotrauma
CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != CharacterTeamType.Team2)); CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != CharacterTeamType.Team2));
//another crew frame for the 2nd team in combat missions //another crew frame for the 2nd team in combat missions
if (gameSession.Mission is CombatMission) if (gameSession.Missions.Any(m => m is CombatMission))
{ {
crewHeader.Text = CombatMission.GetTeamName(CharacterTeamType.Team1); crewHeader.Text = CombatMission.GetTeamName(CharacterTeamType.Team1);
GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight))); GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.45f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
rightPanels.Add(crewFrame2); rightPanels.Add(crewFrame2);
GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame"); GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center)) var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center))
@@ -183,7 +183,8 @@ namespace Barotrauma
//reputation panel ------------------------------------------------------------------------------- //reputation panel -------------------------------------------------------------------------------
if (gameMode is CampaignMode campaignMode) var campaignMode = gameMode as CampaignMode;
if (campaignMode != null)
{ {
GUIFrame reputationframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize)); GUIFrame reputationframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize));
rightPanels.Add(reputationframe); rightPanels.Add(reputationframe);
@@ -198,158 +199,154 @@ namespace Barotrauma
TextManager.Get("reputation"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont); TextManager.Get("reputation"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
reputationHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(reputationHeader.Rect.Height * 2.0f)); reputationHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(reputationHeader.Rect.Height * 2.0f));
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, reputationContent.RectTransform)) CreateReputationInfoPanel(reputationContent, campaignMode);
{
Padding = new Vector4(2, 5, 0, 0)
};
reputationList.ContentBackground.Color = Color.Transparent;
if (startLocation.Type.HasOutpost && startLocation.Reputation != null)
{
var iconStyle = GUI.Style.GetComponentStyle("LocationReputationIcon");
CreateReputationElement(
reputationList.Content,
startLocation.Name,
startLocation.Reputation.Value, startLocation.Reputation.NormalizedValue, initialLocationReputation,
startLocation.Type.Name, "",
iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White);
}
foreach (Faction faction in campaignMode.Factions)
{
float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction))
{
initialReputation = initialFactionReputations[faction];
}
else
{
DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round).");
}
CreateReputationElement(
reputationList.Content,
faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
}
float otherElementHeight = 0.0f;
float maxDescriptionHeight = 0.0f;
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
maxDescriptionHeight = Math.Max(maxDescriptionHeight, descriptionElement.TextSize.Y * 1.1f);
otherElementHeight = Math.Max(otherElementHeight, descriptionElement.Parent.Rect.Height - descriptionElement.TextSize.Y);
}
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
descriptionElement.RectTransform.MaxSize = new Point(int.MaxValue, (int)(maxDescriptionHeight));
child.RectTransform.MaxSize = new Point(int.MaxValue, (int)((maxDescriptionHeight + otherElementHeight) * 1.2f));
(descriptionElement?.Parent as GUILayoutGroup).Recalculate();
}
} }
//mission panel ------------------------------------------------------------------------------- //mission panel -------------------------------------------------------------------------------
GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.39f, 0.22f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4))); GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.39f, 0.3f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4)));
GUIFrame missionframeInner = new GUIFrame(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center), style: "InnerFrame"); GUILayoutGroup missionFrameContent = new GUILayoutGroup(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center))
{
var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionframeInner.RectTransform, Anchor.Center)) Stretch = true,
RelativeSpacing = 0.05f
};
GUIFrame missionframeInner = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), missionFrameContent.RectTransform, Anchor.Center), style: "InnerFrame");
var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.93f), missionframeInner.RectTransform, Anchor.Center))
{ {
RelativeSpacing = 0.05f,
Stretch = true Stretch = true
}; };
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions);
if (!selectedMissions.Any() && startLocation?.SelectedMission != null)
{
if (startLocation.SelectedMission.Locations[0] == startLocation.SelectedMission.Locations[1] ||
startLocation.SelectedMission.Locations.Contains(campaignMode?.Map.SelectedLocation))
{
missionsToDisplay.Add(startLocation.SelectedMission);
}
}
if (missionsToDisplay.Any())
{
var missionHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContent.RectTransform),
TextManager.Get(missionsToDisplay.Count > 1 ? "Missions" : "Mission"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
missionHeader.RectTransform.MinSize = new Point(0, (int)(missionHeader.Rect.Height * 1.2f));
}
GUIListBox missionList = new GUIListBox(new RectTransform(Vector2.One, missionContent.RectTransform, Anchor.Center))
{
Padding = new Vector4(4, 10, 0, 0) * GUI.Scale
};
missionList.ContentBackground.Color = Color.Transparent;
ButtonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), missionFrameContent.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomRight)
{
RelativeSpacing = 0.025f
};
missionFrameContent.Recalculate();
missionContent.Recalculate();
if (!string.IsNullOrWhiteSpace(endMessage)) if (!string.IsNullOrWhiteSpace(endMessage))
{ {
var endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContent.RectTransform), var endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionList.Content.RectTransform),
TextManager.GetServerMessage(endMessage), wrap: true); TextManager.GetServerMessage(endMessage), wrap: true)
{
CanBeFocused = false
};
endText.RectTransform.MinSize = new Point(0, endText.Rect.Height); endText.RectTransform.MinSize = new Point(0, endText.Rect.Height);
var line = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), missionContent.RectTransform), style: "HorizontalLine"); var line = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), missionList.Content.RectTransform), style: "HorizontalLine");
line.RectTransform.NonScaledSize = new Point(line.Rect.Width, GUI.IntScale(5.0f)); line.RectTransform.NonScaledSize = new Point(line.Rect.Width, GUI.IntScale(5.0f));
} }
var missionContentHorizontal = new GUILayoutGroup(new RectTransform(Vector2.One, missionContent.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true) foreach (Mission displayedMission in missionsToDisplay)
{ {
RelativeSpacing = 0.025f, var missionContentHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.8f), missionList.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
Stretch = true {
}; RelativeSpacing = 0.025f,
Stretch = true
};
Mission displayedMission = selectedMission ?? startLocation.SelectedMission; string missionMessage =
string missionMessage = ""; selectedMissions.Contains(displayedMission) ?
GUIImage missionIcon;
if (displayedMission != null)
{
missionMessage =
displayedMission == selectedMission ?
displayedMission.Completed ? displayedMission.SuccessMessage : displayedMission.FailureMessage : displayedMission.Completed ? displayedMission.SuccessMessage : displayedMission.FailureMessage :
displayedMission.Description; displayedMission.Description;
missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), displayedMission.Prefab.Icon, scaleToFit: true) GUIImage missionIcon = new GUIImage(new RectTransform(new Point((int)(missionContentHorizontal.Rect.Height)), missionContentHorizontal.RectTransform), displayedMission.Prefab.Icon, scaleToFit: true)
{ {
Color = displayedMission.Prefab.IconColor Color = displayedMission.Prefab.IconColor,
HoverColor = displayedMission.Prefab.IconColor,
SelectedColor = displayedMission.Prefab.IconColor
}; };
if (displayedMission == selectedMission) missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.9f));
if (selectedMissions.Contains(displayedMission))
{ {
new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true); new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true);
} }
}
else
{
missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
}
var missionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, missionContentHorizontal.RectTransform))
{
RelativeSpacing = 0.05f
};
missionContentHorizontal.Recalculate();
missionContent.Recalculate();
missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height);
missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width);
GUITextBlock missionDescription = null; var missionTextContent = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), missionContentHorizontal.RectTransform))
if (displayedMission == null) {
{ RelativeSpacing = 0.05f
};
var missionNameTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
displayedMission.Name, font: GUI.SubHeadingFont);
if (displayedMission.Difficulty.HasValue)
{
var groupSize = missionNameTextBlock.Rect.Size;
groupSize.X -= (int)(missionNameTextBlock.Padding.X + missionNameTextBlock.Padding.Z);
var indicatorGroup = new GUILayoutGroup(new RectTransform(groupSize, missionTextContent.RectTransform) { AbsoluteOffset = new Point((int)missionNameTextBlock.Padding.X, 0) },
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
};
var difficultyColor = displayedMission.GetDifficultyColor();
for (int i = 0; i < displayedMission.Difficulty; i++)
{
new GUIImage(new RectTransform(Vector2.One, indicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
{
CanBeFocused = false,
Color = difficultyColor
};
}
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.Get("nomission"), font: GUI.LargeFont); missionMessage, wrap: true, parseRichText: true);
} if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && displayedMission.Reward > 0)
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("Mission"), displayedMission.Name), font: GUI.SubHeadingFont);
missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
missionMessage, wrap: true);
if (displayedMission == selectedMission && displayedMission.Completed)
{ {
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward)); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), displayedMission.GetMissionRewardText(), parseRichText: true);
TextManager.GetWithVariable("MissionReward", "[reward]", rewardText)); }
if (displayedMission != missionsToDisplay.Last())
{
var spacing = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), missionList.Content.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(15)) }, style: null);
new GUIFrame(new RectTransform(new Vector2(0.8f, 1.0f), spacing.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.1f, 0.0f) }, "HorizontalLine");
} }
} }
ButtonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), missionContent.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomRight) if (!missionsToDisplay.Any())
{ {
IgnoreLayoutGroups = true, var missionContentHorizontal = new GUILayoutGroup(new RectTransform(Vector2.One, missionList.Content.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
RelativeSpacing = 0.025f {
}; RelativeSpacing = 0.025f,
Stretch = true
};
GUIImage missionIcon = new GUIImage(new RectTransform(new Point((int)(missionContentHorizontal.Rect.Height * 0.7f)), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.7f));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContentHorizontal.RectTransform),
TextManager.Get("nomission"), font: GUI.LargeFont);
}
/*missionContentHorizontal.Recalculate();
missionContent.Recalculate();
missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height);
missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width);*/
ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close")); ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close"));
ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height); ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height);
ButtonArea.RectTransform.IsFixedSize = true; ButtonArea.RectTransform.IsFixedSize = true;
missionContent.Recalculate(); missionFrameContent.Recalculate();
//description overlapping with the buttons -> switch to small font
if (missionDescription != null && missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y)
{
missionDescription.Font = GUI.Style.SmallFont;
//still overlapping -> shorten the text
if (missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y && missionDescription.WrappedText.Contains('\n'))
{
missionDescription.ToolTip = missionDescription.Text;
missionDescription.Text = missionDescription.WrappedText.Split('\n').First() + "...";
}
}
// set layout ------------------------------------------------------------------- // set layout -------------------------------------------------------------------
@@ -378,9 +375,114 @@ namespace Barotrauma
return background; return background;
} }
public void CreateReputationInfoPanel(GUIComponent parent, CampaignMode campaignMode)
{
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
Padding = new Vector4(4, 10, 0, 0) * GUI.Scale
};
reputationList.ContentBackground.Color = Color.Transparent;
if (startLocation.Type.HasOutpost && startLocation.Reputation != null)
{
var iconStyle = GUI.Style.GetComponentStyle("LocationReputationIcon");
var locationFrame = CreateReputationElement(
reputationList.Content,
startLocation.Name,
startLocation.Reputation.Value, startLocation.Reputation.NormalizedValue, initialLocationReputation,
startLocation.Type.Name, "",
iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White);
CreatePathUnlockElement(locationFrame, null, startLocation);
}
foreach (Faction faction in campaignMode.Factions)
{
float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction))
{
initialReputation = initialFactionReputations[faction];
}
else
{
DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round).");
}
var factionFrame = CreateReputationElement(
reputationList.Content,
faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
CreatePathUnlockElement(factionFrame, faction, null);
}
float maxDescriptionHeight = 0.0f;
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
maxDescriptionHeight = Math.Max(maxDescriptionHeight, descriptionElement.TextSize.Y * 1.1f);
}
foreach (GUIComponent child in reputationList.Content.Children)
{
var headerElement = child.FindChild("header", recursive: true) as GUITextBlock;
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
descriptionElement.RectTransform.NonScaledSize = new Point(descriptionElement.Rect.Width, (int)maxDescriptionHeight);
descriptionElement.RectTransform.IsFixedSize = true;
child.RectTransform.NonScaledSize = new Point(child.Rect.Width, headerElement.Rect.Height + descriptionElement.RectTransform.Parent.Children.Sum(c => c.Rect.Height + ((GUILayoutGroup)descriptionElement.Parent).AbsoluteSpacing));
}
void CreatePathUnlockElement(GUIComponent reputationFrame, Faction faction, Location location)
{
if (GameMain.GameSession?.Campaign?.Map != null)
{
foreach (LocationConnection connection in GameMain.GameSession.Campaign.Map.Connections)
{
if (!connection.Locked || (!connection.Locations[0].Discovered && !connection.Locations[1].Discovered)) { continue; }
var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1];
var unlockEvent =
EventSet.PrefabList.Find(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == gateLocation.LevelData.Biome.Identifier) ??
EventSet.PrefabList.Find(ep => ep.UnlockPathEvent && string.IsNullOrEmpty(ep.BiomeIdentifier));
if (unlockEvent == null) { continue; }
if (string.IsNullOrEmpty(unlockEvent.UnlockPathFaction) || unlockEvent.UnlockPathFaction.Equals("location", StringComparison.OrdinalIgnoreCase))
{
if (location == null || gateLocation != location) { continue; }
}
else
{
if (faction == null || !faction.Prefab.Identifier.Equals(unlockEvent.UnlockPathFaction, StringComparison.OrdinalIgnoreCase)) { continue; }
}
if (unlockEvent != null)
{
Reputation unlockReputation = gateLocation.Reputation;
Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction))
{
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier.Equals(unlockEvent.UnlockPathFaction, StringComparison.OrdinalIgnoreCase));
unlockReputation = unlockFaction?.Reputation;
}
float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation);
string unlockText = TextManager.GetWithVariables(
"lockedpathreputationrequirement",
new string[] { "[reputation]", "[biomename]" },
new string[] { Reputation.GetFormattedReputationText(normalizedUnlockReputation, unlockEvent.UnlockPathReputation, addColorTags: true), $"‖color:gui.orange‖{connection.LevelData.Biome.DisplayName}‖end‖" });
var unlockInfoPanel = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), reputationFrame.RectTransform, Anchor.BottomCenter) { MinSize = new Point(0, GUI.IntScale(30)), AbsoluteOffset = new Point(0, GUI.IntScale(3)) },
unlockText, style: "GUIButtonRound", textAlignment: Alignment.Center, textColor: GUI.Style.TextColor, parseRichText: true);
unlockInfoPanel.Color = Color.Lerp(unlockInfoPanel.Color, Color.Black, 0.8f);
if (unlockInfoPanel.TextSize.X > unlockInfoPanel.Rect.Width * 0.7f)
{
unlockInfoPanel.Font = GUI.SmallFont;
}
}
}
}
}
}
private string GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType) private string GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{ {
string locationName = Submarine.MainSub.AtEndPosition ? endLocation?.Name : startLocation?.Name; string locationName = Submarine.MainSub.AtEndExit ? endLocation?.Name : startLocation?.Name;
string textTag; string textTag;
if (gameOver) if (gameOver)
@@ -396,17 +498,23 @@ namespace Barotrauma
textTag = "RoundSummaryLeaving"; textTag = "RoundSummaryLeaving";
break; break;
case CampaignMode.TransitionType.ProgressToNextLocation: case CampaignMode.TransitionType.ProgressToNextLocation:
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
locationName = endLocation?.Name; locationName = endLocation?.Name;
textTag = "RoundSummaryProgress"; textTag = "RoundSummaryProgress";
break; break;
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
locationName = endLocation?.Name;
textTag = "RoundSummaryProgressToEmptyLocation";
break;
case CampaignMode.TransitionType.ReturnToPreviousLocation: case CampaignMode.TransitionType.ReturnToPreviousLocation:
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
locationName = startLocation?.Name; locationName = startLocation?.Name;
textTag = "RoundSummaryReturn"; textTag = "RoundSummaryReturn";
break; break;
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
locationName = startLocation?.Name;
textTag = "RoundSummaryReturnToEmptyLocation";
break;
default: default:
textTag = Submarine.MainSub.AtEndPosition ? "RoundSummaryProgress" : "RoundSummaryReturn"; textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
break; break;
} }
} }
@@ -456,7 +564,7 @@ namespace Barotrauma
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform)) GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{ {
Padding = new Vector4(2, 5, 0, 0), Padding = new Vector4(4, 10, 0, 0) * GUI.Scale,
AutoHideScrollBar = false AutoHideScrollBar = false
}; };
crewList.ContentBackground.Color = Color.Transparent; crewList.ContentBackground.Color = Color.Transparent;
@@ -547,11 +655,11 @@ namespace Barotrauma
ToolBox.LimitString(statusText, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor); ToolBox.LimitString(statusText, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
} }
private void CreateReputationElement(GUIComponent parent, private GUIFrame CreateReputationElement(GUIComponent parent,
string name, float reputation, float normalizedReputation, float initialReputation, string name, float reputation, float normalizedReputation, float initialReputation,
string shortDescription, string fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor) string shortDescription, string fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{ {
var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform), style: null); var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), style: null);
if (backgroundPortrait != null) if (backgroundPortrait != null)
{ {
@@ -568,13 +676,13 @@ namespace Barotrauma
var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true) var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{ {
RelativeSpacing = 0.02f, AbsoluteSpacing = GUI.IntScale(5),
Stretch = true Stretch = true
}; };
var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform)) var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform))
{ {
RelativeSpacing = 0.05f, AbsoluteSpacing = GUI.IntScale(10),
Stretch = true Stretch = true
}; };
var factionIcon = new GUIImage(new RectTransform(new Point((int)(factionInfoHorizontal.Rect.Height * 0.7f)), factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true) var factionIcon = new GUIImage(new RectTransform(new Point((int)(factionInfoHorizontal.Rect.Height * 0.7f)), factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
@@ -583,36 +691,28 @@ namespace Barotrauma
}; };
factionInfoHorizontal.Recalculate(); factionInfoHorizontal.Recalculate();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), factionTextContent.RectTransform), var header = new GUITextBlock(new RectTransform(new Point(factionTextContent.Rect.Width, GUI.IntScale(40)), factionTextContent.RectTransform),
name, font: GUI.SubHeadingFont) name, font: GUI.SubHeadingFont)
{ {
Padding = Vector4.Zero Padding = Vector4.Zero,
UserData = "header"
}; };
var factionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.6f), factionTextContent.RectTransform), header.RectTransform.IsFixedSize = true;
shortDescription, font: GUI.SmallFont, wrap: true)
{
UserData = "description",
Padding = Vector4.Zero
};
if (shortDescription != fullDescription && !string.IsNullOrEmpty(fullDescription))
{
factionDescription.ToolTip = fullDescription;
}
var sliderHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), factionTextContent.RectTransform), var sliderHolder = new GUILayoutGroup(new RectTransform(new Point((int)(factionTextContent.Rect.Width * 0.8f), GUI.IntScale(20.0f)), factionTextContent.RectTransform),
childAnchor: Anchor.CenterLeft, isHorizontal: true) childAnchor: Anchor.CenterLeft, isHorizontal: true)
{ {
RelativeSpacing = 0.05f, RelativeSpacing = 0.05f,
Stretch = true Stretch = true
}; };
sliderHolder.RectTransform.MaxSize = new Point(int.MaxValue, GUI.IntScale(25.0f)); sliderHolder.RectTransform.IsFixedSize = true;
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, normalizedReputation)); onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, normalizedReputation));
string reputationText = ((int)Math.Round(reputation)).ToString(); string reputationText = Reputation.GetFormattedReputationText(normalizedReputation, reputation, addColorTags: true);
int reputationChange = (int)Math.Round( reputation - initialReputation); int reputationChange = (int)Math.Round(reputation - initialReputation);
if (Math.Abs(reputationChange) > 0) if (Math.Abs(reputationChange) > 0)
{ {
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}"; string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
@@ -626,24 +726,49 @@ namespace Barotrauma
{ {
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform), new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
reputationText, reputationText,
textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont); textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont, parseRichText: true);
} }
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), factionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(5)) }, style: null);
var factionDescription = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.6f), factionTextContent.RectTransform),
shortDescription, font: GUI.SmallFont, wrap: true)
{
UserData = "description",
Padding = Vector4.Zero
};
if (shortDescription != fullDescription && !string.IsNullOrEmpty(fullDescription))
{
factionDescription.ToolTip = fullDescription;
}
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), factionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(5)) }, style: null);
factionInfoHorizontal.Recalculate();
factionTextContent.Recalculate();
return factionFrame;
} }
public static void DrawReputationBar(SpriteBatch sb, Rectangle rect, float normalizedReputation) public static void DrawReputationBar(SpriteBatch sb, Rectangle rect, float normalizedReputation)
{ {
GUI.DrawRectangle(sb, rect, GUI.Style.ColorInventoryBackground, isFilled: true); int segmentWidth = rect.Width / 5;
if (normalizedReputation < 0.5f) rect.Width = segmentWidth * 5;
for (int i = 0; i < 5; i++)
{ {
int barWidth = (int)((0.5f - normalizedReputation) * rect.Width); GUI.DrawRectangle(sb, new Rectangle(rect.X + (segmentWidth * i), rect.Y, segmentWidth, rect.Height), Reputation.GetReputationColor(i / 5.0f), isFilled: true);
GUI.DrawRectangle(sb, new Rectangle(rect.Center.X - barWidth, rect.Y, barWidth, rect.Height), GUI.Style.Red, isFilled: true); GUI.DrawRectangle(sb, new Rectangle(rect.X + (segmentWidth * i), rect.Y, segmentWidth, rect.Height), GUI.Style.ColorInventoryBackground, isFilled: false);
} }
else if (normalizedReputation > 0.5f) GUI.DrawRectangle(sb, rect, GUI.Style.ColorInventoryBackground, isFilled: false);
{
int barWidth = (int)((normalizedReputation - 0.5f) * rect.Width); GUI.Arrow.Draw(sb, new Vector2(rect.X + rect.Width * normalizedReputation, rect.Y), GUI.Style.ColorInventoryBackground, scale: GUI.Scale, spriteEffect: SpriteEffects.FlipVertically);
GUI.DrawRectangle(sb, new Rectangle(rect.Center.X, rect.Y, barWidth, rect.Height), GUI.Style.Green, isFilled: true); GUI.Arrow.Draw(sb, new Vector2(rect.X + rect.Width * normalizedReputation, rect.Y), GUI.Style.TextColor, scale: GUI.Scale * 0.8f, spriteEffect: SpriteEffects.FlipVertically);
}
GUI.DrawLine(sb, new Vector2(rect.Center.X, rect.Y - 2), new Vector2(rect.Center.X, rect.Bottom + 2), GUI.Style.TextColor); GUI.DrawString(sb, new Vector2(rect.X, rect.Bottom), "-100", GUI.Style.TextColor, font: GUI.SmallFont);
Vector2 textSize = GUI.SmallFont.MeasureString("100");
GUI.DrawString(sb, new Vector2(rect.Right - textSize.X, rect.Bottom), "100", GUI.Style.TextColor, font: GUI.SmallFont);
} }
} }
} }
@@ -20,6 +20,7 @@ namespace Barotrauma
Audio, Audio,
VoiceChat, VoiceChat,
Controls, Controls,
Gameplay,
#if DEBUG #if DEBUG
Debug Debug
#endif #endif
@@ -532,29 +533,19 @@ namespace Barotrauma
UserData = tab UserData = tab
}; };
float tabWidth = 0.25f; float tabWidth = 1.0f / tabs.Length;
#if DEBUG #if DEBUG
tabWidth = 0.2f; string buttonText = tab != Tab.Debug ? TextManager.Get("SettingsTab." + tab.ToString()) : "Debug";
if (tab != Tab.Debug) #else
{ string buttonText = TextManager.Get("SettingsTab." + tab.ToString());
#endif #endif
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform),
TextManager.Get("SettingsTab." + tab.ToString()), style: "GUITabButton") tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform), style: "GUITabButton")
{
UserData = tab,
OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; }
};
#if DEBUG
}
else
{ {
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform), "Debug", style: "GUITabButton") UserData = tab,
{ OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; }
UserData = tab, };
OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; } tabButtons[(int)tab].Text = ToolBox.LimitString(buttonText, tabButtons[(int)tab].Font, (int)(0.75f * tabWidth * tabButtonHolder.Rect.Width));
};
}
#endif
} }
new GUIButton(new RectTransform(new Vector2(0.05f, 0.75f), tabButtonHolder.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.2f) }, style: "GUIBugButton") new GUIButton(new RectTransform(new Vector2(0.05f, 0.75f), tabButtonHolder.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.2f) }, style: "GUIBugButton")
@@ -669,19 +660,6 @@ namespace Barotrauma
Selected = TextureCompressionEnabled Selected = TextureCompressionEnabled
}; };
GUITickBox pauseOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, leftColumn.RectTransform),
TextManager.Get("PauseOnFocusLost"))
{
Selected = PauseOnFocusLost,
ToolTip = TextManager.Get("PauseOnFocusLostToolTip"),
OnSelected = (tickBox) =>
{
PauseOnFocusLost = tickBox.Selected;
UnsavedSettings = true;
return true;
}
};
GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit"), font: GUI.SubHeadingFont, wrap: true); GUITextBlock particleLimitText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("ParticleLimit"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar particleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: "GUISlider", GUIScrollBar particleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), style: "GUISlider",
barSize: 0.1f) barSize: 0.1f)
@@ -773,56 +751,6 @@ namespace Barotrauma
} }
}; };
GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = HUDScaleText,
BarScroll = (HUDScale - MinHUDScale) / (MaxHUDScale - MinHUDScale),
OnMoved = (scrollBar, scroll) =>
{
HUDScale = MathHelper.Lerp(MinHUDScale, MaxHUDScale, scroll);
ChangeSliderText(scrollBar, HUDScale);
OnHUDScaleChanged?.Invoke();
return true;
},
Step = 0.02f
};
HUDScaleScrollBar.OnMoved(HUDScaleScrollBar, HUDScaleScrollBar.BarScroll);
GUITextBlock inventoryScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("InventoryScale"), font: GUI.SubHeadingFont);
GUIScrollBar inventoryScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = inventoryScaleText,
BarScroll = (InventoryScale - MinInventoryScale) / (MaxInventoryScale - MinInventoryScale),
OnMoved = (scrollBar, scroll) =>
{
InventoryScale = MathHelper.Lerp(MinInventoryScale, MaxInventoryScale, scroll);
ChangeSliderText(scrollBar, InventoryScale);
return true;
},
Step = 0.02f
};
inventoryScaleScrollBar.OnMoved(inventoryScaleScrollBar, inventoryScaleScrollBar.BarScroll);
GUITextBlock textScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform), TextManager.Get("TextScale"), font: GUI.SubHeadingFont);
GUIScrollBar textScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), rightColumn.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = textScaleText,
BarScroll = (TextScale - MinTextScale) / (MaxTextScale - MinTextScale),
OnMoved = (scrollBar, scroll) =>
{
TextScale = MathHelper.Lerp(MinTextScale, MaxTextScale, scroll);
textScaleDirty = true;
ChangeSliderText(scrollBar, TextScale);
return true;
},
Step = 0.01f
};
textScaleScrollBar.OnMoved(textScaleScrollBar, textScaleScrollBar.BarScroll);
/// Audio tab ---------------------------------------------------------------- /// Audio tab ----------------------------------------------------------------
var audioContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.Audio].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var audioContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.Audio].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
@@ -1177,13 +1105,14 @@ namespace Barotrauma
style: "GUISlider", barSize: 0.05f) style: "GUISlider", barSize: 0.05f)
{ {
UserData = micVolumeText, UserData = micVolumeText,
Range = new Vector2(0,540), Range = new Vector2(0, ((float)VoipConfig.BUFFER_SIZE / (float)VoipConfig.FREQUENCY) * 1000.0f * 25.0f),
Step = 1.0f / 9.0f Step = 1.0f / 25.0f
}; };
cutoffPreventionSlider.BarScrollValue = VoiceChatCutoffPrevention; cutoffPreventionSlider.BarScrollValue = VoiceChatCutoffPrevention;
cutoffPreventionSlider.OnMoved = (scrollBar, scroll) => cutoffPreventionSlider.OnMoved = (scrollBar, scroll) =>
{ {
VoiceChatCutoffPrevention = (int)scrollBar.BarScrollValue; int bufferMsLength = (int)(((float)VoipConfig.BUFFER_SIZE / (float)VoipConfig.FREQUENCY) * 1000.0f);
VoiceChatCutoffPrevention = (int)Math.Round(scrollBar.BarScrollValue / bufferMsLength) * bufferMsLength;
cutoffPreventionText.Text = TextManager.Get("CutoffPrevention") + cutoffPreventionText.Text = TextManager.Get("CutoffPrevention") +
" " + TextManager.GetWithVariable("timeformatmilliseconds", "[milliseconds]", VoiceChatCutoffPrevention.ToString()); " " + TextManager.GetWithVariable("timeformatmilliseconds", "[milliseconds]", VoiceChatCutoffPrevention.ToString());
return true; return true;
@@ -1379,6 +1308,93 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(defaultBindingsButton.TextBlock, legacyBindingsButton.TextBlock); GUITextBlock.AutoScaleAndNormalize(defaultBindingsButton.TextBlock, legacyBindingsButton.TextBlock);
}; };
/// Gameplay tab -------------------------------------------------------------
var gameplaySettingsGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Gameplay].RectTransform, Anchor.TopLeft)
{ RelativeOffset = new Vector2(0.025f, 0.02f) })
{ RelativeSpacing = 0.01f };
GUITickBox pauseOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform),
TextManager.Get("PauseOnFocusLost"))
{
Selected = PauseOnFocusLost,
ToolTip = TextManager.Get("PauseOnFocusLostToolTip"),
OnSelected = (tickBox) =>
{
PauseOnFocusLost = tickBox.Selected;
UnsavedSettings = true;
return true;
}
};
GUITickBox disableInGameHintsBox = new GUITickBox(new RectTransform(tickBoxScale, gameplaySettingsGroup.RectTransform),
TextManager.Get("DisableInGameHints"))
{
Selected = DisableInGameHints,
ToolTip = TextManager.Get("DisableInGameHintsToolTip"),
OnSelected = (tickBox) =>
{
DisableInGameHints = tickBox.Selected;
if (!DisableInGameHints && GameMain.Config?.IgnoredHints != null)
{
// Reset the ignored hints when the hints are re-enabled (to-be-replaced by a separate button)
GameMain.Config.IgnoredHints.Clear();
}
UnsavedSettings = true;
return true;
}
};
GUITextBlock HUDScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("HUDScale"), font: GUI.SubHeadingFont, wrap: true);
GUIScrollBar HUDScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = HUDScaleText,
BarScroll = (HUDScale - MinHUDScale) / (MaxHUDScale - MinHUDScale),
OnMoved = (scrollBar, scroll) =>
{
HUDScale = MathHelper.Lerp(MinHUDScale, MaxHUDScale, scroll);
ChangeSliderText(scrollBar, HUDScale);
OnHUDScaleChanged?.Invoke();
return true;
},
Step = 0.02f
};
HUDScaleScrollBar.OnMoved(HUDScaleScrollBar, HUDScaleScrollBar.BarScroll);
GUITextBlock inventoryScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("InventoryScale"), font: GUI.SubHeadingFont);
GUIScrollBar inventoryScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = inventoryScaleText,
BarScroll = (InventoryScale - MinInventoryScale) / (MaxInventoryScale - MinInventoryScale),
OnMoved = (scrollBar, scroll) =>
{
InventoryScale = MathHelper.Lerp(MinInventoryScale, MaxInventoryScale, scroll);
ChangeSliderText(scrollBar, InventoryScale);
return true;
},
Step = 0.02f
};
inventoryScaleScrollBar.OnMoved(inventoryScaleScrollBar, inventoryScaleScrollBar.BarScroll);
GUITextBlock textScaleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform), TextManager.Get("TextScale"), font: GUI.SubHeadingFont);
GUIScrollBar textScaleScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), gameplaySettingsGroup.RectTransform),
style: "GUISlider", barSize: 0.1f)
{
UserData = textScaleText,
BarScroll = (TextScale - MinTextScale) / (MaxTextScale - MinTextScale),
OnMoved = (scrollBar, scroll) =>
{
TextScale = MathHelper.Lerp(MinTextScale, MaxTextScale, scroll);
textScaleDirty = true;
ChangeSliderText(scrollBar, TextScale);
return true;
},
Step = 0.01f
};
textScaleScrollBar.OnMoved(textScaleScrollBar, textScaleScrollBar.BarScroll);
/// Bottom buttons -------------------------------------------------------------
new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft), new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft),
TextManager.Get("Cancel")) TextManager.Get("Cancel"))
{ {
@@ -1464,55 +1480,54 @@ namespace Barotrauma
{ RelativeOffset = new Vector2(0.02f, 0.02f) }) { RelativeOffset = new Vector2(0.02f, 0.02f) })
{ RelativeSpacing = 0.01f }; { RelativeSpacing = 0.01f };
var automaticQuickStartTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic quickstart enabled", style: "GUITickBox"); void addDebugTickBox(bool initialValue, Action<bool> set, string label, string tooltip)
automaticQuickStartTickBox.Selected = AutomaticQuickStartEnabled;
automaticQuickStartTickBox.ToolTip = "Will the game automatically move on to Quickstart when the game is launched";
automaticQuickStartTickBox.OnSelected = (tickBox) =>
{ {
AutomaticQuickStartEnabled = tickBox.Selected; var tickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), label, style: "GUITickBox");
UnsavedSettings = true; tickBox.Selected = initialValue;
return true; tickBox.ToolTip = tooltip;
}; tickBox.OnSelected = (tickBox) =>
{
set(tickBox.Selected);
UnsavedSettings = true;
return true;
};
}
var automaticCampaignLoadTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic campaign load enabled", style: "GUITickBox"); addDebugTickBox(
automaticCampaignLoadTickBox.Selected = AutomaticCampaignLoadEnabled; AutomaticQuickStartEnabled,
automaticCampaignLoadTickBox.ToolTip = "Will the game automatically load the latest campaign save when the game is launched"; (b) => AutomaticQuickStartEnabled = b,
automaticCampaignLoadTickBox.OnSelected = (tickBox) => "Automatic quickstart enabled",
{ "Will the game automatically move on to Quickstart when the game is launched");
AutomaticCampaignLoadEnabled = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Splash screen enabled", style: "GUITickBox"); addDebugTickBox(
showSplashScreenTickBox.Selected = EnableSplashScreen; AutomaticCampaignLoadEnabled,
showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched"; (b) => AutomaticCampaignLoadEnabled = b,
showSplashScreenTickBox.OnSelected = (tickBox) => "Automatic campaign load enabled",
{ "Will the game automatically load the latest campaign save when the game is launched");
EnableSplashScreen = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var verboseLoggingTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Verbose logging enabled", style: "GUITickBox"); addDebugTickBox(
verboseLoggingTickBox.Selected = VerboseLogging; EnableSplashScreen,
verboseLoggingTickBox.ToolTip = "Should verbose logging be used"; (b) => EnableSplashScreen = b,
verboseLoggingTickBox.OnSelected = (tickBox) => "Splash screen enabled",
{ "Are the splash screens shown when the game is launched");
VerboseLogging = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var textManagerDebugModeTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "TextManager debug mode enabled", style: "GUITickBox"); addDebugTickBox(
textManagerDebugModeTickBox.Selected = TextManagerDebugModeEnabled; VerboseLogging,
textManagerDebugModeTickBox.ToolTip = "Does the TextManager return the text tags for debug purposes?"; (b) => VerboseLogging = b,
textManagerDebugModeTickBox.OnSelected = (tickBox) => "Verbose logging enabled",
{ "Should verbose logging be used");
TextManagerDebugModeEnabled = tickBox.Selected;
UnsavedSettings = true; addDebugTickBox(
return true; TextManagerDebugModeEnabled,
}; (b) => TextManagerDebugModeEnabled = b,
"TextManager debug mode enabled",
"Does the TextManager return the text tags for debug purposes?");
addDebugTickBox(
ModBreakerMode,
(b) => ModBreakerMode = b,
"Mod breaker mode enabled",
"Do horrible things when loading mods to see if it breaks?");
#endif #endif
UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created
@@ -160,13 +160,6 @@ namespace Barotrauma
CreateSlots(); CreateSlots();
} }
public override void RemoveItem(Item item)
{
if (!Contains(item)) { return; }
base.RemoveItem(item);
CreateSlots();
}
public override void CreateSlots() public override void CreateSlots()
{ {
if (visualSlots == null) { visualSlots = new VisualSlot[capacity]; } if (visualSlots == null) { visualSlots = new VisualSlot[capacity]; }
@@ -639,10 +632,16 @@ namespace Barotrauma
foreach (Item doubleClickedItem in doubleClickedItems) foreach (Item doubleClickedItem in doubleClickedItems)
{ {
QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First()); QuickUseItem(doubleClickedItem, true, true, true, quickUseAction, playSound: doubleClickedItem == doubleClickedItems.First());
//only use one item if we're equipping or using it as a treatment
if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment) if (quickUseAction == QuickUseAction.Equip || quickUseAction == QuickUseAction.UseTreatment)
{ {
break; break;
} }
//if the item was put in a limb slot, only put one item from the stack
if (doubleClickedItem.ParentInventory == this && !IsInLimbSlot(doubleClickedItem, InvSlotType.Any))
{
break;
}
} }
} }
@@ -696,6 +695,7 @@ namespace Barotrauma
if (firstItem != null && !DraggingItems.Contains(firstItem) && Character.Controlled?.Inventory == this && if (firstItem != null && !DraggingItems.Contains(firstItem) && Character.Controlled?.Inventory == this &&
GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(visualSlots[i].InventoryKeyIndex)) GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(visualSlots[i].InventoryKeyIndex))
{ {
if (SubEditorScreen.IsSubEditor() && SubEditorScreen.SkipInventorySlotUpdate) { continue; }
#if LINUX #if LINUX
// some window managers on Linux use windows key + number to change workspaces or perform other actions // some window managers on Linux use windows key + number to change workspaces or perform other actions
if (PlayerInput.KeyDown(Keys.RightWindows) || PlayerInput.KeyDown(Keys.LeftWindows)) { continue; } if (PlayerInput.KeyDown(Keys.RightWindows) || PlayerInput.KeyDown(Keys.LeftWindows)) { continue; }
@@ -810,6 +810,8 @@ namespace Barotrauma
highlightedSubInventorySlot.Inventory.HideTimer = 0.0f; highlightedSubInventorySlot.Inventory.HideTimer = 0.0f;
} }
} }
HintManager.OnShowSubInventory(slotRef?.Item);
} }
public void AssignQuickUseNumKeys() public void AssignQuickUseNumKeys()
@@ -835,6 +837,13 @@ namespace Barotrauma
if (item.ParentInventory != this) if (item.ParentInventory != this)
{ {
if (Screen.Selected == GameMain.GameScreen)
{
if (item.NonInteractable || item.NonPlayerTeamInteractable)
{
return QuickUseAction.None;
}
}
if (item.ParentInventory == null || item.ParentInventory.Locked) if (item.ParentInventory == null || item.ParentInventory.Locked)
{ {
return QuickUseAction.None; return QuickUseAction.None;
@@ -70,17 +70,15 @@ namespace Barotrauma.Items.Components
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
base.ClientRead(type, msg, sendingTime); base.ClientRead(type, msg, sendingTime);
bool readAttachData = msg.ReadBoolean();
if (!readAttachData) { return; }
bool shouldBeAttached = msg.ReadBoolean(); bool shouldBeAttached = msg.ReadBoolean();
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle()); Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
UInt16 submarineID = msg.ReadUInt16(); UInt16 submarineID = msg.ReadUInt16();
Submarine sub = Entity.FindEntityByID(submarineID) as Submarine; Submarine sub = Entity.FindEntityByID(submarineID) as Submarine;
if (!attachable)
{
DebugConsole.ThrowError("Received an attachment event for an item that's not attachable.");
return;
}
if (shouldBeAttached) if (shouldBeAttached)
{ {
if (!attached) if (!attached)
@@ -96,7 +94,6 @@ namespace Barotrauma.Items.Components
if (attached) if (attached)
{ {
DropConnectedWires(null); DropConnectedWires(null);
if (body != null) if (body != null)
{ {
item.body = body; item.body = body;
@@ -106,6 +103,11 @@ namespace Barotrauma.Items.Components
DeattachFromWall(); DeattachFromWall();
} }
else
{
item.SetTransform(simPosition, 0.0f);
item.Submarine = sub;
}
} }
} }
} }
@@ -179,6 +179,23 @@ namespace Barotrauma.Items.Components
{ {
CanBeFocused = false CanBeFocused = false
}; };
// Expand the frame vertically if it's too small to fit the text
if (label != null && label.RectTransform.RelativeSize.Y > 0.5f)
{
int newHeight = (int)(GuiFrame.Rect.Height + (2 * (label.RectTransform.RelativeSize.Y - 0.5f) * content.Rect.Height));
if (newHeight > GuiFrame.RectTransform.MaxSize.Y)
{
Point newMaxSize = GuiFrame.RectTransform.MaxSize;
newMaxSize.Y = newHeight;
GuiFrame.RectTransform.MaxSize = newMaxSize;
}
GuiFrame.RectTransform.Resize(new Point(GuiFrame.Rect.Width, newHeight));
content.RectTransform.Resize(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin);
label.CalculateHeightFromText();
guiCustomComponent.RectTransform.Resize(new Vector2(1.0f, Math.Max(1.0f - label.RectTransform.RelativeSize.Y, minInventoryAreaSize)));
}
Inventory.RectTransform = guiCustomComponent.RectTransform; Inventory.RectTransform = guiCustomComponent.RectTransform;
} }
@@ -39,6 +39,8 @@ namespace Barotrauma.Items.Components
private Pair<Rectangle, string> tooltip; private Pair<Rectangle, string> tooltip;
private GUITextBlock requiredTimeBlock;
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
CreateGUI(); CreateGUI();
@@ -384,8 +386,6 @@ namespace Barotrauma.Items.Components
FabricationRecipe targetItem = fabricatedItem ?? selectedItem; FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
if (targetItem != null) if (targetItem != null)
{ {
var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.sprite;
Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect; Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect;
if (fabricatedItem != null) if (fabricatedItem != null)
@@ -398,11 +398,15 @@ namespace Barotrauma.Items.Components
GUI.Style.Green * 0.5f, isFilled: true); GUI.Style.Green * 0.5f, isFilled: true);
} }
itemIcon.Draw( if (outputContainer.Inventory.IsEmpty())
spriteBatch, {
slotRect.Center.ToVector2(), var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.sprite;
color: targetItem.TargetItem.InventoryIconColor * 0.4f, itemIcon.Draw(
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f); spriteBatch,
slotRect.Center.ToVector2(),
color: targetItem.TargetItem.InventoryIconColor * 0.4f,
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f);
}
} }
if (tooltip != null) if (tooltip != null)
@@ -522,7 +526,7 @@ namespace Barotrauma.Items.Components
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime), requiredTimeBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime),
font: GUI.SmallFont); font: GUI.SmallFont);
return true; return true;
} }
@@ -606,6 +610,12 @@ namespace Barotrauma.Items.Components
} }
} }
partial void UpdateRequiredTimeProjSpecific()
{
if (requiredTimeBlock == null) { return; }
requiredTimeBlock.Text = ToolBox.SecondsToReadableTime(timeUntilReady > 0.0f ? timeUntilReady : requiredTime);
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem); int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem);
@@ -21,18 +21,14 @@ namespace Barotrauma.Items.Components
private readonly List<Submarine> displayedSubs = new List<Submarine>(); private readonly List<Submarine> displayedSubs = new List<Submarine>();
private Point prevResolution;
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
noPowerTip = TextManager.Get("SteeringNoPowerTip"); noPowerTip = TextManager.Get("SteeringNoPowerTip");
CreateGUI(); CreateGUI();
} }
protected override void OnResolutionChanged()
{
base.OnResolutionChanged();
CreateHUD();
}
protected override void CreateGUI() protected override void CreateGUI()
{ {
GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f);
@@ -76,15 +72,10 @@ namespace Barotrauma.Items.Components
hullInfoFrame.AddToGUIUpdateList(order: 1); hullInfoFrame.AddToGUIUpdateList(order: 1);
} }
public override void OnMapLoaded()
{
base.OnMapLoaded();
CreateHUD();
}
private void CreateHUD() private void CreateHUD()
{ {
submarineContainer.ClearChildren(); prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
submarineContainer?.ClearChildren();
if (item.Submarine == null) { return; } if (item.Submarine == null) { return; }
@@ -94,19 +85,15 @@ namespace Barotrauma.Items.Components
displayedSubs.AddRange(item.Submarine.DockedTo); displayedSubs.AddRange(item.Submarine.DockedTo);
} }
public override void FlipX(bool relativeToSub)
{
CreateHUD();
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
//recreate HUD if the subs we should display have changed //recreate HUD if the subs we should display have changed
if ((item.Submarine == null && displayedSubs.Count > 0) || //item not inside a sub anymore, but display is still showing subs if ((item.Submarine == null && displayedSubs.Count > 0) || //item not inside a sub anymore, but display is still showing subs
!displayedSubs.Contains(item.Submarine) || //current sub not displayer !displayedSubs.Contains(item.Submarine) || //current sub not displayer
item.Submarine.DockedTo.Any(s => !displayedSubs.Contains(s)) || //some of the docked subs not diplayed prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || //resolution changed
!submarineContainer.Children.Any() || // We lack a GUI item.Submarine.DockedTo.Any(s => !displayedSubs.Contains(s)) || //some of the docked subs not diplayed
displayedSubs.Any(s => s != item.Submarine && !item.Submarine.DockedTo.Contains(s))) //displaying a sub that shouldn't be displayed !submarineContainer.Children.Any() || // We lack a GUI
displayedSubs.Any(s => s != item.Submarine && !item.Submarine.DockedTo.Contains(s))) //displaying a sub that shouldn't be displayed
{ {
CreateHUD(); CreateHUD();
} }
@@ -16,7 +16,8 @@ namespace Barotrauma.Items.Components
{ {
Default, Default,
Disruption, Disruption,
Destructible Destructible,
LongRange
} }
private PathFinder pathFinder; private PathFinder pathFinder;
@@ -69,6 +70,9 @@ namespace Barotrauma.Items.Components
private const float DisruptionUpdateInterval = 0.2f; private const float DisruptionUpdateInterval = 0.2f;
private float disruptionUpdateTimer; private float disruptionUpdateTimer;
private const float LongRangeUpdateInterval = 10.0f;
private float longRangeUpdateTimer;
private float showDirectionalIndicatorTimer; private float showDirectionalIndicatorTimer;
private readonly List<LevelObject> nearbyObjects = new List<LevelObject>(); private readonly List<LevelObject> nearbyObjects = new List<LevelObject>();
@@ -122,6 +126,10 @@ namespace Barotrauma.Items.Components
{ {
BlipType.Destructible, BlipType.Destructible,
new Color[] { Color.TransparentBlack, new Color(74, 113, 75) * 0.8f, new Color(151, 236, 172) * 0.8f, new Color(153, 217, 234) * 0.8f } new Color[] { Color.TransparentBlack, new Color(74, 113, 75) * 0.8f, new Color(151, 236, 172) * 0.8f, new Color(153, 217, 234) * 0.8f }
},
{
BlipType.LongRange,
new Color[] { Color.TransparentBlack, Color.TransparentBlack, new Color(254, 68, 19) * 0.8f, Color.TransparentBlack }
} }
}; };
@@ -133,7 +141,7 @@ namespace Barotrauma.Items.Components
public static Vector2 GUISizeCalculation => Vector2.One * Math.Min(GUI.RelativeHorizontalAspectRatio, 1f) * sonarAreaSize; public static Vector2 GUISizeCalculation => Vector2.One * Math.Min(GUI.RelativeHorizontalAspectRatio, 1f) * sonarAreaSize;
private List<Tuple<Vector2, List<Item>>> MineralClusters { get; set; } private List<(Vector2 center, List<Item> resources)> MineralClusters { get; set; }
private readonly List<GUITextBlock> textBlocksToScaleAndNormalize = new List<GUITextBlock>(); private readonly List<GUITextBlock> textBlocksToScaleAndNormalize = new List<GUITextBlock>();
@@ -471,25 +479,26 @@ namespace Barotrauma.Items.Components
{ {
if (MineralClusters == null) if (MineralClusters == null)
{ {
MineralClusters = new List<Tuple<Vector2, List<Item>>>(); MineralClusters = new List<(Vector2, List<Item>)>();
foreach (var p in Level.Loaded.PathPoints) Level.Loaded.PathPoints.ForEach(p => p.ClusterLocations.ForEach(c => AddIfValid(c)));
Level.Loaded.AbyssResources.ForEach(c => AddIfValid(c));
void AddIfValid(Level.ClusterLocation c)
{ {
foreach (var c in p.ClusterLocations) if (c.Resources == null) { return; }
if (c.Resources.None(i => i != null && !i.Removed && i.Tags.Contains("ore"))) { return; }
var pos = Vector2.Zero;
foreach (var r in c.Resources)
{ {
if (c.Resources.None(i => i != null && !i.Removed && i.Tags.Contains("ore"))) { continue; } pos += r.WorldPosition;
var pos = Vector2.Zero;
foreach (var r in c.Resources)
{
pos += r.WorldPosition;
}
pos /= c.Resources.Count;
MineralClusters.Add(new Tuple<Vector2, List<Item>>(pos, c.Resources));
} }
pos /= c.Resources.Count;
MineralClusters.Add((center: pos, resources: c.Resources));
} }
} }
else else
{ {
MineralClusters.RemoveAll(t => t.Item2 == null || t.Item2.None() || t.Item2.All(i => i == null || i.Removed)); MineralClusters.RemoveAll(c => c.resources == null || c.resources.None() || c.resources.All(i => i == null || i.Removed));
} }
} }
@@ -673,7 +682,6 @@ namespace Barotrauma.Items.Components
} }
disruptionUpdateTimer -= deltaTime; disruptionUpdateTimer -= deltaTime;
for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex) for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
{ {
var activePing = activePings[pingIndex]; var activePing = activePings[pingIndex];
@@ -683,12 +691,46 @@ namespace Barotrauma.Items.Components
pingRadius, activePing.PrevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f); pingRadius, activePing.PrevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f);
activePing.PrevPingRadius = pingRadius; activePing.PrevPingRadius = pingRadius;
} }
if (disruptionUpdateTimer <= 0.0f) if (disruptionUpdateTimer <= 0.0f)
{ {
disruptionUpdateTimer = DisruptionUpdateInterval; disruptionUpdateTimer = DisruptionUpdateInterval;
} }
longRangeUpdateTimer -= deltaTime;
if (longRangeUpdateTimer <= 0.0f)
{
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; }
if (c.Params.HideInSonar) { continue; }
if (!c.IsUnconscious && c.Params.DistantSonarRange > 0.0f &&
((c.WorldPosition - transducerCenter) * displayScale).LengthSquared() > DisplayRadius * DisplayRadius)
{
Vector2 targetVector = c.WorldPosition - transducerCenter;
if (targetVector.LengthSquared() > MathUtils.Pow2(c.Params.DistantSonarRange)) { continue; }
float dist = targetVector.Length();
Vector2 targetDir = targetVector / dist;
int blipCount = (int)MathHelper.Clamp(c.Mass, 50, 200);
for (int i = 0; i < blipCount; i++)
{
float angle = Rand.Range(-0.5f, 0.5f);
Vector2 blipDir = MathUtils.RotatePoint(targetDir, angle);
Vector2 invBlipDir = MathUtils.RotatePoint(targetDir, -angle);
var longRangeBlip = new SonarBlip(transducerCenter + blipDir * Range * 0.9f, Rand.Range(1.9f, 2.1f), Rand.Range(1.0f, 1.5f), BlipType.LongRange)
{
Velocity = -invBlipDir * (MathUtils.Round(Rand.Range(8000.0f, 15000.0f), 2000.0f) - Math.Abs(angle * angle * 10000.0f)),
Rotation = (float)Math.Atan2(-invBlipDir.Y, invBlipDir.X),
Alpha = MathUtils.Pow2((c.Params.DistantSonarRange - dist) / c.Params.DistantSonarRange)
};
longRangeBlip.Size.Y *= 5.0f;
sonarBlips.Add(longRangeBlip);
}
}
}
longRangeUpdateTimer = LongRangeUpdateInterval;
}
if (currentMode == Mode.Active && currentPingIndex != -1) if (currentMode == Mode.Active && currentPingIndex != -1)
{ {
return; return;
@@ -828,8 +870,8 @@ namespace Barotrauma.Items.Components
float directionalPingVisibility = useDirectionalPing && currentMode == Mode.Active ? 1.0f : showDirectionalIndicatorTimer; float directionalPingVisibility = useDirectionalPing && currentMode == Mode.Active ? 1.0f : showDirectionalIndicatorTimer;
if (directionalPingVisibility > 0.0f) if (directionalPingVisibility > 0.0f)
{ {
Vector2 sector1 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, DirectionalPingSector * 0.5f); Vector2 sector1 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, MathHelper.ToRadians(DirectionalPingSector * 0.5f));
Vector2 sector2 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, -DirectionalPingSector * 0.5f); Vector2 sector2 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, MathHelper.ToRadians(-DirectionalPingSector * 0.5f));
DrawLine(spriteBatch, Vector2.Zero, sector1, Color.LightCyan * 0.2f * directionalPingVisibility, width: 3); DrawLine(spriteBatch, Vector2.Zero, sector1, Color.LightCyan * 0.2f * directionalPingVisibility, width: 3);
DrawLine(spriteBatch, Vector2.Zero, sector2, Color.LightCyan * 0.2f * directionalPingVisibility, width: 3); DrawLine(spriteBatch, Vector2.Zero, sector2, Color.LightCyan * 0.2f * directionalPingVisibility, width: 3);
} }
@@ -862,9 +904,9 @@ namespace Barotrauma.Items.Components
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.StartLocation.Name, Level.Loaded.StartLocation.Name,
"outpost", Level.Loaded.StartOutpost != null ? "outpost" : "location",
Level.Loaded.StartLocation.Name, Level.Loaded.StartLocation.Name,
Level.Loaded.StartPosition, transducerCenter, Level.Loaded.StartExitPosition, transducerCenter,
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
} }
@@ -872,9 +914,9 @@ namespace Barotrauma.Items.Components
{ {
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
Level.Loaded.EndLocation.Name, Level.Loaded.EndLocation.Name,
"outpost", Level.Loaded.EndOutpost != null ? "outpost" : "location",
Level.Loaded.EndLocation.Name, Level.Loaded.EndLocation.Name,
Level.Loaded.EndPosition, transducerCenter, Level.Loaded.EndExitPosition, transducerCenter,
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
} }
@@ -906,10 +948,8 @@ namespace Barotrauma.Items.Components
} }
} }
if (GameMain.GameSession.Mission != null) foreach (Mission mission in GameMain.GameSession.Missions)
{ {
var mission = GameMain.GameSession.Mission;
if (!string.IsNullOrWhiteSpace(mission.SonarLabel)) if (!string.IsNullOrWhiteSpace(mission.SonarLabel))
{ {
foreach (Vector2 sonarPosition in mission.SonarPositions) foreach (Vector2 sonarPosition in mission.SonarPositions)
@@ -926,16 +966,16 @@ namespace Barotrauma.Items.Components
if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null) if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null)
{ {
foreach (var t in MineralClusters) foreach (var c in MineralClusters)
{ {
var unobtainedMinerals = t.Item2.Where(i => i != null && i.GetRootInventoryOwner() == i); var unobtainedMinerals = c.resources.Where(i => i != null && i.GetRootInventoryOwner() == i);
if (unobtainedMinerals.None()) { continue; } if (unobtainedMinerals.None()) { continue; }
if (!CheckResourceMarkerVisibility(t.Item1, transducerCenter)) { continue; } if (!CheckResourceMarkerVisibility(c.center, transducerCenter)) { continue; }
var i = unobtainedMinerals.FirstOrDefault(); var i = unobtainedMinerals.FirstOrDefault();
if (i == null) { continue; } if (i == null) { continue; }
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
i.Name, "mineral", i, i.Name, "mineral", i,
t.Item1, transducerCenter, c.center, transducerCenter,
displayScale, center, DisplayRadius * 0.95f, displayScale, center, DisplayRadius * 0.95f,
onlyShowTextOnMouseOver: true); onlyShowTextOnMouseOver: true);
} }
@@ -947,6 +987,19 @@ namespace Barotrauma.Items.Components
if (connectedSubs.Contains(sub)) { continue; } if (connectedSubs.Contains(sub)) { continue; }
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (item.Submarine != null)
{
//hide enemy team
if (sub.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2))
{
continue;
}
else if (sub.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1))
{
continue;
}
}
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
sub.Info.DisplayName, sub.Info.DisplayName,
sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine", sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine",
@@ -1046,15 +1099,18 @@ namespace Barotrauma.Items.Components
foreach (DockingPort dockingPort in DockingPort.List) foreach (DockingPort dockingPort in DockingPort.List)
{ {
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (dockingPort.Item.Submarine == null) { continue; } if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; } if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
if (!dockingPort.Item.Submarine.ShowSonarMarker && !dockingPort.Item.Submarine.Info.IsOutpost) { continue; }
//don't show the docking ports of the opposing team on the sonar //don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null) if (item.Submarine != null)
{ {
if ((dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team1 && item.Submarine.TeamID == CharacterTeamType.Team2) || if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2))
(dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team2 && item.Submarine.TeamID == CharacterTeamType.Team1)) {
continue;
}
else if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1))
{ {
continue; continue;
} }
@@ -1382,6 +1438,7 @@ namespace Barotrauma.Items.Components
MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f)); MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; } if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
sonarBlips.Add(blip); sonarBlips.Add(blip);
HintManager.OnSonarSpottedCharacter(Item, c);
} }
continue; continue;
} }
@@ -1401,6 +1458,7 @@ namespace Barotrauma.Items.Components
MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f)); MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; } if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
sonarBlips.Add(blip); sonarBlips.Add(blip);
HintManager.OnSonarSpottedCharacter(Item, c);
} }
} }
} }
@@ -1554,12 +1612,12 @@ namespace Barotrauma.Items.Components
float scale = (strength + 3.0f) * blip.Scale * blipScale; float scale = (strength + 3.0f) * blip.Scale * blipScale;
Color color = ToolBox.GradientLerp(strength, blipColorGradient[blip.BlipType]); Color color = ToolBox.GradientLerp(strength, blipColorGradient[blip.BlipType]);
sonarBlip.Draw(spriteBatch, center + pos, color, sonarBlip.Origin, blip.Rotation ?? MathUtils.VectorToAngle(pos), sonarBlip.Draw(spriteBatch, center + pos, color * blip.Alpha, sonarBlip.Origin, blip.Rotation ?? MathUtils.VectorToAngle(pos),
blip.Size * scale * 0.5f, SpriteEffects.None, 0); blip.Size * scale * 0.5f, SpriteEffects.None, 0);
pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal; pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal;
sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f, sonarBlip.Origin, 0, scale, SpriteEffects.None, 0); sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f * blip.Alpha, sonarBlip.Origin, 0, scale, SpriteEffects.None, 0);
} }
private void DrawMarker(SpriteBatch spriteBatch, string label, string iconIdentifier, object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, float scale, Vector2 center, float radius, private void DrawMarker(SpriteBatch spriteBatch, string label, string iconIdentifier, object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, float scale, Vector2 center, float radius,
@@ -1644,7 +1702,7 @@ namespace Barotrauma.Items.Components
} }
} }
if (string.IsNullOrEmpty(iconIdentifier) || !targetIcons.ContainsKey(iconIdentifier)) if (iconIdentifier == null || !targetIcons.ContainsKey(iconIdentifier))
{ {
GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), markerColor, thickness: 2); GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), markerColor, thickness: 2);
} }
@@ -1777,6 +1835,7 @@ namespace Barotrauma.Items.Components
public float? Rotation; public float? Rotation;
public Vector2 Size; public Vector2 Size;
public Sonar.BlipType BlipType; public Sonar.BlipType BlipType;
public float Alpha = 1.0f;
public SonarBlip(Vector2 pos, float fadeTimer, float scale, Sonar.BlipType blipType = Sonar.BlipType.Default) public SonarBlip(Vector2 pos, float fadeTimer, float scale, Sonar.BlipType blipType = Sonar.BlipType.Default)
{ {
@@ -351,14 +351,19 @@ namespace Barotrauma.Items.Components
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (GameMain.GameSession?.Campaign is CampaignMode campaign) if (GameMain.GameSession?.Missions.Any(m => !m.AllowUndocking) ?? false)
{
new GUIMessageBox("", TextManager.Get("undockingdisabledbymission"));
return false;
}
else if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{ {
if (Level.IsLoadedOutpost && if (Level.IsLoadedOutpost &&
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false))) DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
{ {
// Undocking from an outpost // Undocking from an outpost
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
campaign.ShowCampaignUI = true; campaign.ShowCampaignUI = true;
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
return false; return false;
} }
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null && else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
@@ -398,7 +403,7 @@ namespace Barotrauma.Items.Components
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
item.SendSignal(0, "1", "toggle_docking", sender: null); item.SendSignal("1", "toggle_docking");
} }
else else
{ {
@@ -722,7 +727,19 @@ namespace Barotrauma.Items.Components
} }
} }
pressureWarningText.Visible = item.Submarine != null && item.Submarine.AtDamageDepth && 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 &&
item.Submarine.RealWorldDepth > Level.Loaded.RealWorldCrushDepth - depthEffectThreshold && item.Submarine.RealWorldDepth > item.Submarine.RealWorldCrushDepth - depthEffectThreshold)
{
pressureWarningText.Visible = true;
pressureWarningText.Text = item.Submarine.AtDamageDepth ? TextManager.Get("SteeringDepthWarning") : TextManager.Get("SteeringDepthWarningLow").Replace("[crushdepth]", ((int)item.Submarine.RealWorldCrushDepth).ToString());
}
else
{
pressureWarningText.Visible = false;
}
iceSpireWarningText.Visible = item.Submarine != null && !pressureWarningText.Visible && showIceSpireWarning && Timing.TotalTime % 1.0f < 0.8f; iceSpireWarningText.Visible = item.Submarine != null && !pressureWarningText.Visible && showIceSpireWarning && Timing.TotalTime % 1.0f < 0.8f;
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
@@ -748,7 +765,7 @@ namespace Barotrauma.Items.Components
} }
if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null) if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null)
{ {
steeringAdjustSpeed = character == null ? 0.2f : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f); steeringAdjustSpeed = character == null ? DefaultSteeringAdjustSpeed : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f);
Vector2 input = Vector2.Zero; Vector2 input = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; } if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; } if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; }
@@ -914,7 +931,7 @@ namespace Barotrauma.Items.Components
if (dockingButtonClicked) if (dockingButtonClicked)
{ {
item.SendSignal(0, "1", "toggle_docking", sender: null); item.SendSignal("1", "toggle_docking");
} }
if (autoPilot) if (autoPilot)
@@ -98,8 +98,11 @@ namespace Barotrauma.Items.Components
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
float chargeRatio = charge / capacity; if (chargeIndicator != null)
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green); {
float chargeRatio = charge / capacity;
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
}
} }
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
@@ -1,5 +1,4 @@
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.Collections.Generic; using System.Collections.Generic;
@@ -28,15 +27,7 @@ namespace Barotrauma.Items.Components
int x = panelRect.X, y = panelRect.Y; int x = panelRect.X, y = panelRect.Y;
int width = panelRect.Width, height = panelRect.Height; int width = panelRect.Width, height = panelRect.Height;
Vector2 scale = new Vector2(GUI.Scale); Vector2 scale = GetScale(panel.GuiFrame.RectTransform.MaxSize, panel.GuiFrame.Rect.Size);
if (panel.GuiFrame.RectTransform.MaxSize.X < int.MaxValue)
{
scale.X = panel.GuiFrame.RectTransform.MaxSize.X / panel.GuiFrame.Rect.Width;
}
if (panel.GuiFrame.RectTransform.MaxSize.Y < int.MaxValue)
{
scale.Y = panel.GuiFrame.RectTransform.MaxSize.Y / panel.GuiFrame.Rect.Height;
}
bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition); bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition);
@@ -66,15 +57,15 @@ namespace Barotrauma.Items.Components
//two passes: first the connector, then the wires to get the wires to render in front //two passes: first the connector, then the wires to get the wires to render in front
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
Vector2 rightPos = new Vector2(x + width - 80 * scale.X, y + 60 * scale.Y); Vector2 rightPos = GetRightPos(x, y, width, scale);
Vector2 leftPos = new Vector2(x + 80 * scale.X, y + 60 * scale.Y); Vector2 leftPos = GetLeftPos(x, y, scale);
Vector2 rightWirePos = new Vector2(x + width - 5 * scale.X, y + 30 * scale.Y); Vector2 rightWirePos = new Vector2(x + width - 5 * scale.X, y + 30 * scale.Y);
Vector2 leftWirePos = new Vector2(x + 5 * scale.X, y + 30 * scale.Y); Vector2 leftWirePos = new Vector2(x + 5 * scale.X, y + 30 * scale.Y);
int wireInterval = (height - (int)(20 * scale.Y)) / Math.Max(totalWireCount, 1); int wireInterval = (height - (int)(20 * scale.Y)) / Math.Max(totalWireCount, 1);
int connectorIntervalLeft = (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => c.IsOutput), 1); int connectorIntervalLeft = GetConnectorIntervalLeft(height, scale, panel);
int connectorIntervalRight = (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => !c.IsOutput), 1); int connectorIntervalRight = GetConnectorIntervalRight(height, scale, panel);
foreach (Connection c in panel.Connections) foreach (Connection c in panel.Connections)
{ {
@@ -101,15 +92,12 @@ namespace Barotrauma.Items.Components
{ {
if (i == 0) if (i == 0)
{ {
c.DrawConnection(spriteBatch, panel, rightPos, c.DrawConnection(spriteBatch, panel, rightPos, GetOutputLabelPosition(rightPos, panel, c), scale);
new Vector2(rightPos.X - GUI.SmallFont.MeasureString(c.DisplayName.ToUpper()).X - 25 * panel.Scale, rightPos.Y + 5 * panel.Scale),
scale);
} }
else else
{ {
c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval); c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval);
} }
rightPos.Y += connectorIntervalLeft; rightPos.Y += connectorIntervalLeft;
rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
} }
@@ -117,15 +105,12 @@ namespace Barotrauma.Items.Components
{ {
if (i == 0) if (i == 0)
{ {
c.DrawConnection(spriteBatch, panel, leftPos, c.DrawConnection(spriteBatch, panel, leftPos, GetInputLabelPosition(leftPos, panel, c), scale);
new Vector2(leftPos.X + 25 * panel.Scale, leftPos.Y - 5 * panel.Scale - GUI.SmallFont.MeasureString(c.DisplayName.ToUpper()).Y),
scale);
} }
else else
{ {
c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval); c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval);
} }
leftPos.Y += connectorIntervalRight; leftPos.Y += connectorIntervalRight;
leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
} }
@@ -215,14 +200,11 @@ namespace Barotrauma.Items.Components
private void DrawConnection(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos, Vector2 scale) private void DrawConnection(SpriteBatch spriteBatch, ConnectionPanel panel, Vector2 position, Vector2 labelPos, Vector2 scale)
{ {
string text = DisplayName.ToUpper(); string text = DisplayName.ToUpper();
Vector2 textSize = GUI.SmallFont.MeasureString(text);
//nasty //nasty
var labelSprite = GUI.Style.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First(); if (GUI.Style.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First() is UISprite labelSprite)
if (labelSprite != null)
{ {
Rectangle labelArea = new Rectangle(labelPos.ToPoint(), textSize.ToPoint()); Rectangle labelArea = GetLabelArea(labelPos, text, scale);
labelArea.Inflate(10 * scale.X, 3 * scale.Y);
labelSprite.Draw(spriteBatch, labelArea, IsPower ? GUI.Style.Red : Color.SteelBlue); labelSprite.Draw(spriteBatch, labelArea, IsPower ? GUI.Style.Red : Color.SteelBlue);
} }
@@ -256,7 +238,8 @@ namespace Barotrauma.Items.Components
if (!PlayerInput.PrimaryMouseButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
if (GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) if ((GameMain.NetworkMember != null || panel.CheckCharacterSuccess(Character.Controlled)) &&
Wires.Count(w => w != null) < MaxPlayerConnectableWires)
{ {
//find an empty cell for the new connection //find an empty cell for the new connection
int index = FindEmptyIndex(); int index = FindEmptyIndex();
@@ -390,5 +373,115 @@ namespace Barotrauma.Items.Components
} }
} }
} }
public static bool CheckConnectionLabelOverlap(ConnectionPanel panel, out Point newRectSize)
{
Rectangle panelRect = panel.GuiFrame.Rect;
int x = panelRect.X, y = panelRect.Y;
Vector2 scale = GetScale(panel.GuiFrame.RectTransform.MaxSize, panel.GuiFrame.Rect.Size);
Vector2 rightPos = GetRightPos(x, y, panelRect.Width, scale);
Vector2 leftPos = GetLeftPos(x, y, scale);
int connectorIntervalLeft = GetConnectorIntervalLeft(panelRect.Height, scale, panel);
int connectorIntervalRight = GetConnectorIntervalRight(panelRect.Height, scale, panel);
newRectSize = panelRect.Size;
var labelAreas = new List<Rectangle>();
for (int i = 0; i < 100; i++)
{
labelAreas.Clear();
foreach (var c in panel.Connections)
{
if (c.IsOutput)
{
var labelArea = GetLabelArea(GetOutputLabelPosition(rightPos, panel, c), c.DisplayName.ToUpper(), scale);
labelAreas.Add(labelArea);
rightPos.Y += connectorIntervalLeft;
}
else
{
var labelArea = GetLabelArea(GetInputLabelPosition(leftPos, panel, c), c.DisplayName.ToUpper(), scale);
labelAreas.Add(labelArea);
leftPos.Y += connectorIntervalRight;
}
}
bool foundOverlap = false;
for (int j = 0; j < labelAreas.Count; j++)
{
for (int k = 0; k < labelAreas.Count; k++)
{
if (k == j) { continue; }
if (!labelAreas[j].Intersects(labelAreas[k])) { continue; }
newRectSize += new Point(10);
Point maxSize = new Point(
Math.Max(panel.GuiFrame.RectTransform.MaxSize.X, newRectSize.X),
Math.Max(panel.GuiFrame.RectTransform.MaxSize.Y, newRectSize.Y));
scale = GetScale(maxSize, newRectSize);
rightPos = GetRightPos(x, y, newRectSize.X, scale);
leftPos = GetLeftPos(x, y, scale);
connectorIntervalLeft = GetConnectorIntervalLeft(newRectSize.Y, scale, panel);
connectorIntervalRight = GetConnectorIntervalRight(newRectSize.Y, scale, panel);
foundOverlap = true;
break;
}
}
if (!foundOverlap) { break; }
}
return newRectSize.X != panel.GuiFrame.Rect.Width || newRectSize.Y > panel.GuiFrame.Rect.Height;
}
private static Vector2 GetScale(Point maxSize, Point size)
{
Vector2 scale = new Vector2(GUI.Scale);
if (maxSize.X < int.MaxValue)
{
scale.X = maxSize.X / size.X;
}
if (maxSize.Y < int.MaxValue)
{
scale.Y = maxSize.Y / size.Y;
}
return scale;
}
private static Vector2 GetInputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection)
{
return new Vector2(
connectorPosition.X + 25 * panel.Scale,
connectorPosition.Y - 5 * panel.Scale - GUI.SmallFont.MeasureString(connection.DisplayName.ToUpper()).Y);
}
private static Vector2 GetOutputLabelPosition(Vector2 connectorPosition, ConnectionPanel panel, Connection connection)
{
return new Vector2(
connectorPosition.X - 25 * panel.Scale - GUI.SmallFont.MeasureString(connection.DisplayName.ToUpper()).X,
connectorPosition.Y + 5 * panel.Scale);
}
private static Rectangle GetLabelArea(Vector2 labelPos, string text, Vector2 scale)
{
Vector2 textSize = GUI.SmallFont.MeasureString(text);
Rectangle labelArea = new Rectangle(labelPos.ToPoint(), textSize.ToPoint());
labelArea.Inflate(10 * scale.X, 3 * scale.Y);
return labelArea;
}
private static Vector2 GetLeftPos(int x, int y, Vector2 scale)
{
return new Vector2(x + 80 * scale.X, y + 60 * scale.Y);
}
private static Vector2 GetRightPos(int x, int y, int width, Vector2 scale)
{
return new Vector2(x + width - 80 * scale.X, y + 60 * scale.Y);
}
private static int GetConnectorIntervalLeft(int height, Vector2 scale, ConnectionPanel panel)
{
return (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => c.IsOutput), 1);
}
private static int GetConnectorIntervalRight(int height, Vector2 scale, ConnectionPanel panel)
{
return (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => !c.IsOutput), 1);
}
} }
} }
@@ -24,9 +24,15 @@ namespace Barotrauma.Items.Components
get { return GuiFrame.Rect.Width / 400.0f; } get { return GuiFrame.Rect.Width / 400.0f; }
} }
partial void InitProjSpecific(XElement element) private Point originalMaxSize;
private Vector2 originalRelativeSize;
partial void InitProjSpecific()
{ {
if (GuiFrame == null) { return; } if (GuiFrame == null) { return; }
originalMaxSize = GuiFrame.RectTransform.MaxSize;
originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
CheckForLabelOverlap();
new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null) new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
{ {
UserData = this UserData = this
@@ -40,7 +46,7 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
foreach (Wire wire in DisconnectedWires) foreach (var _ in DisconnectedWires)
{ {
if (Rand.Range(0.0f, 500.0f) < 1.0f) if (Rand.Range(0.0f, 500.0f) < 1.0f)
{ {
@@ -112,6 +118,32 @@ namespace Barotrauma.Items.Components
} }
} }
protected override void OnResolutionChanged()
{
base.OnResolutionChanged();
if (GuiFrame == null) { return; }
CheckForLabelOverlap();
}
private void CheckForLabelOverlap()
{
GuiFrame.RectTransform.MaxSize = originalMaxSize;
GuiFrame.RectTransform.Resize(originalRelativeSize);
if (Connection.CheckConnectionLabelOverlap(this, out Point newRectSize))
{
int xCenter = (int)(GameMain.GraphicsWidth / 2.0f);
int maxNewWidth = 2 * Math.Min(xCenter - HUDLayoutSettings.CrewArea.Right, xCenter - HUDLayoutSettings.ChatBoxArea.Right);
int yCenter = (int)(GameMain.GraphicsHeight / 2.0f);
int maxNewHeight = 2 * Math.Min(yCenter - HUDLayoutSettings.MessageAreaTop.Bottom, HUDLayoutSettings.InventoryTopY - yCenter);
// Make sure we don't expand the panel interface too much
newRectSize = new Point(Math.Min(newRectSize.X, maxNewWidth), Math.Min(newRectSize.Y, maxNewHeight));
GuiFrame.RectTransform.MaxSize = new Point(
Math.Max(GuiFrame.RectTransform.MaxSize.X, newRectSize.X),
Math.Max(GuiFrame.RectTransform.MaxSize.Y, newRectSize.Y));
GuiFrame.RectTransform.Resize(newRectSize);
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (GameMain.Client.MidRoundSyncing) if (GameMain.Client.MidRoundSyncing)
@@ -50,7 +50,8 @@ namespace Barotrauma.Items.Components
var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon") var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon")
{ {
OverflowClip = true, OverflowClip = true,
UserData = ciElement UserData = ciElement,
MaxTextLength = ciElement.MaxTextLength
}; };
//reset size restrictions set by the Style to make sure the elements can fit the interface //reset size restrictions set by the Style to make sure the elements can fit the interface
textBox.RectTransform.MinSize = textBox.Frame.RectTransform.MinSize = new Point(0, 0); textBox.RectTransform.MinSize = textBox.Frame.RectTransform.MinSize = new Point(0, 0);
@@ -63,21 +63,23 @@ namespace Barotrauma.Items.Components
} }
OutputValue = input; OutputValue = input;
item.SendSignal(0, input, "signal_out", null);
ShowOnDisplay(input); ShowOnDisplay(input);
item.SendSignal(input, "signal_out");
} }
partial void ShowOnDisplay(string input) partial void ShowOnDisplay(string input, bool addToHistory = true)
{ {
messageHistory.Add(input); if (addToHistory)
while (messageHistory.Count > MaxMessages)
{ {
messageHistory.RemoveAt(0); messageHistory.Add(input);
} while (messageHistory.Count > MaxMessages)
{
while (historyBox.Content.CountChildren > MaxMessages) messageHistory.RemoveAt(0);
{ }
historyBox.RemoveChild(historyBox.Content.Children.First()); while (historyBox.Content.CountChildren > MaxMessages)
{
historyBox.RemoveChild(historyBox.Content.Children.First());
}
} }
GUITextBlock newBlock = new GUITextBlock( GUITextBlock newBlock = new GUITextBlock(
@@ -156,7 +156,8 @@ namespace Barotrauma.Items.Components
drawOffset = sub.DrawPosition + sub.HiddenSubPosition; drawOffset = sub.DrawPosition + sub.HiddenSubPosition;
} }
float depth = item.IsSelected ? 0.0f : SubEditorScreen.IsWiringMode() ? 0.02f : wireSprite.Depth + (item.ID % 100) * 0.000001f;// item.GetDrawDepth(wireSprite.Depth, wireSprite); float baseDepth = UseSpriteDepth ? item.SpriteDepth : wireSprite.Depth;
float depth = item.IsSelected ? 0.0f : SubEditorScreen.IsWiringMode() ? 0.02f : baseDepth + (item.ID % 100) * 0.000001f;// item.GetDrawDepth(wireSprite.Depth, wireSprite);
if (item.IsHighlighted) if (item.IsHighlighted)
{ {
@@ -225,7 +225,7 @@ namespace Barotrauma.Items.Components
foreach (AfflictionPrefab affliction in combinedAfflictionStrengths.Keys) foreach (AfflictionPrefab affliction in combinedAfflictionStrengths.Keys)
{ {
texts.Add(TextManager.AddPunctuation(':', affliction.Name, ((int)combinedAfflictionStrengths[affliction]).ToString() + " %")); texts.Add(TextManager.AddPunctuation(':', affliction.Name, Math.Max(((int)combinedAfflictionStrengths[affliction]), 1).ToString() + " %"));
textColors.Add(Color.Lerp(GUI.Style.Orange, GUI.Style.Red, combinedAfflictionStrengths[affliction] / affliction.MaxStrength)); textColors.Add(Color.Lerp(GUI.Style.Orange, GUI.Style.Red, combinedAfflictionStrengths[affliction] / affliction.MaxStrength));
} }
} }
@@ -130,12 +130,15 @@ namespace Barotrauma.Items.Components
} }
} }
powerIndicator = new GUIProgressBar(new RectTransform(new Vector2(0.18f, 0.03f), GUI.Canvas, Anchor.TopCenter) powerIndicator = new GUIProgressBar(new RectTransform(new Vector2(0.18f, 0.03f), GUI.Canvas, Anchor.BottomCenter)
{ {
MinSize = new Point(100,20), MinSize = new Point(100, 20),
RelativeOffset = new Vector2(0.0f, 0.01f) RelativeOffset = new Vector2(0.0f, 0.01f)
}, },
barSize: 0.0f, style: "DeviceProgressBar"); barSize: 0.0f, style: "DeviceProgressBar")
{
CanBeFocused = false
};
} }
public override void Move(Vector2 amount) public override void Move(Vector2 amount)
@@ -497,7 +500,13 @@ namespace Barotrauma.Items.Components
foreach (MapEntity e in item.linkedTo) foreach (MapEntity e in item.linkedTo)
{ {
if (!(e is Item linkedItem)) { continue; } if (!(e is Item linkedItem)) { continue; }
availableAmmo.AddRange(linkedItem.ContainedItems); var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
availableAmmo.AddRange(itemContainer.Inventory.AllItems);
for (int i = 0; i < itemContainer.Inventory.Capacity - itemContainer.Inventory.AllItems.Count(); i++)
{
availableAmmo.Add(null);
}
} }
float chargeRate = float chargeRate =
@@ -531,15 +540,16 @@ namespace Barotrauma.Items.Components
if (ShowProjectileIndicator) if (ShowProjectileIndicator)
{ {
Point slotSize = (Inventory.SlotSpriteSmall.size * Inventory.UIScale).ToPoint(); Point slotSize = (Inventory.SlotSpriteSmall.size * Inventory.UIScale).ToPoint();
int spacing = 5; Point spacing = new Point(GUI.IntScale(5), GUI.IntScale(20));
int slotsPerRow = Math.Min(availableAmmo.Count, 6); int slotsPerRow = Math.Min(availableAmmo.Count, 6);
int totalWidth = slotSize.X * slotsPerRow + spacing * (slotsPerRow - 1); int totalWidth = slotSize.X * slotsPerRow + spacing.X * (slotsPerRow - 1);
Point invSlotPos = new Point(GameMain.GraphicsWidth / 2 - totalWidth / 2, (int)(60 * GUI.Scale)); int rows = (int)Math.Ceiling(availableAmmo.Count / (float)slotsPerRow);
Point invSlotPos = new Point(GameMain.GraphicsWidth / 2 - totalWidth / 2, powerIndicator.Rect.Y - (slotSize.Y + spacing.Y) * rows);
for (int i = 0; i < availableAmmo.Count; i++) for (int i = 0; i < availableAmmo.Count; i++)
{ {
// TODO: Optimize? Creates multiple new objects per frame? // TODO: Optimize? Creates multiple new objects per frame?
Inventory.DrawSlot(spriteBatch, null, Inventory.DrawSlot(spriteBatch, null,
new VisualSlot(new Rectangle(invSlotPos + new Point((i % slotsPerRow) * (slotSize.X + spacing), (int)Math.Floor(i / (float)slotsPerRow) * (slotSize.Y + spacing)), slotSize)), new VisualSlot(new Rectangle(invSlotPos + new Point((i % slotsPerRow) * (slotSize.X + spacing.X), (int)Math.Floor(i / (float)slotsPerRow) * (slotSize.Y + spacing.Y)), slotSize)),
availableAmmo[i], -1, true); availableAmmo[i], -1, true);
} }
if (flashNoAmmo) if (flashNoAmmo)
@@ -578,7 +588,7 @@ namespace Barotrauma.Items.Components
//ID ushort.MaxValue = launched without a projectile //ID ushort.MaxValue = launched without a projectile
if (projectileID == ushort.MaxValue) if (projectileID == ushort.MaxValue)
{ {
Launch(null); Launch(null, user);
} }
else else
{ {
@@ -587,7 +597,7 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found"); DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found");
return; return;
} }
Launch(projectile, launchRotation: newTargetRotation); Launch(projectile, user, launchRotation: newTargetRotation);
} }
} }
} }
@@ -165,7 +165,14 @@ namespace Barotrauma.Items.Components
if (isLocked) if (isLocked)
{ {
Lock(isNetworkMessage: true, forcePosition: true); if (DockingTarget.joint != null)
{
DockingTarget.Lock(isNetworkMessage: true);
}
else
{
Lock(isNetworkMessage: true);
}
} }
} }
else else
@@ -215,8 +215,10 @@ namespace Barotrauma
public Inventory Inventory; public Inventory Inventory;
public readonly Item Item; public readonly Item Item;
public readonly bool IsSubSlot; public readonly bool IsSubSlot;
public string Tooltip; public string Tooltip { get; private set; }
public List<RichTextData> TooltipRichTextData; public List<RichTextData> TooltipRichTextData { get; private set;}
public int tooltipDisplayedCondition;
public SlotReference(Inventory parentInventory, VisualSlot slot, int slotIndex, bool isSubSlot, Inventory subInventory = null) public SlotReference(Inventory parentInventory, VisualSlot slot, int slotIndex, bool isSubSlot, Inventory subInventory = null)
{ {
@@ -227,16 +229,29 @@ namespace Barotrauma
IsSubSlot = isSubSlot; IsSubSlot = isSubSlot;
Item = ParentInventory.GetItemAt(slotIndex); Item = ParentInventory.GetItemAt(slotIndex);
int stackCount = 1; RefreshTooltip();
if (parentInventory != null && Item != null)
{
stackCount = parentInventory.GetItemsAt(slotIndex).Count();
}
TooltipRichTextData = RichTextData.GetRichTextData(GetTooltip(Item, stackCount), out Tooltip);
} }
private string GetTooltip(Item item, int stackCount) public bool TooltipNeedsRefresh()
{
if (Item == null) { return false; }
return (int)Item.ConditionPercentage != tooltipDisplayedCondition;
}
public void RefreshTooltip()
{
if (Item == null) { return; }
IEnumerable<Item> itemsInSlot = null;
if (ParentInventory != null && Item != null)
{
itemsInSlot = ParentInventory.GetItemsAt(SlotIndex);
}
TooltipRichTextData = RichTextData.GetRichTextData(GetTooltip(Item, itemsInSlot), out string newTooltip);
Tooltip = newTooltip;
tooltipDisplayedCondition = (int)Item.ConditionPercentage;
}
private string GetTooltip(Item item, IEnumerable<Item> itemsInSlot)
{ {
if (item == null) { return null; } if (item == null) { return null; }
@@ -288,9 +303,13 @@ namespace Barotrauma
} }
} }
string colorStr = XMLExtensions.ColorToString(item.SpawnedInOutpost ? GUI.Style.Red : Color.White); string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White);
toolTip = $"‖color:{colorStr}‖{item.Name}‖color:end‖"; toolTip = $"‖color:{colorStr}‖{item.Name}‖color:end‖";
if (itemsInSlot.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))
{
toolTip += " " + TextManager.Get("connectionlocked");
}
if (!item.IsFullCondition && !item.Prefab.HideConditionBar) if (!item.IsFullCondition && !item.Prefab.HideConditionBar)
{ {
string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull)); string conditionColorStr = XMLExtensions.ColorToString(ToolBox.GradientLerp(item.Condition / item.MaxCondition, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull));
@@ -298,7 +317,7 @@ namespace Barotrauma
} }
if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; } if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; }
} }
if (stackCount > 2) if (itemsInSlot.Count() > 1)
{ {
string colorStr = XMLExtensions.ColorToString(GUI.Style.Blue); string colorStr = XMLExtensions.ColorToString(GUI.Style.Blue);
toolTip += $"\n‖color:{colorStr}‖[{GameMain.Config.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖"; toolTip += $"\n‖color:{colorStr}‖[{GameMain.Config.KeyBindText(InputType.TakeOneFromInventorySlot)}] {TextManager.Get("inputtype.takeonefrominventoryslot")}‖color:end‖";
@@ -315,10 +334,10 @@ namespace Barotrauma
{ {
get get
{ {
return DraggingItems.Any() && return Character.Controlled != null &&
Character.Controlled != null &&
Character.Controlled.SelectedConstruction == null && Character.Controlled.SelectedConstruction == null &&
CharacterHealth.OpenHealthWindow == null; CharacterHealth.OpenHealthWindow == null &&
DraggingItems.Any();
} }
} }
@@ -565,39 +584,41 @@ namespace Barotrauma
if (!DraggingItems.Any()) if (!DraggingItems.Any())
{ {
if (PlayerInput.PrimaryMouseButtonDown() && slots[slotIndex].Any()) var interactableItems = Screen.Selected == GameMain.GameScreen ? slots[slotIndex].Items.Where(it => !it.NonInteractable && !it.NonPlayerTeamInteractable) : slots[slotIndex].Items;
if (PlayerInput.PrimaryMouseButtonDown() && interactableItems.Any())
{ {
if (PlayerInput.KeyDown(InputType.TakeHalfFromInventorySlot)) if (PlayerInput.KeyDown(InputType.TakeHalfFromInventorySlot))
{ {
DraggingItems.AddRange(slots[slotIndex].Items.Skip(slots[slotIndex].ItemCount / 2)); DraggingItems.AddRange(interactableItems.Skip(interactableItems.Count() / 2));
} }
else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot)) else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot))
{ {
DraggingItems.Add(slots[slotIndex].First()); DraggingItems.Add(interactableItems.First());
} }
else else
{ {
DraggingItems.AddRange(slots[slotIndex].Items); DraggingItems.AddRange(interactableItems);
} }
DraggingSlot = slot; DraggingSlot = slot;
} }
} }
else if (PlayerInput.PrimaryMouseButtonReleased()) else if (PlayerInput.PrimaryMouseButtonReleased())
{ {
if (PlayerInput.DoubleClicked() && slots[slotIndex].Any()) var interactableItems = Screen.Selected == GameMain.GameScreen ? slots[slotIndex].Items.Where(it => !it.NonInteractable && !it.NonPlayerTeamInteractable) : slots[slotIndex].Items;
if (PlayerInput.DoubleClicked() && interactableItems.Any())
{ {
doubleClickedItems.Clear(); doubleClickedItems.Clear();
if (PlayerInput.KeyDown(InputType.TakeHalfFromInventorySlot)) if (PlayerInput.KeyDown(InputType.TakeHalfFromInventorySlot))
{ {
doubleClickedItems.AddRange(slots[slotIndex].Items.Skip(slots[slotIndex].ItemCount / 2)); doubleClickedItems.AddRange(interactableItems.Skip(interactableItems.Count() / 2));
} }
else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot)) else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot))
{ {
doubleClickedItems.Add(slots[slotIndex].First()); doubleClickedItems.Add(interactableItems.First());
} }
else else
{ {
doubleClickedItems.AddRange(slots[slotIndex].Items); doubleClickedItems.AddRange(interactableItems);
} }
} }
} }
@@ -1241,9 +1262,11 @@ namespace Barotrauma
if (selectedSlot.ParentInventory?.Owner != Character.Controlled && if (selectedSlot.ParentInventory?.Owner != Character.Controlled &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter && selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction && selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) &&
rootOwner != Character.Controlled && rootOwner != Character.Controlled &&
rootOwner != Character.Controlled.SelectedCharacter && rootOwner != Character.Controlled.SelectedCharacter &&
rootOwner != Character.Controlled.SelectedConstruction) rootOwner != Character.Controlled.SelectedConstruction &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false))
{ {
selectedSlot = null; selectedSlot = null;
} }
@@ -1362,6 +1385,10 @@ namespace Barotrauma
{ {
Rectangle slotRect = selectedSlot.Slot.Rect; Rectangle slotRect = selectedSlot.Slot.Rect;
slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint(); slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint();
if (selectedSlot.TooltipNeedsRefresh())
{
selectedSlot.RefreshTooltip();
}
DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect, selectedSlot.TooltipRichTextData); DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect, selectedSlot.TooltipRichTextData);
} }
} }
@@ -1533,7 +1560,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) { spriteColor *= 0.5f; } if (inventory != null && (inventory.Locked || inventory.slots[slotIndex].Items.All(it => it.NonInteractable || it.NonPlayerTeamInteractable))) { spriteColor *= 0.5f; }
if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface) if (CharacterHealth.OpenHealthWindow != null && !item.UseInHealthInterface)
{ {
spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f); spriteColor = Color.Lerp(spriteColor, Color.TransparentBlack, 0.5f);
@@ -1544,7 +1571,7 @@ namespace Barotrauma
} }
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
if (item.SpawnedInOutpost && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand)) if (!item.AllowStealing && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
{ {
var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand]; var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand];
Vector2 iconSize = new Vector2(25 * GUI.Scale); Vector2 iconSize = new Vector2(25 * GUI.Scale);
@@ -1559,7 +1586,7 @@ namespace Barotrauma
{ {
maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent<ItemContainer>()?.MaxStackSize ?? maxStackSize); maxStackSize = Math.Min(maxStackSize, item.Container.GetComponent<ItemContainer>()?.MaxStackSize ?? maxStackSize);
} }
if (maxStackSize > 1) if (maxStackSize > 1 && inventory != null)
{ {
int itemCount = slot.MouseOn() ? inventory.slots[slotIndex].ItemCount : inventory.slots[slotIndex].Items.Where(it => !DraggingItems.Contains(it)).Count(); int itemCount = slot.MouseOn() ? inventory.slots[slotIndex].ItemCount : inventory.slots[slotIndex].Items.Where(it => !DraggingItems.Contains(it)).Count();
if (item.IsFullCondition || MathUtils.NearlyEqual(item.Condition, 0.0f) || itemCount > 1) if (item.IsFullCondition || MathUtils.NearlyEqual(item.Condition, 0.0f) || itemCount > 1)
@@ -87,7 +87,7 @@ namespace Barotrauma
{ {
get get
{ {
if (!GameMain.SubEditorScreen.ShowThalamus && prefab.Category.HasFlag(MapEntityCategory.Thalamus)) if (GameMain.SubEditorScreen.IsSubcategoryHidden(prefab.Subcategory))
{ {
return false; return false;
} }
@@ -618,6 +618,7 @@ namespace Barotrauma
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null) GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
{ {
CanTakeKeyBoardFocus = false,
Spacing = (int)(25 * GUI.Scale) Spacing = (int)(25 * GUI.Scale)
}; };
@@ -1463,6 +1464,7 @@ namespace Barotrauma
byte bodyType = msg.ReadByte(); byte bodyType = msg.ReadByte();
bool spawnedInOutpost = msg.ReadBoolean(); bool spawnedInOutpost = msg.ReadBoolean();
bool allowStealing = msg.ReadBoolean();
byte teamID = msg.ReadByte(); byte teamID = msg.ReadByte();
bool tagsChanged = msg.ReadBoolean(); bool tagsChanged = msg.ReadBoolean();
string tags = ""; string tags = "";
@@ -1534,7 +1536,8 @@ namespace Barotrauma
var item = new Item(itemPrefab, pos, sub, id: itemId) var item = new Item(itemPrefab, pos, sub, id: itemId)
{ {
SpawnedInOutpost = spawnedInOutpost SpawnedInOutpost = spawnedInOutpost,
AllowStealing = allowStealing
}; };
if (item.body != null) if (item.body != null)
@@ -22,47 +22,47 @@ namespace Barotrauma
var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition, Vector2.Zero, 0.0f, hull); var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition, Vector2.Zero, 0.0f, hull);
if (underwaterExplosion != null) if (underwaterExplosion != null)
{ {
underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 150.0f, 0.5f, 10.0f); underwaterExplosion.Size *= MathHelper.Clamp(Attack.Range / 150.0f, 0.5f, 10.0f);
underwaterExplosion.StartDelay = 0.0f; underwaterExplosion.StartDelay = 0.0f;
} }
} }
for (int i = 0; i < attack.Range * 0.1f; i++) for (int i = 0; i < Attack.Range * 0.1f; i++)
{ {
if (!underwater) if (!underwater)
{ {
float particleSpeed = Rand.Range(0.0f, 1.0f); float particleSpeed = Rand.Range(0.0f, 1.0f);
particleSpeed = particleSpeed * particleSpeed * attack.Range; particleSpeed = particleSpeed * particleSpeed * Attack.Range;
if (flames) if (flames)
{ {
float particleScale = MathHelper.Clamp(attack.Range * 0.0025f, 0.5f, 2.0f); float particleScale = MathHelper.Clamp(Attack.Range * 0.0025f, 0.5f, 2.0f);
var flameParticle = GameMain.ParticleManager.CreateParticle("explosionfire", var flameParticle = GameMain.ParticleManager.CreateParticle("explosionfire",
ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull), ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, Attack.Range))), hull),
Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull); Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
if (flameParticle != null) flameParticle.Size *= particleScale; if (flameParticle != null) flameParticle.Size *= particleScale;
} }
if (smoke) if (smoke)
{ {
GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke", GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke",
ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull), ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, Attack.Range))), hull),
Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull); Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
} }
} }
else if (underwaterBubble) else if (underwaterBubble)
{ {
Vector2 bubblePos = Rand.Vector(Rand.Range(0.0f, attack.Range * 0.5f)); Vector2 bubblePos = Rand.Vector(Rand.Range(0.0f, Attack.Range * 0.5f));
GameMain.ParticleManager.CreateParticle("risingbubbles", worldPosition + bubblePos, GameMain.ParticleManager.CreateParticle("risingbubbles", worldPosition + bubblePos,
Vector2.Zero, 0.0f, hull); Vector2.Zero, 0.0f, hull);
if (i < attack.Range * 0.02f) if (i < Attack.Range * 0.02f)
{ {
var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition + bubblePos, var underwaterExplosion = GameMain.ParticleManager.CreateParticle("underwaterexplosion", worldPosition + bubblePos,
Vector2.Zero, 0.0f, hull); Vector2.Zero, 0.0f, hull);
if (underwaterExplosion != null) if (underwaterExplosion != null)
{ {
underwaterExplosion.Size *= MathHelper.Clamp(attack.Range / 300.0f, 0.5f, 2.0f) * Rand.Range(0.8f, 1.2f); underwaterExplosion.Size *= MathHelper.Clamp(Attack.Range / 300.0f, 0.5f, 2.0f) * Rand.Range(0.8f, 1.2f);
} }
} }
@@ -77,7 +77,7 @@ namespace Barotrauma
if (flash) if (flash)
{ {
float displayRange = flashRange.HasValue ? flashRange.Value : attack.Range; float displayRange = flashRange.HasValue ? flashRange.Value : Attack.Range;
if (displayRange < 0.1f) { return; } if (displayRange < 0.1f) { return; }
var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null); var light = new LightSource(worldPosition, displayRange, Color.LightYellow, null);
CoroutineManager.StartCoroutine(DimLight(light)); CoroutineManager.StartCoroutine(DimLight(light));
@@ -90,7 +90,10 @@ namespace Barotrauma
private GUIComponent CreateEditingHUD(bool inGame = false) private GUIComponent CreateEditingHUD(bool inGame = false)
{ {
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null); GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
{
CanTakeKeyBoardFocus = false
};
new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont); new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont);
PositionEditingHUD(); PositionEditingHUD();
@@ -302,7 +302,7 @@ 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);
} }
foreach (Vector2 point in cell.BodyVertices) foreach (Vector2 point in cell.BodyVertices)
@@ -322,6 +322,11 @@ namespace Barotrauma
} }
}*/ }*/
foreach (var abyssIsland in level.AbyssIslands)
{
GUI.DrawRectangle(spriteBatch, new Vector2(abyssIsland.Area.X, -abyssIsland.Area.Y - abyssIsland.Area.Height), abyssIsland.Area.Size.ToVector2(), Color.Cyan, thickness: 5);
}
foreach (var ruin in level.Ruins) foreach (var ruin in level.Ruins)
{ {
ruin.DebugDraw(spriteBatch); ruin.DebugDraw(spriteBatch);
@@ -48,6 +48,7 @@ namespace Barotrauma.Lights
private readonly List<LightSource> lights; private readonly List<LightSource> lights;
public bool LosEnabled = true; public bool LosEnabled = true;
public float LosAlpha = 1f;
public LosMode LosMode = LosMode.Transparent; public LosMode LosMode = LosMode.Transparent;
public bool LightingEnabled = true; public bool LightingEnabled = true;
@@ -497,7 +498,6 @@ namespace Barotrauma.Lights
graphics.SetRenderTarget(LosTexture); graphics.SetRenderTarget(LosTexture);
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameMain.Config.LightMapScale, GameMain.Config.LightMapScale, 1.0f)));
if (ObstructVision) if (ObstructVision)
{ {
graphics.Clear(Color.Black); graphics.Clear(Color.Black);
@@ -507,16 +507,17 @@ namespace Barotrauma.Lights
float rotation = MathUtils.VectorToAngle(losOffset); float rotation = MathUtils.VectorToAngle(losOffset);
Vector2 scale = new Vector2( Vector2 scale = new Vector2(
MathHelper.Clamp(losOffset.Length() / 256.0f, 2.0f, 5.0f), 2.0f); MathHelper.Clamp(losOffset.Length() / 256.0f, 4.0f, 5.0f), 3.0f);
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameMain.Config.LightMapScale, GameMain.Config.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(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
spriteBatch.End();
} }
else else
{ {
graphics.Clear(Color.White); graphics.Clear(Color.White);
} }
spriteBatch.End();
//-------------------------------------- //--------------------------------------
@@ -2,8 +2,8 @@
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma namespace Barotrauma
{ {
@@ -63,7 +63,19 @@ namespace Barotrauma
private Sprite[,] mapTiles; private Sprite[,] mapTiles;
private bool[,] tileDiscovered; private bool[,] tileDiscovered;
private Pair<Rectangle, string> connectionTooltip; private float connectionHighlightState;
private (Rectangle targetArea, string tip)? tooltip;
private string sanitizedTooltip;
private List<RichTextData> tooltipRichTextData;
private string prevTooltip;
private (SubmarineInfo pendingSub, float realWorldCrushDepth) pendingSubInfo;
/*private (Rectangle targetArea, string tip)? connectionTooltip;
private string sanitizedConnectionTooltip;
private List<RichTextData> connectionTooltipRichTextData;
private string prevConnectionTooltip;*/
#if DEBUG #if DEBUG
private GUIComponent editor; private GUIComponent editor;
@@ -93,13 +105,6 @@ namespace Barotrauma
}; };
} }
#endif #endif
public Location CurrentDisplayLocation
{
get
{
return GameMain.GameSession.Campaign.CurrentDisplayLocation;
}
}
partial void InitProjectSpecific() partial void InitProjectSpecific()
{ {
@@ -120,7 +125,6 @@ namespace Barotrauma
DrawOffset = -CurrentLocation.MapPosition; DrawOffset = -CurrentLocation.MapPosition;
} }
Vector2 tileSize = generationParams.MapTiles.Values.First().First().size * generationParams.MapTileScale; Vector2 tileSize = generationParams.MapTiles.Values.First().First().size * generationParams.MapTileScale;
int tilesX = (int)Math.Ceiling(Width / tileSize.X); int tilesX = (int)Math.Ceiling(Width / tileSize.X);
int tilesY = (int)Math.Ceiling(Height / tileSize.Y); int tilesY = (int)Math.Ceiling(Height / tileSize.Y);
@@ -227,8 +231,8 @@ namespace Barotrauma
if (change.Messages.Any()) if (change.Messages.Any())
{ {
string msg = change.Messages[Rand.Range(0, change.Messages.Count)] string msg = change.Messages[Rand.Range(0, change.Messages.Count)]
.Replace("[previousname]", prevName) .Replace("[previousname]", $"‖color:gui.orange‖{prevName}‖end‖")
.Replace("[name]", location.Name); .Replace("[name]", $"‖color:gui.orange‖{location.Name}‖end‖");
location.LastTypeChangeMessage = msg; location.LastTypeChangeMessage = msg;
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
@@ -254,28 +258,52 @@ namespace Barotrauma
{ {
Rectangle rect = mapContainer.Rect; Rectangle rect = mapContainer.Rect;
if (CurrentDisplayLocation != null) var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
if (currentDisplayLocation != null)
{ {
if (!CurrentDisplayLocation.Discovered) if (!currentDisplayLocation.Discovered)
{ {
RemoveFogOfWar(CurrentDisplayLocation); RemoveFogOfWar(currentDisplayLocation);
CurrentDisplayLocation.Discovered = true; currentDisplayLocation.Discovered = true;
if (CurrentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{ {
furthestDiscoveredLocation = CurrentDisplayLocation; furthestDiscoveredLocation = currentDisplayLocation;
} }
} }
} }
currLocationIndicatorPos = Vector2.Lerp(currLocationIndicatorPos, CurrentDisplayLocation.MapPosition, deltaTime); Vector2 currentPosition = currentDisplayLocation.MapPosition;
if (Level.Loaded?.Type == LevelData.LevelType.LocationConnection && Level.Loaded.StartLocation != null && Level.Loaded.EndLocation != null)
{
Vector2 startPos = currentDisplayLocation == Level.Loaded.StartLocation ? Level.Loaded.StartLocation.MapPosition : Level.Loaded.EndLocation.MapPosition;
int moveDir = currentDisplayLocation == Level.Loaded.StartLocation ? 1 : -1;
Vector2 diff = Level.Loaded.EndLocation.MapPosition - Level.Loaded.StartLocation.MapPosition;
currentPosition = startPos +
Vector2.Normalize(diff) * Math.Min(100, diff.Length() * 0.2f) * moveDir;
}
else
{
currentPosition += Vector2.UnitY * 35;
}
currLocationIndicatorPos = Vector2.Lerp(currLocationIndicatorPos, currentPosition, deltaTime);
#if DEBUG #if DEBUG
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
if (editor == null) CreateEditor(); if (editor == null) CreateEditor();
editor.AddToGUIUpdateList(order: 1); editor.AddToGUIUpdateList(order: 1);
} }
if (PlayerInput.KeyHit(Keys.Space))
{
Radiation?.OnStep();
}
#endif #endif
Radiation?.MapUpdate(deltaTime);
if (mapAnimQueue.Count > 0) if (mapAnimQueue.Count > 0)
{ {
hudVisibility = Math.Max(hudVisibility - deltaTime, 0.0f); hudVisibility = Math.Max(hudVisibility - deltaTime, 0.0f);
@@ -299,15 +327,16 @@ namespace Barotrauma
for (int i = 0; i < Locations.Count; i++) for (int i = 0; i < Locations.Count; i++)
{ {
Location location = Locations[i]; Location location = Locations[i];
if (IsInFogOfWar(location) && !(CurrentDisplayLocation?.Connections.Any(c => c.Locations.Contains(location)) ?? false) && !GameMain.DebugDraw) { continue; } if (IsInFogOfWar(location) && !(currentDisplayLocation?.Connections.Any(c => c.Locations.Contains(location)) ?? false) && !GameMain.DebugDraw) { continue; }
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
if (!rect.Contains(pos)) { continue; } if (!rect.Contains(pos)) { continue; }
float iconScale = generationParams.LocationIconSize / location.Type.Sprite.size.X; Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
if (location == CurrentDisplayLocation) { iconScale *= 1.2f; } float iconScale = generationParams.LocationIconSize / locationSprite.size.X;
if (location == currentDisplayLocation) { iconScale *= 1.2f; }
Rectangle drawRect = location.Type.Sprite.SourceRect; Rectangle drawRect = locationSprite.SourceRect;
drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f); drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f);
drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f); drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f);
drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.X = (int)pos.X - drawRect.Width / 2;
@@ -324,6 +353,15 @@ namespace Barotrauma
} }
} }
if (SelectedConnection != null)
{
connectionHighlightState = Math.Min(connectionHighlightState + deltaTime, 1.0f);
}
else
{
connectionHighlightState = 0.0f;
}
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
float moveSpeed = 1000.0f; float moveSpeed = 1000.0f;
@@ -336,21 +374,27 @@ namespace Barotrauma
} }
targetZoom = MathHelper.Clamp(targetZoom, generationParams.MinZoom, generationParams.MaxZoom); targetZoom = MathHelper.Clamp(targetZoom, generationParams.MinZoom, generationParams.MaxZoom);
zoom = MathHelper.Lerp(zoom, targetZoom, 0.1f); zoom = MathHelper.Lerp(zoom, targetZoom * GUI.Scale, 0.1f);
if (GUI.MouseOn == mapContainer) if (GUI.MouseOn == mapContainer)
{ {
foreach (LocationConnection connection in Connections) foreach (LocationConnection connection in Connections)
{ {
if (HighlightedLocation != CurrentDisplayLocation && if (HighlightedLocation != currentDisplayLocation &&
connection.Locations.Contains(HighlightedLocation) && connection.Locations.Contains(CurrentDisplayLocation)) connection.Locations.Contains(HighlightedLocation) &&
connection.Locations.Contains(currentDisplayLocation))
{ {
if (PlayerInput.PrimaryMouseButtonClicked() && if (PlayerInput.PrimaryMouseButtonClicked() &&
SelectedLocation != HighlightedLocation && HighlightedLocation != null) SelectedLocation != HighlightedLocation && HighlightedLocation != null)
{ {
//clients aren't allowed to select the location without a permission if (connection.Locked)
if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false)
{ {
new GUIMessageBox(string.Empty, TextManager.Get("LockedPathTooltip"));
}
//clients aren't allowed to select the location without a permission
else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign() ?? false)
{
connectionHighlightState = 0.0f;
SelectedConnection = connection; SelectedConnection = connection;
SelectedLocation = HighlightedLocation; SelectedLocation = HighlightedLocation;
@@ -371,13 +415,13 @@ namespace Barotrauma
{ {
if (PlayerInput.DoubleClicked() && HighlightedLocation != null) if (PlayerInput.DoubleClicked() && HighlightedLocation != null)
{ {
var passedConnection = CurrentDisplayLocation.Connections.Find(c => c.OtherLocation(CurrentDisplayLocation) == HighlightedLocation); var passedConnection = currentDisplayLocation.Connections.Find(c => c.OtherLocation(currentDisplayLocation) == HighlightedLocation);
if (passedConnection != null) if (passedConnection != null)
{ {
passedConnection.Passed = true; passedConnection.Passed = true;
} }
Location prevLocation = CurrentDisplayLocation; Location prevLocation = currentDisplayLocation;
CurrentLocation = HighlightedLocation; CurrentLocation = HighlightedLocation;
Level.Loaded.DebugSetStartLocation(CurrentLocation); Level.Loaded.DebugSetStartLocation(CurrentLocation);
Level.Loaded.DebugSetEndLocation(null); Level.Loaded.DebugSetEndLocation(null);
@@ -389,6 +433,7 @@ namespace Barotrauma
{ {
CurrentLocation.CreateStore(); CurrentLocation.CreateStore();
ProgressWorld(); ProgressWorld();
Radiation?.OnStep(1);
} }
else else
{ {
@@ -412,12 +457,13 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, GUICustomComponent mapContainer) public void Draw(SpriteBatch spriteBatch, GUICustomComponent mapContainer)
{ {
connectionTooltip = null; tooltip = null;
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
Rectangle rect = mapContainer.Rect; Rectangle rect = mapContainer.Rect;
Vector2 viewSize = new Vector2(rect.Width / zoom, rect.Height / zoom); Vector2 viewSize = new Vector2(rect.Width / zoom, rect.Height / zoom);
Vector2 edgeBuffer = rect.Size.ToVector2() / 2; Vector2 edgeBuffer = new Vector2(rect.Width * 0.05f);
DrawOffset.X = MathHelper.Clamp(DrawOffset.X, -Width - edgeBuffer.X + viewSize.X / 2.0f, edgeBuffer.X - viewSize.X / 2.0f); DrawOffset.X = MathHelper.Clamp(DrawOffset.X, -Width - edgeBuffer.X + viewSize.X / 2.0f, edgeBuffer.X - viewSize.X / 2.0f);
DrawOffset.Y = MathHelper.Clamp(DrawOffset.Y, -Height - edgeBuffer.Y + viewSize.Y / 2.0f, edgeBuffer.Y - viewSize.Y / 2.0f); DrawOffset.Y = MathHelper.Clamp(DrawOffset.Y, -Height - edgeBuffer.Y + viewSize.Y / 2.0f, edgeBuffer.Y - viewSize.Y / 2.0f);
@@ -481,30 +527,16 @@ namespace Barotrauma
} }
float rawNoiseScale = 1.0f + PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1)); float rawNoiseScale = 1.0f + PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1));
cameraNoiseStrength = PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1)); DrawNoise(spriteBatch, rect, rawNoiseScale);
noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(), Radiation?.Draw(spriteBatch, rect, zoom);
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color : Color.White * cameraNoiseStrength * 0.1f,
textureScale: Vector2.One * rawNoiseScale);
noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(),
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color: new Color(20,20,20,50),
textureScale: Vector2.One * rawNoiseScale * 2);
noiseOverlay.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color: Color.White * cameraNoiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale);
Pair<Rectangle, string> tooltip = null;
if (generationParams.ShowLocations) if (generationParams.ShowLocations)
{ {
foreach (LocationConnection connection in Connections) foreach (LocationConnection connection in Connections)
{ {
if (IsInFogOfWar(connection.Locations[0]) && IsInFogOfWar(connection.Locations[1])) { continue; } if (IsInFogOfWar(connection.Locations[0]) && IsInFogOfWar(connection.Locations[1])) { continue; }
DrawConnection(spriteBatch, connection, rect, viewOffset); DrawConnection(spriteBatch, connection, rect, viewOffset, currentDisplayLocation);
} }
for (int i = 0; i < Locations.Count; i++) for (int i = 0; i < Locations.Count; i++)
@@ -513,18 +545,47 @@ namespace Barotrauma
if (IsInFogOfWar(location)) { continue; } if (IsInFogOfWar(location)) { continue; }
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
Rectangle drawRect = location.Type.Sprite.SourceRect; Sprite locationSprite = location.IsCriticallyRadiated() ? location.Type.RadiationSprite ?? location.Type.Sprite : location.Type.Sprite;
Rectangle drawRect = locationSprite.SourceRect;
drawRect.X = (int)pos.X - drawRect.Width / 2; drawRect.X = (int)pos.X - drawRect.Width / 2;
drawRect.Y = (int)pos.Y - drawRect.Width / 2; drawRect.Y = (int)pos.Y - drawRect.Width / 2;
if (!rect.Intersects(drawRect)) { continue; } if (!rect.Intersects(drawRect)) { continue; }
if (location == CurrentDisplayLocation ) Color color = location.Type.SpriteColor;
if (!location.Discovered) { color = Color.White; }
if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null)
{ {
color *= 0.5f;
}
float iconScale = location == currentDisplayLocation ? 1.2f : 1.0f;
if (location == HighlightedLocation)
{
iconScale *= 1.2f;
}
locationSprite.Draw(spriteBatch, pos, color,
scale: generationParams.LocationIconSize / locationSprite.size.X * iconScale * zoom);
if (location == currentDisplayLocation)
{
if (SelectedLocation != null)
{
Vector2 dir = Vector2.Normalize(SelectedLocation.MapPosition - currLocationIndicatorPos);
GUI.Arrow.Draw(spriteBatch,
rectCenter + (currLocationIndicatorPos + viewOffset) * zoom + dir * generationParams.LocationIconSize * 0.6f * zoom,
generationParams.IndicatorColor,
GUI.Arrow.Origin,
rotate: MathUtils.VectorToAngle(dir) + MathHelper.PiOver2,
new Vector2(0.5f, 1.0f) * zoom);
}
generationParams.CurrentLocationIndicator.Draw(spriteBatch, generationParams.CurrentLocationIndicator.Draw(spriteBatch,
rectCenter + (currLocationIndicatorPos + viewOffset) * zoom, rectCenter + (currLocationIndicatorPos + viewOffset) * zoom,
generationParams.IndicatorColor, generationParams.IndicatorColor,
generationParams.CurrentLocationIndicator.Origin, 0, Vector2.One * (generationParams.LocationIconSize / generationParams.CurrentLocationIndicator.size.X) * 1.7f * zoom); generationParams.CurrentLocationIndicator.Origin, 0, Vector2.One * (generationParams.LocationIconSize / generationParams.CurrentLocationIndicator.size.X) * 0.8f * zoom);
} }
if (location == SelectedLocation) if (location == SelectedLocation)
@@ -535,31 +596,15 @@ namespace Barotrauma
generationParams.SelectedLocationIndicator.Origin, 0, Vector2.One * (generationParams.LocationIconSize / generationParams.SelectedLocationIndicator.size.X) * 1.7f * zoom); generationParams.SelectedLocationIndicator.Origin, 0, Vector2.One * (generationParams.LocationIconSize / generationParams.SelectedLocationIndicator.size.X) * 1.7f * zoom);
} }
Color color = location.Type.SpriteColor;
if (!location.Discovered) { color = Color.White; }
if (location.Connections.Find(c => c.Locations.Contains(CurrentDisplayLocation)) == null)
{
color *= 0.5f;
}
float iconScale = location == CurrentDisplayLocation ? 1.2f : 1.0f;
if (location == HighlightedLocation)
{
iconScale *= 1.2f;
}
location.Type.Sprite.Draw(spriteBatch, pos, color,
scale: generationParams.LocationIconSize / location.Type.Sprite.size.X * iconScale * zoom);
if (location.TimeSinceLastTypeChange < 1 && !string.IsNullOrEmpty(location.LastTypeChangeMessage) && generationParams.TypeChangeIcon != null) if (location.TimeSinceLastTypeChange < 1 && !string.IsNullOrEmpty(location.LastTypeChangeMessage) && generationParams.TypeChangeIcon != null)
{ {
Vector2 typeChangeIconPos = pos + new Vector2(1.35f, -0.35f) * generationParams.LocationIconSize * 0.5f * zoom; Vector2 typeChangeIconPos = pos + new Vector2(1.35f, -0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float typeChangeIconScale = 18.0f / generationParams.TypeChangeIcon.SourceRect.Width; float typeChangeIconScale = 18.0f / generationParams.TypeChangeIcon.SourceRect.Width;
generationParams.TypeChangeIcon.Draw(spriteBatch, typeChangeIconPos, GUI.Style.Red, scale: typeChangeIconScale * zoom); generationParams.TypeChangeIcon.Draw(spriteBatch, typeChangeIconPos, GUI.Style.Red, scale: typeChangeIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, typeChangeIconPos) < generationParams.TypeChangeIcon.SourceRect.Width * zoom) if (Vector2.Distance(PlayerInput.MousePosition, typeChangeIconPos) < generationParams.TypeChangeIcon.SourceRect.Width * zoom &&
(tooltip == null || IsPreferredTooltip(typeChangeIconPos)))
{ {
tooltip = new Pair<Rectangle, string>( tooltip = (new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)), location.LastTypeChangeMessage);
new Rectangle(typeChangeIconPos.ToPoint(), new Point(30)),
location.LastTypeChangeMessage);
} }
} }
if (location != CurrentLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location)) && generationParams.MissionIcon != null) if (location != CurrentLocation && CurrentLocation.AvailableMissions.Any(m => m.Locations.Contains(location)) && generationParams.MissionIcon != null)
@@ -567,16 +612,14 @@ namespace Barotrauma
Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom; Vector2 missionIconPos = pos + new Vector2(1.35f, 0.35f) * generationParams.LocationIconSize * 0.5f * zoom;
float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width; float missionIconScale = 18.0f / generationParams.MissionIcon.SourceRect.Width;
generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom); generationParams.MissionIcon.Draw(spriteBatch, missionIconPos, generationParams.IndicatorColor, scale: missionIconScale * zoom);
if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom) if (Vector2.Distance(PlayerInput.MousePosition, missionIconPos) < generationParams.MissionIcon.SourceRect.Width * zoom && IsPreferredTooltip(missionIconPos))
{ {
var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location)); var availableMissions = CurrentLocation.AvailableMissions.Where(m => m.Locations.Contains(location));
tooltip = new Pair<Rectangle, string>( tooltip = (new Rectangle(missionIconPos.ToPoint(), new Point(30)), TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name)));
new Rectangle(missionIconPos.ToPoint(), new Point(30)),
TextManager.Get("mission") + '\n'+ string.Join('\n', availableMissions.Select(m => "- " + m.Name)));
} }
} }
if (GameMain.DebugDraw && location == HighlightedLocation && (!location.Discovered || !location.Type.HasOutpost)) if (GameMain.DebugDraw && location == HighlightedLocation && (!location.Discovered || !location.HasOutpost()))
{ {
if (location.Reputation != null) if (location.Reputation != null)
{ {
@@ -603,12 +646,27 @@ namespace Barotrauma
DrawDecorativeHUD(spriteBatch, rect); DrawDecorativeHUD(spriteBatch, rect);
if (HighlightedLocation != null) bool drawRadiationTooltip = true;
if (tooltip != null)
{ {
if (tooltip.Value.tip != prevTooltip)
{
prevTooltip = tooltip.Value.tip;
tooltipRichTextData = RichTextData.GetRichTextData(tooltip.Value.tip, out sanitizedTooltip);
}
GUIComponent.DrawToolTip(spriteBatch, sanitizedTooltip, tooltip.Value.targetArea, tooltipRichTextData);
drawRadiationTooltip = false;
}
else if (HighlightedLocation != null)
{
drawRadiationTooltip = false;
Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom; Vector2 pos = rectCenter + (HighlightedLocation.MapPosition + viewOffset) * zoom;
pos.X += 50 * zoom; pos.X += 50 * zoom;
pos.X = (int)pos.X;
pos.Y = (int)pos.Y;
Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name); Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name);
Vector2 typeSize = GUI.Font.MeasureString(HighlightedLocation.Type.Name); Vector2 typeSize = string.IsNullOrEmpty(HighlightedLocation.Type.Name) ? Vector2.Zero : GUI.Font.MeasureString(HighlightedLocation.Type.Name);
Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y); Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y);
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null; bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
string repLabelText = null, repValueText = null; string repLabelText = null, repValueText = null;
@@ -617,11 +675,10 @@ namespace Barotrauma
{ {
repLabelText = TextManager.Get("reputation"); repLabelText = TextManager.Get("reputation");
repLabelSize = GUI.Font.MeasureString(repLabelText); repLabelSize = GUI.Font.MeasureString(repLabelText);
size.X = Math.Max(size.X, repLabelSize.X); repBarSize = new Vector2(GUI.IntScale(200), repLabelSize.Y);
repBarSize = new Vector2(Math.Max(0.75f * size.X, 100), repLabelSize.Y); size.Y += 2 * repLabelSize.Y + GUI.IntScale(5) + repBarSize.Y;
size.X = Math.Max(size.X, (4.0f / 3.0f) * repBarSize.X); repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
size.Y += 2 * repLabelSize.Y + 4 + repBarSize.Y; size.X = Math.Max(size.X, repBarSize.X + GUI.Font.MeasureString(repValueText).X + GUI.IntScale(10));
repValueText = ((int)HighlightedLocation.Reputation.Value).ToString();
} }
GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw( GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
spriteBatch, new Rectangle((int)(pos.X - 60 * GUI.Scale), (int)(pos.Y - size.Y), (int)(size.X + 120 * GUI.Scale), (int)(size.Y * 2.2f)), Color.Black * hudVisibility); spriteBatch, new Rectangle((int)(pos.X - 60 * GUI.Scale), (int)(pos.Y - size.Y), (int)(size.X + 120 * GUI.Scale), (int)(size.Y * 2.2f)), Color.Black * hudVisibility);
@@ -633,26 +690,51 @@ namespace Barotrauma
{ {
topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y); topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
GUI.DrawString(spriteBatch, topLeftPos, repLabelText, GUI.Style.TextColor * hudVisibility * 1.5f); GUI.DrawString(spriteBatch, topLeftPos, repLabelText, GUI.Style.TextColor * hudVisibility * 1.5f);
topLeftPos += new Vector2(0.0f, repLabelSize.Y + 4); topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10));
Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y)); Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y));
RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue); RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue);
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + 4, repBarRect.Top), repValueText, GUI.Style.TextColor); GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue));
} }
} }
if (tooltip != null)
if (drawRadiationTooltip)
{ {
GUIComponent.DrawToolTip(spriteBatch, tooltip.Second, tooltip.First); Radiation?.DrawFront(spriteBatch);
}
if (connectionTooltip != null)
{
GUIComponent.DrawToolTip(spriteBatch, connectionTooltip.Second, connectionTooltip.First);
} }
spriteBatch.End(); spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect; GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
private void DrawConnection(SpriteBatch spriteBatch, LocationConnection connection, Rectangle viewArea, Vector2 viewOffset, Color? overrideColor = null) public static void DrawNoise(SpriteBatch spriteBatch, Rectangle rect, float strength)
{
noiseOverlay ??= new Sprite("Content/UI/noise.png", Vector2.Zero);
float noiseT = (float)(Timing.TotalTime * 0.01f);
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 5.0f;
float rawNoiseScale = 1.0f + GetPerlinNoise();
noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(),
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color : Color.White * strength * 0.1f,
textureScale: Vector2.One * rawNoiseScale);
noiseOverlay.DrawTiled(spriteBatch, rect.Location.ToVector2(), rect.Size.ToVector2(),
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color: new Color(20,20,20,50),
textureScale: Vector2.One * rawNoiseScale * 2);
noiseOverlay.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
startOffset: new Vector2(Rand.Range(0.0f, noiseOverlay.SourceRect.Width), Rand.Range(0.0f, noiseOverlay.SourceRect.Height)),
color: Color.White * strength * 0.1f,
textureScale: Vector2.One * noiseScale);
}
private static float GetPerlinNoise() => PerlinNoise.GetPerlin((int)(Timing.TotalTime * 1 - 1), (int)(Timing.TotalTime * 1 - 1));
private void DrawConnection(SpriteBatch spriteBatch, LocationConnection connection, Rectangle viewArea, Vector2 viewOffset, Location currentDisplayLocation, Color? overrideColor = null)
{ {
Color connectionColor; Color connectionColor;
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
@@ -674,19 +756,22 @@ namespace Barotrauma
int width = (int)(generationParams.LocationConnectionWidth * zoom); int width = (int)(generationParams.LocationConnectionWidth * zoom);
//current level
if (Level.Loaded?.LevelData == connection.LevelData) if (Level.Loaded?.LevelData == connection.LevelData)
{ {
connectionColor = generationParams.HighlightedConnectionColor; connectionColor = generationParams.HighlightedConnectionColor;
width = (int)(width * 1.5f); width = (int)(width * 1.5f);
} }
if (SelectedLocation != CurrentDisplayLocation && //selected connection
(connection.Locations.Contains(SelectedLocation) && connection.Locations.Contains(CurrentDisplayLocation))) if (SelectedLocation != currentDisplayLocation &&
connection.Locations.Contains(SelectedLocation) && connection.Locations.Contains(currentDisplayLocation))
{ {
connectionColor = generationParams.HighlightedConnectionColor; connectionColor = generationParams.HighlightedConnectionColor;
width *= 2; width *= 2;
} }
else if (HighlightedLocation != CurrentDisplayLocation && //highlighted connection
(connection.Locations.Contains(HighlightedLocation) && connection.Locations.Contains(CurrentDisplayLocation))) else if (HighlightedLocation != currentDisplayLocation &&
connection.Locations.Contains(HighlightedLocation) && connection.Locations.Contains(currentDisplayLocation))
{ {
connectionColor = generationParams.HighlightedConnectionColor; connectionColor = generationParams.HighlightedConnectionColor;
width *= 2; width *= 2;
@@ -741,16 +826,52 @@ namespace Barotrauma
} }
float dist = Vector2.Distance(start, end); float dist = Vector2.Distance(start, end);
var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite; var connectionSprite = connection.Passed ? generationParams.PassedConnectionSprite : generationParams.ConnectionSprite;
Color segmentColor = connectionColor;
int segmentWidth = width;
if (connection == SelectedConnection)
{
float t = (i - startIndex) / (float)(endIndex - startIndex - 1);
if (currentDisplayLocation == connection.Locations[1]) { t = 1.0f - t; }
if (t > connectionHighlightState)
{
segmentWidth /= 2;
segmentColor = connection.Passed ? generationParams.ConnectionColor : generationParams.UnvisitedConnectionColor;
}
else
{
}
}
spriteBatch.Draw(connectionSprite.Texture, spriteBatch.Draw(connectionSprite.Texture,
new Rectangle((int)start.X, (int)start.Y, (int)(dist - 1 * zoom), width), new Rectangle((int)start.X, (int)start.Y, (int)(dist - 1 * zoom), segmentWidth),
connectionSprite.SourceRect, connectionColor * a, MathUtils.VectorToAngle(end - start), connectionSprite.SourceRect, segmentColor * a,
MathUtils.VectorToAngle(end - start),
new Vector2(0, connectionSprite.size.Y / 2), SpriteEffects.None, 0.01f); new Vector2(0, connectionSprite.size.Y / 2), SpriteEffects.None, 0.01f);
} }
int iconCount = 0, iconIndex = 0;
if (connectionStart.HasValue && connectionEnd.HasValue) if (connectionStart.HasValue && connectionEnd.HasValue)
{ {
GUIComponentStyle crushDepthWarningIconStyle = null; if (connection.LevelData.HasBeaconStation) { iconCount++; }
if (connection.LevelData.HasHuntingGrounds) { iconCount++; }
if (connection.Locked) { iconCount++; }
string tooltip = null; string tooltip = null;
var subCrushDepth = Submarine.MainSub?.RealWorldCrushDepth ?? Level.DefaultRealWorldCrushDepth; float subCrushDepth = Level.DefaultRealWorldCrushDepth;
var currentOrPendingSub = SubmarineSelection.CurrentOrPendingSubmarine();
if (Submarine.MainSub != null && Submarine.MainSub.Info == currentOrPendingSub)
{
subCrushDepth = Submarine.MainSub.RealWorldCrushDepth;
}
else if (currentOrPendingSub != null)
{
if (pendingSubInfo.pendingSub != currentOrPendingSub)
{
// Store the real world crush depth for the pending sub so that we don't have to calculate it again every time
pendingSubInfo = (currentOrPendingSub, currentOrPendingSub.GetRealWorldCrushDepth());
}
subCrushDepth = pendingSubInfo.realWorldCrushDepth;
}
if (GameMain.GameSession?.Campaign?.UpgradeManager != null) if (GameMain.GameSession?.Campaign?.UpgradeManager != null)
{ {
var hullUpgradePrefab = UpgradePrefab.Find("increasewallhealth"); var hullUpgradePrefab = UpgradePrefab.Find("increasewallhealth");
@@ -769,38 +890,71 @@ namespace Barotrauma
} }
} }
string crushDepthWarningIconStyle = null;
if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth) if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth)
{ {
crushDepthWarningIconStyle = GUI.Style.GetComponentStyle("CrushDepthWarningHighIcon"); iconCount++;
crushDepthWarningIconStyle = "CrushDepthWarningHighIcon";
tooltip = "crushdepthwarninghigh"; tooltip = "crushdepthwarninghigh";
} }
else if ((connection.LevelData.InitialDepth + connection.LevelData.Size.Y) * Physics.DisplayToRealWorldRatio > subCrushDepth) else if ((connection.LevelData.InitialDepth + connection.LevelData.Size.Y) * Physics.DisplayToRealWorldRatio > subCrushDepth)
{ {
crushDepthWarningIconStyle = GUI.Style.GetComponentStyle("CrushDepthWarningLowIcon"); iconCount++;
crushDepthWarningIconStyle = "CrushDepthWarningLowIcon";
tooltip = "crushdepthwarninglow"; tooltip = "crushdepthwarninglow";
} }
if (connection.LevelData.HasBeaconStation)
{
var beaconStationIconStyle = connection.LevelData.IsBeaconActive ? "BeaconStationActive" : "BeaconStationInactive";
DrawIcon(beaconStationIconStyle, (int)(28 * zoom), TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip"));
}
if (connection.Locked)
{
var gateLocation = connection.Locations[0].IsGateBetweenBiomes ? connection.Locations[0] : connection.Locations[1];
var unlockEvent =
EventSet.PrefabList.Find(ep => ep.UnlockPathEvent && ep.BiomeIdentifier == gateLocation.LevelData.Biome.Identifier) ??
EventSet.PrefabList.Find(ep => ep.UnlockPathEvent && string.IsNullOrEmpty(ep.BiomeIdentifier));
if (unlockEvent != null)
{
Reputation unlockReputation = CurrentLocation.Reputation;
Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction))
{
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier.Equals(unlockEvent.UnlockPathFaction, StringComparison.OrdinalIgnoreCase));
unlockReputation = unlockFaction?.Reputation;
}
DrawIcon(
"LockedLocationConnection", (int)(28 * zoom),
TextManager.GetWithVariables(unlockEvent.UnlockPathTooltip ?? "LockedPathTooltip",
new string[] { "[requiredreputation]", "[currentreputation]" },
new string[] { Reputation.GetFormattedReputationText(MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation), unlockEvent.UnlockPathReputation, addColorTags: true), unlockReputation.GetFormattedReputationText(addColorTags: true) }));
}
else
{
DrawIcon("LockedLocationConnection", (int)(28 * zoom), TextManager.Get("LockedPathTooltip"));
}
}
if (connection.LevelData.HasHuntingGrounds)
{
DrawIcon("HuntingGrounds", (int)(28 * zoom), TextManager.Get("HuntingGroundsTooltip"));
}
if (crushDepthWarningIconStyle != null) if (crushDepthWarningIconStyle != null)
{ {
Vector2 iconPos = (connectionStart.Value + connectionEnd.Value) / 2; DrawIcon(crushDepthWarningIconStyle, (int)(32 * zoom),
float iconSize = 32.0f * GUI.Scale; TextManager.Get(tooltip)
bool mouseOn = HighlightedLocation == null && Vector2.DistanceSquared(iconPos, PlayerInput.MousePosition) < iconSize * iconSize; .Replace("[initialdepth]", $"‖color:gui.orange‖{(int)(connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio)}‖end‖")
Sprite crushDepthWarningIcon = crushDepthWarningIconStyle.GetDefaultSprite(); .Replace("[submarinecrushdepth]", $"‖color:gui.orange‖{(int)subCrushDepth}‖end‖"));
crushDepthWarningIcon.Draw(spriteBatch, iconPos,
mouseOn ? crushDepthWarningIconStyle.HoverColor : crushDepthWarningIconStyle.Color,
scale: iconSize / crushDepthWarningIcon.size.X);
if (mouseOn)
{
connectionTooltip = new Pair<Rectangle, string>(
new Rectangle(iconPos.ToPoint(), new Point((int)iconSize)),
TextManager.Get(tooltip)
.Replace("[initialdepth]", ((int)(connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio)).ToString())
.Replace("[submarinecrushdepth]", ((int)subCrushDepth).ToString()));
}
} }
} }
if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames) if (GameMain.DebugDraw && zoom > (1.0f * GUI.Scale) && generationParams.ShowLevelTypeNames)
{ {
Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom; Vector2 center = rectCenter + (connection.CenterPos + viewOffset) * zoom;
if (viewArea.Contains(center) && connection.Biome != null) if (viewArea.Contains(center) && connection.Biome != null)
@@ -808,6 +962,30 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, center, connection.Biome.Identifier + " (" + connection.Difficulty + ")", Color.White); GUI.DrawString(spriteBatch, center, connection.Biome.Identifier + " (" + connection.Difficulty + ")", Color.White);
} }
} }
void DrawIcon(string iconStyle, int iconSize, string tooltipText)
{
Vector2 iconPos = (connectionStart.Value + connectionEnd.Value) / 2;
Vector2 iconDiff = Vector2.Normalize(connectionEnd.Value - connectionStart.Value) * iconSize;
iconPos += (iconDiff * -(iconCount - 1) / 2.0f) + iconDiff * iconIndex;
var style = GUI.Style.GetComponentStyle(iconStyle);
bool mouseOn = Vector2.DistanceSquared(iconPos, PlayerInput.MousePosition) < iconSize * iconSize && IsPreferredTooltip(iconPos);
Sprite iconSprite = style.GetDefaultSprite();
iconSprite.Draw(spriteBatch, iconPos, (mouseOn ? style.HoverColor : style.Color) * 0.7f,
scale: iconSize / iconSprite.size.X);
if (mouseOn)
{
tooltip = (new Rectangle((iconPos - Vector2.One * iconSize / 2).ToPoint(), new Point(iconSize)), tooltipText);
}
iconIndex++;
}
}
private bool IsPreferredTooltip(Vector2 tooltipPos)
{
return tooltip == null || Vector2.DistanceSquared(tooltipPos, PlayerInput.MousePosition) < Vector2.DistanceSquared(tooltip.Value.targetArea.Center.ToVector2(), PlayerInput.MousePosition);
} }
private float hudVisibility; private float hudVisibility;
@@ -828,8 +1006,8 @@ namespace Barotrauma
private void UpdateMapAnim(MapAnim anim, float deltaTime) private void UpdateMapAnim(MapAnim anim, float deltaTime)
{ {
//pause animation while there are messageboxes on screen //pause animation while there are messageboxes (other than hints) on screen
if (GUIMessageBox.MessageBoxes.Count > 0) return; if (GUIMessageBox.MessageBoxes.Count(c => !(c is GUIMessageBox mb) || mb.MessageBoxType != GUIMessageBox.Type.Hint) > 0) { return; }
if (!string.IsNullOrEmpty(anim.StartMessage)) if (!string.IsNullOrEmpty(anim.StartMessage))
{ {
@@ -838,8 +1016,9 @@ namespace Barotrauma
return; return;
} }
if (anim.StartZoom == null) { anim.StartZoom = MathUtils.InverseLerp(generationParams.MinZoom, generationParams.MaxZoom, zoom); } float unscaledZoom = zoom / GUI.Scale;
if (anim.EndZoom == null) { anim.EndZoom = MathUtils.InverseLerp(generationParams.MinZoom, generationParams.MaxZoom, zoom); } if (anim.StartZoom == null) { anim.StartZoom = MathUtils.InverseLerp(generationParams.MinZoom, generationParams.MaxZoom, unscaledZoom); }
if (anim.EndZoom == null) { anim.EndZoom = MathUtils.InverseLerp(generationParams.MinZoom, generationParams.MaxZoom, unscaledZoom); }
anim.StartPos = (anim.StartLocation == null) ? -DrawOffset : anim.StartLocation.MapPosition; anim.StartPos = (anim.StartLocation == null) ? -DrawOffset : anim.StartLocation.MapPosition;
@@ -852,7 +1031,8 @@ namespace Barotrauma
zoom = zoom =
MathHelper.Lerp(generationParams.MinZoom, generationParams.MaxZoom, MathHelper.Lerp(generationParams.MinZoom, generationParams.MaxZoom,
MathHelper.SmoothStep(anim.StartZoom.Value, anim.EndZoom.Value, t)); MathHelper.SmoothStep(anim.StartZoom.Value, anim.EndZoom.Value, t))
* GUI.Scale;
if (anim.Timer >= anim.Duration) if (anim.Timer >= anim.Duration)
{ {
@@ -0,0 +1,68 @@
#nullable enable
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal partial class Radiation
{
private static readonly string radiationTooltip = TextManager.Get("RadiationTooltip");
private static float spriteIndex;
private readonly SpriteSheet sheet = GUI.Style.RadiationAnimSpriteSheet;
private int maxFrames => sheet.FrameCount + 1;
private bool isHovingOver;
public void Draw(SpriteBatch spriteBatch, Rectangle container, float zoom)
{
if (!Enabled) { return; }
UISprite uiSprite = GUI.Style.RadiationSprite;
var (offsetX, offsetY) = Map.DrawOffset * zoom;
var (centerX, centerY) = container.Center.ToVector2();
var (halfSizeX, halfSizeY) = new Vector2(container.Width / 2f, container.Height / 2f) * zoom;
float viewBottom = centerY + Map.Height * zoom;
Vector2 topLeft = new Vector2(centerX + offsetX - halfSizeX, centerY + offsetY - halfSizeY);
Vector2 size = new Vector2((Amount - increasedAmount) * zoom + halfSizeX, viewBottom - topLeft.Y);
if (size.X < 0) { return; }
Vector2 spriteScale = new Vector2(zoom);
uiSprite.Sprite.DrawTiled(spriteBatch, topLeft, size, Params.RadiationAreaColor, Vector2.Zero, textureScale: spriteScale);
Vector2 topRight = topLeft + Vector2.UnitX * size.X;
int index = 0;
for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
{
bool isEven = ++index % 2 == 0;
Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
// every other sprite's animation is reversed to make it seem more chaotic
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex);
sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
}
isHovingOver = container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X;
}
public void DrawFront(SpriteBatch spriteBatch)
{
if (isHovingOver)
{
GUIComponent.DrawToolTip(spriteBatch, radiationTooltip, PlayerInput.MousePosition + new Vector2(18 * GUI.Scale));
}
}
public void MapUpdate(float deltaTime)
{
float spriteStep = Params.BorderAnimationSpeed * deltaTime;
spriteIndex = (spriteIndex + spriteStep) % maxFrames;
if (increasedAmount > 0)
{
increasedAmount -= (lastIncrease / Params.AnimationSpeed) * deltaTime;
}
}
}
}
@@ -20,9 +20,6 @@ namespace Barotrauma
public static Vector2 StartMovingPos => startMovingPos; public static Vector2 StartMovingPos => startMovingPos;
// Quick undo/redo for size and movement only. TODO: Remove if we do a more general implementation.
private Memento<Rectangle> rectMemento;
public event Action<Rectangle> Resized; public event Action<Rectangle> Resized;
private static bool resizing; private static bool resizing;
@@ -65,8 +62,6 @@ namespace Barotrauma
} }
} }
//protected bool isSelected;
private static bool disableSelect; private static bool disableSelect;
public static bool DisableSelect public static bool DisableSelect
{ {
@@ -805,7 +800,7 @@ namespace Barotrauma
} }
if (selectionPos != null && selectionPos != Vector2.Zero) if (selectionPos != null && selectionPos != Vector2.Zero)
{ {
GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed, false, 0, (int)Math.Max(1.5f / GameScreen.Selected.Cam.Zoom, 1.0f)); GUI.DrawRectangle(spriteBatch, new Vector2(selectionPos.X, -selectionPos.Y), selectionSize, Color.DarkRed, false, 0, 2f / GameScreen.Selected.Cam.Zoom);
} }
} }
@@ -24,7 +24,7 @@ namespace Barotrauma
{ {
get get
{ {
if (!GameMain.SubEditorScreen.ShowThalamus && prefab.Category.HasFlag(MapEntityCategory.Thalamus)) if (GameMain.SubEditorScreen.IsSubcategoryHidden(prefab.Subcategory))
{ {
return false; return false;
} }
@@ -32,12 +32,15 @@ namespace Barotrauma
} }
} }
private string specialTag; #if DEBUG
[Editable, Serialize("", true)] [Editable, Serialize("", true)]
#else
[Serialize("", true)]
#endif
public string SpecialTag public string SpecialTag
{ {
get { return specialTag; } get;
set { specialTag = value; } set;
} }
partial void InitProjSpecific() partial void InitProjSpecific()
@@ -92,7 +95,10 @@ namespace Barotrauma
{ {
int heightScaled = (int)(20 * GUI.Scale); int heightScaled = (int)(20 * GUI.Scale);
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null); GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
{
CanTakeKeyBoardFocus = false
};
var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont) { UserData = this }; var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont) { UserData = this };
if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule) if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule)
@@ -79,14 +79,22 @@ namespace Barotrauma
if (maxDamageStructure != null) if (maxDamageStructure != null)
{ {
SoundPlayer.PlayDamageSound( PlayDamageSound(impactSimPos, impact, soundTag, maxDamageStructure);
soundTag,
impact * 10.0f,
ConvertUnits.ToDisplayUnits(impactSimPos),
MathHelper.Lerp(2000.0f, 10000.0f, (impact - MinCollisionImpact) / 2.0f),
maxDamageStructure.Tags);
} }
} }
private void PlayDamageSound(Vector2 impactSimPos, float impact, string soundTag, Structure hitStructure = null)
{
if (impact < MinCollisionImpact) { return; }
SoundPlayer.PlayDamageSound(
soundTag,
impact * 10.0f,
ConvertUnits.ToDisplayUnits(impactSimPos),
MathHelper.Lerp(2000.0f, 10000.0f, (impact - MinCollisionImpact) / 2.0f),
hitStructure?.Tags);
}
} }
} }
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -19,7 +20,7 @@ namespace Barotrauma
{ {
var texture = TextureLoader.FromStream(mem, path: FilePath, compress: false); var texture = TextureLoader.FromStream(mem, path: FilePath, compress: false);
if (texture == null) { throw new Exception("PreviewImage texture returned null"); } if (texture == null) { throw new Exception("PreviewImage texture returned null"); }
PreviewImage = new Sprite(texture, null, null); PreviewImage = new Sprite(texture, sourceRectangle: null, newOffset: null, path: FilePath);
} }
} }
catch (Exception e) catch (Exception e)
@@ -32,21 +33,49 @@ namespace Barotrauma
} }
} }
public void CreatePreviewWindow(GUIComponent parent) public void CreatePreviewWindow(GUIComponent parent)
{ {
var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null); var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null);
if (PreviewImage == null) var previewButton = new GUIButton(new RectTransform(new Vector2(1f, 0.5f), content.RectTransform), style: null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded")); CanBeFocused = SubmarineElement != null,
OnClicked = (btn, obj) => { SubmarinePreview.Create(this); return false; },
};
var previewImage = PreviewImage ?? savedSubmarines.Find(s => s.Name.Equals(Name, StringComparison.OrdinalIgnoreCase))?.PreviewImage;
if (previewImage == null)
{
new GUITextBlock(new RectTransform(Vector2.One, previewButton.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded"));
} }
else else
{ {
var submarinePreviewBackground = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), style: null) { Color = Color.Black }; var submarinePreviewBackground = new GUIFrame(new RectTransform(Vector2.One, previewButton.RectTransform), style: null)
new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), PreviewImage, scaleToFit: true); {
new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black); Color = Color.Black,
HoverColor = Color.Black,
SelectedColor = Color.Black,
PressedColor = Color.Black,
CanBeFocused = false,
};
new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), previewImage, scaleToFit: true) { CanBeFocused = false };
new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black) { CanBeFocused = false };
} }
if (SubmarineElement != null)
{
new GUIFrame(new RectTransform(Vector2.One * 0.12f, previewButton.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight)
{
AbsoluteOffset = new Point((int)(0.03f * previewButton.Rect.Height))
},
"ExpandButton", Color.White)
{
Color = Color.White,
HoverColor = Color.White,
PressedColor = Color.White
};
}
var descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter)) var descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter))
{ {
UserData = "descriptionbox", UserData = "descriptionbox",
@@ -56,39 +85,31 @@ namespace Barotrauma
ScalableFont font = parent.Rect.Width < 350 ? GUI.SmallFont : GUI.Font; ScalableFont font = parent.Rect.Width < 350 ? GUI.SmallFont : GUI.Font;
CreateSpecsWindow(descriptionBox, font); CreateSpecsWindow(descriptionBox, font, includeDescription: true);
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null);
if (!string.IsNullOrEmpty(Description))
{
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true)
{ CanBeFocused = false, ForceUpperCase = true };
}
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), Description, font: font, wrap: true)
{
CanBeFocused = false
};
} }
public void CreateSpecsWindow(GUIListBox parent, ScalableFont font) public void CreateSpecsWindow(GUIListBox parent, ScalableFont font, bool includeTitle = true, bool includeClass = true, bool includeDescription = false)
{ {
float leftPanelWidth = 0.6f; float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth; float rightPanelWidth = 0.4f;
string className = !HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{SubmarineClass}") : TextManager.Get("shuttle"); string className = !HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{SubmarineClass}") : TextManager.Get("shuttle");
int nameHeight = (int)GUI.LargeFont.MeasureString(DisplayName, true).Y;
int classHeight = (int)GUI.SubHeadingFont.MeasureString(className).Y; int classHeight = (int)GUI.SubHeadingFont.MeasureString(className).Y;
int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth); int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth);
var submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUI.LargeFont) { CanBeFocused = false }; GUITextBlock submarineNameText = null;
submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y); GUITextBlock submarineClassText = null;
var submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) { CanBeFocused = false }; if (includeTitle)
submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y); {
int nameHeight = (int)GUI.LargeFont.MeasureString(DisplayName, true).Y;
submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUI.LargeFont) { CanBeFocused = false };
submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y);
}
if (includeClass)
{
submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) { CanBeFocused = false };
submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y);
}
Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio; Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio;
if (realWorldDimensions != Vector2.Zero) if (realWorldDimensions != Vector2.Zero)
{ {
@@ -149,8 +170,30 @@ namespace Barotrauma
versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height); versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height);
} }
submarineNameText.AutoScaleHorizontal = true; if (submarineNameText != null)
GUITextBlock.AutoScaleAndNormalize(parent.Content.Children.Where(c => c is GUITextBlock && c != submarineNameText).Cast<GUITextBlock>()); {
submarineNameText.AutoScaleHorizontal = true;
}
GUITextBlock descBlock = null;
if (includeDescription)
{
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), parent.Content.RectTransform), style: null);
if (!string.IsNullOrEmpty(Description))
{
var wsItemDesc = new GUITextBlock(new RectTransform(new Vector2(1, 0), parent.Content.RectTransform),
TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true)
{ CanBeFocused = false, ForceUpperCase = true };
descBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), parent.Content.RectTransform), Description, font: font, wrap: true)
{
CanBeFocused = false
};
}
}
GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock));
} }
} }
} }
@@ -0,0 +1,648 @@
using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
class SubmarinePreview : IDisposable
{
private SpriteRecorder spriteRecorder;
private SubmarineInfo submarineInfo;
private Camera camera;
private Task loadTask;
private volatile bool isDisposed;
private GUIFrame previewFrame;
private class HullCollection
{
public readonly List<Rectangle> Rects;
public readonly string Name;
public HullCollection(string identifier)
{
Rects = new List<Rectangle>();
Name = TextManager.Get(identifier, returnNull: true) ?? identifier;
}
public void AddRect(XElement element)
{
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
rect.Y = -rect.Y;
Rects.Add(rect);
}
}
private struct Door
{
public readonly Rectangle Rect;
public Door(Rectangle rect)
{
rect.Y = -rect.Y;
Rect = rect;
}
}
private Dictionary<string,HullCollection> hullCollections;
private List<Door> doors;
private static SubmarinePreview instance = null;
public static void Create(SubmarineInfo submarineInfo)
{
Close();
instance = new SubmarinePreview(submarineInfo);
}
public static void Close()
{
instance?.Dispose();
}
private SubmarinePreview(SubmarineInfo subInfo)
{
camera = new Camera();
submarineInfo = subInfo;
spriteRecorder = new SpriteRecorder();
isDisposed = false;
loadTask = null;
hullCollections = new Dictionary<string, HullCollection>();
doors = new List<Door>();
previewFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, previewFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
new GUIButton(new RectTransform(Vector2.One, previewFrame.RectTransform), "", style: null)
{
OnClicked = (btn, obj) => { Dispose(); return false; }
};
var innerFrame = new GUIFrame(new RectTransform(Vector2.One * 0.9f, previewFrame.RectTransform, Anchor.Center));
int innerPadding = GUI.IntScale(100f);
var innerPadded = new GUIFrame(new RectTransform(new Point(innerFrame.Rect.Width - innerPadding, innerFrame.Rect.Height - innerPadding), previewFrame.RectTransform, Anchor.Center), style: null)
{
OutlineColor = Color.Black,
OutlineThickness = 2
};
GUITextBlock titleText = null;
GUIListBox specsContainer = null;
new GUICustomComponent(new RectTransform(Vector2.One, innerPadded.RectTransform, Anchor.Center),
(spriteBatch, component) => {
camera.UpdateTransform(interpolate: true, updateListener: false);
Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2);
RenderSubmarine(spriteBatch, drawRect);
},
(deltaTime, component) => {
bool isMouseOnComponent = GUI.MouseOn == component;
camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent, followSub: false);
if (isMouseOnComponent &&
(PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld()))
{
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
moveSpeed.X = -moveSpeed.X;
camera.Position += moveSpeed;
}
if (titleText != null && specsContainer != null)
{
specsContainer.Visible = GUI.IsMouseOn(titleText);
}
});
var topContainer = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), innerPadded.RectTransform, Anchor.TopLeft), style: null)
{
Color = Color.Black * 0.65f
};
var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 5f / 7f), topContainer.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft);
titleText = new GUITextBlock(new RectTransform(new Vector2(0.95f, 1f), topLayout.RectTransform), subInfo.DisplayName, font: GUI.LargeFont);
new GUIButton(new RectTransform(new Vector2(0.05f, 1f), topLayout.RectTransform), TextManager.Get("Close"))
{
OnClicked = (btn, obj) => { Dispose(); return false; }
};
specsContainer = new GUIListBox(new RectTransform(new Vector2(0.4f, 1f), innerPadded.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.015f, 0.07f) })
{
Color = Color.Black * 0.65f,
ScrollBarEnabled = false,
ScrollBarVisible = false,
Spacing = 5
};
subInfo.CreateSpecsWindow(specsContainer, GUI.Font, includeTitle: false, includeDescription: true);
int width = specsContainer.Rect.Width;
void recalculateSpecsContainerHeight()
{
int totalSize = 0;
var children = specsContainer.Content.Children.Where(c => c.Visible);
foreach (GUIComponent child in children)
{
totalSize += child.Rect.Height;
}
totalSize += specsContainer.Content.CountChildren * specsContainer.Spacing;
if (specsContainer.PadBottom)
{
GUIComponent last = specsContainer.Content.Children.LastOrDefault();
if (last != null)
{
totalSize += specsContainer.Rect.Height - last.Rect.Height;
}
}
specsContainer.RectTransform.Resize(new Point(width, totalSize), true);
specsContainer.RecalculateChildren();
}
//hell
recalculateSpecsContainerHeight();
specsContainer.Content.GetAllChildren<GUITextBlock>().ForEach(c =>
{
var firstChild = c.Children.FirstOrDefault() as GUITextBlock;
if (firstChild != null)
{
firstChild.CalculateHeightFromText(); firstChild.SetTextPos();
c.RectTransform.MinSize = new Point(0, firstChild.Rect.Height);
}
c.CalculateHeightFromText(); c.SetTextPos();
});
recalculateSpecsContainerHeight();
GeneratePreviewMeshes();
}
public static void AddToGUIUpdateList()
{
instance?.previewFrame?.AddToGUIUpdateList();
}
public Task GeneratePreviewMeshes()
{
if (loadTask != null) { throw new InvalidOperationException("Tried to start SubmarinePreview loadTask more than once!"); }
loadTask = Task.Run(GeneratePreviewMeshesInternal);
return loadTask;
}
private async Task GeneratePreviewMeshesInternal()
{
await Task.Yield();
spriteRecorder.Begin(SpriteSortMode.BackToFront);
HashSet<int> toIgnore = new HashSet<int>();
foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{
switch (subElement.Name.LocalName.ToLowerInvariant())
{
case "item":
foreach (var component in subElement.Elements())
{
switch (component.Name.LocalName.ToLowerInvariant())
{
case "itemcontainer":
ExtractItemContainerIds(component, toIgnore);
break;
case "connectionpanel":
ExtractConnectionPanelLinks(component, toIgnore);
break;
}
}
break;
}
if (isDisposed) { return; }
await Task.Yield();
}
foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{
switch (subElement.Name.LocalName.ToLowerInvariant())
{
case "item":
if (!toIgnore.Contains(subElement.GetAttributeInt("ID", 0)))
{
BakeMapEntity(subElement);
}
break;
case "structure":
BakeMapEntity(subElement);
break;
case "hull":
string identifier = subElement.GetAttributeString("roomname", "").ToLowerInvariant();
if (!string.IsNullOrEmpty(identifier))
{
HullCollection hullCollection = null;
if (!hullCollections.TryGetValue(identifier, out hullCollection))
{
hullCollection = new HullCollection(identifier);
hullCollections.Add(identifier, hullCollection);
}
hullCollection.AddRect(subElement);
}
break;
}
if (isDisposed) { return; }
await Task.Yield();
}
spriteRecorder.End();
camera.Position = (spriteRecorder.Min + spriteRecorder.Max) * 0.5f;
float scaledSpan = (spriteRecorder.Max - spriteRecorder.Min).X / camera.Resolution.X;
camera.Zoom = 0.8f / scaledSpan;
camera.StopMovement();
}
private void ExtractItemContainerIds(XElement component, HashSet<int> ids)
{
string containedString = component.GetAttributeString("contained", "");
string[] itemIdStrings = containedString.Split(',');
for (int i = 0; i < itemIdStrings.Length; i++)
{
foreach (string idStr in itemIdStrings[i].Split(';'))
{
if (!int.TryParse(idStr, NumberStyles.Any, CultureInfo.InvariantCulture, out int id)) { continue; }
if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
}
}
}
private void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids)
{
var pins = component.Elements("input").Concat(component.Elements("output"));
foreach (var pin in pins)
{
var links = pin.Elements("link");
foreach (var link in links)
{
int id = link.GetAttributeInt("w", 0);
if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
}
}
}
private void BakeMapEntity(XElement element)
{
string identifier = element.GetAttributeString("identifier", "");
if (string.IsNullOrEmpty(identifier)) { return; }
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
if (rect.Equals(Rectangle.Empty)) { return; }
float depth = element.GetAttributeFloat("spritedepth", 1f);
bool flippedX = element.GetAttributeBool("flippedx", false);
bool flippedY = element.GetAttributeBool("flippedy", false);
float scale = element.GetAttributeFloat("scale", 1f);
Color color = element.GetAttributeColor("spritecolor", Color.White);
float rotation = element.GetAttributeFloat("rotation", 0f);
MapEntityPrefab prefab = MapEntityPrefab.List.FirstOrDefault(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (prefab == null) { return; }
var texture = prefab.sprite.Texture;
var srcRect = prefab.sprite.SourceRect;
SpriteEffects spriteEffects = SpriteEffects.None;
if (flippedX)
{
spriteEffects |= SpriteEffects.FlipHorizontally;
}
if (flippedY)
{
spriteEffects |= SpriteEffects.FlipVertically;
}
var prevEffects = prefab.sprite.effects;
prefab.sprite.effects ^= spriteEffects;
bool overrideSprite = false;
ItemPrefab itemPrefab = prefab as ItemPrefab;
StructurePrefab structurePrefab = prefab as StructurePrefab;
if (itemPrefab != null)
{
BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite);
}
if (!overrideSprite)
{
if (structurePrefab != null)
{
ParseUpgrades(structurePrefab.ConfigElement, ref scale);
if (!prefab.ResizeVertical)
{
rect.Height = (int)(rect.Height * scale / prefab.Scale);
}
if (!prefab.ResizeHorizontal)
{
rect.Width = (int)(rect.Width * scale / prefab.Scale);
}
var textureScale = element.GetAttributeVector2("texturescale", Vector2.One);
Vector2 backGroundOffset = Vector2.Zero;
Vector2 textureOffset = element.GetAttributeVector2("textureoffset", Vector2.Zero);
if (flippedX) { textureOffset.X = -textureOffset.X; }
if (flippedY) { textureOffset.Y = -textureOffset.Y; }
backGroundOffset = new Vector2(
MathUtils.PositiveModulo((int)-textureOffset.X, prefab.sprite.SourceRect.Width),
MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.sprite.SourceRect.Height));
prefab.sprite.DrawTiled(
spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f),
rect.Size.ToVector2(),
color: color,
startOffset: backGroundOffset,
textureScale: textureScale * scale,
depth: depth);
}
else if (itemPrefab != null)
{
bool usePrefabValues = element.GetAttributeBool("isoverride", false) != itemPrefab.IsOverride;
if (usePrefabValues)
{
scale = itemPrefab.ConfigElement.GetAttributeFloat(scale, "scale", "Scale");
}
ParseUpgrades(itemPrefab.ConfigElement, ref scale);
if (prefab.ResizeVertical || prefab.ResizeHorizontal)
{
if (!prefab.ResizeHorizontal)
{
rect.Width = (int)(prefab.sprite.size.X * scale);
}
if (!prefab.ResizeVertical)
{
rect.Height = (int)(prefab.sprite.size.Y * scale);
}
var spritePos = rect.Center.ToVector2();
//spritePos.Y = rect.Height - spritePos.Y;
prefab.sprite.DrawTiled(
spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f),
rect.Size.ToVector2(),
color: color,
textureScale: Vector2.One * scale,
depth: depth);
foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
{
float offsetState = 0f;
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteRecorder,
new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)),
rect.Size.ToVector2(), color: color,
textureScale: Vector2.One * scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.sprite.Depth), 0.999f));
}
}
else
{
rect.Width = (int)(rect.Width * scale / prefab.Scale);
rect.Height = (int)(rect.Height * scale / prefab.Scale);
var spritePos = rect.Center.ToVector2();
spritePos.Y -= rect.Height;
//spritePos.Y = rect.Height - spritePos.Y;
prefab.sprite.Draw(
spriteRecorder,
spritePos * new Vector2(1f, -1f),
color,
prefab.sprite.Origin,
rotation,
scale,
prefab.sprite.effects, depth);
foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
{
float rotationState = 0f; float offsetState = 0f;
float rot = decorativeSprite.GetRotation(ref rotationState, 0f);
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.sprite.Depth), 0.999f));
}
}
}
}
prefab.sprite.effects = prevEffects;
}
private void BakeItemComponents(
ItemPrefab prefab,
Rectangle rect, Color color,
float scale, float rotation, float depth,
out bool overrideSprite)
{
overrideSprite = false;
foreach (var subElement in prefab.ConfigElement.Elements())
{
switch (subElement.Name.LocalName.ToLowerInvariant())
{
case "turret":
Sprite barrelSprite = null;
Sprite railSprite = null;
foreach (XElement turretSubElem in subElement.Elements())
{
switch (turretSubElem.Name.ToString().ToLowerInvariant())
{
case "barrelsprite":
barrelSprite = new Sprite(turretSubElem);
break;
case "railsprite":
railSprite = new Sprite(turretSubElem);
break;
}
}
var transformedBarrelPos = MathUtils.RotatePointAroundTarget(
subElement.GetAttributeVector2("barrelpos", Vector2.Zero) * scale,
new Vector2(rect.Width / 2, rect.Height / 2),
MathHelper.ToRadians(rotation));
Vector2 drawPos = new Vector2(rect.X + transformedBarrelPos.X, rect.Y - transformedBarrelPos.Y);
drawPos.Y = -drawPos.Y;
railSprite?.Draw(spriteRecorder,
drawPos,
color,
rotation + MathHelper.PiOver2, scale,
SpriteEffects.None, depth + (railSprite.Depth - prefab.sprite.Depth));
barrelSprite?.Draw(spriteRecorder,
drawPos - new Vector2((float)Math.Cos(MathHelper.ToRadians(rotation)), (float)Math.Sin(MathHelper.ToRadians(rotation))) * scale,
color,
rotation + MathHelper.PiOver2, scale,
SpriteEffects.None, depth + (barrelSprite.Depth - prefab.sprite.Depth));
break;
case "door":
doors.Add(new Door(rect));
var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase));
if (doorSpriteElem != null)
{
string texturePath = doorSpriteElem.GetAttributeString("texture", "");
Vector2 pos = rect.Location.ToVector2() * new Vector2(1f, -1f);
if (subElement.GetAttributeBool("horizontal", false))
{
pos.Y += (float)rect.Height * 0.5f;
}
else
{
pos.X += (float)rect.Width * 0.5f;
}
Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath));
spriteRecorder.Draw(doorSprite.Texture, pos,
new Rectangle((int)doorSprite.SourceRect.X,
(int)doorSprite.SourceRect.Y,
(int)doorSprite.size.X, (int)doorSprite.size.Y),
color, 0.0f, doorSprite.Origin, new Vector2(scale), SpriteEffects.None, doorSprite.Depth);
}
break;
case "ladder":
var backgroundSprElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("backgroundsprite", StringComparison.OrdinalIgnoreCase));
if (backgroundSprElem != null)
{
Sprite backgroundSprite = new Sprite(backgroundSprElem);
backgroundSprite.DrawTiled(spriteRecorder,
new Vector2(rect.Left, -rect.Top) - backgroundSprite.Origin * scale,
new Vector2(backgroundSprite.size.X * scale, rect.Height), color: color,
textureScale: Vector2.One * scale,
depth: depth + 0.1f);
}
break;
}
}
}
public void ParseUpgrades(XElement prefabConfigElement, ref float scale)
{
foreach (var upgrade in prefabConfigElement.Elements("Upgrade"))
{
var upgradeVersion = new Version(upgrade.GetAttributeString("gameversion", "0.0.0.0"));
if (upgradeVersion >= submarineInfo.GameVersion)
{
string scaleModifier = upgrade.GetAttributeString("scale", "*1");
if (scaleModifier.StartsWith("*"))
{
if (float.TryParse(scaleModifier.Substring(1), NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedScale))
{
scale *= parsedScale;
}
}
else
{
if (float.TryParse(scaleModifier, NumberStyles.Any, CultureInfo.InvariantCulture, out float parsedScale))
{
scale = parsedScale;
}
}
}
}
}
private void RenderSubmarine(SpriteBatch spriteBatch, Rectangle scissorRectangle)
{
if (spriteRecorder == null) { return; }
GUI.DrawRectangle(spriteBatch, scissorRectangle, new Color(0.051f, 0.149f, 0.271f, 1.0f), isFilled: true);
if (!spriteRecorder.ReadyToRender)
{
string waitText = !loadTask.IsCompleted ?
"Generating preview..." :
(loadTask.Exception?.ToString() ?? "Task completed without marking as ready to render");
Vector2 origin = (GUI.Font.MeasureString(waitText) * 0.5f);
origin.X = MathF.Round(origin.X);
origin.Y = MathF.Round(origin.Y);
GUI.Font.DrawString(
spriteBatch,
waitText,
scissorRectangle.Center.ToVector2(),
Color.White,
0f,
origin,
1f,
SpriteEffects.None,
0f);
return;
}
spriteBatch.End();
var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
spriteRecorder.Render(camera);
var mousePos = camera.ScreenToWorld(PlayerInput.MousePosition);
mousePos.Y = -mousePos.Y;
spriteBatch.Begin(SpriteSortMode.BackToFront, rasterizerState: GameMain.ScissorTestEnable, transformMatrix: camera.Transform);
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
foreach (var hullCollection in hullCollections.Values)
{
bool mouseOver = false;
foreach (var rect in hullCollection.Rects)
{
mouseOver = rect.Contains(mousePos);
if (mouseOver) { break; }
}
foreach (var rect in hullCollection.Rects)
{
GUI.DrawRectangle(spriteBatch, rect, mouseOver ? Color.Red : Color.Blue, depth: mouseOver ? 0.45f : 0.5f, thickness: (mouseOver ? 4f : 2f) / camera.Zoom);
}
if (mouseOver)
{
string str = hullCollection.Name;
Vector2 strSize = GUI.Font.MeasureString(str) / camera.Zoom;
Vector2 padding = new Vector2(30, 30) / camera.Zoom;
Vector2 shift = new Vector2(10, 0) / camera.Zoom;
GUI.DrawRectangle(spriteBatch, mousePos + shift, strSize + padding, Color.Black, isFilled: true, depth: 0.25f);
GUI.Font.DrawString(spriteBatch, str, mousePos + shift + (strSize + padding) * 0.5f, Color.White, 0f, strSize * camera.Zoom * 0.5f, 1f / camera.Zoom, SpriteEffects.None, 0f);
}
}
foreach (var door in doors)
{
GUI.DrawRectangle(spriteBatch, door.Rect, GUI.Style.Green * 0.5f, isFilled: true, depth: 0.4f);
}
spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred);
}
public void Dispose()
{
previewFrame = null;
spriteRecorder?.Dispose();
isDisposed = true;
}
}
}
@@ -222,18 +222,20 @@ namespace Barotrauma
private bool ChangeSpawnType(GUIButton button, object obj) private bool ChangeSpawnType(GUIButton button, object obj)
{ {
GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock; GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock;
spawnType += (int)button.UserData; var values = (SpawnType[])Enum.GetValues(typeof(SpawnType));
var values = Enum.GetValues(typeof(SpawnType)); int currIndex = values.IndexOf(spawnType);
currIndex += (int)button.UserData;
int firstIndex = 1; int firstIndex = 1;
int lastIndex = values.Length - 1; int lastIndex = values.Length - 1;
if ((int)spawnType > lastIndex) if (currIndex > lastIndex)
{ {
spawnType = (SpawnType)firstIndex; currIndex = firstIndex;
} }
if ((int)spawnType < firstIndex) if (currIndex < firstIndex)
{ {
spawnType = (SpawnType)values.GetValue(lastIndex); currIndex = lastIndex;
} }
spawnType = values[currIndex];
spawnTypeText.Text = spawnType.ToString(); spawnTypeText.Text = spawnType.ToString();
return true; return true;
} }
@@ -3,12 +3,13 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
partial class BannedPlayer partial class BannedPlayer
{ {
public BannedPlayer(string name, UInt16 uniqueIdentifier, bool isRangeBan, string endPoint, ulong steamID) public BannedPlayer(string name, UInt16 uniqueIdentifier, bool isRangeBan, string endPoint, ulong steamID, string reason, DateTime? expiration)
{ {
this.Name = name; this.Name = name;
this.EndPoint = endPoint; this.EndPoint = endPoint;
@@ -16,6 +17,8 @@ namespace Barotrauma.Networking
ParseEndPointAsSteamId(); ParseEndPointAsSteamId();
this.IsRangeBan = isRangeBan; this.IsRangeBan = isRangeBan;
this.UniqueIdentifier = uniqueIdentifier; this.UniqueIdentifier = uniqueIdentifier;
this.Reason = reason;
this.ExpirationTime = expiration;
} }
} }
@@ -152,11 +155,23 @@ namespace Barotrauma.Networking
bannedPlayers.Clear(); bannedPlayers.Clear();
UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32(); UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
for (int i = 0; i < (int)bannedPlayerCount; i++) for (int i = 0; i < (int)bannedPlayerCount; i++)
{ {
string name = incMsg.ReadString(); string name = incMsg.ReadString();
UInt16 uniqueIdentifier = incMsg.ReadUInt16(); UInt16 uniqueIdentifier = incMsg.ReadUInt16();
bool isRangeBan = incMsg.ReadBoolean(); incMsg.ReadPadBits(); bool isRangeBan = incMsg.ReadBoolean();
bool includesExpiration = incMsg.ReadBoolean();
incMsg.ReadPadBits();
DateTime? expiration = null;
if (includesExpiration)
{
double hoursFromNow = incMsg.ReadDouble();
expiration = DateTime.Now + TimeSpan.FromHours(hoursFromNow);
}
string reason = incMsg.ReadString();
string endPoint = ""; string endPoint = "";
UInt64 steamID = 0; UInt64 steamID = 0;
@@ -170,7 +185,7 @@ namespace Barotrauma.Networking
endPoint = "Endpoint concealed by host"; endPoint = "Endpoint concealed by host";
steamID = 0; steamID = 0;
} }
bannedPlayers.Add(new BannedPlayer(name, uniqueIdentifier, isRangeBan, endPoint, steamID)); bannedPlayers.Add(new BannedPlayer(name, uniqueIdentifier, isRangeBan, endPoint, steamID, reason, expiration));
} }
if (banFrame != null) if (banFrame != null)
@@ -15,7 +15,7 @@ namespace Barotrauma.Networking
public static void ClientRead(IReadMessage msg) public static void ClientRead(IReadMessage msg)
{ {
UInt16 ID = msg.ReadUInt16(); UInt16 id = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte(); ChatMessageType type = (ChatMessageType)msg.ReadByte();
PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None; PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None;
string txt = ""; string txt = "";
@@ -29,6 +29,14 @@ namespace Barotrauma.Networking
string senderName = msg.ReadString(); string senderName = msg.ReadString();
Character senderCharacter = null; Character senderCharacter = null;
Client senderClient = null;
bool hasSenderClient = msg.ReadBoolean();
if (hasSenderClient)
{
UInt64 clientId = msg.ReadUInt64();
senderClient = GameMain.Client.ConnectedClients.Find(c => c.SteamID == clientId || c.ID == clientId);
if (senderClient != null) { senderName = senderClient.Name; }
}
bool hasSenderCharacter = msg.ReadBoolean(); bool hasSenderCharacter = msg.ReadBoolean();
if (hasSenderCharacter) if (hasSenderCharacter)
{ {
@@ -38,6 +46,7 @@ namespace Barotrauma.Networking
senderName = senderCharacter.Name; senderName = senderCharacter.Name;
} }
} }
msg.ReadPadBits();
switch (type) switch (type)
{ {
@@ -48,7 +57,54 @@ namespace Barotrauma.Networking
UInt16 targetCharacterID = msg.ReadUInt16(); UInt16 targetCharacterID = msg.ReadUInt16();
Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character; Character targetCharacter = Entity.FindEntityByID(targetCharacterID) as Character;
Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16()); Entity targetEntity = Entity.FindEntityByID(msg.ReadUInt16());
int optionIndex = msg.ReadByte();
Order orderPrefab = null;
int? optionIndex = null;
string orderOption = null;
// The option of a Dismiss order is written differently so we know what order we target
// now that the game supports multiple current orders simultaneously
if (orderIndex >= 0 && orderIndex < Order.PrefabList.Count)
{
orderPrefab = Order.PrefabList[orderIndex];
if (orderPrefab.Identifier != "dismissed")
{
optionIndex = msg.ReadByte();
}
// Does the dismiss order have a specified target?
else if (msg.ReadBoolean())
{
int identifierCount = msg.ReadByte();
if (identifierCount > 0)
{
int dismissedOrderIndex = msg.ReadByte();
Order dismissedOrderPrefab = null;
if (dismissedOrderIndex >= 0 && dismissedOrderIndex < Order.PrefabList.Count)
{
dismissedOrderPrefab = Order.PrefabList[dismissedOrderIndex];
orderOption = dismissedOrderPrefab.Identifier;
}
if (identifierCount > 1)
{
int dismissedOrderOptionIndex = msg.ReadByte();
if (dismissedOrderPrefab != null)
{
var options = dismissedOrderPrefab.Options;
if (options != null && dismissedOrderOptionIndex >= 0 && dismissedOrderOptionIndex < options.Length)
{
orderOption += $".{options[dismissedOrderOptionIndex]}";
}
}
}
}
}
}
else
{
optionIndex = msg.ReadByte();
}
int orderPriority = msg.ReadByte();
OrderTarget orderTargetPosition = null; OrderTarget orderTargetPosition = null;
Order.OrderTargetType orderTargetType = (Order.OrderTargetType)msg.ReadByte(); Order.OrderTargetType orderTargetType = (Order.OrderTargetType)msg.ReadByte();
int wallSectionIndex = 0; int wallSectionIndex = 0;
@@ -64,22 +120,18 @@ namespace Barotrauma.Networking
wallSectionIndex = msg.ReadByte(); wallSectionIndex = msg.ReadByte();
} }
Order orderPrefab;
if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count) if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count)
{ {
DebugConsole.ThrowError("Invalid order message - order index out of bounds."); DebugConsole.ThrowError("Invalid order message - order index out of bounds.");
if (NetIdUtils.IdMoreRecent(ID, LastID)) { LastID = ID; } if (NetIdUtils.IdMoreRecent(id, LastID)) { LastID = id; }
return; return;
} }
else else
{ {
orderPrefab = Order.PrefabList[orderIndex]; orderPrefab ??= Order.PrefabList[orderIndex];
}
string orderOption = "";
if (optionIndex >= 0 && optionIndex < orderPrefab.Options.Length)
{
orderOption = orderPrefab.Options[optionIndex];
} }
orderOption ??= optionIndex.HasValue && optionIndex >= 0 && optionIndex < orderPrefab.Options.Length ? orderPrefab.Options[optionIndex.Value] : "";
txt = orderPrefab.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption); txt = orderPrefab.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
@@ -107,16 +159,16 @@ namespace Barotrauma.Networking
} }
else if (targetCharacter != null) else if (targetCharacter != null)
{ {
targetCharacter.SetOrder(order, orderOption, senderCharacter); targetCharacter.SetOrder(order, orderOption, orderPriority, senderCharacter);
} }
} }
} }
if (NetIdUtils.IdMoreRecent(ID, LastID)) if (NetIdUtils.IdMoreRecent(id, LastID))
{ {
GameMain.Client.AddChatMessage( GameMain.Client.AddChatMessage(
new OrderChatMessage(orderPrefab, orderOption, txt, orderTargetPosition ?? targetEntity as ISpatialEntity, targetCharacter, senderCharacter)); new OrderChatMessage(orderPrefab, orderOption, orderPriority, txt, orderTargetPosition ?? targetEntity as ISpatialEntity, targetCharacter, senderCharacter));
LastID = ID; LastID = id;
} }
return; return;
case ChatMessageType.ServerMessageBox: case ChatMessageType.ServerMessageBox:
@@ -128,7 +180,7 @@ namespace Barotrauma.Networking
break; break;
} }
if (NetIdUtils.IdMoreRecent(ID, LastID)) if (NetIdUtils.IdMoreRecent(id, LastID))
{ {
switch (type) switch (type)
{ {
@@ -154,10 +206,10 @@ namespace Barotrauma.Networking
GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType); GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType);
break; break;
default: default:
GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter, changeType); GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType);
break; break;
} }
LastID = ID; LastID = id;
} }
} }
} }
@@ -114,7 +114,7 @@ namespace Barotrauma.Networking
return; return;
} }
Permissions = permissions; Permissions = permissions;
PermittedConsoleCommands = new List<DebugConsole.Command>(permittedConsoleCommands); PermittedConsoleCommands.Clear(); PermittedConsoleCommands.AddRange(permittedConsoleCommands);
} }
public void GivePermission(ClientPermissions permission) public void GivePermission(ClientPermissions permission)
@@ -56,6 +56,11 @@ namespace Barotrauma.Networking
public readonly NetStats NetStats; public readonly NetStats NetStats;
protected GUITickBox cameraFollowsSub; protected GUITickBox cameraFollowsSub;
public GUITickBox FollowSubTickBox => cameraFollowsSub;
public bool IsFollowSubTickBoxVisible =>
gameStarted && Screen.Selected == GameMain.GameScreen &&
cameraFollowsSub != null && cameraFollowsSub.Visible;
public CameraTransition EndCinematic; public CameraTransition EndCinematic;
@@ -159,6 +164,12 @@ namespace Barotrauma.Networking
get { return entityEventManager; } get { return entityEventManager; }
} }
public bool? WaitForNextRoundRespawn
{
get;
set;
}
private readonly object serverEndpoint; private readonly object serverEndpoint;
private readonly int ownerKey; private readonly int ownerKey;
private readonly bool steamP2POwner; private readonly bool steamP2POwner;
@@ -185,10 +196,10 @@ namespace Barotrauma.Networking
CanBeFocused = false CanBeFocused = false
}; };
cameraFollowsSub = new GUITickBox(new RectTransform(new Vector2(0.05f, 0.05f), inGameHUD.RectTransform, anchor: Anchor.TopCenter) cameraFollowsSub = new GUITickBox(new RectTransform(new Vector2(0.05f, 0.05f), inGameHUD.RectTransform, anchor: Anchor.TopCenter, pivot: Pivot.CenterLeft)
{ {
AbsoluteOffset = new Point(0, 5), AbsoluteOffset = new Point(0, HUDLayoutSettings.ButtonAreaTop.Y + HUDLayoutSettings.ButtonAreaTop.Height / 2),
MaxSize = new Point(25, 25) MaxSize = new Point(GUI.IntScale(25))
}, TextManager.Get("CamFollowSubmarine")) }, TextManager.Get("CamFollowSubmarine"))
{ {
Selected = Camera.FollowSub, Selected = Camera.FollowSub,
@@ -857,12 +868,25 @@ namespace Barotrauma.Networking
string endMessage = string.Empty; string endMessage = string.Empty;
endMessage = inc.ReadString(); endMessage = inc.ReadString();
bool missionSuccessful = inc.ReadBoolean(); byte missionCount = inc.ReadByte();
for (int i = 0; i < missionCount; i++)
{
bool missionSuccessful = inc.ReadBoolean();
var mission = GameMain.GameSession?.GetMission(i);
if (mission != null)
{
mission.Completed = missionSuccessful;
}
}
CharacterTeamType winningTeam = (CharacterTeamType)inc.ReadByte(); CharacterTeamType winningTeam = (CharacterTeamType)inc.ReadByte();
if (missionSuccessful && GameMain.GameSession?.Mission != null) if (winningTeam != CharacterTeamType.None)
{ {
GameMain.GameSession.WinningTeam = winningTeam; GameMain.GameSession.WinningTeam = winningTeam;
GameMain.GameSession.Mission.Completed = true; var combatMission = GameMain.GameSession.Missions.FirstOrDefault(m => m is CombatMission);
if (combatMission != null)
{
combatMission.Completed = true;
}
} }
byte traitorCount = inc.ReadByte(); byte traitorCount = inc.ReadByte();
@@ -928,7 +952,11 @@ namespace Barotrauma.Networking
ReadTraitorMessage(inc); ReadTraitorMessage(inc);
break; break;
case ServerPacketHeader.MISSION: case ServerPacketHeader.MISSION:
GameMain.GameSession?.Mission?.ClientRead(inc); {
int missionIndex = inc.ReadByte();
Mission mission = GameMain.GameSession?.GetMission(missionIndex);
mission?.ClientRead(inc);
}
break; break;
case ServerPacketHeader.EVENTACTION: case ServerPacketHeader.EVENTACTION:
GameMain.GameSession?.EventManager.ClientRead(inc); GameMain.GameSession?.EventManager.ClientRead(inc);
@@ -959,17 +987,26 @@ namespace Barotrauma.Networking
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
string missionIdentifier = inc.ReadString() ?? ""; byte missionCount = inc.ReadByte();
if (missionIdentifier != (GameMain.GameSession.Mission?.Prefab.Identifier ?? "")) if (missionCount != GameMain.GameSession.Missions.Count())
{ {
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {missionIdentifier ?? "null"}, client: {GameMain.GameSession.Mission?.Prefab.Identifier ?? ""})"; string errorMsg = $"Mission equality check failed. Mission count doesn't match the server (server: {missionCount}, client: {GameMain.GameSession.Missions.Count()})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
foreach (Mission mission in GameMain.GameSession.Missions)
{
string missionIdentifier = inc.ReadString() ?? "";
if (missionIdentifier != mission.Prefab.Identifier)
{
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {missionIdentifier ?? "null"}, client: {mission.Prefab.Identifier})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg);
}
}
byte equalityCheckValueCount = inc.ReadByte(); byte equalityCheckValueCount = inc.ReadByte();
List<int> levelEqualityCheckValues = new List<int>(); List<int> levelEqualityCheckValues = new List<int>();
for (int i = 0; i<equalityCheckValueCount; i++) for (int i = 0; i < equalityCheckValueCount; i++)
{ {
levelEqualityCheckValues.Add(inc.ReadInt32()); levelEqualityCheckValues.Add(inc.ReadInt32());
} }
@@ -1004,7 +1041,10 @@ namespace Barotrauma.Networking
} }
} }
GameMain.GameSession.Mission?.ClientReadInitial(inc); foreach (Mission mission in GameMain.GameSession.Missions)
{
mission.ClientReadInitial(inc);
}
roundInitStatus = RoundInitStatus.Started; roundInitStatus = RoundInitStatus.Started;
} }
@@ -1395,6 +1435,8 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false; EndVoteTickBox.Selected = false;
WaitForNextRoundRespawn = null;
roundInitStatus = RoundInitStatus.Starting; roundInitStatus = RoundInitStatus.Starting;
int seed = inc.ReadInt32(); int seed = inc.ReadInt32();
@@ -1411,6 +1453,7 @@ namespace Barotrauma.Networking
bool respawnAllowed = inc.ReadBoolean(); bool respawnAllowed = inc.ReadBoolean();
serverSettings.AllowDisguises = inc.ReadBoolean(); serverSettings.AllowDisguises = inc.ReadBoolean();
serverSettings.AllowRewiring = inc.ReadBoolean(); serverSettings.AllowRewiring = inc.ReadBoolean();
serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean();
GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte(); GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
@@ -1432,7 +1475,12 @@ namespace Barotrauma.Networking
string subHash = inc.ReadString(); string subHash = inc.ReadString();
string shuttleName = inc.ReadString(); string shuttleName = inc.ReadString();
string shuttleHash = inc.ReadString(); string shuttleHash = inc.ReadString();
int missionIndex = inc.ReadInt16(); List<int> missionIndices = new List<int>();
int missionCount = inc.ReadByte();
for (int i = 0; i < missionCount; i++)
{
missionIndices.Add(inc.ReadInt16());
}
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList)) if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList))
{ {
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
@@ -1486,7 +1534,9 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, gameMode, missionPrefab: missionIndex < 0 ? null : MissionPrefab.List[missionIndex]); var selectedMissions = missionIndices.Select(i => MissionPrefab.List[i]);
GameMain.GameSession = new GameSession(GameMain.NetLobbyScreen.SelectedSub, gameMode, missionPrefabs: selectedMissions);
GameMain.GameSession.StartRound(levelSeed, levelDifficulty); GameMain.GameSession.StartRound(levelSeed, levelDifficulty);
} }
else else
@@ -1653,13 +1703,15 @@ namespace Barotrauma.Networking
} }
foreach (Submarine sub in Submarine.MainSubs[i].DockedTo) foreach (Submarine sub in Submarine.MainSubs[i].DockedTo)
{ {
if (sub.Info.Type == SubmarineType.Outpost) { continue; }
sub.TeamID = teamID; sub.TeamID = teamID;
} }
} }
if (respawnAllowed) if (respawnAllowed)
{ {
respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle && gameMode != GameModePreset.MultiPlayerCampaign ? GameMain.NetLobbyScreen.SelectedShuttle : null); bool isOutpost = GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && Level.Loaded?.Type == LevelData.LevelType.Outpost;
respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle && !isOutpost ? GameMain.NetLobbyScreen.SelectedShuttle : null);
} }
gameStarted = true; gameStarted = true;
@@ -1702,6 +1754,7 @@ namespace Barotrauma.Networking
gameStarted = false; gameStarted = false;
Character.Controlled = null; Character.Controlled = null;
WaitForNextRoundRespawn = null;
SpawnAsTraitor = false; SpawnAsTraitor = false;
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
@@ -1712,7 +1765,7 @@ namespace Barotrauma.Networking
// Enable characters near the main sub for the endCinematic // Enable characters near the main sub for the endCinematic
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < NetConfig.EnableCharacterDistSqr) if (Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, c.WorldPosition) < MathUtils.Pow2(c.Params.DisableDistance))
{ {
c.Enabled = true; c.Enabled = true;
} }
@@ -1757,8 +1810,10 @@ namespace Barotrauma.Networking
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash);
if (matchingSub == null) if (matchingSub == null)
{ {
matchingSub = new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false); matchingSub = new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false)
matchingSub.SubmarineClass = (SubmarineClass)subClass; {
SubmarineClass = (SubmarineClass)subClass
};
} }
matchingSub.RequiredContentPackagesInstalled = requiredContentPackagesInstalled; matchingSub.RequiredContentPackagesInstalled = requiredContentPackagesInstalled;
ServerSubmarines.Add(matchingSub); ServerSubmarines.Add(matchingSub);
@@ -1928,14 +1983,19 @@ namespace Barotrauma.Networking
} }
foreach (Client client in ConnectedClients) foreach (Client client in ConnectedClients)
{ {
if (!previouslyConnectedClients.Any(c => c.ID == client.ID)) int index = previouslyConnectedClients.FindIndex(c => c.ID == client.ID);
if (index < 0)
{ {
while (previouslyConnectedClients.Count > 100) if (previouslyConnectedClients.Count > 100)
{ {
previouslyConnectedClients.RemoveAt(0); previouslyConnectedClients.RemoveRange(0, previouslyConnectedClients.Count - 100);
} }
previouslyConnectedClients.Add(client);
} }
else
{
previouslyConnectedClients.RemoveAt(index);
}
previouslyConnectedClients.Add(client);
} }
if (updateClientListId) { LastClientListUpdateID = listId; } if (updateClientListId) { LastClientListUpdateID = listId; }
@@ -2028,6 +2088,8 @@ namespace Barotrauma.Networking
bool autoRestartEnabled = inc.ReadBoolean(); bool autoRestartEnabled = inc.ReadBoolean();
float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f;
bool radiationEnabled = inc.ReadBoolean();
//ignore the message if we already a more up-to-date one //ignore the message if we already a more up-to-date one
//or if we're still waiting for the initial update //or if we're still waiting for the initial update
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) && if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) &&
@@ -2038,8 +2100,14 @@ namespace Barotrauma.Networking
serverSettings.ClientRead(settingsBuf); serverSettings.ClientRead(settingsBuf);
if (!IsServerOwner) if (!IsServerOwner)
{ {
ServerInfo info = GameMain.ServerListScreen.UpdateServerInfoWithServerSettings(serverEndpoint, serverSettings); ServerInfo info = serverSettings.GetServerListInfo();
GameMain.ServerListScreen.AddToRecentServers(info); GameMain.ServerListScreen.AddToRecentServers(info);
GameMain.NetLobbyScreen.Favorite.Visible = true;
GameMain.NetLobbyScreen.Favorite.Selected = GameMain.ServerListScreen.IsFavorite(info);
}
else
{
GameMain.NetLobbyScreen.Favorite.Visible = false;
} }
GameMain.NetLobbyScreen.LastUpdateID = updateID; GameMain.NetLobbyScreen.LastUpdateID = updateID;
@@ -2083,8 +2151,9 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
GameMain.NetLobbyScreen.LevelSeed = levelSeed; GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetBotCount(botCount); GameMain.NetLobbyScreen.SetRadiationEnabled(radiationEnabled);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount);
GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer);
serverSettings.VoiceChatEnabled = voiceChatEnabled; serverSettings.VoiceChatEnabled = voiceChatEnabled;
@@ -2325,7 +2394,7 @@ namespace Barotrauma.Networking
if (outmsg.LengthBytes > MsgConstants.MTU) if (outmsg.LengthBytes > MsgConstants.MTU)
{ {
DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU); DebugConsole.ThrowError($"Maximum packet size exceeded ({outmsg.LengthBytes} > {MsgConstants.MTU})");
} }
clientPeer.Send(outmsg, DeliveryMethod.Unreliable); clientPeer.Send(outmsg, DeliveryMethod.Unreliable);
@@ -2376,7 +2445,7 @@ namespace Barotrauma.Networking
if (outmsg.LengthBytes > MsgConstants.MTU) if (outmsg.LengthBytes > MsgConstants.MTU)
{ {
DebugConsole.ThrowError("Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU); DebugConsole.ThrowError($"Maximum packet size exceeded ({outmsg.LengthBytes} > {MsgConstants.MTU})");
} }
clientPeer.Send(outmsg, DeliveryMethod.Unreliable); clientPeer.Send(outmsg, DeliveryMethod.Unreliable);
@@ -2406,6 +2475,15 @@ namespace Barotrauma.Networking
chatMsgQueue.Add(chatMessage); chatMsgQueue.Add(chatMessage);
} }
public void SendRespawnPromptResponse(bool waitForNextRoundRespawn)
{
WaitForNextRoundRespawn = waitForNextRoundRespawn;
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.READY_TO_SPAWN);
msg.Write((bool)waitForNextRoundRespawn);
clientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void RequestFile(FileTransferType fileType, string file, string fileHash) public void RequestFile(FileTransferType fileType, string file, string fileHash)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
@@ -2528,7 +2606,7 @@ namespace Barotrauma.Networking
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID) if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.CampaignID != campaignID)
{ {
string savePath = transfer.FilePath; string savePath = transfer.FilePath;
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign); GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Unsure);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode; campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -2876,7 +2954,7 @@ namespace Barotrauma.Networking
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
public void SetupNewCampaign(SubmarineInfo sub, string saveName, string mapSeed) public void SetupNewCampaign(SubmarineInfo sub, string saveName, string mapSeed, CampaignSettings settings)
{ {
GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false; GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false;
GameMain.NetLobbyScreen.CampaignFrame.Visible = false; GameMain.NetLobbyScreen.CampaignFrame.Visible = false;
@@ -2891,6 +2969,7 @@ namespace Barotrauma.Networking
msg.Write(mapSeed); msg.Write(mapSeed);
msg.Write(sub.Name); msg.Write(sub.Name);
msg.Write(sub.MD5Hash.Hash); msg.Write(sub.MD5Hash.Hash);
settings.Serialize(msg);
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
@@ -3096,11 +3175,11 @@ namespace Barotrauma.Networking
cameraFollowsSub.Visible = Character.Controlled == null; cameraFollowsSub.Visible = Character.Controlled == null;
} }
if (Character.Controlled == null || Character.Controlled.IsDead) /*if (Character.Controlled == null || Character.Controlled.IsDead)
{ {
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
} }*/
} }
//tab doesn't autoselect the chatbox when debug console is open, //tab doesn't autoselect the chatbox when debug console is open,
@@ -3208,9 +3287,13 @@ namespace Barotrauma.Networking
if (respawnManager != null) if (respawnManager != null)
{ {
string respawnText = ""; string respawnText = string.Empty;
float textScale = 1.0f;
Color textColor = Color.White; Color textColor = Color.White;
bool canChooseRespawn =
GameMain.GameSession.GameMode is CampaignMode &&
Character.Controlled == null &&
Level.Loaded?.Type != LevelData.LevelType.Outpost &&
(characterInfo == null || HasSpawned);
if (respawnManager.CurrentState == RespawnManager.State.Waiting && if (respawnManager.CurrentState == RespawnManager.State.Waiting &&
respawnManager.RespawnCountdownStarted) respawnManager.RespawnCountdownStarted)
{ {
@@ -3228,18 +3311,18 @@ namespace Barotrauma.Networking
{ {
//oscillate between 0-1 //oscillate between 0-1
float phase = (float)(Math.Sin(timeLeft * MathHelper.Pi) + 1.0f) * 0.5f; float phase = (float)(Math.Sin(timeLeft * MathHelper.Pi) + 1.0f) * 0.5f;
textScale = 1.0f + phase * 0.5f; //textScale = 1.0f + phase * 0.5f;
textColor = Color.Lerp(GUI.Style.Red, Color.White, 1.0f - phase); textColor = Color.Lerp(GUI.Style.Red, Color.White, 1.0f - phase);
} }
canChooseRespawn = false;
} }
if (!string.IsNullOrEmpty(respawnText)) GameMain.GameSession?.SetRespawnInfo(
{ visible: !string.IsNullOrEmpty(respawnText) || canChooseRespawn, text: respawnText, textColor: textColor,
GUI.SmallFont.DrawString(spriteBatch, respawnText, new Vector2(120.0f, 10), textColor, 0.0f, Vector2.Zero, textScale, Microsoft.Xna.Framework.Graphics.SpriteEffects.None, 0.0f); buttonsVisible: canChooseRespawn, waitForNextRoundRespawn: (WaitForNextRoundRespawn ?? true));
}
} }
if (!ShowNetStats) return; if (!ShowNetStats) { return; }
NetStats.Draw(spriteBatch, new Rectangle(300, 10, 300, 150)); NetStats.Draw(spriteBatch, new Rectangle(300, 10, 300, 150));
@@ -3367,9 +3450,12 @@ namespace Barotrauma.Networking
{ {
var banReasonPrompt = new GUIMessageBox( var banReasonPrompt = new GUIMessageBox(
TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"), TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"),
"", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, new Vector2(0.25f, 0.22f), new Point(400, 220)); "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, new Vector2(0.25f, 0.25f), new Point(400, 260));
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center)); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center))
{
AbsoluteSpacing = GUI.IntScale(5)
};
var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform))
{ {
Wrap = true, Wrap = true,
@@ -3381,9 +3467,8 @@ namespace Barotrauma.Networking
if (ban) if (ban)
{ {
var labelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), content.RectTransform), isHorizontal: false); var labelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), content.RectTransform), isHorizontal: false);
new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), labelContainer.RectTransform), TextManager.Get("BanDuration")) { Padding = Vector4.Zero }; new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), labelContainer.RectTransform), TextManager.Get("BanDuration"), font: GUI.SubHeadingFont) { Padding = Vector4.Zero };
var buttonContent = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), labelContainer.RectTransform), isHorizontal: true); var buttonContent = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), labelContainer.RectTransform), isHorizontal: true);
permaBanTickBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 0.15f), buttonContent.RectTransform), TextManager.Get("BanPermanent")) permaBanTickBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 0.15f), buttonContent.RectTransform), TextManager.Get("BanPermanent"))
{ {
@@ -3494,12 +3579,19 @@ namespace Barotrauma.Networking
errorLines.Add("Campaign ID: " + campaign.CampaignID); errorLines.Add("Campaign ID: " + campaign.CampaignID);
errorLines.Add("Campaign save ID: " + campaign.LastSaveID + "(pending: " + campaign.PendingSaveID + ")"); errorLines.Add("Campaign save ID: " + campaign.LastSaveID + "(pending: " + campaign.PendingSaveID + ")");
} }
errorLines.Add("Mission: " + (GameMain.GameSession?.Mission?.Prefab.Identifier ?? "none")); foreach (Mission mission in GameMain.GameSession.Missions)
{
errorLines.Add("Mission: " + mission.Prefab.Identifier);
}
} }
if (GameMain.GameSession?.Submarine != null) if (GameMain.GameSession?.Submarine != null)
{ {
errorLines.Add("Submarine: " + GameMain.GameSession.Submarine.Info.Name); errorLines.Add("Submarine: " + GameMain.GameSession.Submarine.Info.Name);
} }
if (GameMain.NetworkMember?.RespawnManager?.RespawnShuttle != null)
{
errorLines.Add("Respawn shuttle: " + GameMain.NetworkMember.RespawnManager.RespawnShuttle.Info.Name);
}
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X")))); errorLines.Add("Level: " + Level.Loaded.Seed + ", " + string.Join(", ", Level.Loaded.EqualityCheckValues.Select(cv => cv.ToString("X"))));
@@ -1,6 +1,4 @@
using System; namespace Barotrauma.Networking
namespace Barotrauma.Networking
{ {
partial class OrderChatMessage : ChatMessage partial class OrderChatMessage : ChatMessage
{ {
@@ -9,26 +7,7 @@ namespace Barotrauma.Networking
msg.Write((byte)ClientNetObject.CHAT_MESSAGE); msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID); msg.Write(NetStateID);
msg.Write((byte)ChatMessageType.Order); msg.Write((byte)ChatMessageType.Order);
msg.Write((byte)Order.PrefabList.IndexOf(Order.Prefab)); WriteOrder(msg);
msg.Write(TargetCharacter == null ? (UInt16)0 : TargetCharacter.ID);
msg.Write(TargetEntity is Entity ? (TargetEntity as Entity).ID : (UInt16)0);
msg.Write((byte)Array.IndexOf(Order.Prefab.Options, OrderOption));
msg.Write((byte)Order.TargetType);
if (Order.TargetType == Order.OrderTargetType.Position && TargetEntity is OrderTarget orderTarget)
{
msg.Write(true);
msg.Write(orderTarget.Position.X);
msg.Write(orderTarget.Position.Y);
msg.Write(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
}
else
{
msg.Write(false);
if (Order.TargetType == Order.OrderTargetType.WallSection)
{
msg.Write((byte)(WallSectionIndex ?? Order.WallSectionIndex ?? 0));
}
}
} }
} }
} }
@@ -147,13 +147,36 @@ namespace Barotrauma.Networking
if (missingPackages.Count > 0) if (missingPackages.Count > 0)
{ {
var nonDownloadable = missingPackages.Where(p => p.WorkshopId == 0); var nonDownloadable = missingPackages.Where(p => p.WorkshopId == 0);
var mismatchedButDownloaded = missingPackages.Where(p =>
{
var localMatching = ContentPackage.RegularPackages.Find(l => l.SteamWorkshopId != 0 && p.WorkshopId == l.SteamWorkshopId);
localMatching ??= ContentPackage.CorePackages.Find(l => l.SteamWorkshopId != 0 && p.WorkshopId == l.SteamWorkshopId);
if (nonDownloadable.Any()) return localMatching != null;
});
if (mismatchedButDownloaded.Any())
{
string disconnectMsg;
if (mismatchedButDownloaded.Count() == 1)
{
disconnectMsg = $"DisconnectMessage.MismatchedWorkshopMod~[incompatiblecontentpackage]={GetPackageStr(mismatchedButDownloaded.First())}";
}
else
{
List<string> packageStrs = new List<string>();
mismatchedButDownloaded.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
disconnectMsg = $"DisconnectMessage.MismatchedWorkshopMods~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}";
}
Close(disconnectMsg, disableReconnect: true);
OnDisconnectMessageReceived?.Invoke(DisconnectReason.MissingContentPackage + "/" + disconnectMsg);
}
else if (nonDownloadable.Any())
{ {
string disconnectMsg; string disconnectMsg;
if (nonDownloadable.Count() == 1) if (nonDownloadable.Count() == 1)
{ {
disconnectMsg = $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}"; disconnectMsg = $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(nonDownloadable.First())}";
} }
else else
{ {
@@ -56,6 +56,7 @@ namespace Barotrauma.Networking
Steamworks.SteamNetworking.AllowP2PPacketRelay(true); Steamworks.SteamNetworking.AllowP2PPacketRelay(true);
ServerConnection = new SteamP2PConnection("Server", hostSteamId); ServerConnection = new SteamP2PConnection("Server", hostSteamId);
ServerConnection.SetOwnerSteamIDIfUnknown(hostSteamId);
incomingInitializationMessages = new List<IReadMessage>(); incomingInitializationMessages = new List<IReadMessage>();
incomingDataMessages = new List<IReadMessage>(); incomingDataMessages = new List<IReadMessage>();
@@ -17,6 +17,7 @@ namespace Barotrauma.Networking
class RemotePeer class RemotePeer
{ {
public UInt64 SteamID; public UInt64 SteamID;
public UInt64 OwnerSteamID;
public double? DisconnectTime; public double? DisconnectTime;
public bool Authenticating; public bool Authenticating;
public bool Authenticated; public bool Authenticated;
@@ -31,6 +32,7 @@ namespace Barotrauma.Networking
public RemotePeer(UInt64 steamId) public RemotePeer(UInt64 steamId)
{ {
SteamID = steamId; SteamID = steamId;
OwnerSteamID = 0;
DisconnectTime = null; DisconnectTime = null;
Authenticating = false; Authenticating = false;
Authenticated = false; Authenticated = false;
@@ -90,10 +92,19 @@ namespace Barotrauma.Networking
if (status == Steamworks.AuthResponse.OK) if (status == Steamworks.AuthResponse.OK)
{ {
remotePeer.OwnerSteamID = ownerID;
remotePeer.Authenticated = true; remotePeer.Authenticated = true;
remotePeer.Authenticating = false; remotePeer.Authenticating = false;
foreach (var msg in remotePeer.UnauthedMessages) foreach (var msg in remotePeer.UnauthedMessages)
{ {
//rewrite the owner id before
//forwarding the messages to
//the server, since it's only
//known now
int prevBitPosition = msg.Message.BitPosition;
msg.Message.BitPosition = sizeof(ulong) * 8;
msg.Message.Write(ownerID);
msg.Message.BitPosition = prevBitPosition;
byte[] msgToSend = (byte[])msg.Message.Buffer.Clone(); byte[] msgToSend = (byte[])msg.Message.Buffer.Clone();
Array.Resize(ref msgToSend, msg.Message.LengthBytes); Array.Resize(ref msgToSend, msg.Message.LengthBytes);
ChildServerRelay.Write(msgToSend); ChildServerRelay.Write(msgToSend);
@@ -131,6 +142,7 @@ namespace Barotrauma.Networking
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(steamId); outMsg.Write(steamId);
outMsg.Write(remotePeer.OwnerSteamID);
outMsg.Write(data, 1, dataLength - 1); outMsg.Write(data, 1, dataLength - 1);
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
@@ -142,34 +154,27 @@ namespace Barotrauma.Networking
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0; bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0; bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!remotePeer.Authenticated) if (!remotePeer.Authenticated & !remotePeer.Authenticating && isConnectionInitializationStep)
{ {
if (!remotePeer.Authenticating) remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{ {
if (isConnectionInitializationStep) remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{ {
remotePeer.DisconnectTime = null; DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
//Console.WriteLine("received init step from "+steamId.ToString()+" ("+initializationStep.ToString()+")");
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
}
} }
} }
} }
@@ -336,6 +341,7 @@ namespace Barotrauma.Networking
{ {
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(selfSteamID); outMsg.Write(selfSteamID);
outMsg.Write(selfSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep)); outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
outMsg.Write(Name); outMsg.Write(Name);
@@ -428,6 +434,7 @@ namespace Barotrauma.Networking
byte[] msgData = new byte[msg.LengthBytes]; byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length); msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
msgToSend.Write(selfSteamID); msgToSend.Write(selfSteamID);
msgToSend.Write(selfSteamID);
msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None)); msgToSend.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
msgToSend.Write((UInt16)length); msgToSend.Write((UInt16)length);
msgToSend.Write(msgData, 0, length); msgToSend.Write(msgData, 0, length);
@@ -159,6 +159,20 @@ namespace Barotrauma.Networking
color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader"); color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader");
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint(); playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
playStyleName.RectTransform.IsFixedSize = true; playStyleName.RectTransform.IsFixedSize = true;
var serverTypeContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.2f), playStyleBanner.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft),
"MainMenuNotifBackground", Color.Black)
{
CanBeFocused = false,
};
var serverType = new GUITextBlock(new RectTransform(Vector2.One, serverTypeContainer.RectTransform, Anchor.CenterLeft),
TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.CenterLeft);
}
else
{
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), previewContainer.RectTransform, Anchor.CenterLeft),
TextManager.Get((OwnerID != 0 || LobbyID != 0) ? "SteamP2PServer" : "DedicatedServer"), textAlignment: Alignment.CenterLeft);
} }
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.RectTransform)) var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.RectTransform))
@@ -553,5 +567,10 @@ namespace Barotrauma.Networking
(other.LobbyID == LobbyID || other.LobbyID == 0 || LobbyID == 0) && (other.LobbyID == LobbyID || other.LobbyID == 0 || LobbyID == 0) &&
((OwnerID == 0) ? (other.IP == IP && other.Port == Port) : true); ((OwnerID == 0) ? (other.IP == IP && other.Port == Port) : true);
} }
public bool MatchesByEndpoint(ServerInfo other)
{
return OwnerID == other.OwnerID && (OwnerID != 0 ? true : (IP == other.IP && Port == other.Port));
}
} }
} }
@@ -242,16 +242,7 @@ namespace Barotrauma.Networking
textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea()
{ {
Data = data, Data = data,
OnClick = (component, area) => OnClick = GameMain.NetLobbyScreen.SelectPlayer
{
if (!UInt64.TryParse(area.Data.Metadata, out UInt64 id)) { return; }
Client client = GameMain.Client.ConnectedClients.Find(c => c.SteamID == id)
?? GameMain.Client.ConnectedClients.Find(c => c.ID == id)
?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.SteamID == id)
?? GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.ID == id);
if (client == null) { return; }
GameMain.NetLobbyScreen.SelectPlayer(client);
}
}); });
} }
} }
@@ -126,11 +126,15 @@ namespace Barotrauma.Networking
public void ClientRead(IReadMessage incMsg) public void ClientRead(IReadMessage incMsg)
{ {
cachedServerListInfo = null;
ServerName = incMsg.ReadString(); ServerName = incMsg.ReadString();
ServerMessageText = incMsg.ReadString(); ServerMessageText = incMsg.ReadString();
MaxPlayers = incMsg.ReadByte(); MaxPlayers = incMsg.ReadByte();
HasPassword = incMsg.ReadBoolean(); HasPassword = incMsg.ReadBoolean();
IsPublic = incMsg.ReadBoolean(); IsPublic = incMsg.ReadBoolean();
GameMain.NetLobbyScreen.SetPublic(IsPublic);
AllowFileTransfers = incMsg.ReadBoolean();
incMsg.ReadPadBits(); incMsg.ReadPadBits();
TickRate = incMsg.ReadRangedInteger(1, 60); TickRate = incMsg.ReadRangedInteger(1, 60);
GameMain.NetworkMember.TickRate = TickRate; GameMain.NetworkMember.TickRate = TickRate;
@@ -147,7 +151,7 @@ namespace Barotrauma.Networking
} }
} }
public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? useRespawnShuttle = null) public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? radiationEnabled = null, bool? useRespawnShuttle = null)
{ {
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return; if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
@@ -212,6 +216,7 @@ namespace Barotrauma.Networking
outMsg.Write(autoRestart != null); outMsg.Write(autoRestart != null);
outMsg.Write(autoRestart ?? false); outMsg.Write(autoRestart ?? false);
outMsg.Write(radiationEnabled ?? RadiationEnabled);
outMsg.WritePadBits(); outMsg.WritePadBits();
} }
@@ -500,8 +505,8 @@ namespace Barotrauma.Networking
CreateLabeledSlider(roundsTab, "ServerSettingsRespawnInterval", out slider, out sliderLabel); CreateLabeledSlider(roundsTab, "ServerSettingsRespawnInterval", out slider, out sliderLabel);
string intervalLabel = sliderLabel.Text; string intervalLabel = sliderLabel.Text;
slider.Step = 0.05f;
slider.Range = new Vector2(10.0f, 600.0f); slider.Range = new Vector2(10.0f, 600.0f);
slider.StepValue = 10.0f;
GetPropertyData("RespawnInterval").AssignGUIComponent(slider); GetPropertyData("RespawnInterval").AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{ {
@@ -646,7 +651,14 @@ namespace Barotrauma.Networking
foreach (ItemPrefab ip in ItemPrefab.Prefabs) foreach (ItemPrefab ip in ItemPrefab.Prefabs)
{ {
if (!ip.CanBeBought && !ip.Tags.Contains("smallitem")) continue; 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), cargoFrame.Content.RectTransform) { MinSize = new Point(0, 30) }, isHorizontal: true) var itemFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), cargoFrame.Content.RectTransform) { MinSize = new Point(0, 30) }, isHorizontal: true)
{ {
@@ -725,6 +737,10 @@ namespace Barotrauma.Networking
TextManager.Get("ServerSettingsDestructibleOutposts")); TextManager.Get("ServerSettingsDestructibleOutposts"));
GetPropertyData("DestructibleOutposts").AssignGUIComponent(destructibleOutposts); GetPropertyData("DestructibleOutposts").AssignGUIComponent(destructibleOutposts);
var lockAllDefaultWires = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsLockAllDefaultWires"));
GetPropertyData("LockAllDefaultWires").AssignGUIComponent(lockAllDefaultWires);
var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowRewiring")); TextManager.Get("ServerSettingsAllowRewiring"));
GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring); GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring);
@@ -915,6 +931,7 @@ namespace Barotrauma.Networking
public bool ToggleSettingsFrame(GUIButton button, object obj) public bool ToggleSettingsFrame(GUIButton button, object obj)
{ {
if (GameMain.NetworkMember == null) { return false; }
if (settingsFrame == null) if (settingsFrame == null)
{ {
CreateSettingsFrame(); CreateSettingsFrame();
@@ -936,5 +953,12 @@ namespace Barotrauma.Networking
return false; return false;
} }
private ServerInfo cachedServerListInfo = null;
public ServerInfo GetServerListInfo()
{
cachedServerListInfo ??= GameMain.ServerListScreen.UpdateServerInfoWithServerSettings(GameMain.Client.ClientPeer.ServerConnection, this);
return cachedServerListInfo;
}
} }
} }
@@ -1,6 +1,8 @@
using Microsoft.Xna.Framework; using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using OpenAL; using OpenAL;
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@@ -56,7 +58,7 @@ namespace Barotrauma.Networking
public bool Disconnected { get; private set; } public bool Disconnected { get; private set; }
public static void Create(string deviceName, UInt16? storedBufferID=null) public static void Create(string deviceName, UInt16? storedBufferID = null)
{ {
if (Instance != null) if (Instance != null)
{ {
@@ -84,7 +86,7 @@ namespace Barotrauma.Networking
if (captureDevice == IntPtr.Zero) if (captureDevice == IntPtr.Zero)
{ {
DebugConsole.NewMessage("Alc.CaptureOpenDevice attempt 1 failed: error code " + Alc.GetError(IntPtr.Zero).ToString(),Color.Orange); DebugConsole.NewMessage("Alc.CaptureOpenDevice attempt 1 failed: error code " + Alc.GetError(IntPtr.Zero).ToString(), Color.Orange);
//attempt using a smaller buffer size //attempt using a smaller buffer size
captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 2); captureDevice = Alc.CaptureOpenDevice(deviceName, VoipConfig.FREQUENCY, Al.FormatMono16, VoipConfig.BUFFER_SIZE * 2);
} }
@@ -162,6 +164,7 @@ namespace Barotrauma.Networking
} }
} }
IntPtr nativeBuffer;
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
bool prevCaptured = true; bool prevCaptured = true;
@@ -171,143 +174,198 @@ namespace Barotrauma.Networking
{ {
Array.Copy(uncompressedBuffer, 0, prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE); Array.Copy(uncompressedBuffer, 0, prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE);
Array.Clear(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE); Array.Clear(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE);
while (capturing && !Disconnected) nativeBuffer = Marshal.AllocHGlobal(VoipConfig.BUFFER_SIZE * 2);
try
{ {
int alcError; while (capturing)
if (CanDetectDisconnect)
{ {
Alc.GetInteger(captureDevice, Alc.EnumConnected, out int isConnected); int alcError;
if (CanDetectDisconnect)
{
Alc.GetInteger(captureDevice, Alc.EnumConnected, out int isConnected);
alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{
throw new Exception("Failed to determine if capture device is connected: " + alcError.ToString());
}
if (isConnected == 0)
{
DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings.");
Disconnected = true;
break;
}
}
FillBuffer();
alcError = Alc.GetError(captureDevice); alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError) if (alcError != Alc.NoError)
{ {
throw new Exception("Failed to determine if capture device is connected: " + alcError.ToString()); throw new Exception("Failed to capture samples: " + alcError.ToString());
} }
if (isConnected == 0) double maxAmplitude = 0.0f;
for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++)
{ {
DebugConsole.ThrowError("Capture device has been disconnected. You can select another available device in the settings."); uncompressedBuffer[i] = (short)MathHelper.Clamp((uncompressedBuffer[i] * Gain), -short.MaxValue, short.MaxValue);
Disconnected = true; double sampleVal = uncompressedBuffer[i] / (double)short.MaxValue;
break; maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
} }
} double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0);
Alc.GetInteger(captureDevice, Alc.EnumCaptureSamples, out int sampleCount); LastdB = dB;
LastAmplitude = maxAmplitude;
alcError = Alc.GetError(captureDevice); bool allowEnqueue = overrideSound != null;
if (alcError != Alc.NoError) if (GameMain.WindowActive)
{
throw new Exception("Failed to determine sample count: " + alcError.ToString());
}
if (sampleCount < VoipConfig.BUFFER_SIZE)
{
int sleepMs = (VoipConfig.BUFFER_SIZE - sampleCount) * 800 / VoipConfig.FREQUENCY;
if (sleepMs < 5) sleepMs = 5;
Thread.Sleep(sleepMs);
continue;
}
GCHandle handle = GCHandle.Alloc(uncompressedBuffer, GCHandleType.Pinned);
try
{
Alc.CaptureSamples(captureDevice, handle.AddrOfPinnedObject(), VoipConfig.BUFFER_SIZE);
}
finally
{
handle.Free();
}
alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{
throw new Exception("Failed to capture samples: " + alcError.ToString());
}
double maxAmplitude = 0.0f;
for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++)
{
uncompressedBuffer[i] = (short)MathHelper.Clamp((uncompressedBuffer[i] * Gain), -short.MaxValue, short.MaxValue);
double sampleVal = uncompressedBuffer[i] / (double)short.MaxValue;
maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
}
double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0);
LastdB = dB;
LastAmplitude = maxAmplitude;
bool allowEnqueue = false;
if (GameMain.WindowActive)
{
ForceLocal = captureTimer > 0 ? ForceLocal : GameMain.Config.UseLocalVoiceByDefault;
bool pttDown = false;
if ((PlayerInput.KeyDown(InputType.Voice) || PlayerInput.KeyDown(InputType.LocalVoice)) &&
GUI.KeyboardDispatcher.Subscriber == null)
{ {
pttDown = true; ForceLocal = captureTimer > 0 ? ForceLocal : GameMain.Config.UseLocalVoiceByDefault;
if (PlayerInput.KeyDown(InputType.LocalVoice)) bool pttDown = false;
if ((PlayerInput.KeyDown(InputType.Voice) || PlayerInput.KeyDown(InputType.LocalVoice)) &&
GUI.KeyboardDispatcher.Subscriber == null)
{ {
ForceLocal = true; pttDown = true;
if (PlayerInput.KeyDown(InputType.LocalVoice))
{
ForceLocal = true;
}
else
{
ForceLocal = false;
}
} }
else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Activity)
{ {
ForceLocal = false; if (dB > GameMain.Config.NoiseGateThreshold)
{
allowEnqueue = true;
}
}
else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.PushToTalk)
{
if (pttDown)
{
allowEnqueue = true;
}
} }
} }
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Activity)
if (allowEnqueue || captureTimer > 0)
{ {
if (dB > GameMain.Config.NoiseGateThreshold) LastEnqueueAudio = DateTime.Now;
if (GameMain.Client?.Character != null)
{ {
allowEnqueue = true; var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default;
GameMain.Client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
} }
} //encode audio and enqueue it
else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.PushToTalk) lock (buffers)
{
if (pttDown)
{ {
allowEnqueue = true; if (!prevCaptured) //enqueue the previous buffer if not sent to avoid cutoff
{
int compressedCountPrev = VoipConfig.Encoder.Encode(prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE);
EnqueueBuffer(compressedCountPrev);
}
int compressedCount = VoipConfig.Encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE);
EnqueueBuffer(compressedCount);
}
captureTimer -= (VoipConfig.BUFFER_SIZE * 1000) / VoipConfig.FREQUENCY;
if (allowEnqueue)
{
captureTimer = GameMain.Config.VoiceChatCutoffPrevention;
}
prevCaptured = true;
}
else
{
captureTimer = 0;
prevCaptured = false;
//enqueue silence
lock (buffers)
{
EnqueueBuffer(0);
} }
} }
} }
}
catch (Exception e)
{
DebugConsole.ThrowError($"VoipCapture threw an exception. Disabling capture...", e);
capturing = false;
}
finally
{
Marshal.FreeHGlobal(nativeBuffer);
}
}
if (allowEnqueue || captureTimer > 0) private Sound overrideSound;
private int overridePos;
private short[] overrideBuf = new short[VoipConfig.BUFFER_SIZE];
private void FillBuffer()
{
if (overrideSound != null)
{
int totalSampleCount = 0;
while (totalSampleCount < VoipConfig.BUFFER_SIZE)
{ {
LastEnqueueAudio = DateTime.Now; int sampleCount = overrideSound.FillStreamBuffer(overridePos, overrideBuf);
if (GameMain.Client?.Character != null) overridePos += sampleCount * 2;
Array.Copy(overrideBuf, 0, uncompressedBuffer, totalSampleCount, sampleCount);
totalSampleCount += sampleCount;
if (sampleCount == 0)
{ {
var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default; overridePos = 0;
GameMain.Client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
} }
//encode audio and enqueue it }
lock (buffers) int sleepMs = VoipConfig.BUFFER_SIZE * 800 / VoipConfig.FREQUENCY;
Thread.Sleep(sleepMs - 1);
}
else
{
int sampleCount = 0;
while (sampleCount < VoipConfig.BUFFER_SIZE)
{
Alc.GetInteger(captureDevice, Alc.EnumCaptureSamples, out sampleCount);
int alcError = Alc.GetError(captureDevice);
if (alcError != Alc.NoError)
{ {
if (!prevCaptured) //enqueue the previous buffer if not sent to avoid cutoff throw new Exception("Failed to determine sample count: " + alcError.ToString());
}
if (sampleCount < VoipConfig.BUFFER_SIZE)
{
int sleepMs = (VoipConfig.BUFFER_SIZE - sampleCount) * 800 / VoipConfig.FREQUENCY;
if (sleepMs >= 1)
{ {
int compressedCountPrev = VoipConfig.Encoder.Encode(prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE); Thread.Sleep(sleepMs);
EnqueueBuffer(compressedCountPrev);
} }
int compressedCount = VoipConfig.Encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE);
EnqueueBuffer(compressedCount);
}
captureTimer -= (VoipConfig.BUFFER_SIZE * 1000) / VoipConfig.FREQUENCY;
if (allowEnqueue)
{
captureTimer = GameMain.Config.VoiceChatCutoffPrevention;
}
prevCaptured = true;
}
else
{
captureTimer = 0;
prevCaptured = false;
//enqueue silence
lock (buffers)
{
EnqueueBuffer(0);
} }
if (!capturing) { return; }
} }
Thread.Sleep(10); Alc.CaptureSamples(captureDevice, nativeBuffer, VoipConfig.BUFFER_SIZE);
Marshal.Copy(nativeBuffer, uncompressedBuffer, 0, uncompressedBuffer.Length);
}
}
public void SetOverrideSound(string fileName)
{
overrideSound?.Dispose();
if (string.IsNullOrEmpty(fileName))
{
overrideSound = null;
}
else
{
overrideSound = GameMain.SoundManager.LoadSound(fileName, true);
} }
} }
@@ -94,6 +94,7 @@ namespace Barotrauma.Networking
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.Name, GameMain.SoundManager, client.VoipQueue);
} }
GameMain.SoundManager.ForceStreamUpdate();
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f) if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
{ {
@@ -102,7 +103,7 @@ namespace Barotrauma.Networking
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameMain.Config.DisableVoiceChatFilters; client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameMain.Config.DisableVoiceChatFilters;
if (client.VoipSound.UseRadioFilter) if (messageType == ChatMessageType.Radio)
{ {
client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range); client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range);
} }
@@ -110,7 +111,7 @@ namespace Barotrauma.Networking
{ {
client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange); client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange);
} }
if (!client.VoipSound.UseRadioFilter && Character.Controlled != null && !GameMain.Config.DisableVoiceChatFilters) if (messageType != ChatMessageType.Radio && Character.Controlled != null && !GameMain.Config.DisableVoiceChatFilters)
{ {
client.VoipSound.UseMuffleFilter = SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull); client.VoipSound.UseMuffleFilter = SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull);
} }
@@ -12,8 +12,8 @@ namespace Barotrauma.Networking
{ {
public static bool Ready = false; public static bool Ready = false;
public const int FREQUENCY = 48000; //not amazing, but not bad audio quality public const int FREQUENCY = 48000;
public const int BUFFER_SIZE = 2880; //60ms window, the max Opus seems to support public const int BUFFER_SIZE = 960; //20ms window
public static OpusEncoder Encoder public static OpusEncoder Encoder
{ {
@@ -113,13 +113,16 @@ namespace Barotrauma
switch (voteType) switch (voteType)
{ {
case VoteType.Sub: case VoteType.Sub:
SubmarineInfo sub = data as SubmarineInfo; if (!(data is SubmarineInfo sub)) { return; }
if (sub == null) { return; }
msg.Write(sub.EqualityCheckVal); msg.Write(sub.EqualityCheckVal);
if (sub.EqualityCheckVal == 0)
{
//sub doesn't exist client-side, use hash to let the server know which one we voted for
msg.Write(sub.MD5Hash.Hash);
}
break; break;
case VoteType.Mode: case VoteType.Mode:
GameModePreset gameMode = data as GameModePreset; if (!(data is GameModePreset gameMode)) { return; }
if (gameMode == null) { return; }
msg.Write(gameMode.Identifier); msg.Write(gameMode.Identifier);
break; break;
case VoteType.EndRound: case VoteType.EndRound:
@@ -127,8 +130,7 @@ namespace Barotrauma
msg.Write((bool)data); msg.Write((bool)data);
break; break;
case VoteType.Kick: case VoteType.Kick:
Client votedClient = data as Client; if (!(data is Client votedClient)) { return; }
if (votedClient == null) return;
msg.Write(votedClient.ID); msg.Write(votedClient.ID);
break; break;
@@ -27,7 +27,8 @@ namespace Barotrauma.Particles
private float angularVelocity; private float angularVelocity;
private Vector2 dragVec = Vector2.Zero; private Vector2 dragVec = Vector2.Zero;
private int dragWait = 0; private float dragWait = 0;
private float collisionIgnoreTimer = 0;
private Vector2 size; private Vector2 size;
private Vector2 sizeChange; private Vector2 sizeChange;
@@ -103,7 +104,7 @@ namespace Barotrauma.Particles
return debugName; return debugName;
} }
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false) public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f)
{ {
this.prefab = prefab; this.prefab = prefab;
debugName = $"Particle ({prefab.Name})"; debugName = $"Particle ({prefab.Name})";
@@ -174,14 +175,22 @@ namespace Barotrauma.Particles
} }
DrawOnTop = drawOnTop; DrawOnTop = drawOnTop;
this.collisionIgnoreTimer = collisionIgnoreTimer;
} }
public bool Update(float deltaTime) public enum UpdateResult
{
Normal,
Delete
}
public UpdateResult Update(float deltaTime)
{ {
if (startDelay > 0.0f) if (startDelay > 0.0f)
{ {
startDelay -= deltaTime; startDelay -= deltaTime;
return true; return UpdateResult.Normal;
} }
prevPosition = position; prevPosition = position;
@@ -251,7 +260,7 @@ namespace Barotrauma.Particles
} }
lifeTime -= deltaTime; lifeTime -= deltaTime;
if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) { return false; } if (lifeTime <= 0.0f || color.A <= 0 || size.X <= 0.0f || size.Y <= 0.0f) { return UpdateResult.Delete; }
if (hasSubEmitters) if (hasSubEmitters)
{ {
@@ -261,7 +270,13 @@ namespace Barotrauma.Particles
} }
} }
if (!prefab.UseCollision) { return true; } if (collisionIgnoreTimer > 0f)
{
collisionIgnoreTimer -= deltaTime;
if (collisionIgnoreTimer <= 0f) { currentHull ??= Hull.FindHull(position); }
return UpdateResult.Normal;
}
if (!prefab.UseCollision) { return UpdateResult.Normal; }
if (HighQualityCollisionDetection) if (HighQualityCollisionDetection)
{ {
@@ -278,17 +293,17 @@ namespace Barotrauma.Particles
} }
} }
return true; return UpdateResult.Normal;
} }
private bool CollisionUpdate() private UpdateResult CollisionUpdate()
{ {
if (currentHull == null) if (currentHull == null)
{ {
Hull collidedHull = Hull.FindHull(position); Hull collidedHull = Hull.FindHull(position);
if (collidedHull != null) if (collidedHull != null)
{ {
if (prefab.DeleteOnCollision) return false; if (prefab.DeleteOnCollision) return UpdateResult.Delete;
OnWallCollisionOutside(collidedHull); OnWallCollisionOutside(collidedHull);
} }
} }
@@ -298,12 +313,12 @@ namespace Barotrauma.Particles
Vector2 collisionNormal = Vector2.Zero; Vector2 collisionNormal = Vector2.Zero;
if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height) if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height)
{ {
if (prefab.DeleteOnCollision) { return false; } if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
collisionNormal = new Vector2(0.0f, 1.0f); collisionNormal = new Vector2(0.0f, 1.0f);
} }
else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y) else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y)
{ {
if (prefab.DeleteOnCollision) { return false; } if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
collisionNormal = new Vector2(0.0f, -1.0f); collisionNormal = new Vector2(0.0f, -1.0f);
} }
@@ -328,12 +343,12 @@ namespace Barotrauma.Particles
if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X) if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
{ {
if (prefab.DeleteOnCollision) { return false; } if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
collisionNormal = new Vector2(1.0f, 0.0f); collisionNormal = new Vector2(1.0f, 0.0f);
} }
else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right) else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right)
{ {
if (prefab.DeleteOnCollision) { return false; } if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
collisionNormal = new Vector2(-1.0f, 0.0f); collisionNormal = new Vector2(-1.0f, 0.0f);
} }
@@ -374,7 +389,7 @@ namespace Barotrauma.Particles
} }
} }
return true; return UpdateResult.Normal;
} }
private void ApplyDrag(float dragCoefficient, float deltaTime) private void ApplyDrag(float dragCoefficient, float deltaTime)
@@ -389,10 +404,10 @@ namespace Barotrauma.Particles
//TODO: some better way to handle particle drag //TODO: some better way to handle particle drag
//this doesn't work that well because the drag vector is only updated every 0.5 seconds, allowing the particle to accelerate way more than it should //this doesn't work that well because the drag vector is only updated every 0.5 seconds, allowing the particle to accelerate way more than it should
//(e.g. a falling particle can freely accelerate for 0.5 seconds before the drag takes effect) //(e.g. a falling particle can freely accelerate for 0.5 seconds before the drag takes effect)
dragWait--; dragWait-=deltaTime;
if (dragWait <= 0) if (dragWait <= 0f)
{ {
dragWait = 30; dragWait = 0.5f;
float speed = velocity.Length(); float speed = velocity.Length();
@@ -115,12 +115,12 @@ namespace Barotrauma.Particles
Prefabs.RemoveByFile(configFile); Prefabs.RemoveByFile(configFile);
} }
public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null) public Particle CreateParticle(string prefabName, Vector2 position, float angle, float speed, Hull hullGuess = null, float collisionIgnoreTimer = 0f)
{ {
return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess); return CreateParticle(prefabName, position, new Vector2((float)Math.Cos(angle), (float)-Math.Sin(angle)) * speed, angle, hullGuess, collisionIgnoreTimer);
} }
public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null) public Particle CreateParticle(string prefabName, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, float collisionIgnoreTimer = 0f)
{ {
ParticlePrefab prefab = FindPrefab(prefabName); ParticlePrefab prefab = FindPrefab(prefabName);
@@ -130,10 +130,10 @@ namespace Barotrauma.Particles
return null; return null;
} }
return CreateParticle(prefab, position, velocity, rotation, hullGuess); return CreateParticle(prefab, position, velocity, rotation, hullGuess, collisionIgnoreTimer: collisionIgnoreTimer);
} }
public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false) public Particle CreateParticle(ParticlePrefab prefab, Vector2 position, Vector2 velocity, float rotation = 0.0f, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f)
{ {
if (particleCount >= MaxParticles || prefab == null || prefab.Sprites.Count == 0) { return null; } if (particleCount >= MaxParticles || prefab == null || prefab.Sprites.Count == 0) { return null; }
@@ -149,7 +149,7 @@ namespace Barotrauma.Particles
if (particles[particleCount] == null) particles[particleCount] = new Particle(); if (particles[particleCount] == null) particles[particleCount] = new Particle();
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop); particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer);
particleCount++; particleCount++;
@@ -181,10 +181,10 @@ namespace Barotrauma.Particles
for (int i = 0; i < particleCount; i++) for (int i = 0; i < particleCount; i++)
{ {
bool remove = false; bool remove;
try try
{ {
remove = !particles[i].Update(deltaTime); remove = particles[i].Update(deltaTime) == Particle.UpdateResult.Delete;
} }
catch (Exception e) catch (Exception e)
{ {
@@ -476,6 +476,11 @@ namespace Barotrauma
#endif #endif
} }
public static bool IsAltDown()
{
return KeyDown(Keys.LeftAlt) || KeyDown(Keys.RightAlt);
}
public static void Update(double deltaTime) public static void Update(double deltaTime)
{ {
timeSinceClick += deltaTime; timeSinceClick += deltaTime;
@@ -23,7 +23,7 @@ namespace Barotrauma
private GUIButton loadGameButton, deleteMpSaveButton; private GUIButton loadGameButton, deleteMpSaveButton;
public Action<SubmarineInfo, string, string> StartNewGame; public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
public Action<string> LoadGame; public Action<string> LoadGame;
private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 }; private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 };
@@ -41,6 +41,8 @@ namespace Barotrauma
private set; private set;
} }
public GUITickBox EnableRadiationToggle { get; set; }
private readonly bool isMultiplayer; private readonly bool isMultiplayer;
public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null) public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null)
@@ -171,6 +173,16 @@ namespace Barotrauma
string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text); string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings();
if (isMultiplayer)
{
settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled();
}
else
{
settings.RadiationEnabled = EnableRadiationToggle?.Selected ?? false;
}
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{ {
if (!hasRequiredContentPackages) if (!hasRequiredContentPackages)
@@ -184,7 +196,7 @@ namespace Barotrauma
{ {
if (GUIMessageBox.MessageBoxes.Count == 0) if (GUIMessageBox.MessageBoxes.Count == 0)
{ {
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer) if (isMultiplayer)
{ {
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
@@ -204,7 +216,7 @@ namespace Barotrauma
msgBox.Buttons[0].OnClicked = (button, obj) => msgBox.Buttons[0].OnClicked = (button, obj) =>
{ {
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer) if (isMultiplayer)
{ {
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
@@ -219,7 +231,7 @@ namespace Barotrauma
} }
else else
{ {
StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
if (isMultiplayer) if (isMultiplayer)
{ {
CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup");
@@ -230,8 +242,7 @@ namespace Barotrauma
} }
}; };
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(isMultiplayer ? 0.6f : 0.3f, 1f), buttonContainer.RectTransform), "", font: isMultiplayer ? GUI.Style.SmallFont : GUI.Style.Font, textColor: GUI.Style.Green)
font: isMultiplayer ? GUI.Style.SmallFont : GUI.Style.Font, textColor: GUI.Style.Green)
{ {
TextGetter = () => TextGetter = () =>
{ {
@@ -254,6 +265,15 @@ namespace Barotrauma
if (!isMultiplayer) if (!isMultiplayer)
{ {
if (MapGenerationParams.Instance.RadiationParams != null)
{
EnableRadiationToggle = new GUITickBox(new RectTransform(new Vector2(0.3f, 1f), buttonContainer.RectTransform), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font)
{
Selected = true,
ToolTip = TextManager.Get("campaignoption.enableradiation.tooltip")
};
}
var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton") var disclaimerBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.8f), rightColumn.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(5) }, style: "GUINotificationButton")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -53,7 +53,7 @@ namespace Barotrauma
campaign.Map.OnLocationSelected += SelectLocation; campaign.Map.OnLocationSelected += SelectLocation;
campaign.Map.OnMissionSelected += (connection, mission) => campaign.Map.OnMissionSelected += (connection, mission) =>
{ {
missionList.Select(mission); missionList?.Select(mission);
}; };
} }
@@ -306,7 +306,7 @@ namespace Barotrauma
{ {
var map = GameMain.GameSession?.Map; var map = GameMain.GameSession?.Map;
if (map == null) { return; } if (map == null) { return; }
if (selectedLocation != null && selectedLocation == map.CurrentDisplayLocation) if (selectedLocation != null && selectedLocation == GameMain.GameSession.Campaign.GetCurrentDisplayLocation())
{ {
map.SelectLocation(-1); map.SelectLocation(-1);
} }
@@ -394,6 +394,42 @@ namespace Barotrauma
var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), var difficultyLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
TextManager.Get("LevelDifficulty"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft); TextManager.Get("LevelDifficulty"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), ((int)connection.LevelData.Difficulty) + " %", textAlignment: Alignment.CenterRight); new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), ((int)connection.LevelData.Difficulty) + " %", textAlignment: Alignment.CenterRight);
if (connection.LevelData.HasBeaconStation)
{
var beaconStationContent = new GUILayoutGroup(new RectTransform(biomeLabel.RectTransform.NonScaledSize, textContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
string style = connection.LevelData.IsBeaconActive ? "BeaconStationActive" : "BeaconStationInactive";
var icon = new GUIImage(new RectTransform(new Point((int)(beaconStationContent.Rect.Height * 1.2f)), beaconStationContent.RectTransform),
style, scaleToFit: true)
{
Color = MapGenerationParams.Instance.IndicatorColor,
HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f),
ToolTip = TextManager.Get(connection.LevelData.IsBeaconActive ? "BeaconStationActiveTooltip" : "BeaconStationInactiveTooltip")
};
new GUITextBlock(new RectTransform(Vector2.One, beaconStationContent.RectTransform),
TextManager.Get("submarinetype.beaconstation"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
Padding = Vector4.Zero,
ToolTip = icon.ToolTip
};
}
if (connection.LevelData.HasHuntingGrounds)
{
var huntingGroundsContent = new GUILayoutGroup(new RectTransform(biomeLabel.RectTransform.NonScaledSize, textContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
var icon = new GUIImage(new RectTransform(new Point((int)(huntingGroundsContent.Rect.Height * 1.5f)), huntingGroundsContent.RectTransform),
"HuntingGrounds", scaleToFit: true)
{
Color = MapGenerationParams.Instance.IndicatorColor,
HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f),
ToolTip = TextManager.Get("HuntingGroundsTooltip")
};
new GUITextBlock(new RectTransform(Vector2.One, huntingGroundsContent.RectTransform),
TextManager.Get("missionname.huntinggrounds"), font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
Padding = Vector4.Zero,
ToolTip = icon.ToolTip
};
}
} }
missionList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), content.RectTransform)) missionList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), content.RectTransform))
@@ -402,7 +438,7 @@ namespace Barotrauma
}; };
SelectedLevel = connection?.LevelData; SelectedLevel = connection?.LevelData;
Location currentDisplayLocation = Campaign.CurrentDisplayLocation; Location currentDisplayLocation = Campaign.GetCurrentDisplayLocation();
if (connection != null && connection.Locations.Contains(currentDisplayLocation)) if (connection != null && connection.Locations.Contains(currentDisplayLocation))
{ {
List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList(); List<Mission> availableMissions = currentDisplayLocation.GetMissionsInConnection(connection).ToList();
@@ -437,11 +473,44 @@ namespace Barotrauma
SelectedColor = MapGenerationParams.Instance.IndicatorColor, SelectedColor = MapGenerationParams.Instance.IndicatorColor,
HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f) HoverColor = Color.Lerp(MapGenerationParams.Instance.IndicatorColor, Color.White, 0.5f)
}; };
missionName.Padding = new Vector4(missionName.Padding.X + icon.Rect.Width * 1.5f, missionName.Padding.Y, missionName.Padding.Z, missionName.Padding.W); icon.RectTransform.IsFixedSize = true;
GUILayoutGroup difficultyIndicatorGroup = null;
if (mission.Difficulty.HasValue)
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor: Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = 1,
UserData = "difficulty"
};
var difficultyColor = mission.GetDifficultyColor();
for (int i = 0; i < mission.Difficulty; i++)
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest) { IsFixedSize = true }, "DifficultyIndicator", scaleToFit: true)
{
Color = difficultyColor * 0.5f,
SelectedColor = difficultyColor,
HoverColor = Color.Lerp(difficultyColor, Color.White, 0.5f)
};
}
}
float extraPadding = 0.5f * icon.Rect.Width;
float extraZPadding = difficultyIndicatorGroup != null ? mission.Difficulty.Value * (difficultyIndicatorGroup.Children.First().Rect.Width + difficultyIndicatorGroup.AbsoluteSpacing) : 0;
missionName.Padding = new Vector4(missionName.Padding.X + icon.Rect.Width + extraPadding,
missionName.Padding.Y,
missionName.Padding.Z + extraZPadding + extraPadding,
missionName.Padding.W);
missionName.CalculateHeightFromText();
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.GetWithVariable("missionreward", "[reward]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward)), wrap: true); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(), wrap: true, parseRichText: true);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true);
string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true, parseRichText: true);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.Description, wrap: true, parseRichText: true);
} }
missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20)); missionPanel.RectTransform.MinSize = new Point(0, (int)(missionTextContent.Children.Sum(c => c.Rect.Height) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(20));
foreach (GUIComponent child in missionTextContent.Children) foreach (GUIComponent child in missionTextContent.Children)
@@ -454,6 +523,10 @@ namespace Barotrauma
missionPanel.OnAddedToGUIUpdateList = (c) => missionPanel.OnAddedToGUIUpdateList = (c) =>
{ {
missionTextContent.Children.ForEach(child => child.State = c.State); missionTextContent.Children.ForEach(child => child.State = c.State);
if (missionTextContent.FindChild("difficulty", recursive: true) is GUILayoutGroup group)
{
group.State = c.State;
}
}; };
if (mission != availableMissions.Last()) if (mission != availableMissions.Last())
@@ -491,7 +564,25 @@ namespace Barotrauma
StartButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), content.RectTransform), StartButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), content.RectTransform),
TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge") TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge")
{ {
OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }, OnClicked = (GUIButton btn, object obj) =>
{
if (missionList.Content.Children.Any(c => c.UserData is Mission) && !(missionList.SelectedData is Mission))
{
var noMissionVerification = new GUIMessageBox(string.Empty, TextManager.Get("nomissionprompt"), new string[] { TextManager.Get("yes"), TextManager.Get("no") });
noMissionVerification.Buttons[0].OnClicked = (btn, userdata) =>
{
StartRound?.Invoke();
noMissionVerification.Close();
return true;
};
noMissionVerification.Buttons[1].OnClicked = noMissionVerification.Close;
}
else
{
StartRound?.Invoke();
}
return true;
},
Enabled = true, Enabled = true,
Visible = Campaign.AllowedToEndRound() Visible = Campaign.AllowedToEndRound()
}; };
@@ -507,6 +598,11 @@ namespace Barotrauma
public void SelectTab(CampaignMode.InteractionType tab) public void SelectTab(CampaignMode.InteractionType tab)
{ {
if (Campaign.ShowCampaignUI || (Campaign.ForceMapUI && tab == CampaignMode.InteractionType.Map))
{
HintManager.OnShowCampaignInterface(tab);
}
selectedTab = tab; selectedTab = tab;
for (int i = 0; i < tabs.Length; i++) for (int i = 0; i < tabs.Length; i++)
{ {
@@ -711,7 +711,7 @@ namespace Barotrauma.CharacterEditor
} }
} }
// Camera // Camera
Cam.MoveCamera((float)deltaTime, allowMove: false); Cam.MoveCamera((float)deltaTime, allowMove: false, allowZoom: GUI.MouseOn == null);
Vector2 targetPos = character.WorldPosition; Vector2 targetPos = character.WorldPosition;
if (PlayerInput.MidButtonHeld()) if (PlayerInput.MidButtonHeld())
{ {
@@ -766,7 +766,7 @@ namespace Barotrauma.CharacterEditor
if (editLimbs && !spriteSheetRect.Contains(PlayerInput.MousePosition) && if (editLimbs && !spriteSheetRect.Contains(PlayerInput.MousePosition) &&
MathUtils.RectangleContainsPoint(GetLimbPhysicRect(limb), PlayerInput.MousePosition)) { return CursorState.Hand; } MathUtils.RectangleContainsPoint(GetLimbPhysicRect(limb), PlayerInput.MousePosition)) { return CursorState.Hand; }
// spritesheet // spritesheet
if (GetLimbSpritesheetRect(limb).Contains(PlayerInput.MousePosition)) { return CursorState.Hand; } if (showSpritesheet && GetLimbSpritesheetRect(limb).Contains(PlayerInput.MousePosition)) { return CursorState.Hand; }
} }
return CursorState.Default; return CursorState.Default;
} }
@@ -1761,9 +1761,9 @@ namespace Barotrauma.CharacterEditor
#endif #endif
// Add to the selected content package // Add to the selected content package
contentPackage.AddFile(configFilePath, ContentType.Character); contentPackage.AddFile(configFilePath, ContentType.Character);
Barotrauma.IO.Validation.DevException = true; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
contentPackage.Save(contentPackage.Path); contentPackage.Save(contentPackage.Path);
Barotrauma.IO.Validation.DevException = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
// Ragdoll // Ragdoll

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