Unstable 0.1300.0.3

This commit is contained in:
Markus Isberg
2021-03-25 15:40:24 +02:00
parent 874616027b
commit 58c50a235d
136 changed files with 2486 additions and 1008 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;
@@ -67,16 +65,9 @@ namespace Barotrauma
public float Shake; public float Shake;
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, Rectangle? overrideMouseOn = null) public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true)
{ {
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)
@@ -294,7 +288,7 @@ namespace Barotrauma
} }
} }
if (allowZoom && (GUI.MouseOn == null || (overrideMouseOn?.Contains(PlayerInput.MousePosition) ?? false))) if (allowZoom)
{ {
Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition); Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition);
Vector2 diffViewCenter; Vector2 diffViewCenter;
@@ -318,14 +312,14 @@ 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.6f);
offset.Y = -offset.Y / (resolution.Y * 0.3f); offset.Y = -offset.Y / (Resolution.Y * 0.6f);
if (offset.LengthSquared() > 1.0f) offset.Normalize(); if (offset.LengthSquared() > 1.0f) offset.Normalize();
offset *= offsetAmount; 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;
} }
@@ -343,25 +337,21 @@ namespace Barotrauma
previousOffset = offset; previousOffset = offset;
} }
//how much to zoom out (zoom completely out when offset is 1000) //TODO: remove magic numbers
float zoomOutAmount = GetZoomAmount(offset); float minMultiplier = OffsetAmount > 0f ? ((DefaultZoom * 8f * 250f) / OffsetAmount) : 15f;
//zoom amount when resolution is not taken into account
float unscaledZoom = MathHelper.Lerp(DefaultZoom, MinZoom, zoomOutAmount);
//zoom with resolution taken into account (zoom further out on smaller resolutions)
float scaledZoom = unscaledZoom * globalZoomScale;
//an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution, //how much to zoom out (0.0 = Default zoom, 1.0 = zoom completely out)
//while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling float zoomOutAmount = GetZoomAmount(offset);
//on lower resolutions, which doesn't look that good) float newZoom = MathHelper.Lerp(DefaultZoom, MinZoom * minMultiplier, zoomOutAmount) * globalZoomScale;
float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom, //zoom in further if zoomOutAmount is low and resolution is lower than reference
(GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(zoomOutAmount) : 0.3f); newZoom *= MathHelper.Lerp(0.5f * (1f - Math.Min(globalZoomScale, 1f)), 0f, zoomOutAmount) + 1f;
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;
} }
@@ -426,7 +416,7 @@ namespace Barotrauma
private float GetZoomAmount(Vector2 offset) private float GetZoomAmount(Vector2 offset)
{ {
return Math.Min(offset.Length() / 1000.0f, 1.0f); return Math.Min(offset.Length() / Math.Max(1f, OffsetAmount), 1.0f);
} }
public float GetZoomAmountFromPrevious() public float GetZoomAmountFromPrevious()
@@ -224,8 +224,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;
@@ -382,19 +381,44 @@ 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.GameSession?.GameMode is CampaignMode && GameMain.NetworkMember.RespawnManager != null && Level.Loaded?.Type != LevelData.LevelType.Outpost)
{
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;
} }
PlaySound(CharacterSound.SoundType.Die); PlaySound(CharacterSound.SoundType.Die);
} }
@@ -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)
@@ -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
{ {
@@ -516,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();
@@ -997,8 +997,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 =
@@ -1247,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);
}); });
@@ -14,10 +14,7 @@ namespace Barotrauma
base.State = value; base.State = value;
if (state == HostagesKilledState && !string.IsNullOrEmpty(hostagesKilledMessage)) if (state == HostagesKilledState && !string.IsNullOrEmpty(hostagesKilledMessage))
{ {
new GUIMessageBox(string.Empty, hostagesKilledMessage, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon, parseRichText: true) CreateMessageBox(string.Empty, hostagesKilledMessage);
{
IconColor = Prefab.IconColor
};
} }
} }
} }
@@ -44,6 +41,15 @@ namespace Barotrauma
{ {
Item.ReadSpawnData(msg); 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)) if (characters.Contains(null))
{ {
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@@ -7,6 +8,19 @@ 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() public string GetMissionRewardText()
{ {
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward)); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward));
@@ -71,6 +85,7 @@ namespace Barotrauma
protected void CreateMessageBox(string header, string message) 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) new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon, parseRichText: true)
{ {
IconColor = Prefab.IconColor IconColor = Prefab.IconColor
@@ -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
@@ -565,34 +565,26 @@ namespace Barotrauma
} }
else else
{ {
string guiScaleString = $"GUI.Scale: {Scale}"; string[] 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); $"Cam.Zoom: {Screen.Selected.Cam?.Zoom ?? 0f}",
Vector2 relativeHorizontalAspectRatioStringSize = SmallFont.MeasureString(relativeHorizontalAspectRatioString); };
Vector2 relativeVerticalAspectRatioStringSize = SmallFont.MeasureString(relativeVerticalAspectRatioString);
int padding = IntScale(10); int padding = IntScale(10);
int yPos = padding; int yPos = padding;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiScaleStringSize.X - padding, yPos), guiScaleString, Color.LightGreen, Color.Black, 0, SmallFont); foreach (string str in strings)
yPos += (int)guiScaleStringSize.Y + padding / 2; {
Vector2 stringSize = SmallFont.MeasureString(str);
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guixScaleStringSize.X - padding, yPos), guixScaleString, 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)guixScaleStringSize.Y + padding / 2; yPos += (int)stringSize.Y + padding / 2;
}
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiyScaleStringSize.X - padding, yPos), guiyScaleString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)guiyScaleStringSize.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;
} }
} }
@@ -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
@@ -891,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;
@@ -261,7 +261,7 @@ namespace Barotrauma
public readonly List<RichTextData> RichTextData = null; public readonly List<RichTextData> RichTextData = null;
private readonly bool hasColorHighlight = false; public bool HasColorHighlight => RichTextData != null;
public struct ClickableArea public struct ClickableArea
{ {
@@ -295,7 +295,6 @@ namespace Barotrauma
if (parseRichText) if (parseRichText)
{ {
RichTextData = Barotrauma.RichTextData.GetRichTextData(text, out text); RichTextData = Barotrauma.RichTextData.GetRichTextData(text, out text);
hasColorHighlight = RichTextData != null;
} }
//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,
@@ -326,7 +325,6 @@ namespace Barotrauma
: 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)
@@ -634,7 +632,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);
@@ -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; }
@@ -18,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, Reputation, 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;
@@ -181,77 +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");
Vector2 contentFrameSize = selectedTab switch //this used to be a switch expression but i changed it because it killed enc :(
Vector2 contentFrameSize;
switch (selectedTab)
{ {
InfoFrameTab.MyCharacter => new Vector2(0.33f, 0.5f), case InfoFrameTab.MyCharacter:
_ => new Vector2(0.33f, 0.667f) contentFrameSize = new Vector2(0.45f, 0.5f);
}; break;
default:
contentFrameSize = new Vector2(0.45f, 0.667f);
break;
}
contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.025f, 0.12f) }); contentFrame = new GUIFrame(new RectTransform(contentFrameSize, infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.025f, 0.12f) });
var innerLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(17.5f)) }) 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)
{
RelativeSpacing = 0.01f,
Stretch = true
};
var buttonArea = new GUILayoutGroup(new RectTransform(new Point(innerLayoutGroup.Rect.Width, GUI.IntScale(25f)), parent: innerLayoutGroup.RectTransform), isHorizontal: true)
{ {
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(0.07f, 1f), parent: horizontalLayoutGroup.RectTransform), isHorizontal: false)
{
AbsoluteSpacing = GUI.IntScale(5f)
};
var innerLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.92f, 1f), horizontalLayoutGroup.RectTransform))
{
RelativeSpacing = 0.01f,
Stretch = true
};
float absoluteSpacing = innerLayoutGroup.RelativeSpacing * innerLayoutGroup.Rect.Height; float absoluteSpacing = innerLayoutGroup.RelativeSpacing * innerLayoutGroup.Rect.Height;
int multiplier = GameMain.GameSession?.GameMode is CampaignMode ? 2 : 1; int multiplier = GameMain.GameSession?.GameMode is CampaignMode ? 2 : 1;
int infoFrameHolderHeight = Math.Min((int)(0.926f * innerLayoutGroup.Rect.Height), (int)(innerLayoutGroup.Rect.Height - multiplier * (GUI.IntScale(25f) + absoluteSpacing))); 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); infoFrameHolder = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: null);
var crewButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("Crew"), style: "GUITabButton") GUIButton createTabButton(InfoFrameTab tab, string textTag)
{ {
UserData = InfoFrameTab.Crew, var newButton = new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: $"InfoFrameTabButton.{tab}")
OnClicked = SelectInfoFrameTab {
}; UserData = tab,
tabButtons.Add(crewButton); ToolTip = TextManager.Get(textTag),
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(newButton);
return newButton;
}
var missionButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("Mission"), style: "GUITabButton") var crewButton = createTabButton(InfoFrameTab.Crew, "crew");
{
UserData = InfoFrameTab.Mission, var missionButton = createTabButton(InfoFrameTab.Mission, "mission");
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(missionButton);
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode) if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{ {
var reputationButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("reputation"), style: "GUITabButton") var reputationButton = createTabButton(InfoFrameTab.Reputation, "reputation");
{
UserData = InfoFrameTab.Reputation,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(reputationButton);
var balanceFrame = new GUIFrame(new RectTransform(new Point(buttonArea.RectTransform.NonScaledSize.X, (int)(buttonArea.RectTransform.NonScaledSize.Y * 1.5f)), parent: innerLayoutGroup.RectTransform), style: "InnerFrame"); var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
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) 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)) 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 = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("tabmenu.traitor"), style: "GUITabButton") bool isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
if (isTraitor && GameMain.Client.TraitorMission != null)
{ {
UserData = InfoFrameTab.Traitor, var traitorButton = createTabButton(InfoFrameTab.Traitor, "tabmenu.traitor");
OnClicked = SelectInfoFrameTab }
};
tabButtons.Add(traitorButton);
} }
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);
} }
} }
@@ -288,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;
@@ -879,13 +884,16 @@ namespace Barotrauma
UserData = line UserData = line
}; };
textBlock.CalculateHeightFromText(); textBlock.CalculateHeightFromText();
foreach (var data in textBlock.RichTextData) if (textBlock.HasColorHighlight)
{ {
textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() foreach (var data in textBlock.RichTextData)
{ {
Data = data, textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea()
OnClick = GameMain.NetLobbyScreen.SelectPlayer {
}); Data = data,
OnClick = GameMain.NetLobbyScreen.SelectPlayer
});
}
} }
} }
} }
@@ -930,27 +938,38 @@ namespace Barotrauma
foreach (Mission mission in GameMain.GameSession.Missions) foreach (Mission mission in GameMain.GameSession.Missions)
{ {
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: null); GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(Vector2.One, missionList.Content.RectTransform), style: 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); 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)
{
AbsoluteSpacing = GUI.IntScale(5)
};
string descriptionText = mission.Description;
foreach (string missionMessage in mission.ShownMessages)
{
descriptionText += "\n\n" + missionMessage;
}
string rewardText = mission.GetMissionRewardText(); string rewardText = mission.GetMissionRewardText();
string reputationText = mission.GetReputationRewardText(mission.Locations[0]); string reputationText = mission.GetReputationRewardText(mission.Locations[0]);
var missionNameRichTextData = RichTextData.GetRichTextData(mission.Name, out string missionNameString); var missionNameRichTextData = RichTextData.GetRichTextData(mission.Name, out string missionNameString);
var missionDescriptionRichTextData = RichTextData.GetRichTextData(mission.Description, out string missionDescriptionString);
var missionRewardRichTextData = RichTextData.GetRichTextData(rewardText, out string missionRewardString); var missionRewardRichTextData = RichTextData.GetRichTextData(rewardText, out string missionRewardString);
var missionReputationRichTextData = RichTextData.GetRichTextData(reputationText, out string missionReputationString); 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); missionNameString = ToolBox.WrapText(missionNameString, missionTextGroup.Rect.Width, GUI.LargeFont);
missionDescriptionString = ToolBox.WrapText(missionDescriptionString, missionTextGroup.Rect.Width, GUI.Font);
missionRewardString = ToolBox.WrapText(missionRewardString, missionTextGroup.Rect.Width, GUI.Font); missionRewardString = ToolBox.WrapText(missionRewardString, missionTextGroup.Rect.Width, GUI.Font);
missionReputationString = ToolBox.WrapText(missionReputationString, 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 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString); Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
Vector2 missionRewardSize = GUI.Font.MeasureString(missionRewardString); Vector2 missionRewardSize = GUI.Font.MeasureString(missionRewardString);
Vector2 missionReputationSize = GUI.Font.MeasureString(missionReputationString); Vector2 missionReputationSize = GUI.Font.MeasureString(missionReputationString);
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y + missionReputationSize.Y)); 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); missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
if (mission.Prefab.Icon != null) if (mission.Prefab.Icon != null)
@@ -960,10 +979,38 @@ namespace Barotrauma
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight); Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor, CanBeFocused = false }; 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); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameRichTextData, missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardRichTextData, missionRewardString); 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), 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), missionDescriptionRichTextData, missionDescriptionString);
} }
@@ -1004,5 +1051,84 @@ 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)
{
var upgradeRootLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft), 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;
};
}
}
} }
} }
@@ -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,9 +572,9 @@ 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)
{ {
GUIImage upgradeIndicator = new GUIImage(rectT(0.1f, 1f, indicatorLayout), style: "UpgradeIndicator", scaleToFit: true) { UserData = prefab, CanBeFocused = false }; GUIImage upgradeIndicator = new GUIImage(rectT(0.1f, 1f, indicatorLayout), style: "UpgradeIndicator", scaleToFit: true) { UserData = prefab, CanBeFocused = false };
@@ -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);
@@ -1229,13 +1229,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"
}; };
@@ -168,6 +168,7 @@ namespace Barotrauma
{ {
chatBox.ToggleButton = new GUIButton(new RectTransform(new Point((int)(182f * GUI.Scale * 0.4f), (int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style: "ChatToggleButton") chatBox.ToggleButton = new GUIButton(new RectTransform(new Point((int)(182f * GUI.Scale * 0.4f), (int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style: "ChatToggleButton")
{ {
ToolTip = TextManager.Get("chat"),
ClampMouseRectToParent = false ClampMouseRectToParent = false
}; };
chatBox.ToggleButton.RectTransform.AbsoluteOffset = new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height); chatBox.ToggleButton.RectTransform.AbsoluteOffset = new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height);
@@ -289,19 +290,7 @@ namespace Barotrauma
new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight),
style: "CrewListBackground") style: "CrewListBackground")
{ {
UserData = character, UserData = character
OnSecondaryClicked = (comp, data) =>
{
if (data == null) { return false; }
var client = GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data);
if (client != null)
{
CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client);
return true;
}
return false;
}
}; };
var iconRelativeWidth = (float)crewListEntrySize.Y / background.Rect.Width; var iconRelativeWidth = (float)crewListEntrySize.Y / background.Rect.Width;
@@ -379,7 +368,17 @@ namespace Barotrauma
background.RectTransform), background.RectTransform),
style: null) style: null)
{ {
UserData = character UserData = character,
OnSecondaryClicked = (comp, data) =>
{
if (data == null) { return false; }
if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client)
{
CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client);
return true;
}
return false;
}
}; };
SetCharacterButtonTooltip(characterButton); SetCharacterButtonTooltip(characterButton);
@@ -389,7 +388,6 @@ namespace Barotrauma
} }
else else
{ {
characterButton.CanBeFocused = false;
characterButton.CanBeSelected = false; characterButton.CanBeSelected = false;
} }
@@ -1089,7 +1087,8 @@ namespace Barotrauma
public void CreateModerationContextMenu(Point mousePos, Client client) public void CreateModerationContextMenu(Point mousePos, Client client)
{ {
if (GUIContextMenu.CurrentContextMenu != null) { return; } if (GUIContextMenu.CurrentContextMenu != null) { return; }
if (IsSinglePlayer || client == null || (!GameMain.Client?.PreviouslyConnectedClients?.Contains(client) ?? true)) { return; } if (IsSinglePlayer || client == null || ((!GameMain.Client?.PreviouslyConnectedClients?.Contains(client)) ?? true)) { return; }
bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized, bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized,
canKick = GameMain.Client.HasPermission(ClientPermissions.Kick), canKick = GameMain.Client.HasPermission(ClientPermissions.Kick),
@@ -1638,7 +1637,8 @@ namespace Barotrauma
#if DEBUG #if DEBUG
if (Character.Controlled == null) { return true; } if (Character.Controlled == null) { return true; }
#endif #endif
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)); return Character.Controlled != null &&
(characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)));
} }
private Entity FindEntityContext() private Entity FindEntityContext()
@@ -124,22 +124,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);
} }
@@ -152,7 +152,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);
@@ -211,6 +211,7 @@ namespace Barotrauma
HintManager.OnAvailableTransition(availableTransition); 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 //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(); HintManager.Update();
Map.SelectLocation(-1);
endRoundButton.OnClicked(EndRoundButton, null); endRoundButton.OnClicked(EndRoundButton, null);
prevCampaignUIAutoOpenType = availableTransition; prevCampaignUIAutoOpenType = availableTransition;
} }
@@ -239,11 +239,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 +283,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,
@@ -327,7 +337,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 +345,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)
{ {
@@ -498,7 +508,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)
@@ -298,11 +298,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
@@ -327,19 +336,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,
@@ -409,13 +412,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)
{ {
@@ -509,7 +512,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)
@@ -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)
{ {
@@ -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();
@@ -40,56 +40,82 @@ namespace Barotrauma
private GUILayoutGroup topLeftButtonGroup; private GUILayoutGroup topLeftButtonGroup;
private GUIButton crewListButton, commandButton, tabMenuButton; private GUIButton crewListButton, commandButton, tabMenuButton;
private GUILayoutGroup TopLeftButtonGroup
{
get
{
if (topLeftButtonGroup == null)
{
topLeftButtonGroup = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUI.Canvas), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = HUDLayoutSettings.Padding,
CanBeFocused = false
};
int buttonHeight = GUI.IntScale(40); private GUIComponent respawnInfoFrame, respawnButtonContainer;
Vector2 buttonSpriteSize = GUI.Style.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size; private GUITextBlock respawnInfoText;
int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X); private GUITickBox respawnTickBox;
Point buttonSize = new Point(buttonWidth, buttonHeight); private GUILayoutGroup TopLeftButtonGroup;
// Crew list button private void CreateTopLeftButtons()
crewListButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "CrewListToggleButton") {
{ if (topLeftButtonGroup != null)
ToolTip = TextManager.GetWithVariable("hudbutton.crewlist", "[key]", GameMain.Config.KeyBindText(InputType.CrewOrders)), {
OnClicked = (GUIButton btn, object userdata) => topLeftButtonGroup.RectTransform.Parent = null;
{ topLeftButtonGroup = null;
if (CrewManager == null) { return false; } crewListButton = commandButton = tabMenuButton = null;
CrewManager.IsCrewMenuOpen = !CrewManager.IsCrewMenuOpen;
return true;
}
};
// Command interface button
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;
}
};
// Tab menu button
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();
}
};
}
return topLeftButtonGroup;
} }
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()
@@ -101,20 +127,15 @@ namespace Barotrauma
if ((!(GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) && if ((!(GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition")) !CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
{ {
if (crewListButton != null) if (topLeftButtonGroup == null)
{ {
crewListButton.Selected = CrewManager != null && CrewManager.IsCrewMenuOpen; CreateTopLeftButtons();
} }
if (commandButton != null) crewListButton.Selected = CrewManager != null && CrewManager.IsCrewMenuOpen;
{ commandButton.Selected = CrewManager.IsCommandInterfaceOpen;
commandButton.Selected = CrewManager.IsCommandInterfaceOpen; commandButton.Enabled = CrewManager.CanIssueOrders;
commandButton.Enabled = CrewManager.CanIssueOrders; tabMenuButton.Selected = IsTabMenuOpen;
} topLeftButtonGroup.AddToGUIUpdateList();
if (tabMenuButton != null)
{
tabMenuButton.Selected = IsTabMenuOpen;
}
TopLeftButtonGroup.AddToGUIUpdateList();
} }
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
@@ -130,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();
} }
@@ -138,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();
} }
@@ -167,6 +188,17 @@ namespace Barotrauma
HintManager.Update(); 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)
{ {
GameMode?.Draw(spriteBatch); GameMode?.Draw(spriteBatch);
@@ -1,4 +1,5 @@
using Barotrauma.IO; using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
@@ -21,41 +22,16 @@ namespace Barotrauma
private static GUIMessageBox ActiveHintMessageBox { get; set; } private static GUIMessageBox ActiveHintMessageBox { get; set; }
private static Action OnUpdate { get; set; } private static Action OnUpdate { get; set; }
private static double TimeStoppedInteracting { get; set; } private static double TimeStoppedInteracting { get; set; }
private static double TimeRoundStarted { get; set; }
/// <summary> /// <summary>
/// Seconds before any reminders can be shown /// Seconds before any reminders can be shown
/// </summary> /// </summary>
private static int TimeBeforeReminders { get; set; } private static int TimeBeforeReminders { get; set; }
/// <summary> /// <summary>
/// Seconds before another reminder can be shown /// Seconds before another reminder can be shown
/// </summary> /// </summary>
private static int ReminderCooldown { get; set; } private static int ReminderCooldown { get; set; }
private static double TimeReminderLastDisplayed { get; set; } private static double TimeReminderLastDisplayed { get; set; }
private static Queue<HintInfo> HintQueue { get; } = new Queue<HintInfo>();
public struct HintInfo
{
public string Identifier { get; }
public string Text { get; }
public Sprite Icon { get; }
public Color? IconColor { get; }
public Action OnDisplay { get; }
public Action OnUpdate { get; }
public HintInfo(string hintIdentifier, string text, Sprite icon, Color? iconColor, Action onDisplay, Action onUpdate)
{
Identifier = hintIdentifier;
Text = text;
Icon = icon;
IconColor = iconColor;
OnDisplay = onDisplay;
OnUpdate = onUpdate;
}
}
private static HashSet<Hull> BallastHulls { get; } = new HashSet<Hull>(); private static HashSet<Hull> BallastHulls { get; } = new HashSet<Hull>();
public static void Init() public static void Init()
@@ -127,15 +103,6 @@ namespace Barotrauma
return; return;
} }
} }
else if (HintQueue.TryDequeue(out var hint))
{
ActiveHintMessageBox = new GUIMessageBox(hint.Identifier, hint.Text, hint.Icon);
if (hint.IconColor.HasValue) { ActiveHintMessageBox.IconColor = hint.IconColor.Value; }
OnUpdate = hint.OnUpdate;
ActiveHintMessageBox.InnerFrame.Flash(color: hint.IconColor ?? Color.Orange, flashDuration: 0.75f);
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
hint.OnDisplay?.Invoke();
}
CheckIsInteracting(); CheckIsInteracting();
CheckIfDivingGearOutOfOxygen(); CheckIfDivingGearOutOfOxygen();
@@ -166,21 +133,21 @@ namespace Barotrauma
// onstartedinteracting.brokenitem // onstartedinteracting.brokenitem
if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold)) if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold))
{ {
if (EnqueueHint($"{hintIdentifierBase}.brokenitem")) { return; } if (DisplayHint($"{hintIdentifierBase}.brokenitem")) { return; }
} }
// onstartedinteracting.lootingisstealing // onstartedinteracting.lootingisstealing
if (item.Submarine?.Info?.Type == SubmarineType.Outpost && if (item.Submarine?.Info?.Type == SubmarineType.Outpost &&
item.ContainedItems.Any(i => i.SpawnedInOutpost)) item.ContainedItems.Any(i => i.SpawnedInOutpost))
{ {
if (EnqueueHint($"{hintIdentifierBase}.lootingisstealing")) { return; } if (DisplayHint($"{hintIdentifierBase}.lootingisstealing")) { return; }
} }
// onstartedinteracting.turretperiscope // onstartedinteracting.turretperiscope
if (item.HasTag("periscope") && if (item.HasTag("periscope") &&
item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret) item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret)
{ {
if (EnqueueHint($"{hintIdentifierBase}.turretperiscope", if (DisplayHint($"{hintIdentifierBase}.turretperiscope",
variableTags: new string[] { "[shootkey]", "[deselectkey]", }, variableTags: new string[] { "[shootkey]", "[deselectkey]", },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect) })) variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect) }))
{ return; } { return; }
@@ -193,7 +160,7 @@ namespace Barotrauma
if (!hintIdentifier.StartsWith(hintIdentifierBase)) { continue; } if (!hintIdentifier.StartsWith(hintIdentifierBase)) { continue; }
if (!HintTags.TryGetValue(hintIdentifier, out var hintTags)) { continue; } if (!HintTags.TryGetValue(hintIdentifier, out var hintTags)) { continue; }
if (!item.HasTag(hintTags)) { continue; } if (!item.HasTag(hintTags)) { continue; }
if (EnqueueHint(hintIdentifier)) { return; } if (DisplayHint(hintIdentifier)) { return; }
} }
} }
@@ -206,7 +173,7 @@ namespace Barotrauma
Character.Controlled.SelectedConstruction.OwnInventory?.AllItems is IEnumerable<Item> containedItems && Character.Controlled.SelectedConstruction.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag("reactorfuel")) > 1) containedItems.Count(i => i.HasTag("reactorfuel")) > 1)
{ {
if (EnqueueHint("onisinteracting.reactorwithextrarods")) { return; } if (DisplayHint("onisinteracting.reactorwithextrarods")) { return; }
} }
} }
@@ -214,6 +181,7 @@ namespace Barotrauma
{ {
// Make sure everything's been reset properly, OnRoundEnded() isn't always called when exiting a game // Make sure everything's been reset properly, OnRoundEnded() isn't always called when exiting a game
Reset(); Reset();
TimeRoundStarted = GameMain.GameScreen.GameTime;
var initRoundHandle = CoroutineManager.StartCoroutine(InitRound(), "HintManager.InitRound"); var initRoundHandle = CoroutineManager.StartCoroutine(InitRound(), "HintManager.InitRound");
if (!CanDisplayHints(requireGameScreen: false, requireControllingCharacter: false)) { return; } if (!CanDisplayHints(requireGameScreen: false, requireControllingCharacter: false)) { return; }
@@ -252,10 +220,15 @@ namespace Barotrauma
OnStartedControlling(); OnStartedControlling();
while (ActiveHintMessageBox != null)
{
yield return CoroutineStatus.Running;
}
if (!GameMain.GameSession.GameMode.IsSinglePlayer && if (!GameMain.GameSession.GameMode.IsSinglePlayer &&
GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled)
{ {
EnqueueHint("onroundstarted.voipdisabled", onUpdate: () => DisplayHint("onroundstarted.voipdisabled", onUpdate: () =>
{ {
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) { return; } if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) { return; }
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
@@ -281,7 +254,6 @@ namespace Barotrauma
ActiveHintMessageBox = null; ActiveHintMessageBox = null;
} }
OnUpdate = null; OnUpdate = null;
HintQueue.Clear();
HintsIgnoredThisRound.Clear(); HintsIgnoredThisRound.Clear();
} }
@@ -292,7 +264,7 @@ namespace Barotrauma
if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; } if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; }
if (Character.Controlled.SelectedConstruction != sonar) { return; } if (Character.Controlled.SelectedConstruction != sonar) { return; }
if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; } if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; }
EnqueueHint("onsonarspottedenemy"); DisplayHint("onsonarspottedenemy");
} }
public static void OnAfflictionDisplayed(Character character, List<Affliction> displayedAfflictions) public static void OnAfflictionDisplayed(Character character, List<Affliction> displayedAfflictions)
@@ -305,7 +277,8 @@ namespace Barotrauma
if (affliction.Prefab.IsBuff) { continue; } if (affliction.Prefab.IsBuff) { continue; }
if (affliction.Prefab == AfflictionPrefab.OxygenLow) { continue; } if (affliction.Prefab == AfflictionPrefab.OxygenLow) { continue; }
if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.IsEntityRadiated(character) ?? false)) { continue; } if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.IsEntityRadiated(character) ?? false)) { continue; }
EnqueueHint("onafflictiondisplayed", if (affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
DisplayHint("onafflictiondisplayed",
variableTags: new string[1] { "[key]" }, variableTags: new string[1] { "[key]" },
variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Health) }, variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Health) },
icon: affliction.Prefab.Icon, icon: affliction.Prefab.Icon,
@@ -325,12 +298,13 @@ namespace Barotrauma
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
if (character.SelectedConstruction != null || character.FocusedItem != null) { return; } if (character.SelectedConstruction != null || character.FocusedItem != null) { return; }
if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; } if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; }
if (GUI.MouseOn != null) { return; }
if (TimeStoppedInteracting + 1 > Timing.TotalTime) { 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"; string hintIdentifier = "onshootwithoutaiming";
if (!HintTags.TryGetValue(hintIdentifier, out var tags)) { return; } if (!HintTags.TryGetValue(hintIdentifier, out var tags)) { return; }
if (!item.HasTag(tags)) { return; } if (!item.HasTag(tags)) { return; }
EnqueueHint(hintIdentifier, DisplayHint(hintIdentifier,
variableTags: new string[1] { "[key]" }, variableTags: new string[1] { "[key]" },
variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Aim) }, variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Aim) },
onUpdate: () => onUpdate: () =>
@@ -347,14 +321,14 @@ namespace Barotrauma
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
if (door == null || door.Stuck < 20.0f) { return; } if (door == null || door.Stuck < 20.0f) { return; }
EnqueueHint("onweldingdoor"); DisplayHint("onweldingdoor");
} }
public static void OnTryOpenStuckDoor(Character character) public static void OnTryOpenStuckDoor(Character character)
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
EnqueueHint("ontryopenstuckdoor"); DisplayHint("ontryopenstuckdoor");
} }
public static void OnShowCampaignInterface(CampaignMode.InteractionType interactionType) public static void OnShowCampaignInterface(CampaignMode.InteractionType interactionType)
@@ -362,36 +336,34 @@ namespace Barotrauma
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (interactionType == CampaignMode.InteractionType.None) { return; } if (interactionType == CampaignMode.InteractionType.None) { return; }
string hintIdentifier = $"onshowcampaigninterface.{interactionType.ToString().ToLowerInvariant()}"; string hintIdentifier = $"onshowcampaigninterface.{interactionType.ToString().ToLowerInvariant()}";
EnqueueHint(hintIdentifier, DisplayHint(hintIdentifier, onUpdate: () =>
onUpdate: () => {
{
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign) || if (!(GameMain.GameSession?.Campaign is CampaignMode campaign) ||
(!campaign.ShowCampaignUI && !campaign.ForceMapUI) || (!campaign.ShowCampaignUI && !campaign.ForceMapUI) ||
campaign.CampaignUI?.SelectedTab != CampaignMode.InteractionType.Map) campaign.CampaignUI?.SelectedTab != CampaignMode.InteractionType.Map)
{ {
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
} }
}); });
} }
public static void OnShowCommandInterface() public static void OnShowCommandInterface()
{ {
IgnoreReminder("commandinterface"); IgnoreReminder("commandinterface");
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
EnqueueHint("onshowcommandinterface", DisplayHint("onshowcommandinterface", onUpdate: () =>
onUpdate: () => {
{ if (CrewManager.IsCommandInterfaceOpen) { return; }
if (CrewManager.IsCommandInterfaceOpen) { return; } ActiveHintMessageBox.Close();
ActiveHintMessageBox.Close(); });
});
} }
public static void OnShowHealthInterface() public static void OnShowHealthInterface()
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (CharacterHealth.OpenHealthWindow == null) { return; } if (CharacterHealth.OpenHealthWindow == null) { return; }
EnqueueHint("onshowhealthinterface", onUpdate: () => DisplayHint("onshowhealthinterface", onUpdate: () =>
{ {
if (CharacterHealth.OpenHealthWindow != null) { return; } if (CharacterHealth.OpenHealthWindow != null) { return; }
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
@@ -408,7 +380,7 @@ namespace Barotrauma
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
if (item == null || !item.SpawnedInOutpost || !item.StolenDuringRound) { return; } if (item == null || !item.SpawnedInOutpost || !item.StolenDuringRound) { return; }
EnqueueHint("onstoleitem", onUpdate: () => DisplayHint("onstoleitem", onUpdate: () =>
{ {
if (item == null || item.Removed || item.GetRootInventoryOwner() != character) if (item == null || item.Removed || item.GetRootInventoryOwner() != character)
{ {
@@ -421,7 +393,7 @@ namespace Barotrauma
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (character != Character.Controlled || !character.LockHands) { return; } if (character != Character.Controlled || !character.LockHands) { return; }
EnqueueHint("onhandcuffed", onUpdate: () => DisplayHint("onhandcuffed", onUpdate: () =>
{ {
if (character != null && !character.Removed && character.LockHands) { return; } if (character != null && !character.Removed && character.LockHands) { return; }
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
@@ -434,7 +406,7 @@ namespace Barotrauma
if (reactor == null) { return; } if (reactor == null) { return; }
if (reactor.Item.Submarine?.Info?.Type != SubmarineType.Player || reactor.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; } if (reactor.Item.Submarine?.Info?.Type != SubmarineType.Player || reactor.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; }
if (!HasValidJob("engineer")) { return; } if (!HasValidJob("engineer")) { return; }
EnqueueHint("onreactoroutoffuel", onUpdate: () => DisplayHint("onreactoroutoffuel", onUpdate: () =>
{ {
if (reactor?.Item != null && !reactor.Item.Removed && reactor.AvailableFuel < 1) { return; } if (reactor?.Item != null && !reactor.Item.Removed && reactor.AvailableFuel < 1) { return; }
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
@@ -445,7 +417,7 @@ namespace Barotrauma
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (transitionType == CampaignMode.TransitionType.None) { return; } if (transitionType == CampaignMode.TransitionType.None) { return; }
EnqueueHint($"onavailabletransition.{transitionType.ToString().ToLowerInvariant()}"); DisplayHint($"onavailabletransition.{transitionType.ToString().ToLowerInvariant()}");
} }
public static void OnShowSubInventory(Item item) public static void OnShowSubInventory(Item item)
@@ -462,12 +434,31 @@ namespace Barotrauma
IgnoreReminder("characterchange"); 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() private static void OnStartedControlling()
{ {
if (Level.IsLoadedOutpost) { return; } if (Level.IsLoadedOutpost) { return; }
if (Character.Controlled?.Info?.Job?.Prefab == null) { return; } if (Character.Controlled?.Info?.Job?.Prefab == null) { return; }
string hintIdentifier = $"onstartedcontrolling.job.{Character.Controlled.Info.Job.Prefab.Identifier}"; string hintIdentifier = $"onstartedcontrolling.job.{Character.Controlled.Info.Job.Prefab.Identifier}";
EnqueueHint(hintIdentifier, DisplayHint(hintIdentifier,
icon: Character.Controlled.Info.Job.Prefab.Icon, icon: Character.Controlled.Info.Job.Prefab.Icon,
iconColor: Character.Controlled.Info.Job.Prefab.UIColor, iconColor: Character.Controlled.Info.Job.Prefab.UIColor,
onDisplay: () => onDisplay: () =>
@@ -500,7 +491,7 @@ namespace Barotrauma
if (!steering.SteeringPath.Finished && steering.SteeringPath.NextNode != null) { return; } if (!steering.SteeringPath.Finished && steering.SteeringPath.NextNode != null) { return; }
if (steering.LevelStartSelected && (Level.Loaded.StartOutpost == null || !steering.Item.Submarine.AtStartExit)) { 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; } if (steering.LevelEndSelected && (Level.Loaded.EndOutpost == null || !steering.Item.Submarine.AtEndExit)) { return; }
EnqueueHint("onautopilotreachedoutpost"); DisplayHint("onautopilotreachedoutpost");
} }
public static void OnStatusEffectApplied(ItemComponent component, ActionType actionType, Character character) public static void OnStatusEffectApplied(ItemComponent component, ActionType actionType, Character character)
@@ -509,7 +500,7 @@ namespace Barotrauma
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
// Could make this more generic if there will ever be any other status effect related hints // Could make this more generic if there will ever be any other status effect related hints
if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; } if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; }
EnqueueHint("onrepairfailed"); DisplayHint("onrepairfailed");
} }
private static void CheckIfDivingGearOutOfOxygen() private static void CheckIfDivingGearOutOfOxygen()
@@ -517,12 +508,12 @@ namespace Barotrauma
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
var divingGear = Character.Controlled.GetEquippedItem("diving"); var divingGear = Character.Controlled.GetEquippedItem("diving");
if (divingGear?.OwnInventory == null) { return; } if (divingGear?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.05f) { return; } if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; }
EnqueueHint("ondivinggearoutofoxygen", onUpdate: () => DisplayHint("ondivinggearoutofoxygen", onUpdate: () =>
{ {
if (divingGear == null || divingGear.Removed || if (divingGear == null || divingGear.Removed ||
Character.Controlled == null || !Character.Controlled.HasEquippedItem(divingGear) || Character.Controlled == null || !Character.Controlled.HasEquippedItem(divingGear) ||
divingGear.GetContainedItemConditionPercentage() > 0.05f) divingGear.GetContainedItemConditionPercentage() > 0.0f)
{ {
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
} }
@@ -535,15 +526,21 @@ namespace Barotrauma
if (Character.Controlled.CurrentHull == null) { return; } if (Character.Controlled.CurrentHull == null) { return; }
foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps) foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps)
{ {
if (!gap.IsRoomToRoom) { continue; }
if (gap.ConnectedDoor == null || gap.ConnectedDoor.Impassable) { continue; } if (gap.ConnectedDoor == null || gap.ConnectedDoor.Impassable) { continue; }
if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { 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) foreach (var me in gap.linkedTo)
{ {
if (me == Character.Controlled.CurrentHull) { continue; } if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; } if (!(me is Hull adjacentHull)) { continue; }
if (adjacentHull.LethalPressure > 5.0f && EnqueueHint("onadjacenthull.highpressure")) { return; } if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; }
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && EnqueueHint("onadjacenthull.highwaterpercentage")) { return; } if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; }
} }
} }
} }
@@ -552,23 +549,23 @@ namespace Barotrauma
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (Level.Loaded == null) { return; } if (Level.Loaded == null) { return; }
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + TimeBeforeReminders) { return; } if (GameMain.GameScreen.GameTime < TimeRoundStarted + TimeBeforeReminders) { return; }
if (Timing.TotalTime < TimeReminderLastDisplayed + ReminderCooldown) { return; } if (GameMain.GameScreen.GameTime < TimeReminderLastDisplayed + ReminderCooldown) { return; }
string hintIdentifierBase = "reminder"; string hintIdentifierBase = "reminder";
if (GameMain.GameSession.GameMode.IsSinglePlayer) if (GameMain.GameSession.GameMode.IsSinglePlayer)
{ {
if (EnqueueHint($"{hintIdentifierBase}.characterchange")) if (DisplayHint($"{hintIdentifierBase}.characterchange"))
{ {
TimeReminderLastDisplayed = Timing.TotalTime; TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return; return;
} }
} }
if (Level.Loaded.Type != LevelData.LevelType.Outpost) if (Level.Loaded.Type != LevelData.LevelType.Outpost)
{ {
if (EnqueueHint($"{hintIdentifierBase}.commandinterface", if (DisplayHint($"{hintIdentifierBase}.commandinterface",
variableTags: new string[] { "[commandkey]" }, variableTags: new string[] { "[commandkey]" },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Command) }, variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Command) },
onUpdate: () => onUpdate: () =>
@@ -577,12 +574,12 @@ namespace Barotrauma
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
})) }))
{ {
TimeReminderLastDisplayed = Timing.TotalTime; TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return; return;
} }
} }
if (EnqueueHint($"{hintIdentifierBase}.tabmenu", if (DisplayHint($"{hintIdentifierBase}.tabmenu",
variableTags: new string[] { "[infotabkey]" }, variableTags: new string[] { "[infotabkey]" },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.InfoTab) }, variableValues: new string[] { GameMain.Config.KeyBindText(InputType.InfoTab) },
onUpdate: () => onUpdate: () =>
@@ -591,21 +588,21 @@ namespace Barotrauma
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
})) }))
{ {
TimeReminderLastDisplayed = Timing.TotalTime; TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return; return;
} }
if (Character.Controlled.Inventory?.GetItemInLimbSlot(InvSlotType.Bag)?.Prefab?.Identifier == "toolbelt") if (Character.Controlled.Inventory?.GetItemInLimbSlot(InvSlotType.Bag)?.Prefab?.Identifier == "toolbelt")
{ {
if (EnqueueHint($"{hintIdentifierBase}.toolbelt")) if (DisplayHint($"{hintIdentifierBase}.toolbelt"))
{ {
TimeReminderLastDisplayed = Timing.TotalTime; TimeReminderLastDisplayed = GameMain.GameScreen.GameTime;
return; return;
} }
} }
} }
private static bool EnqueueHint(string hintIdentifier, string[] variableTags = null, string[] variableValues = null, Sprite icon = null, Color? iconColor = null, Action onDisplay = null, Action onUpdate = null) 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 (string.IsNullOrEmpty(hintIdentifier)) { return false; }
if (!HintIdentifiers.Contains(hintIdentifier)) { return false; } if (!HintIdentifiers.Contains(hintIdentifier)) { return false; }
@@ -613,7 +610,7 @@ namespace Barotrauma
if (HintsIgnoredThisRound.Contains(hintIdentifier)) { return false; } if (HintsIgnoredThisRound.Contains(hintIdentifier)) { return false; }
string text; string text;
string textTag = $"hint.{hintIdentifier}"; string textTag = extendTextTag ? $"hint.{hintIdentifier}" : hintIdentifier;
if (variableTags != null && variableTags != null && variableTags.Length > 0 && variableTags.Length == variableValues.Length) if (variableTags != null && variableTags != null && variableTags.Length > 0 && variableTags.Length == variableValues.Length)
{ {
text = TextManager.GetWithVariables(textTag, variableTags, variableValues, returnNull: true); text = TextManager.GetWithVariables(textTag, variableTags, variableValues, returnNull: true);
@@ -631,9 +628,16 @@ namespace Barotrauma
return false; return false;
} }
var hint = new HintInfo(hintIdentifier, text, icon, iconColor, onDisplay, onUpdate);
HintQueue.Enqueue(hint);
HintsIgnoredThisRound.Add(hintIdentifier); 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; return true;
} }
@@ -678,6 +682,7 @@ namespace Barotrauma
{ {
if (HintIdentifiers == null) { return false; } if (HintIdentifiers == null) { return false; }
if (GameMain.Config.DisableInGameHints) { return false; } if (GameMain.Config.DisableInGameHints) { return false; }
if (ActiveHintMessageBox != null) { return false; }
if (requireControllingCharacter && Character.Controlled == null) { return false; } if (requireControllingCharacter && Character.Controlled == null) { return false; }
var gameMode = GameMain.GameSession?.GameMode; var gameMode = GameMain.GameSession?.GameMode;
if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; } if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; }
@@ -266,7 +266,9 @@ namespace Barotrauma
displayedMission.Description; displayedMission.Description;
GUIImage missionIcon = new GUIImage(new RectTransform(new Point((int)(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
}; };
missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.9f)); missionIcon.RectTransform.MinSize = new Point((int)(missionContentHorizontal.Rect.Height * 0.9f));
if (selectedMissions.Contains(displayedMission)) if (selectedMissions.Contains(displayedMission))
@@ -278,8 +280,27 @@ namespace Barotrauma
{ {
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), var missionNameTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
displayedMission.Name, font: GUI.SubHeadingFont); 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),
missionMessage, wrap: true, parseRichText: true); missionMessage, wrap: true, parseRichText: true);
if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && displayedMission.Reward > 0) if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && displayedMission.Reward > 0)
@@ -430,7 +451,7 @@ namespace Barotrauma
Faction unlockFaction = null; Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction)) if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction))
{ {
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.UnlockPathFaction); unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier.Equals(unlockEvent.UnlockPathFaction, StringComparison.OrdinalIgnoreCase));
unlockReputation = unlockFaction?.Reputation; unlockReputation = unlockFaction?.Reputation;
} }
float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation); float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation);
@@ -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]; }
@@ -381,15 +381,11 @@ namespace Barotrauma.Items.Components
private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{ {
if (!outputContainer.Inventory.IsEmpty()) { return; }
overlayComponent.RectTransform.SetAsLastChild(); overlayComponent.RectTransform.SetAsLastChild();
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)
@@ -402,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)
@@ -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 }
} }
}; };
@@ -674,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];
@@ -684,12 +691,44 @@ 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)
{
float dist = Vector2.Distance(c.WorldPosition, transducerCenter);
Vector2 targetDir = (c.WorldPosition - transducerCenter) / 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;
@@ -946,6 +985,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",
@@ -1045,15 +1097,17 @@ 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; }
//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;
} }
@@ -1555,12 +1609,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,
@@ -1778,6 +1832,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)
{ {
@@ -727,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)
@@ -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);
} }
@@ -390,5 +372,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)
@@ -67,17 +67,19 @@ namespace Barotrauma.Items.Components
item.SendSignal(input, "signal_out"); 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)
{ {
@@ -302,7 +302,7 @@ namespace Barotrauma
} }
if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; } if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; }
} }
if (itemsInSlot.Count() > 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‖";
@@ -569,47 +569,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);
}
if (Screen.Selected == GameMain.GameScreen)
{
DraggingItems.RemoveAll(it => it.NonInteractable || it.NonPlayerTeamInteractable);
} }
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);
}
if (Screen.Selected == GameMain.GameScreen)
{
doubleClickedItems.RemoveAll(it => it.NonInteractable || it.NonPlayerTeamInteractable);
} }
} }
} }
@@ -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)
}; };
@@ -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();
@@ -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;
@@ -495,9 +496,10 @@ namespace Barotrauma.Lights
if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) return; if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) return;
if (ViewTarget == null) return; if (ViewTarget == null) return;
if (Character.Controlled == null) { DebugConsole.NewMessage("aaa", Color.Orange); }
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 +509,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();
//-------------------------------------- //--------------------------------------
@@ -465,7 +465,7 @@ namespace Barotrauma
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);
@@ -677,11 +677,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.X = Math.Max(size.X, (4.0f / 3.0f) * repBarSize.X);
size.Y += 2 * repLabelSize.Y + GUI.IntScale(5) + repBarSize.Y; size.Y += 2 * repLabelSize.Y + GUI.IntScale(5) + repBarSize.Y;
repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false); repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
size.X = Math.Max(size.X, repBarSize.X + GUI.Font.MeasureString(repValueText).X + GUI.IntScale(10));
} }
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);
@@ -696,7 +695,7 @@ namespace Barotrauma
topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10)); 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, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue)); GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue));
} }
} }
@@ -912,7 +911,7 @@ namespace Barotrauma
Faction unlockFaction = null; Faction unlockFaction = null;
if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction)) if (!string.IsNullOrEmpty(unlockEvent.UnlockPathFaction))
{ {
unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier == unlockEvent.UnlockPathFaction); unlockFaction = GameMain.GameSession.Campaign.Factions.Find(f => f.Prefab.Identifier.Equals(unlockEvent.UnlockPathFaction, StringComparison.OrdinalIgnoreCase));
unlockReputation = unlockFaction?.Reputation; unlockReputation = unlockFaction?.Reputation;
} }
@@ -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
{ {
@@ -95,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)
@@ -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
@@ -32,24 +33,45 @@ namespace Barotrauma
} }
} }
public void CreatePreviewWindow(GUIComponent parent) public void CreatePreviewWindow(GUIComponent parent)
{ {
var content = new GUIButton(new RectTransform(Vector2.One, parent.RectTransform), style: null) var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null);
var previewButton = new GUIButton(new RectTransform(new Vector2(1f, 0.5f), content.RectTransform), style: null)
{ {
OnClicked = (btn, obj) => { SubmarinePreview.Create(this); return false; } OnClicked = (btn, obj) => { SubmarinePreview.Create(this); return false; },
}; };
if (PreviewImage == null) var previewImage = PreviewImage ?? savedSubmarines.Find(s => s.Name.Equals(Name, StringComparison.OrdinalIgnoreCase))?.PreviewImage;
if (previewImage == null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded")); 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 };
} }
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",
@@ -59,36 +81,25 @@ 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, includesDescription: 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 includesDescription = 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); if (includeTitle)
{
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);
}
var submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) { CanBeFocused = false }; var 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); submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y);
@@ -152,8 +163,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 (includesDescription)
{
//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));
} }
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.IO; using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -83,28 +84,94 @@ namespace Barotrauma
}; };
var innerFrame = new GUIFrame(new RectTransform(Vector2.One * 0.9f, previewFrame.RectTransform, Anchor.Center)); var innerFrame = new GUIFrame(new RectTransform(Vector2.One * 0.9f, previewFrame.RectTransform, Anchor.Center));
var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, innerFrame.RectTransform, Anchor.Center), isHorizontal: false); int innerPadding = GUI.IntScale(100f);
var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), verticalLayout.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft); var innerPadded = new GUIFrame(new RectTransform(new Point(innerFrame.Rect.Width - innerPadding, innerFrame.Rect.Height - innerPadding), previewFrame.RectTransform, Anchor.Center), style: null)
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; } OutlineColor = Color.Black,
OutlineThickness = 2
}; };
new GUICustomComponent(new RectTransform(new Vector2(1f, 0.9f), verticalLayout.RectTransform, Anchor.Center), GUITextBlock titleText = null;
(spriteBatch, component) => { camera.UpdateTransform(true); RenderSubmarine(spriteBatch, component.Rect); }, 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) => { (deltaTime, component) => {
camera.MoveCamera(deltaTime, overrideMouseOn: component.Rect); bool isMouseOnComponent = GUI.MouseOn == component;
if (component.Rect.Contains(PlayerInput.MousePosition) && camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent);
if (isMouseOnComponent &&
(PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld())) (PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld()))
{ {
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom; Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
moveSpeed.X = -moveSpeed.X; moveSpeed.X = -moveSpeed.X;
camera.Position += moveSpeed; 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, includesDescription: 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(); GeneratePreviewMeshes();
} }
@@ -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 = "";
@@ -114,7 +114,7 @@ namespace Barotrauma.Networking
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
@@ -155,11 +155,11 @@ namespace Barotrauma.Networking
} }
} }
if (NetIdUtils.IdMoreRecent(ID, LastID)) if (NetIdUtils.IdMoreRecent(id, LastID))
{ {
GameMain.Client.AddChatMessage( GameMain.Client.AddChatMessage(
new OrderChatMessage(orderPrefab, orderOption, orderPriority, 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:
@@ -171,7 +171,7 @@ namespace Barotrauma.Networking
break; break;
} }
if (NetIdUtils.IdMoreRecent(ID, LastID)) if (NetIdUtils.IdMoreRecent(id, LastID))
{ {
switch (type) switch (type)
{ {
@@ -200,7 +200,7 @@ namespace Barotrauma.Networking
GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter, changeType); GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter, changeType);
break; break;
} }
LastID = ID; LastID = id;
} }
} }
} }
@@ -159,6 +159,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 +191,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,
@@ -1424,6 +1430,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();
@@ -1690,13 +1698,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;
@@ -1739,6 +1749,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;
@@ -1965,14 +1976,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; }
@@ -2452,6 +2468,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();
@@ -3143,11 +3168,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,
@@ -3255,9 +3280,10 @@ namespace Barotrauma.Networking
if (respawnManager != null) if (respawnManager != null)
{ {
string respawnText = ""; string respawnText = string.Empty;
float textScale = 1.0f; 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;
if (respawnManager.CurrentState == RespawnManager.State.Waiting && if (respawnManager.CurrentState == RespawnManager.State.Waiting &&
respawnManager.RespawnCountdownStarted) respawnManager.RespawnCountdownStarted)
{ {
@@ -3275,18 +3301,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));
@@ -473,7 +473,36 @@ 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), mission.GetMissionRewardText(), wrap: true, parseRichText: true); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission.GetMissionRewardText(), wrap: true, parseRichText: true);
@@ -494,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())
@@ -171,10 +171,7 @@ namespace Barotrauma
Character.Controlled.ObstructVision && Character.Controlled.ObstructVision &&
(Character.Controlled.ViewTarget == Character.Controlled || Character.Controlled.ViewTarget == null); (Character.Controlled.ViewTarget == Character.Controlled || Character.Controlled.ViewTarget == null);
if (Character.Controlled != null) GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled?.CursorWorldPosition ?? Vector2.Zero);
{
GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled.CursorWorldPosition);
}
//------------------------------------------------------------------------ //------------------------------------------------------------------------
graphics.SetRenderTarget(renderTarget); graphics.SetRenderTarget(renderTarget);
@@ -334,12 +331,13 @@ namespace Barotrauma
} }
spriteBatch.End(); spriteBatch.End();
if (GameMain.LightManager.LosEnabled && GameMain.LightManager.LosMode != LosMode.None && Character.Controlled != null) if (GameMain.LightManager.LosEnabled && GameMain.LightManager.LosMode != LosMode.None && Lights.LightManager.ViewTarget != null)
{ {
GameMain.LightManager.LosEffect.CurrentTechnique = GameMain.LightManager.LosEffect.Techniques["LosShader"]; GameMain.LightManager.LosEffect.CurrentTechnique = GameMain.LightManager.LosEffect.Techniques["LosShader"];
GameMain.LightManager.LosEffect.Parameters["xTexture"].SetValue(renderTargetBackground); GameMain.LightManager.LosEffect.Parameters["xTexture"].SetValue(renderTargetBackground);
GameMain.LightManager.LosEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.LosTexture); GameMain.LightManager.LosEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.LosTexture);
GameMain.LightManager.LosEffect.Parameters["xLosAlpha"].SetValue(GameMain.LightManager.LosAlpha);
Color losColor; Color losColor;
if (GameMain.LightManager.LosMode == LosMode.Transparent) if (GameMain.LightManager.LosMode == LosMode.Transparent)
@@ -348,6 +348,16 @@ namespace Barotrauma
CanBeFocused = false CanBeFocused = false
}; };
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("EditorDisclaimerWikiLink"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = true,
OnClicked = (button, userData) =>
{
string url = TextManager.Get("EditorDisclaimerWikiUrl", returnNull: true) ?? "https://barotraumagame.com/wiki";
GameMain.Instance.ShowOpenUrlInWebBrowserPrompt(url, promptExtensionTag: "wikinotice");
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{ {
ForceUpperCase = true, ForceUpperCase = true,
@@ -1305,9 +1305,9 @@ namespace Barotrauma
StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !CampaignSetupFrame.Visible && !CampaignFrame.Visible; StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !CampaignSetupFrame.Visible && !CampaignFrame.Visible;
ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); ServerName.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings); ServerMessage.Readonly = !GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
shuttleTickBox.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.ManageSettings); shuttleTickBox.Enabled = GameMain.Client.HasPermission(ClientPermissions.ManageSettings);
SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub)); SubList.Enabled = !CampaignFrame.Visible && (GameMain.Client.ServerSettings.Voting.AllowSubVoting || GameMain.Client.HasPermission(ClientPermissions.SelectSub));
shuttleList.Enabled = shuttleList.ButtonEnabled = shuttleTickBox.Enabled = !CampaignFrame.Visible && !CampaignSetupFrame.Visible && GameMain.Client.HasPermission(ClientPermissions.SelectSub); shuttleList.Enabled = shuttleList.ButtonEnabled = GameMain.Client.HasPermission(ClientPermissions.SelectSub);
ModeList.Enabled = GameMain.Client.ServerSettings.Voting.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode); ModeList.Enabled = GameMain.Client.ServerSettings.Voting.AllowModeVoting || GameMain.Client.HasPermission(ClientPermissions.SelectMode);
LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
@@ -533,20 +533,6 @@ namespace Barotrauma
OnSelected = (GUITickBox obj) => OnSelected = (GUITickBox obj) =>
{ {
lightingEnabled = obj.Selected; lightingEnabled = obj.Selected;
if (lightingEnabled)
{
//turn off lights that are inside containers
foreach (Item item in Item.ItemList)
{
foreach (LightComponent lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ?
Color.Transparent :
lightComponent.LightColor;
lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects;
}
}
}
return true; return true;
} }
}; };
@@ -2704,16 +2690,6 @@ namespace Barotrauma
cam.Position = Submarine.MainSub.Position + Submarine.MainSub.HiddenSubPosition; cam.Position = Submarine.MainSub.Position + Submarine.MainSub.HiddenSubPosition;
loadFrame = null; loadFrame = null;
//turn off lights that are inside an inventory (cabinet for example)
foreach (Item item in Item.ItemList)
{
var lightComponent = item.GetComponent<LightComponent>();
if (lightComponent != null)
{
lightComponent.Light.Enabled = item.ParentInventory == null;
}
}
} }
private bool LoadSub(GUIButton button, object obj) private bool LoadSub(GUIButton button, object obj)
@@ -2748,16 +2724,6 @@ namespace Barotrauma
loadFrame = null; loadFrame = null;
//turn off lights that are inside an inventory (cabinet for example)
foreach (Item item in Item.ItemList)
{
var lightComponent = item.GetComponent<LightComponent>();
if (lightComponent != null)
{
lightComponent.Light.Enabled = item.ParentInventory == null;
}
}
if (selectedSub.Info.GameVersion < new Version("0.8.9.0")) if (selectedSub.Info.GameVersion < new Version("0.8.9.0"))
{ {
var adjustLightsPrompt = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("AdjustLightsPrompt"), var adjustLightsPrompt = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("AdjustLightsPrompt"),
@@ -2857,8 +2823,8 @@ namespace Barotrauma
categorizedEntityList.UpdateScrollBarSize(); categorizedEntityList.UpdateScrollBarSize();
categorizedEntityList.BarScroll = 0.0f; categorizedEntityList.BarScroll = 0.0f;
categorizedEntityList.Visible = true; // categorizedEntityList.Visible = true;
allEntityList.Visible = false; // allEntityList.Visible = false;
} }
private void FilterEntities(string filter) private void FilterEntities(string filter)
@@ -3047,6 +3013,37 @@ namespace Barotrauma
public static GUIMessageBox CreatePropertyColorPicker(Color originalColor, SerializableProperty property, ISerializableEntity entity) public static GUIMessageBox CreatePropertyColorPicker(Color originalColor, SerializableProperty property, ISerializableEntity entity)
{ {
var entities = new List<(ISerializableEntity Entity, Color OriginalColor, SerializableProperty Property)> { (entity, originalColor, property) };
foreach (ISerializableEntity selectedEntity in MapEntity.SelectedList.Where(selectedEntity => selectedEntity is ISerializableEntity && entity != selectedEntity).Cast<ISerializableEntity>())
{
switch (entity)
{
case ItemComponent _ when selectedEntity is Item item:
foreach (var component in item.Components)
{
if (component.GetType() == entity.GetType() && component != entity)
{
entities.Add((component, (Color) property.GetValue(component), property));
}
}
break;
default:
if (selectedEntity.GetType() == entity.GetType())
{
entities.Add((selectedEntity, (Color) property.GetValue(selectedEntity), property));
}
else if (selectedEntity is { SerializableProperties: { } props} )
{
if (props.TryGetValue(property.NameToLowerInvariant, out SerializableProperty foundProp))
{
entities.Add((selectedEntity, (Color) foundProp.GetValue(selectedEntity), foundProp));
}
}
break;
}
}
bool setValues = true; bool setValues = true;
object sliderMutex = new object(), object sliderMutex = new object(),
sliderTextMutex = new object(), sliderTextMutex = new object(),
@@ -3142,9 +3139,22 @@ namespace Barotrauma
colorPicker.DisposeTextures(); colorPicker.DisposeTextures();
msgBox.Close(); msgBox.Close();
if (entity is MapEntity { Removed: true } me) { return true; }
Color newColor = SetColor(null); Color newColor = SetColor(null);
StoreCommand(new PropertyCommand(entity, property.Name, newColor, originalColor));
Dictionary<object, List<ISerializableEntity>> oldProperties = new Dictionary<object, List<ISerializableEntity>>();
foreach (var (sEntity, color, _) in entities)
{
if (sEntity is MapEntity { Removed: true }) { continue; }
if (!oldProperties.ContainsKey(color))
{
oldProperties.Add(color, new List<ISerializableEntity>());
}
oldProperties[color].Add(sEntity);
}
List<ISerializableEntity> affected = entities.Select(t => t.Entity).Where(se => se is MapEntity { Removed: false }).ToList();
StoreCommand(new PropertyCommand(affected, property.Name, newColor, oldProperties));
if (MapEntity.EditingHUD != null && (MapEntity.EditingHUD.UserData == entity || (!(entity is ItemComponent ic) || MapEntity.EditingHUD.UserData == ic.Item))) if (MapEntity.EditingHUD != null && (MapEntity.EditingHUD.UserData == entity || (!(entity is ItemComponent ic) || MapEntity.EditingHUD.UserData == ic.Item)))
{ {
@@ -3170,8 +3180,12 @@ namespace Barotrauma
{ {
colorPicker.DisposeTextures(); colorPicker.DisposeTextures();
msgBox.Close(); msgBox.Close();
if (entity is MapEntity { Removed: true } me) { return true; }
property.SetValue(entity, originalColor); foreach (var (e, color, prop) in entities)
{
if (e is MapEntity { Removed: true }) { continue; }
prop.TrySetValue(e, color);
}
return true; return true;
}; };
@@ -3218,8 +3232,12 @@ namespace Barotrauma
} }
Color color = ToolBox.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue); Color color = ToolBox.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue);
color.A = originalColor.A; foreach (var (e, origColor, prop) in entities)
property.TrySetValue(entity, color); {
if (e is MapEntity { Removed: true }) { continue; }
color.A = origColor.A;
prop.TrySetValue(e, color);
}
return color; return color;
void SetSliders(Vector3 hsv) void SetSliders(Vector3 hsv)
@@ -3238,9 +3256,11 @@ namespace Barotrauma
void SetColorPicker(Vector3 hsv) void SetColorPicker(Vector3 hsv)
{ {
bool hueChanged = !MathUtils.NearlyEqual(colorPicker.SelectedHue, hsv.X);
colorPicker.SelectedHue = hsv.X; colorPicker.SelectedHue = hsv.X;
colorPicker.SelectedSaturation = hsv.Y; colorPicker.SelectedSaturation = hsv.Y;
colorPicker.SelectedValue = hsv.Z; colorPicker.SelectedValue = hsv.Z;
if (hueChanged) { colorPicker.RefreshHue(); }
} }
void SetHex(Vector3 hsv) void SetHex(Vector3 hsv)
@@ -3496,6 +3516,8 @@ namespace Barotrauma
private bool SelectPrefab(GUIComponent component, object obj) private bool SelectPrefab(GUIComponent component, object obj)
{ {
allEntityList.Deselect();
categorizedEntityList.Deselect();
if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) { return false; } if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) { return false; }
AddPreviouslyUsed(obj as MapEntityPrefab); AddPreviouslyUsed(obj as MapEntityPrefab);
@@ -3586,7 +3608,7 @@ namespace Barotrauma
SoundPlayer.PlayUISound(GUISoundType.PickItem); SoundPlayer.PlayUISound(GUISoundType.PickItem);
MapEntityPrefab.SelectPrefab(obj); MapEntityPrefab.SelectPrefab(obj);
} }
return false; return false;
} }
@@ -4332,6 +4354,17 @@ namespace Barotrauma
if (lightingEnabled) if (lightingEnabled)
{ {
//turn off lights that are inside containers
foreach (Item item in Item.ItemList)
{
foreach (LightComponent lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ?
Color.Transparent :
lightComponent.LightColor;
lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects;
}
}
GameMain.LightManager?.Update((float)deltaTime); GameMain.LightManager?.Update((float)deltaTime);
} }
@@ -1208,7 +1208,7 @@ namespace Barotrauma
SafeAdd((ISerializableEntity) entity, property); SafeAdd((ISerializableEntity) entity, property);
property.PropertyInfo.SetValue(entity, value); property.PropertyInfo.SetValue(entity, value);
} }
else if (entity is ISerializableEntity sEntity && sEntity.SerializableProperties != null) else if (entity is ISerializableEntity { SerializableProperties: { } } sEntity)
{ {
var props = sEntity.SerializableProperties; var props = sEntity.SerializableProperties;
@@ -30,6 +30,7 @@ namespace Barotrauma.Sounds
public override int FillStreamBuffer(int samplePos, short[] buffer) public override int FillStreamBuffer(int samplePos, short[] buffer)
{ {
if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!");
if (reader == null) throw new Exception("Called FillStreamBuffer when the reader is null!");
if (samplePos >= reader.TotalSamples * reader.Channels * 2) return 0; if (samplePos >= reader.TotalSamples * reader.Channels * 2) return 0;
@@ -73,6 +74,8 @@ namespace Barotrauma.Sounds
{ {
if (!Stream) if (!Stream)
{ {
reader ??= new VorbisReader(Filename);
reader.DecodedPosition = 0; reader.DecodedPosition = 0;
int bufferSize = (int)reader.TotalSamples * reader.Channels; int bufferSize = (int)reader.TotalSamples * reader.Channels;
@@ -126,7 +129,7 @@ namespace Barotrauma.Sounds
{ {
if (Stream) if (Stream)
{ {
reader.Dispose(); reader?.Dispose();
} }
base.Dispose(); base.Dispose();
@@ -14,35 +14,15 @@ namespace Barotrauma.Sounds
get { return disposed; } get { return disposed; }
} }
public SoundManager Owner public readonly SoundManager Owner;
{
get;
protected set;
}
public string Filename public readonly string Filename;
{
get;
protected set;
}
public XElement XElement public readonly XElement XElement;
{
get;
protected set;
}
public bool Stream public readonly bool Stream;
{
get;
protected set;
}
public bool StreamsReliably public readonly bool StreamsReliably;
{
get;
protected set;
}
public virtual SoundManager.SourcePoolIndex SourcePoolIndex public virtual SoundManager.SourcePoolIndex SourcePoolIndex
{ {
@@ -79,10 +59,10 @@ namespace Barotrauma.Sounds
public float BaseNear; public float BaseNear;
public float BaseFar; public float BaseFar;
public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement=null) public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement=null, bool getFullPath=true)
{ {
Owner = owner; Owner = owner;
Filename = Path.GetFullPath(filename.CleanUpPath()).CleanUpPath(); Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename;
Stream = stream; Stream = stream;
StreamsReliably = streamsReliably; StreamsReliably = streamsReliably;
XElement = xElement; XElement = xElement;
@@ -21,12 +21,14 @@ namespace Barotrauma
public readonly string requiredTag; public readonly string requiredTag;
public DamageSound(Sound sound, Vector2 damageRange, string damageType, string requiredTag = "") public bool ignoreMuffling;
public DamageSound(Sound sound, Vector2 damageRange, string damageType, bool ignoreMuffling, string requiredTag = "")
{ {
this.sound = sound; this.sound = sound;
this.damageRange = damageRange; this.damageRange = damageRange;
this.damageType = damageType; this.damageType = damageType;
this.ignoreMuffling = ignoreMuffling;
this.requiredTag = requiredTag; this.requiredTag = requiredTag;
} }
} }
@@ -269,6 +271,7 @@ namespace Barotrauma
damageSound, damageSound,
soundElement.GetAttributeVector2("damagerange", Vector2.Zero), soundElement.GetAttributeVector2("damagerange", Vector2.Zero),
damageSoundType, damageSoundType,
soundElement.GetAttributeBool("ignoremuffling", false),
soundElement.GetAttributeString("requiredtag", ""))); soundElement.GetAttributeString("requiredtag", "")));
break; break;
@@ -531,7 +534,7 @@ namespace Barotrauma
Vector2 diff = gap.WorldPosition - listenerPos; Vector2 diff = gap.WorldPosition - listenerPos;
if (Math.Abs(diff.X) < FlowSoundRange && Math.Abs(diff.Y) < FlowSoundRange) if (Math.Abs(diff.X) < FlowSoundRange && Math.Abs(diff.Y) < FlowSoundRange)
{ {
if (gap.Open < 0.01f) { continue; } if (gap.Open < 0.01f || gap.LerpedFlowForce.LengthSquared() < 100.0f) { continue; }
float gapFlow = Math.Abs(gap.LerpedFlowForce.X) + Math.Abs(gap.LerpedFlowForce.Y) * 2.5f; float gapFlow = Math.Abs(gap.LerpedFlowForce.X) + Math.Abs(gap.LerpedFlowForce.Y) * 2.5f;
if (!gap.IsRoomToRoom) { gapFlow *= 2.0f; } if (!gap.IsRoomToRoom) { gapFlow *= 2.0f; }
if (gapFlow < 10.0f) { continue; } if (gapFlow < 10.0f) { continue; }
@@ -1123,7 +1126,11 @@ namespace Barotrauma
tempList.Add(s); tempList.Add(s);
} }
} }
tempList.GetRandom().sound?.Play(1.0f, range, position, muffle: ShouldMuffleSound(Character.Controlled, position, range, null)); var damageSound = tempList.GetRandom();
if (damageSound.sound != null)
{
damageSound.sound.Play(1.0f, range, position, muffle: !damageSound.ignoreMuffling && ShouldMuffleSound(Character.Controlled, position, range, null));
}
} }
public static void PlayUISound(GUISoundType soundType) public static void PlayUISound(GUISoundType soundType)
@@ -63,10 +63,8 @@ namespace Barotrauma.Sounds
get { return soundChannel?.CurrentAmplitude ?? 0.0f; } get { return soundChannel?.CurrentAmplitude ?? 0.0f; }
} }
public VoipSound(string name, SoundManager owner, VoipQueue q) : base(owner, "voip", true, true) public VoipSound(string name, SoundManager owner, VoipQueue q) : base(owner, $"VoIP ({name})", true, true, getFullPath: false)
{ {
Filename = $"VoIP ({name})";
VoipConfig.SetupEncoding(); VoipConfig.SetupEncoding();
ALFormat = Al.FormatMono16; ALFormat = Al.FormatMono16;
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1300.0.2</Version> <Version>0.1300.0.3</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1300.0.2</Version> <Version>0.1300.0.3</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -26,6 +26,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; }
Texture2D xLosTexture; Texture2D xLosTexture;
sampler LosSampler = sampler_state { Texture = <xLosTexture>; }; sampler LosSampler = sampler_state { Texture = <xLosTexture>; };
float xLosAlpha;
float4 xColor; float4 xColor;
float4 mainPS(VertexShaderOutput input) : COLOR0 float4 mainPS(VertexShaderOutput input) : COLOR0
@@ -39,7 +41,7 @@ float4 mainPS(VertexShaderOutput input) : COLOR0
sampleColor.r * xColor.r, sampleColor.r * xColor.r,
sampleColor.g * xColor.g, sampleColor.g * xColor.g,
sampleColor.b * xColor.b, sampleColor.b * xColor.b,
obscureAmount); obscureAmount * xLosAlpha);
return outColor; return outColor;
} }
@@ -26,6 +26,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = <xTexture>; }
Texture xLosTexture; Texture xLosTexture;
sampler LosSampler = sampler_state { Texture = <xLosTexture>; }; sampler LosSampler = sampler_state { Texture = <xLosTexture>; };
float xLosAlpha;
float4 xColor; float4 xColor;
float4 mainPS(VertexShaderOutput input) : COLOR0 float4 mainPS(VertexShaderOutput input) : COLOR0
@@ -39,7 +41,7 @@ float4 mainPS(VertexShaderOutput input) : COLOR0
sampleColor.r * xColor.r, sampleColor.r * xColor.r,
sampleColor.g * xColor.g, sampleColor.g * xColor.g,
sampleColor.b * xColor.b, sampleColor.b * xColor.b,
obscureAmount); obscureAmount * xLosAlpha);
return outColor; return outColor;
} }
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.1300.0.2</Version> <Version>0.1300.0.3</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1300.0.2</Version> <Version>0.1300.0.3</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1300.0.2</Version> <Version>0.1300.0.3</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -25,12 +25,15 @@ namespace Barotrauma.Items.Components
} }
} }
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)
{
messageHistory.RemoveAt(0);
}
} }
} }
@@ -73,6 +73,7 @@ namespace Barotrauma.Networking
public NetworkConnection Connection { get; set; } public NetworkConnection Connection { get; set; }
public bool SpectateOnly; public bool SpectateOnly;
public bool? WaitForNextRoundRespawn;
public int KarmaKickCount; public int KarmaKickCount;
@@ -486,6 +486,11 @@ namespace Barotrauma.Networking
endRoundTimer += deltaTime; endRoundTimer += deltaTime;
#endif #endif
} }
else if (isCrewDead && (GameMain.GameSession?.GameMode is CampaignMode))
{
endRoundDelay = 1.0f;
endRoundTimer += deltaTime;
}
else else
{ {
endRoundTimer = 0.0f; endRoundTimer = 0.0f;
@@ -505,10 +510,14 @@ namespace Barotrauma.Networking
{ {
Log("Ending round (submarine reached the end of the level)", ServerLog.MessageType.ServerMessage); Log("Ending round (submarine reached the end of the level)", ServerLog.MessageType.ServerMessage);
} }
else else if (respawnManager == null)
{ {
Log("Ending round (no living players left and respawning is not enabled during this round)", ServerLog.MessageType.ServerMessage); Log("Ending round (no living players left and respawning is not enabled during this round)", ServerLog.MessageType.ServerMessage);
} }
else
{
Log("Ending round (no living players left)", ServerLog.MessageType.ServerMessage);
}
EndGame(); EndGame();
return; return;
} }
@@ -819,6 +828,9 @@ namespace Barotrauma.Networking
case ClientPacketHeader.READY_CHECK: case ClientPacketHeader.READY_CHECK:
ReadyCheck.ServerRead(inc, connectedClient); ReadyCheck.ServerRead(inc, connectedClient);
break; break;
case ClientPacketHeader.READY_TO_SPAWN:
ReadReadyToSpawnMessage(inc, connectedClient);
break;
case ClientPacketHeader.FILE_REQUEST: case ClientPacketHeader.FILE_REQUEST:
if (serverSettings.AllowFileTransfers) if (serverSettings.AllowFileTransfers)
{ {
@@ -1191,6 +1203,15 @@ namespace Barotrauma.Networking
mpCampaign.ServerReadCrew(inc, sender); mpCampaign.ServerReadCrew(inc, sender);
} }
} }
private void ReadReadyToSpawnMessage(IReadMessage inc, Client sender)
{
sender.SpectateOnly = inc.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection);
sender.WaitForNextRoundRespawn = inc.ReadBoolean();
if (!(GameMain.GameSession?.GameMode is CampaignMode))
{
sender.WaitForNextRoundRespawn = null;
}
}
private void ClientReadServerCommand(IReadMessage inc) private void ClientReadServerCommand(IReadMessage inc)
{ {
@@ -2145,12 +2166,12 @@ namespace Barotrauma.Networking
} }
MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode;
bool missionAllowRespawn = GameMain.GameSession.Campaign == null && (missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn)); bool missionAllowRespawn = missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn);
bool outpostAllowRespawn = GameMain.GameSession.Campaign != null && Level.Loaded?.Type == LevelData.LevelType.Outpost; bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost;
if (serverSettings.AllowRespawn && (missionAllowRespawn || outpostAllowRespawn)) if (serverSettings.AllowRespawn && missionAllowRespawn)
{ {
respawnManager = new RespawnManager(this, serverSettings.UseRespawnShuttle && !outpostAllowRespawn ? selectedShuttle : null); respawnManager = new RespawnManager(this, serverSettings.UseRespawnShuttle && !isOutpost ? selectedShuttle : null);
} }
Level.Loaded?.SpawnNPCs(); Level.Loaded?.SpawnNPCs();
@@ -2400,9 +2421,8 @@ namespace Barotrauma.Networking
msg.Write((byte)ServerPacketHeader.STARTGAME); msg.Write((byte)ServerPacketHeader.STARTGAME);
msg.Write(seed); msg.Write(seed);
msg.Write(gameSession.GameMode.Preset.Identifier); msg.Write(gameSession.GameMode.Preset.Identifier);
bool missionAllowRespawn = campaign == null && (missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn)); bool missionAllowRespawn = missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn);
bool outpostAllowRespawn = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; msg.Write(serverSettings.AllowRespawn && missionAllowRespawn);
msg.Write(serverSettings.AllowRespawn && (missionAllowRespawn || outpostAllowRespawn));
msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowDisguises);
msg.Write(serverSettings.AllowRewiring); msg.Write(serverSettings.AllowRewiring);
msg.Write(serverSettings.LockAllDefaultWires); msg.Write(serverSettings.LockAllDefaultWires);
@@ -2563,6 +2583,7 @@ namespace Barotrauma.Networking
client.Character = null; client.Character = null;
client.HasSpawned = false; client.HasSpawned = false;
client.InGame = false; client.InGame = false;
client.WaitForNextRoundRespawn = null;
} }
} }
@@ -2798,6 +2819,7 @@ namespace Barotrauma.Networking
client.Character = null; client.Character = null;
client.HasSpawned = false; client.HasSpawned = false;
client.WaitForNextRoundRespawn = null;
client.InGame = false; client.InGame = false;
if (string.IsNullOrWhiteSpace(msg)) { msg = $"ServerMessage.ClientLeftServer~[client]={ClientLogName(client)}"; } if (string.IsNullOrWhiteSpace(msg)) { msg = $"ServerMessage.ClientLeftServer~[client]={ClientLogName(client)}"; }
@@ -18,13 +18,12 @@ namespace Barotrauma.Networking
if (!c.InGame) { continue; } if (!c.InGame) { continue; }
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { continue; } if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { continue; }
if (c.Character != null && !c.Character.IsDead) { continue; } if (c.Character != null && !c.Character.IsDead) { continue; }
//don't allow respawning if the client has previously disconnected and their corpse is still present on the server //don't allow respawning if the client has previously disconnected and their corpse is still present on the server
var matchingData = campaign?.GetClientCharacterData(c); var matchingData = campaign?.GetClientCharacterData(c);
if (matchingData != null && matchingData.HasSpawned && if (matchingData != null && matchingData.HasSpawned)
Character.CharacterList.Any(c => c.Info == matchingData.CharacterInfo && c.CauseOfDeath?.Type == CauseOfDeathType.Disconnected))
{ {
continue; if (!c.WaitForNextRoundRespawn.HasValue || c.WaitForNextRoundRespawn.Value) { continue; }
} }
yield return c; yield return c;
@@ -77,25 +76,24 @@ namespace Barotrauma.Networking
partial void UpdateWaiting(float deltaTime) partial void UpdateWaiting(float deltaTime)
{ {
if (RespawnShuttle != null)
{
RespawnShuttle.Velocity = Vector2.Zero;
}
bool respawnPending = RespawnPending(); bool respawnPending = RespawnPending();
if (respawnPending != RespawnCountdownStarted) if (respawnPending != RespawnCountdownStarted)
{ {
RespawnCountdownStarted = respawnPending; RespawnCountdownStarted = respawnPending;
RespawnTime = DateTime.Now + new TimeSpan(0,0,0,0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f)); RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f));
GameMain.Server.CreateEntityEvent(this); GameMain.Server.CreateEntityEvent(this);
} }
if (!RespawnCountdownStarted) { return; } if (RespawnCountdownStarted && DateTime.Now > RespawnTime)
if (DateTime.Now > RespawnTime)
{ {
DispatchShuttle(); DispatchShuttle();
RespawnCountdownStarted = false; RespawnCountdownStarted = false;
} }
if (RespawnShuttle == null) { return; }
RespawnShuttle.Velocity = Vector2.Zero;
} }
private void DispatchShuttle() private void DispatchShuttle()
@@ -183,7 +181,6 @@ namespace Barotrauma.Networking
partial void UpdateTransportingProjSpecific(float deltaTime) partial void UpdateTransportingProjSpecific(float deltaTime)
{ {
if (!ReturnCountdownStarted) if (!ReturnCountdownStarted)
{ {
//if there are no living chracters inside, transporting can be stopped immediately //if there are no living chracters inside, transporting can be stopped immediately
@@ -230,6 +227,8 @@ namespace Barotrauma.Networking
//get rid of the existing character //get rid of the existing character
c.Character?.DespawnNow(); c.Character?.DespawnNow();
c.WaitForNextRoundRespawn = null;
var matchingData = campaign?.GetClientCharacterData(c); var matchingData = campaign?.GetClientCharacterData(c);
if (matchingData != null && !matchingData.HasSpawned) if (matchingData != null && !matchingData.HasSpawned)
{ {
@@ -332,6 +331,15 @@ namespace Barotrauma.Networking
} }
var characterData = campaign?.GetClientCharacterData(clients[i]); var characterData = campaign?.GetClientCharacterData(clients[i]);
if (characterData != null && Level.Loaded?.Type != LevelData.LevelType.Outpost)
{
var respawnPenaltyAffliction = AfflictionPrefab.List.FirstOrDefault(a => a.AfflictionType.Equals("respawnpenalty", StringComparison.OrdinalIgnoreCase));
if (respawnPenaltyAffliction != null)
{
character.CharacterHealth.ApplyAffliction(targetLimb: null, respawnPenaltyAffliction.Instantiate(10.0f));
}
}
if (characterData == null || characterData.HasSpawned) if (characterData == null || characterData.HasSpawned)
{ {
//give the character the items they would've gotten if they had spawned in the main sub //give the character the items they would've gotten if they had spawned in the main sub
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.1300.0.2</Version> <Version>0.1300.0.3</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using NLog.Targets; using NLog.Targets;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -18,8 +19,11 @@ namespace Barotrauma
private readonly Alignment? cameraEndPos; private readonly Alignment? cameraEndPos;
private readonly float? startZoom; private readonly float? startZoom;
private readonly float? endZoom; private readonly float? endZoom;
public readonly float Duration;
public readonly float WaitDuration;
public readonly float PanDuration;
public readonly bool FadeOut; public readonly bool FadeOut;
public readonly bool LosFadeIn;
private readonly CoroutineHandle updateCoroutine; private readonly CoroutineHandle updateCoroutine;
@@ -28,10 +32,12 @@ namespace Barotrauma
public bool AllowInterrupt = false; public bool AllowInterrupt = false;
public bool RemoveControlFromCharacter = true; public bool RemoveControlFromCharacter = true;
public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, float duration = 10.0f, float? startZoom = null, float? endZoom = null) public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null)
{ {
Duration = duration; WaitDuration = waitDuration;
PanDuration = panDuration;
FadeOut = fadeOut; FadeOut = fadeOut;
LosFadeIn = losFadeIn;
this.cameraStartPos = cameraStartPos; this.cameraStartPos = cameraStartPos;
this.cameraEndPos = cameraEndPos; this.cameraEndPos = cameraEndPos;
this.startZoom = startZoom; this.startZoom = startZoom;
@@ -77,9 +83,12 @@ namespace Barotrauma
Vector2 initialCameraPos = cam.Position; Vector2 initialCameraPos = cam.Position;
Vector2? initialTargetPos = targetEntity?.WorldPosition; Vector2? initialTargetPos = targetEntity?.WorldPosition;
float timer = 0.0f; float timer = -WaitDuration;
while (timer < Duration)
while (timer < PanDuration)
{ {
float clampedTimer = Math.Max(timer, 0f);
if (Screen.Selected != GameMain.GameScreen) if (Screen.Selected != GameMain.GameScreen)
{ {
yield return new WaitForSeconds(0.1f); yield return new WaitForSeconds(0.1f);
@@ -136,14 +145,20 @@ namespace Barotrauma
MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) : MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) :
prevControlled?.WorldPosition ?? targetEntity.WorldPosition; prevControlled?.WorldPosition ?? targetEntity.WorldPosition;
Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, timer / Duration); Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration);
cam.Translate(cameraPos - cam.Position); cam.Translate(cameraPos - cam.Position);
#if CLIENT #if CLIENT
cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, timer / Duration); cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, clampedTimer / PanDuration);
if (timer / Duration > 0.9f) if (clampedTimer / PanDuration > 0.9f)
{ {
if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / Duration) - 0.9f) * 10.0f); } if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((clampedTimer / PanDuration) - 0.9f) * 10.0f); }
}
if (LosFadeIn && clampedTimer / PanDuration > 0.8f)
{
GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f;
Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity);
GameMain.LightManager.LosEnabled = true;
} }
#endif #endif
timer += CoroutineManager.UnscaledDeltaTime; timer += CoroutineManager.UnscaledDeltaTime;
@@ -158,6 +173,7 @@ namespace Barotrauma
#if CLIENT #if CLIENT
GUI.ScreenOverlayColor = Color.TransparentBlack; GUI.ScreenOverlayColor = Color.TransparentBlack;
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
GameMain.LightManager.LosAlpha = 1f;
#endif #endif
if (prevControlled != null && !prevControlled.Removed) if (prevControlled != null && !prevControlled.Removed)
@@ -172,6 +172,15 @@ namespace Barotrauma
} }
} }
/// <summary>
/// The monster won't try to damage these submarines
/// </summary>
public HashSet<Submarine> UnattackableSubmarines
{
get;
private set;
} = new HashSet<Submarine>();
public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack); public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack);
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter); private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter);
@@ -2150,7 +2159,13 @@ namespace Barotrauma
else else
{ {
// Ignore all structures, items, and hulls inside wrecks and beacons // Ignore all structures, items, and hulls inside wrecks and beacons
if (aiTarget.Entity.Submarine != null && (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon)) { continue; } if (aiTarget.Entity.Submarine != null)
{
if (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon || UnattackableSubmarines.Contains(aiTarget.Entity.Submarine))
{
continue;
}
}
if (aiTarget.Entity is Hull hull) if (aiTarget.Entity is Hull hull)
{ {
// Ignore the target if it's a room and the character is already inside a sub // Ignore the target if it's a room and the character is already inside a sub
@@ -2439,13 +2454,23 @@ namespace Barotrauma
} }
} }
} }
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid) // Don't target characters that are outside of the allowed zone, unless chasing or escaping.
switch (targetParams.State)
{ {
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _)) case AIState.Escape:
{ case AIState.Avoid:
continue; break;
} default:
if (targetParams.State == AIState.Attack)
{
if (State == targetParams.State && SelectedAiTarget == aiTarget) { break; }
}
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
{
continue;
}
break;
} }
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist); valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist);
@@ -1012,11 +1012,6 @@ namespace Barotrauma
} }
else else
{ {
bool allowOffensive = HasItem(attacker, "handlocker", out _, requireEquipped: true);
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
{
cumulativeDamage = 100;
}
// Don't react to minor (accidental) dmg done by characters that are in the same team // Don't react to minor (accidental) dmg done by characters that are in the same team
if (cumulativeDamage < 10) if (cumulativeDamage < 10)
{ {
@@ -1027,7 +1022,7 @@ namespace Barotrauma
} }
else else
{ {
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 20, allowOffensive: allowOffensive), attacker, GetReactionTime() * 2); AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 50), attacker, GetReactionTime() * 2);
} }
} }
} }
@@ -1056,7 +1051,7 @@ namespace Barotrauma
if (!otherHumanAI.IsFriendly(Character)) { continue; } if (!otherHumanAI.IsFriendly(Character)) { continue; }
bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull); bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull);
if (!isWitnessing && !CheckReportRange(Character, otherCharacter, ReportRange)) { continue; } if (!isWitnessing && !CheckReportRange(Character, otherCharacter, ReportRange)) { continue; }
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing); var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing, dmgThreshold: attacker.TeamID == Character.TeamID ? 50 : 10);
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced); float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
otherHumanAI.AddCombatObjective(combatMode, attacker, delay); otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
} }
@@ -1099,6 +1094,15 @@ namespace Barotrauma
} }
else else
{ {
if (c.AIController is HumanAIController humanAI && humanAI.ObjectiveManager.GetActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
{
// Already targeting the attacker -> treat as a more serious threat.
cumulativeDamage *= 2;
}
if (attackResult.Afflictions.Any(a => a is AfflictionHusk))
{
cumulativeDamage = 100;
}
if (cumulativeDamage > dmgThreshold) if (cumulativeDamage > dmgThreshold)
{ {
if (c.IsSecurity) if (c.IsSecurity)
@@ -1154,7 +1158,7 @@ namespace Barotrauma
HoldPosition = HoldPosition =
Character.Info?.Job?.Prefab.Identifier == "watchman" || Character.Info?.Job?.Prefab.Identifier == "watchman" ||
Character.CurrentHull == null || Character.CurrentHull == null ||
Character.IsOnPlayerTeam && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer, Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>()?.Target is Character followTarget && followTarget.IsPlayer,
abortCondition = abortCondition, abortCondition = abortCondition,
allowHoldFire = allowHoldFire, allowHoldFire = allowHoldFire,
}; };
@@ -1841,15 +1845,14 @@ namespace Barotrauma
if (character == null) { continue; } if (character == null) { continue; }
if (c == character) { continue; } if (c == character) { continue; }
if (c.IsDead || c.IsIncapacitated) { continue; } if (c.IsDead || c.IsIncapacitated) { continue; }
if (c.SelectedConstruction != target.Item) { continue; }
if (!IsFriendly(character, c, onlySameTeam: true)) { continue; } if (!IsFriendly(character, c, onlySameTeam: true)) { continue; }
operatingCharacter = c; operatingCharacter = c;
// If the other character is player, don't try to operate
if (c.IsPlayer) { return true; }
if (c.AIController is HumanAIController controllingHumanAi) if (c.AIController is HumanAIController controllingHumanAi)
{ {
Item otherTarget = controllingHumanAi.objectiveManager.GetActiveObjective<AIObjectiveOperateItem>()?.Component.Item ?? c.SelectedConstruction; Item otherTarget = controllingHumanAi.objectiveManager.GetActiveObjective<AIObjectiveOperateItem>()?.Component.Item ?? c.SelectedConstruction;
if (otherTarget != target.Item) { continue; } if (otherTarget != target.Item) { continue; }
// If the other character is player, don't try to operate
if (c.IsPlayer) { return true; }
// If the other character is ordered to operate the item, let him do it // If the other character is ordered to operate the item, let him do it
if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>()) if (controllingHumanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
{ {
@@ -1874,8 +1877,7 @@ namespace Barotrauma
} }
else else
{ {
// Shouldn't go here, unless we allow non-humans to operate items return c.SelectedConstruction == target.Item;
return false;
} }
} }
@@ -178,7 +178,7 @@ namespace Barotrauma
{ {
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f && if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f &&
speaker?.CurrentHull != null && speaker?.CurrentHull != null &&
speaker.TeamID == CharacterTeamType.FriendlyNPC && (speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull)) Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
{ {
currentFlags.Add("EnterOutpost"); currentFlags.Add("EnterOutpost");
@@ -188,6 +188,11 @@ namespace Barotrauma
{ {
currentFlags.Add("Casual"); currentFlags.Add("Casual");
} }
if (GameMain.GameSession.IsCurrentLocationRadiated())
{
currentFlags.Add("InRadiation");
}
} }
if (speaker != null) if (speaker != null)
@@ -50,6 +50,11 @@ namespace Barotrauma
float reduction = IsPriority ? 1 : isSelected ? 2 : 3; float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
float max = AIObjectiveManager.LowestOrderPriority - reduction; float max = AIObjectiveManager.LowestOrderPriority - reduction;
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1)); Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
if (decontainObjective == null)
{
// Halve the priority until there's a decontain objective (a valid container was found).
Priority /= 2;
}
} }
return Priority; return Priority;
} }
@@ -126,5 +131,13 @@ namespace Barotrauma
itemIndex = 0; itemIndex = 0;
decontainObjective = null; decontainObjective = null;
} }
public void DropTarget()
{
if (item != null && character.HasItem(item))
{
item.Drop(character);
}
}
} }
} }
@@ -2,6 +2,7 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System;
namespace Barotrauma namespace Barotrauma
{ {
@@ -29,7 +30,21 @@ namespace Barotrauma
this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null)); this.prioritizedItems.AddRange(prioritizedItems.Where(i => i != null));
} }
protected override float TargetEvaluation() => Targets.Any() ? (objectiveManager.IsOrder(this) ? objectiveManager.GetOrderPriority(this) : AIObjectiveManager.RunPriority - 1) : 0; protected override float TargetEvaluation()
{
if (Targets.None()) { return 0; }
if (objectiveManager.IsOrder(this))
{
float prio = objectiveManager.GetOrderPriority(this);
if (subObjectives.All(so => so.SubObjectives.None()))
{
// If none of the subobjectives have subobjectives, no valid container was found. In this case, let's reduce the priority below the run threshold.
prio = Math.Min(prio, AIObjectiveManager.RunPriority - 1);
}
return prio;
}
return AIObjectiveManager.RunPriority - 0.5f;
}
protected override bool Filter(Item target) protected override bool Filter(Item target)
{ {
@@ -65,10 +80,10 @@ namespace Barotrauma
return true; return true;
} }
public static bool IsValidContainer(Item item, Character character) => public static bool IsValidContainer(Item item, Character character, bool allowUnloading = true) =>
!item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character); !item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && allowUnloading && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character);
public static bool IsValidTarget(Item item, Character character, bool checkInventory) public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true)
{ {
if (item == null) { return false; } if (item == null) { return false; }
if (item.IgnoreByAI) { return false; } if (item.IgnoreByAI) { return false; }
@@ -76,7 +91,7 @@ namespace Barotrauma
if (item.SpawnedInOutpost) { return false; } if (item.SpawnedInOutpost) { return false; }
if (item.ParentInventory != null) if (item.ParentInventory != null)
{ {
if (item.Container == null || !IsValidContainer(item.Container, character)) { return false; } if (item.Container == null || !IsValidContainer(item.Container, character, allowUnloading)) { return false; }
} }
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; } if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
var pickable = item.GetComponent<Pickable>(); var pickable = item.GetComponent<Pickable>();
@@ -127,5 +142,17 @@ namespace Barotrauma
} }
return canEquip; return canEquip;
} }
public override void OnDeselected()
{
base.OnDeselected();
foreach (var subObjective in SubObjectives)
{
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
{
cleanUpObjective.DropTarget();
}
}
}
} }
} }
@@ -457,7 +457,7 @@ namespace Barotrauma
priority /= 2; priority /= 2;
} }
} }
if (Enemy.Stun > 1) if (Enemy.IsKnockedDown)
{ {
// Enemy is stunned, reduce the priority of stunner weapons. // Enemy is stunned, reduce the priority of stunner weapons.
Attack attack = GetAttackDefinition(weapon); Attack attack = GetAttackDefinition(weapon);
@@ -730,11 +730,12 @@ namespace Barotrauma
{ {
IgnoreIfTargetDead = true, IgnoreIfTargetDead = true,
DialogueIdentifier = "dialogcannotreachtarget", DialogueIdentifier = "dialogcannotreachtarget",
TargetName = Enemy.DisplayName TargetName = Enemy.DisplayName,
AlwaysUseEuclideanDistance = false
}, },
onAbandon: () => Abandon = true); onAbandon: () => Abandon = true);
if (followTargetObjective == null) { return; } if (followTargetObjective == null) { return; }
if (Mode == CombatMode.Arrest && Enemy.Stun > 2) if (Mode == CombatMode.Arrest && (Enemy.Stun > 1 || Enemy.IsKnockedDown))
{ {
if (HumanAIController.HasItem(character, "handlocker", out _)) if (HumanAIController.HasItem(character, "handlocker", out _))
{ {
@@ -742,8 +743,8 @@ namespace Barotrauma
{ {
arrestingRegistered = true; arrestingRegistered = true;
followTargetObjective.Completed += OnArrestTargetReached; followTargetObjective.Completed += OnArrestTargetReached;
followTargetObjective.CloseEnough = 100;
} }
followTargetObjective.CloseEnough = 100;
} }
else else
{ {
@@ -759,7 +760,7 @@ namespace Barotrauma
SteeringManager.Reset(); SteeringManager.Reset();
} }
} }
if (followTargetObjective != null) if (!arrestingRegistered && followTargetObjective != null)
{ {
followTargetObjective.CloseEnough = followTargetObjective.CloseEnough =
WeaponComponent is RangedWeapon ? 1000 : WeaponComponent is RangedWeapon ? 1000 :
@@ -782,7 +783,7 @@ namespace Barotrauma
private void OnArrestTargetReached() private void OnArrestTargetReached()
{ {
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy)) if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy))
{ {
var handCuffs = matchingItems.First(); var handCuffs = matchingItems.First();
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true)) if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
@@ -802,8 +803,8 @@ namespace Barotrauma
} }
} }
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f); character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
IsCompleted = true;
} }
IsCompleted = true;
} }
/// <summary> /// <summary>
@@ -937,7 +938,14 @@ namespace Barotrauma
return; return;
} }
if (reloadTimer > 0) { return; } if (reloadTimer > 0) { return; }
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; } if (Mode == CombatMode.Arrest)
{
// If the target is arrested or if it's stunned and we can't lock the target up, consider the objective done.
if (Enemy.IsKnockedDown && !HumanAIController.HasItem(character, "handlocker", out _, requireEquipped: false) || HumanAIController.HasItem(Enemy, "handlocker", out _, requireEquipped: true))
{
IsCompleted = true;
}
}
if (holdFireCondition != null && holdFireCondition()) { return; } if (holdFireCondition != null && holdFireCondition()) { return; }
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
if (WeaponComponent is MeleeWeapon meleeWeapon) if (WeaponComponent is MeleeWeapon meleeWeapon)
@@ -1017,6 +1025,8 @@ namespace Barotrauma
private void UseWeapon(float deltaTime) private void UseWeapon(float deltaTime)
{ {
// Never allow to attack characters with deadly weapons while trying to arrest.
if (Mode == CombatMode.Arrest && isLethalWeapon) { return; }
float reloadTime = 0; float reloadTime = 0;
if (WeaponComponent is RangedWeapon rangedWeapon) if (WeaponComponent is RangedWeapon rangedWeapon)
{ {
@@ -1038,10 +1048,16 @@ namespace Barotrauma
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed); reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
} }
private bool ShouldUnequipWeapon =>
Weapon != null &&
character.Submarine != null &&
character.Submarine.TeamID == character.TeamID &&
Character.CharacterList.None(c => c.Submarine == character.Submarine && HumanAIController.IsActive(c) && !HumanAIController.IsFriendly(character, c) && HumanAIController.VisibleHulls.Contains(c.CurrentHull));
protected override void OnCompleted() protected override void OnCompleted()
{ {
base.OnCompleted(); base.OnCompleted();
if (Weapon != null) if (ShouldUnequipWeapon)
{ {
Unequip(); Unequip();
} }
@@ -1051,7 +1067,7 @@ namespace Barotrauma
protected override void OnAbandon() protected override void OnAbandon()
{ {
base.OnAbandon(); base.OnAbandon();
if (Weapon != null) if (ShouldUnequipWeapon)
{ {
Unequip(); Unequip();
} }
@@ -38,7 +38,7 @@ namespace Barotrauma
Priority = 0; Priority = 0;
Abandon = true; Abandon = true;
} }
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak)) else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
{ {
Priority = 0; Priority = 0;
Abandon = true; Abandon = true;
@@ -10,6 +10,8 @@ namespace Barotrauma
{ {
public override string DebugTag => "get item"; public override string DebugTag => "get item";
public override bool AbandonWhenCannotCompleteSubjectives => false;
private readonly bool equip; private readonly bool equip;
public HashSet<Item> ignoredItems = new HashSet<Item>(); public HashSet<Item> ignoredItems = new HashSet<Item>();
@@ -225,18 +227,8 @@ namespace Barotrauma
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach) return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
{ {
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item) // If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
abortCondition = obj => abortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
{ SpeakIfFails = false
bool abort = targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget;
if (abort)
{
// Fail silently if someone takes the suit.
obj.speakIfFails = false;
}
return abort;
},
DialogueIdentifier = "dialogcannotreachtarget",
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
}; };
}, },
onAbandon: () => onAbandon: () =>
@@ -263,13 +255,18 @@ namespace Barotrauma
if (targetItem == null) if (targetItem == null)
{ {
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item was defined.", Color.Red); DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
#endif #endif
Abandon = true; Abandon = true;
} }
return; return;
} }
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
bool checkPath = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.followControlledCharacter);
bool hasCalledPathFinder = false;
int itemsPerFrame = (int)priority;
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
{ {
currSearchIndex++; currSearchIndex++;
var item = Item.ItemList[currSearchIndex]; var item = Item.ItemList[currSearchIndex];
@@ -310,8 +307,18 @@ namespace Barotrauma
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist)); float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist));
itemPriority *= distanceFactor; itemPriority *= distanceFactor;
itemPriority *= item.Condition / item.MaxCondition; itemPriority *= item.Condition / item.MaxCondition;
//ignore if the item has a lower priority than the currently selected one // Ignore if the item has a lower priority than the currently selected one
if (itemPriority < currItemPriority) { continue; } if (itemPriority < currItemPriority) { continue; }
if (!hasCalledPathFinder && PathSteering != null && checkPath)
{
// While following the player, let's ensure that there's a valid path to the target before accepting it.
// Otherwise it will take some time for us to find a valid item when there are multiple items that we can't reach and some that we can.
// This is relatively expensive, so let's do this only when it significantly improves the behavior.
// Only allow one path find call per frame.
hasCalledPathFinder = true;
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
if (path.Unreachable) { continue; }
}
currItemPriority = itemPriority; currItemPriority = itemPriority;
targetItem = item; targetItem = item;
moveToTarget = rootInventoryOwner ?? item; moveToTarget = rootInventoryOwner ?? item;
@@ -326,7 +333,7 @@ namespace Barotrauma
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab)) if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
{ {
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow); DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
#endif #endif
Abandon = true; Abandon = true;
} }
@@ -345,7 +352,7 @@ namespace Barotrauma
else else
{ {
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow); DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
#endif #endif
Abandon = true; Abandon = true;
} }
@@ -393,11 +400,30 @@ namespace Barotrauma
/// </summary> /// </summary>
private void ResetInternal() private void ResetInternal()
{ {
goToObjective = null; RemoveSubObjective(ref goToObjective);
targetItem = originalTarget; targetItem = originalTarget;
moveToTarget = targetItem?.GetRootInventoryOwner(); moveToTarget = targetItem?.GetRootInventoryOwner();
isDoneSeeking = false; isDoneSeeking = false;
currSearchIndex = 0; currSearchIndex = 0;
currItemPriority = 0;
}
protected override void OnAbandon()
{
base.OnAbandon();
if (moveToTarget == null) { return; }
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Get item failed to reach {moveToTarget}", Color.Yellow);
#endif
if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null)
{
string TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString();
string msg = TargetName == null ? TextManager.Get("dialogcannotreachtarget", true) : TextManager.GetWithVariable("dialogcannotreachtarget", "[name]", TargetName, formatCapitals: !(moveToTarget is Character));
if (msg != null)
{
character.Speak(msg, identifier: "dialogcannotreachtarget", minDurationBetweenSimilar: 20.0f);
}
}
} }
} }
} }
@@ -30,7 +30,7 @@ namespace Barotrauma
public bool followControlledCharacter; public bool followControlledCharacter;
public bool mimic; public bool mimic;
public bool speakIfFails = true; public bool SpeakIfFails { get; set; } = true;
public float extraDistanceWhileSwimming; public float extraDistanceWhileSwimming;
public float extraDistanceOutsideSub; public float extraDistanceOutsideSub;
@@ -67,6 +67,8 @@ namespace Barotrauma
public bool IgnoreIfTargetDead { get; set; } public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; } public bool AllowGoingOutside { get; set; }
public bool AlwaysUseEuclideanDistance { get; set; } = true;
public override bool AbandonWhenCannotCompleteSubjectives => !repeat; public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
public override bool AllowOutsideSubmarine => AllowGoingOutside; public override bool AllowOutsideSubmarine => AllowGoingOutside;
@@ -88,12 +90,7 @@ namespace Barotrauma
Abandon = !isOrder; Abandon = !isOrder;
return Priority; return Priority;
} }
if (followControlledCharacter && Character.Controlled == null) if (Target == null || Target is Entity e && e.Removed)
{
Priority = 0;
Abandon = !isOrder;
}
if (Target is Entity e && e.Removed)
{ {
Priority = 0; Priority = 0;
Abandon = !isOrder; Abandon = !isOrder;
@@ -147,11 +144,10 @@ namespace Barotrauma
private void SpeakCannotReach() private void SpeakCannotReach()
{ {
if (!character.IsOnPlayerTeam) { return; }
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow); DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
#endif #endif
if (objectiveManager.HasOrders() && DialogueIdentifier != null && speakIfFails) if (character.IsOnPlayerTeam && objectiveManager.CurrentOrder != null && DialogueIdentifier != null && SpeakIfFails)
{ {
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character)); string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
if (msg != null) if (msg != null)
@@ -165,12 +161,15 @@ namespace Barotrauma
{ {
if (followControlledCharacter) if (followControlledCharacter)
{ {
if (Character.Controlled == null || !HumanAIController.IsFriendly(Character.Controlled)) if (Character.Controlled != null && HumanAIController.IsFriendly(Character.Controlled))
{
Target = Character.Controlled;
}
if (Target == null)
{ {
Abandon = true; Abandon = true;
return; return;
} }
Target = Character.Controlled;
} }
if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy)) if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy))
{ {
@@ -260,6 +259,7 @@ namespace Barotrauma
} }
if (needsEquipment) if (needsEquipment)
{ {
SteeringManager.Reset();
if (findDivingGear != null && !findDivingGear.CanBeCompleted) if (findDivingGear != null && !findDivingGear.CanBeCompleted)
{ {
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager), TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
@@ -288,9 +288,14 @@ namespace Barotrauma
} }
} }
} }
float maxGapDistance = 500;
Character targetCharacter = Target as Character;
if (character.AnimController.InWater) if (character.AnimController.InWater)
{ {
if (character.CurrentHull == null) if (character.CurrentHull == null ||
followControlledCharacter &&
targetCharacter != null && (targetCharacter.CurrentHull == null) != (character.CurrentHull == null) &&
Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition) < maxGapDistance * maxGapDistance)
{ {
if (seekGapsTimer > 0) if (seekGapsTimer > 0)
{ {
@@ -298,7 +303,7 @@ namespace Barotrauma
} }
else else
{ {
SeekGaps(maxDistance: 500); SeekGaps(maxGapDistance);
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f); seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
if (TargetGap != null) if (TargetGap != null)
{ {
@@ -327,7 +332,7 @@ namespace Barotrauma
} }
if (TargetGap != null) if (TargetGap != null)
{ {
if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, TargetGap.FlowTargetHull.WorldPosition, deltaTime)) if (TargetGap.FlowTargetHull != null && HumanAIController.SteerThroughGap(TargetGap, followControlledCharacter ? Target.WorldPosition : TargetGap.FlowTargetHull.WorldPosition, deltaTime))
{ {
SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1); SteeringManager.SteeringAvoid(deltaTime, avoidLookAheadDistance, weight: 1);
return; return;
@@ -347,7 +352,7 @@ namespace Barotrauma
float closeEnough = 250; float closeEnough = 250;
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition); float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic || bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2)); (targetCharacter != null && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true)) if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, recursive: false, requireEquipped: true))
{ {
// Currently equipped scooter // Currently equipped scooter
@@ -528,17 +533,24 @@ namespace Barotrauma
{ {
Gap selectedGap = null; Gap selectedGap = null;
float selectedDistance = -1; float selectedDistance = -1;
Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition);
foreach (Gap gap in Gap.GapList) foreach (Gap gap in Gap.GapList)
{ {
if (gap.Open < 1) { continue; } if (gap.Open < 1) { continue; }
if (gap.FlowTargetHull == null) { continue; } if (gap.Submarine == null) { continue; }
if (gap.Submarine != Target.Submarine) { continue; } if (!followControlledCharacter)
float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition); {
if (distance > maxDistance * maxDistance) { continue; } if (gap.FlowTargetHull == null) { continue; }
if (selectedGap == null || distance < selectedDistance) if (gap.Submarine != Target.Submarine) { continue; }
}
Vector2 toGap = gap.WorldPosition - character.WorldPosition;
if (Vector2.Dot(Vector2.Normalize(toGap), toTargetNormalized) < 0) { continue; }
float squaredDistance = toGap.LengthSquared();
if (squaredDistance > maxDistance * maxDistance) { continue; }
if (selectedGap == null || squaredDistance < selectedDistance)
{ {
selectedGap = gap; selectedGap = gap;
selectedDistance = distance; selectedDistance = squaredDistance;
} }
} }
TargetGap = selectedGap; TargetGap = selectedGap;
@@ -555,7 +567,7 @@ namespace Barotrauma
//otherwise characters can let go of the ladders too soon once they're close enough to the target //otherwise characters can let go of the ladders too soon once they're close enough to the target
if (PathSteering.CurrentPath.NextNode != null) { return false; } if (PathSteering.CurrentPath.NextNode != null) { return false; }
} }
if (!character.AnimController.InWater) if (!AlwaysUseEuclideanDistance && !character.AnimController.InWater)
{ {
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y); float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
if (yDiff > CloseEnough) { return false; } if (yDiff > CloseEnough) { return false; }
@@ -495,7 +495,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (item.CurrentHull != hull) { continue; } if (item.CurrentHull != hull) { continue; }
if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true) && !ignoredItems.Contains(item)) if (AIObjectiveCleanupItems.IsValidTarget(item, character, checkInventory: true, allowUnloading: false) && !ignoredItems.Contains(item))
{ {
itemsToClean.Add(item); itemsToClean.Add(item);
} }
@@ -540,5 +540,17 @@ namespace Barotrauma
ignoredItems.Clear(); ignoredItems.Clear();
autonomousObjectiveRetryTimer = 10; autonomousObjectiveRetryTimer = 10;
} }
public override void OnDeselected()
{
base.OnDeselected();
foreach (var subObjective in SubObjectives)
{
if (subObjective is AIObjectiveCleanupItem cleanUpObjective)
{
cleanUpObjective.DropTarget();
}
}
}
} }
} }
@@ -445,7 +445,7 @@ namespace Barotrauma
extraDistanceWhileSwimming = 100, extraDistanceWhileSwimming = 100,
AllowGoingOutside = true, AllowGoingOutside = true,
IgnoreIfTargetDead = true, IgnoreIfTargetDead = true,
followControlledCharacter = orderGiver == character, followControlledCharacter = true,
mimic = true, mimic = true,
DialogueIdentifier = "dialogcannotreachplace" DialogueIdentifier = "dialogcannotreachplace"
}; };
@@ -69,10 +69,9 @@ namespace Barotrauma
{ {
if (!isOrder) if (!isOrder)
{ {
if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC || if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC)
HumanAIController.IsTrueForAnyCrewMember(c =>
c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target))
{ {
// The reactor was previously operated by a player -> ignore.
Priority = 0; Priority = 0;
return Priority; return Priority;
} }
@@ -89,7 +88,6 @@ namespace Barotrauma
case "powerup": case "powerup":
// Check that we don't already have another order that is targeting the same item. // Check that we don't already have another order that is targeting the same item.
// Without this the autonomous objective will tell the bot to turn the reactor on again. // Without this the autonomous objective will tell the bot to turn the reactor on again.
if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective))) if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective)))
{ {
Priority = 0; Priority = 0;
@@ -177,9 +175,9 @@ namespace Barotrauma
} }
if (operateTarget != null) if (operateTarget != null)
{ {
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget)) if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
{ {
// Another crew member is already targeting this entity. // Another crew member is already targeting this entity (leak).
Abandon = true; Abandon = true;
return; return;
} }
@@ -471,13 +471,15 @@ namespace Barotrauma
} }
} }
private double pressureProtectionLastSet;
private float pressureProtection; private float pressureProtection;
public float PressureProtection public float PressureProtection
{ {
get { return pressureProtection; } get { return pressureProtection; }
set set
{ {
pressureProtection = MathHelper.Clamp(value, 0.0f, 100.0f); pressureProtection = Math.Max(value, 0.0f);
pressureProtectionLastSet = Timing.TotalTime;
} }
} }
@@ -528,11 +530,10 @@ namespace Barotrauma
public float Stun public float Stun
{ {
get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; } get { return IsRagdolled ? 1.0f : CharacterHealth.Stun; }
set set
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return; if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
SetStun(value, true); SetStun(value, true);
} }
} }
@@ -697,7 +698,7 @@ namespace Barotrauma
{ {
if (!canBeDragged) { return false; } if (!canBeDragged) { return false; }
if (Removed || !AnimController.Draggable) { return false; } if (Removed || !AnimController.Draggable) { return false; }
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated || IsPet; return IsKnockedDown || LockHands || IsPet;
} }
set { canBeDragged = value; } set { canBeDragged = value; }
} }
@@ -715,7 +716,7 @@ namespace Barotrauma
} }
else else
{ {
return IsDead || Stun > 0.0f || LockHands || IsIncapacitated; return IsKnockedDown || LockHands;
} }
} }
set { canInventoryBeAccessed = value; } set { canInventoryBeAccessed = value; }
@@ -1016,7 +1017,7 @@ namespace Barotrauma
{ {
// Get the non husked name and find the ragdoll with it // Get the non husked name and find the ragdoll with it
var matchingAffliction = AfflictionPrefab.List var matchingAffliction = AfflictionPrefab.List
.Where(p => p.AfflictionType == "huskinfection") .Where(p => p is AfflictionPrefabHusk)
.Select(p => p as AfflictionPrefabHusk) .Select(p => p as AfflictionPrefabHusk)
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase))); .FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase)));
string nonHuskedSpeciesName = string.Empty; string nonHuskedSpeciesName = string.Empty;
@@ -1052,7 +1053,7 @@ namespace Barotrauma
else else
{ {
AnimController = new FishAnimController(this, seed, ragdollParams as FishRagdollParams); AnimController = new FishAnimController(this, seed, ragdollParams as FishRagdollParams);
PressureProtection = 100.0f; PressureProtection = int.MaxValue;
} }
AnimController.SetPosition(ConvertUnits.ToSimUnits(position)); AnimController.SetPosition(ConvertUnits.ToSimUnits(position));
@@ -1271,7 +1272,13 @@ namespace Barotrauma
public float GetSkillLevel(string skillIdentifier) public float GetSkillLevel(string skillIdentifier)
{ {
return (Info == null || Info.Job == null) ? 0.0f : Info.Job.GetSkillLevel(skillIdentifier); if (Info?.Job == null) { return 0.0f; }
float skillLevel = Info.Job.GetSkillLevel(skillIdentifier);
foreach (Affliction affliction in CharacterHealth.GetAllAfflictions())
{
skillLevel *= affliction.GetSkillMultiplier();
}
return skillLevel;
} }
// TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner // TODO: reposition? there's also the overrideTargetMovement variable, but it's not in the same manner
@@ -2466,11 +2473,8 @@ namespace Barotrauma
if (NeedsAir) if (NeedsAir)
{ {
bool protectedFromPressure = PressureProtection > 0.0f;
//cannot be protected from pressure when below crush depth
protectedFromPressure = protectedFromPressure && WorldPosition.Y > CharacterHealth.CrushDepth;
//implode if not protected from pressure, and either outside or in a high-pressure hull //implode if not protected from pressure, and either outside or in a high-pressure hull
if (!protectedFromPressure && if (!IsProtectedFromPressure() &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)) (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f))
{ {
if (CharacterHealth.PressureKillDelay <= 0.0f) if (CharacterHealth.PressureKillDelay <= 0.0f)
@@ -2656,7 +2660,10 @@ namespace Barotrauma
{ {
if (NeedsAir) if (NeedsAir)
{ {
PressureProtection -= deltaTime * 100.0f; if (Timing.TotalTime > pressureProtectionLastSet + 0.1)
{
PressureProtection = 0.0f;
}
} }
if (NeedsWater) if (NeedsWater)
{ {
@@ -3281,7 +3288,7 @@ namespace Barotrauma
GameMain.Config.RecentlyEncounteredCreatures.Add(other.SpeciesName); GameMain.Config.RecentlyEncounteredCreatures.Add(other.SpeciesName);
} }
public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1) public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable<Affliction> afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true)
{ {
if (Removed) { return new AttackResult(); } if (Removed) { return new AttackResult(); }
@@ -3327,7 +3334,7 @@ namespace Barotrauma
bool wasDead = IsDead; bool wasDead = IsDead;
Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir); Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir);
AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier); AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier);
CharacterHealth.ApplyDamage(hitLimb, attackResult); CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking);
if (attacker != this) if (attacker != this)
{ {
OnAttacked?.Invoke(attacker, attackResult); OnAttacked?.Invoke(attacker, attackResult);
@@ -3382,6 +3389,12 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Is the character knocked down regardless whether the technical state is dead, unconcious, paralyzed, or stunned.
/// With stunning, the parameter uses a half a second delay before the character is treated as knocked down. The purpose of this is to ignore minor stunning. If you don't want to to ignore any stun, use the Stun property.
/// </summary>
public bool IsKnockedDown => IsDead || IsIncapacitated || CharacterHealth.StunTimer > 0.5f;
public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false) public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false)
{ {
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; } if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
@@ -3391,7 +3404,7 @@ namespace Barotrauma
{ {
AnimController.ResetPullJoints(); AnimController.ResetPullJoints();
} }
CharacterHealth.StunTimer = newStun; CharacterHealth.Stun = newStun;
if (newStun > 0.0f) if (newStun > 0.0f)
{ {
SelectedConstruction = null; SelectedConstruction = null;
@@ -3964,5 +3977,10 @@ namespace Barotrauma
public bool IsWatchman => HasJob("watchman"); public bool IsWatchman => HasJob("watchman");
public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier; public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier;
public bool IsProtectedFromPressure()
{
return PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 0.0f);
}
} }
} }
@@ -961,6 +961,20 @@ namespace Barotrauma
public void Rename(string newName) public void Rename(string newName)
{ {
if (string.IsNullOrEmpty(newName)) { return; } if (string.IsNullOrEmpty(newName)) { return; }
// Replace the name tag of any existing id cards or duffel bags
foreach (var item in Item.ItemList)
{
if (item.Prefab.Identifier != "idcard" && !item.Tags.Contains("despawncontainer")) { continue; }
foreach (var tag in item.Tags.Split(','))
{
var splitTag = tag.Split(":");
if (splitTag.Length < 2) { continue; }
if (splitTag[0] != "name") { continue; }
if (splitTag[1] != Name) { continue; }
item.ReplaceTag(tag, $"name:{newName}");
break;
}
}
Name = newName; Name = newName;
} }
@@ -14,6 +14,9 @@ namespace Barotrauma
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; } public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
public float PendingAdditionStrenght { get; set; }
public float AdditionStrength { get; set; }
protected float _strength; protected float _strength;
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
@@ -26,7 +29,12 @@ namespace Barotrauma
{ {
_nonClampedStrength = value; _nonClampedStrength = value;
} }
_strength = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength); float newValue = MathHelper.Clamp(value, 0.0f, Prefab.MaxStrength);
if (newValue > _strength)
{
PendingAdditionStrenght = Prefab.GrainBurst;
}
_strength = newValue;
} }
} }
@@ -56,6 +64,7 @@ namespace Barotrauma
public Affliction(AfflictionPrefab prefab, float strength) public Affliction(AfflictionPrefab prefab, float strength)
{ {
Prefab = prefab; Prefab = prefab;
PendingAdditionStrenght = Prefab.GrainBurst;
_strength = strength; _strength = strength;
Identifier = prefab?.Identifier; Identifier = prefab?.Identifier;
@@ -109,18 +118,25 @@ namespace Barotrauma
if (currentEffect == null) { return 0.0f; } if (currentEffect == null) { return 0.0f; }
if (MathUtils.NearlyEqual(currentEffect.MaxGrainStrength, 0f)) { return 0.0f; } if (MathUtils.NearlyEqual(currentEffect.MaxGrainStrength, 0f)) { return 0.0f; }
return MathHelper.Lerp( float amount = MathHelper.Lerp(
currentEffect.MinGrainStrength, currentEffect.MinGrainStrength,
currentEffect.MaxGrainStrength, currentEffect.MaxGrainStrength,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
if (Prefab.GrainBurst > 0 && AdditionStrength > amount)
{
return AdditionStrength;
}
return amount;
} }
public float GetScreenDistortStrength() public float GetScreenDistortStrength()
{ {
if (Strength < Prefab.ActivationThreshold) return 0.0f; if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f; if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength <= 0.0f) return 0.0f; if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinScreenDistortStrength, currentEffect.MinScreenDistortStrength,
@@ -130,10 +146,10 @@ namespace Barotrauma
public float GetRadialDistortStrength() public float GetRadialDistortStrength()
{ {
if (Strength < Prefab.ActivationThreshold) return 0.0f; if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f; if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength <= 0.0f) return 0.0f; if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinRadialDistortStrength, currentEffect.MinRadialDistortStrength,
@@ -143,10 +159,10 @@ namespace Barotrauma
public float GetChromaticAberrationStrength() public float GetChromaticAberrationStrength()
{ {
if (Strength < Prefab.ActivationThreshold) return 0.0f; if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f; if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength <= 0.0f) return 0.0f; if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinChromaticAberrationStrength, currentEffect.MinChromaticAberrationStrength,
@@ -156,10 +172,10 @@ namespace Barotrauma
public float GetScreenBlurStrength() public float GetScreenBlurStrength()
{ {
if (Strength < Prefab.ActivationThreshold) return 0.0f; if (Strength < Prefab.ActivationThreshold) { return 0.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) return 0.0f; if (currentEffect == null) { return 0.0f; }
if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength <= 0.0f) return 0.0f; if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; }
return MathHelper.Lerp( return MathHelper.Lerp(
currentEffect.MinScreenBlurStrength, currentEffect.MinScreenBlurStrength,
@@ -167,6 +183,20 @@ namespace Barotrauma
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
} }
public float GetSkillMultiplier()
{
if (Strength < Prefab.ActivationThreshold) { return 1.0f; }
AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength);
if (currentEffect == null) { return 1.0f; }
float amount = MathHelper.Lerp(
currentEffect.MinSkillMultiplier,
currentEffect.MaxSkillMultiplier,
(Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength));
return amount;
}
public void CalculateDamagePerSecond(float currentVitalityDecrease) public void CalculateDamagePerSecond(float currentVitalityDecrease)
{ {
DamagePerSecond = Math.Max(DamagePerSecond, currentVitalityDecrease - PreviousVitalityDecrease); DamagePerSecond = Math.Max(DamagePerSecond, currentVitalityDecrease - PreviousVitalityDecrease);
@@ -245,6 +275,21 @@ namespace Barotrauma
{ {
ApplyStatusEffect(statusEffect, deltaTime, characterHealth, targetLimb); ApplyStatusEffect(statusEffect, deltaTime, characterHealth, targetLimb);
} }
float amount = deltaTime;
if (Prefab.GrainBurst > 0)
{
amount /= Prefab.GrainBurst;
}
if (PendingAdditionStrenght >= 0)
{
AdditionStrength += amount;
PendingAdditionStrenght -= deltaTime;
}
else if (AdditionStrength > 0)
{
AdditionStrength -= amount;
}
} }
public void ApplyStatusEffect(StatusEffect statusEffect, float deltaTime, CharacterHealth characterHealth, Limb targetLimb) public void ApplyStatusEffect(StatusEffect statusEffect, float deltaTime, CharacterHealth characterHealth, Limb targetLimb)
@@ -267,7 +312,7 @@ namespace Barotrauma
{ {
var targets = new List<ISerializableEntity>(); var targets = new List<ISerializableEntity>();
statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets); statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets);
statusEffect.Apply(ActionType.OnActive, deltaTime, targetLimb.character, targets); statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, targets);
} }
} }
@@ -134,6 +134,8 @@ namespace Barotrauma
public float MinSpeedMultiplier, MaxSpeedMultiplier; public float MinSpeedMultiplier, MaxSpeedMultiplier;
public float MinBuffMultiplier, MaxBuffMultiplier; public float MinBuffMultiplier, MaxBuffMultiplier;
public float MinSkillMultiplier, MaxSkillMultiplier;
public float MinResistance, MaxResistance; public float MinResistance, MaxResistance;
public string ResistanceFor; public string ResistanceFor;
public string DialogFlag; public string DialogFlag;
@@ -172,6 +174,9 @@ namespace Barotrauma
MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f); MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f);
MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength); MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength);
MinSkillMultiplier = element.GetAttributeFloat("minskillmultiplier", 1.0f);
MaxSkillMultiplier = element.GetAttributeFloat("maxskillmultiplier", 1.0f);
ResistanceFor = element.GetAttributeString("resistancefor", ""); ResistanceFor = element.GetAttributeString("resistancefor", "");
MinResistance = element.GetAttributeFloat("minresistance", 0.0f); MinResistance = element.GetAttributeFloat("minresistance", 0.0f);
MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f); MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f);
@@ -292,6 +297,8 @@ namespace Barotrauma
public readonly float ShowIconToOthersThreshold = 0.05f; public readonly float ShowIconToOthersThreshold = 0.05f;
public readonly float MaxStrength = 100.0f; public readonly float MaxStrength = 100.0f;
public readonly float GrainBurst;
//how high the strength has to be for the affliction icon to be shown with a health scanner //how high the strength has to be for the affliction icon to be shown with a health scanner
public readonly float ShowInHealthScannerThreshold = 0.05f; public readonly float ShowInHealthScannerThreshold = 0.05f;
@@ -451,6 +458,7 @@ namespace Barotrauma
prefab = new AfflictionPrefab(sourceElement, file.Path, typeof(AfflictionBleeding)); prefab = new AfflictionPrefab(sourceElement, file.Path, typeof(AfflictionBleeding));
break; break;
case "huskinfection": case "huskinfection":
case "alieninfection":
prefab = new AfflictionPrefabHusk(sourceElement, file.Path, typeof(AfflictionHusk)); prefab = new AfflictionPrefabHusk(sourceElement, file.Path, typeof(AfflictionHusk));
break; break;
case "cprsettings": case "cprsettings":
@@ -521,7 +529,7 @@ namespace Barotrauma
if (prefab != null) if (prefab != null)
{ {
loadedAfflictions.Add((prefab, element)); loadedAfflictions.Add((prefab, sourceElement));
Prefabs.Add(prefab, isOverride); Prefabs.Add(prefab, isOverride);
prefab.CalculatePrefabUIntIdentifier(Prefabs); prefab.CalculatePrefabUIntIdentifier(Prefabs);
} }
@@ -582,6 +590,7 @@ namespace Barotrauma
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f)); ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold); ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold);
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f); MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLower(), 0.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f)); ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f)); TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f));
@@ -190,12 +190,14 @@ namespace Barotrauma
set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0.0f, 100.0f); }
} }
public float StunTimer public float Stun
{ {
get { return stunAffliction.Strength; } get { return stunAffliction.Strength; }
set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); } set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); }
} }
public float StunTimer { get; private set; }
public Affliction PressureAffliction public Affliction PressureAffliction
{ {
get { return pressureAffliction; } get { return pressureAffliction; }
@@ -488,7 +490,7 @@ namespace Barotrauma
CalculateVitality(); CalculateVitality();
} }
public void ApplyDamage(Limb hitLimb, AttackResult attackResult) public void ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking = true)
{ {
if (Unkillable || Character.GodMode) { return; } if (Unkillable || Character.GodMode) { return; }
if (hitLimb.HealthIndex < 0 || hitLimb.HealthIndex >= limbHealths.Count) if (hitLimb.HealthIndex < 0 || hitLimb.HealthIndex >= limbHealths.Count)
@@ -502,11 +504,11 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab.LimbSpecific) if (newAffliction.Prefab.LimbSpecific)
{ {
AddLimbAffliction(hitLimb, newAffliction); AddLimbAffliction(hitLimb, newAffliction, allowStacking);
} }
else else
{ {
AddAffliction(newAffliction); AddAffliction(newAffliction, allowStacking);
} }
} }
} }
@@ -573,7 +575,7 @@ namespace Barotrauma
CalculateVitality(); CalculateVitality();
} }
private void AddLimbAffliction(Limb limb, Affliction newAffliction) private void AddLimbAffliction(Limb limb, Affliction newAffliction, bool allowStacking = true)
{ {
if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; } if (!newAffliction.Prefab.LimbSpecific || limb == null) { return; }
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count)
@@ -582,10 +584,10 @@ namespace Barotrauma
"\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex); "\" only has health configured for" + limbHealths.Count + " limbs but the limb " + limb.type + " is targeting index " + limb.HealthIndex);
return; return;
} }
AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction); AddLimbAffliction(limbHealths[limb.HealthIndex], newAffliction, allowStacking);
} }
private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction) private void AddLimbAffliction(LimbHealth limbHealth, Affliction newAffliction, bool allowStacking = true)
{ {
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
@@ -594,7 +596,15 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab == affliction.Prefab) if (newAffliction.Prefab == affliction.Prefab)
{ {
affliction.Strength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)))); float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
if (allowStacking)
{
// Add the existing strength
newStrength += affliction.Strength;
}
newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength);
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
affliction.Strength = newStrength;
affliction.Source = newAffliction.Source; affliction.Source = newAffliction.Source;
CalculateVitality(); CalculateVitality();
if (Vitality <= MinVitality) if (Vitality <= MinVitality)
@@ -624,13 +634,12 @@ namespace Barotrauma
#endif #endif
} }
private void AddAffliction(Affliction newAffliction) private void AddAffliction(Affliction newAffliction, bool allowStacking = true)
{ {
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; } if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
if (newAffliction.Prefab.AfflictionType == "huskinfection") if (newAffliction.Prefab is AfflictionPrefabHusk huskPrefab)
{ {
var huskPrefab = newAffliction.Prefab as AfflictionPrefabHusk;
if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase))) if (huskPrefab.TargetSpecies.None(s => s.Equals(Character.SpeciesName, StringComparison.OrdinalIgnoreCase)))
{ {
return; return;
@@ -640,7 +649,13 @@ namespace Barotrauma
{ {
if (newAffliction.Prefab == affliction.Prefab) if (newAffliction.Prefab == affliction.Prefab)
{ {
float newStrength = Math.Min(affliction.Prefab.MaxStrength, affliction.Strength + (newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier)))); float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(affliction.Prefab.Identifier));
if (allowStacking)
{
// Add the existing strength
newStrength += affliction.Strength;
}
newStrength = Math.Min(affliction.Prefab.MaxStrength, newStrength);
if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); } if (affliction == stunAffliction) { Character.SetStun(newStrength, true, true); }
affliction.Strength = newStrength; affliction.Strength = newStrength;
affliction.Source = newAffliction.Source; affliction.Source = newAffliction.Source;
@@ -676,6 +691,8 @@ namespace Barotrauma
{ {
UpdateOxygen(deltaTime); UpdateOxygen(deltaTime);
StunTimer = Stun > 0 ? StunTimer + deltaTime : 0;
for (int i = 0; i < limbHealths.Count; i++) for (int i = 0; i < limbHealths.Count; i++)
{ {
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
@@ -795,6 +812,13 @@ namespace Barotrauma
Vitality -= vitalityDecrease; Vitality -= vitalityDecrease;
affliction.CalculateDamagePerSecond(vitalityDecrease); affliction.CalculateDamagePerSecond(vitalityDecrease);
} }
#if CLIENT
if (IsUnconscious)
{
HintManager.OnCharacterUnconscious(Character);
}
#endif
} }
private void Kill() private void Kill()
@@ -79,6 +79,9 @@ namespace Barotrauma
[Serialize(0f, true), Editable] [Serialize(0f, true), Editable]
public float SonarDisruption { get; set; } public float SonarDisruption { get; set; }
[Serialize(0f, true), Editable]
public float DistantSonarRange { get; set; }
[Serialize(25000f, true, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)] [Serialize(25000f, true, "If the character is farther than this (in pixels) from the sub and the players, it will be disabled. The halved value is used for triggering simple physics where the ragdoll is disabled and only the main collider is updated."), Editable(MinValueFloat = 10000f, MaxValueFloat = 100000f)]
public float DisableDistance { get; set; } public float DisableDistance { get; set; }
@@ -609,7 +609,7 @@ namespace Barotrauma
commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) => commands.Add(new Command("giveaffliction", "giveaffliction [affliction name] [affliction strength] [character name]: Add an affliction to a character. If the name parameter is omitted, the affliction is added to the controlled character.", (string[] args) =>
{ {
if (args.Length < 2) return; if (args.Length < 2) { return; }
AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a => AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a =>
a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) || a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) ||
@@ -626,9 +626,19 @@ namespace Barotrauma
return; return;
} }
Character targetCharacter = (args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray()); bool relativeStrength = false;
if (args.Length > 2)
{
bool.TryParse(args[2], out relativeStrength);
}
Character targetCharacter = (relativeStrength || args.Length <= 2) ? Character.Controlled : FindMatchingCharacter(args.Skip(2).ToArray());
if (targetCharacter != null) if (targetCharacter != null)
{ {
if (relativeStrength)
{
afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength;
}
targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength));
} }
}, },
@@ -734,19 +744,19 @@ namespace Barotrauma
List<EventPrefab> eventPrefabs = EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).ToList(); List<EventPrefab> eventPrefabs = EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).ToList();
if (GameMain.GameSession?.EventManager != null && args.Length > 0) if (GameMain.GameSession?.EventManager != null && args.Length > 0)
{ {
EventPrefab newEvent = eventPrefabs.Find(prefab => string.Equals(prefab.Identifier, args[0], StringComparison.InvariantCultureIgnoreCase)); EventPrefab eventPrefab = eventPrefabs.Find(prefab => string.Equals(prefab.Identifier, args[0], StringComparison.InvariantCultureIgnoreCase));
if (newEvent != null) if (eventPrefab != null)
{ {
var @event = newEvent.CreateInstance(); var newEvent = eventPrefab.CreateInstance();
if (newEvent == null) if (newEvent == null)
{ {
NewMessage($"Could not initialize event {args[0]} because level did not meet requirements"); NewMessage($"Could not initialize event {args[0]} because level did not meet requirements");
return; return;
} }
GameMain.GameSession.EventManager.ActiveEvents.Add(@event); GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
@event.Init(true); newEvent.Init(true);
NewMessage($"Initialized event {newEvent.Identifier}", Color.Aqua); NewMessage($"Initialized event {eventPrefab.Identifier}", Color.Aqua);
return; return;
} }
@@ -1002,7 +1012,7 @@ namespace Barotrauma
} }
else else
{ {
ThrowError("Could not set location reputation ({args[0]} is not a valid reputation value)."); ThrowError($"Could not set location reputation ({args[0]} is not a valid reputation value).");
} }
} }
else else
@@ -1010,6 +1020,41 @@ namespace Barotrauma
ThrowError("Could not set location reputation (no active campaign)."); ThrowError("Could not set location reputation (no active campaign).");
} }
}, null, true)); }, null, true));
commands.Add(new Command("setreputation", "setreputation [faction] [value]: Set the reputation of a cation to the specified value.", (string[] args) =>
{
if (args.Length < 2)
{
ThrowError("Insufficient arguments (expected 2)");
return;
}
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
if (campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(args[0], StringComparison.OrdinalIgnoreCase)) is { } faction)
{
if (float.TryParse(args[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float reputation))
{
faction.Reputation.Value = reputation;
}
else
{
ThrowError($"Could not set faction reputation ({args[1]} is not a valid reputation value).");
}
}
else
{
ThrowError($"Could not set faction reputation (faction {args[0]} not found).");
}
}
else
{
ThrowError("Could not set faction reputation (no active campaign).");
}
}, () =>
{
return new[] { FactionPrefab.Prefabs.Select(f => f.Identifier).ToArray() };
}, true));
commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) => commands.Add(new Command("fixitems", "fixitems: Repairs all items and restores them to full condition.", (string[] args) =>
{ {
@@ -1133,6 +1178,56 @@ namespace Barotrauma
UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray() UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray()
}; };
}, true)); }, true));
commands.Add(new Command("maxupgrades", "maxupgrades [category] [prefab]: Maxes out all upgrades or only specific one if given arguments.", args =>
{
UpgradeManager upgradeManager = GameMain.GameSession?.Campaign?.UpgradeManager;
if (upgradeManager == null)
{
ThrowError("This command can only be used in campaign.");
return;
}
string categoryIdentifier = null;
string prefabIdentifier = null;
switch (args.Length)
{
case 1:
categoryIdentifier = args[0];
break;
case 2:
categoryIdentifier = args[0];
prefabIdentifier = args[1];
break;
}
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
if (!string.IsNullOrWhiteSpace(categoryIdentifier) && !category.Identifier.Equals(categoryIdentifier, StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (UpgradePrefab prefab in UpgradePrefab.Prefabs)
{
if (!prefab.UpgradeCategories.Contains(category)) { continue; }
if (!string.IsNullOrWhiteSpace(prefabIdentifier) && !prefab.Identifier.Equals(prefabIdentifier, StringComparison.OrdinalIgnoreCase)) { continue; }
int targetLevel = prefab.MaxLevel - upgradeManager.GetRealUpgradeLevel(prefab, category);
for (int i = 0; i < targetLevel; i++)
{
upgradeManager.PurchaseUpgrade(prefab, category, force: true);
}
NewMessage($"Upgraded {category.Identifier}.{prefab.Identifier} by {targetLevel} levels.", Color.DarkGreen);
}
}
NewMessage($"Start a new round to apply the upgrades.", Color.Lime);
}, () =>
{
return new[]
{
UpgradeCategory.Categories.Select(c => c.Identifier).Distinct().ToArray(),
UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray()
};
}, true));
commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) => commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) =>
{ {
@@ -253,8 +253,8 @@ namespace Barotrauma
}; };
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed); potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false).ToList();
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false).ToList();
if (moduleFlags != null && moduleFlags.Any()) if (moduleFlags != null && moduleFlags.Any())
{ {
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList(); List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList();
@@ -303,6 +303,12 @@ namespace Barotrauma
return potentialSpawnPoints.GetRandom(); return potentialSpawnPoints.GetRandom();
} }
//avoid using waypoints if there's any actual spawnpoints available
if (validSpawnPoints.Any(wp => wp.SpawnType != SpawnType.Path))
{
validSpawnPoints = validSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
}
//if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags //if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags
if (spawnpointTags == null || !spawnpointTags.Any()) if (spawnpointTags == null || !spawnpointTags.Any())
{ {
@@ -6,12 +6,17 @@ namespace Barotrauma
{ {
class TagAction : EventAction class TagAction : EventAction
{ {
public enum SubType { Any= 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
[Serialize("", true)] [Serialize("", true)]
public string Criteria { get; set; } public string Criteria { get; set; }
[Serialize("", true)] [Serialize("", true)]
public string Tag { get; set; } public string Tag { get; set; }
[Serialize(SubType.Any, true)]
public SubType SubmarineType { get; set; }
[Serialize(true, true)] [Serialize(true, true)]
public bool IgnoreIncapacitatedCharacters { get; set; } public bool IgnoreIncapacitatedCharacters { get; set; }
@@ -63,17 +68,37 @@ namespace Barotrauma
private void TagStructuresByIdentifier(string identifier) private void TagStructuresByIdentifier(string identifier)
{ {
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)); ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
} }
private void TagItemsByIdentifier(string identifier) private void TagItemsByIdentifier(string identifier)
{ {
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)); ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
} }
private void TagItemsByTag(string tag) private void TagItemsByTag(string tag)
{ {
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.HasTag(tag)); ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.HasTag(tag));
}
private bool SubmarineTypeMatches(Submarine sub)
{
if (SubmarineType == SubType.Any) { return true; }
if (sub == null) { return false; }
switch (sub.Info.Type)
{
case Barotrauma.SubmarineType.Player:
return SubmarineType.HasFlag(SubType.Player);
case Barotrauma.SubmarineType.Outpost:
case Barotrauma.SubmarineType.OutpostModule:
return SubmarineType.HasFlag(SubType.Outpost);
case Barotrauma.SubmarineType.Wreck:
return SubmarineType.HasFlag(SubType.Wreck);
case Barotrauma.SubmarineType.BeaconStation:
return SubmarineType.HasFlag(SubType.BeaconStation);
default:
return false;
}
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
@@ -113,7 +138,7 @@ namespace Barotrauma
public override string ToDebugString() public override string ToDebugString()
{ {
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()})"; return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()}, Sub: {SubmarineType.ColorizeObject()})";
} }
} }
} }
@@ -134,7 +134,7 @@ namespace Barotrauma
if (level?.LevelData?.Type == LevelData.LevelType.Outpost) if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
{ {
//if the outpost is connected to a locked connection, create an event to unlock it //if the outpost is connected to a locked connection, create an event to unlock it
if (level.StartLocation?.Connections.Any(c => c.Locked) ?? false) if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
{ {
var unlockPathPrefabs = EventSet.PrefabList.FindAll(e => e.UnlockPathEvent); var unlockPathPrefabs = EventSet.PrefabList.FindAll(e => e.UnlockPathEvent);
var unlockPathPrefabsForBiome = unlockPathPrefabs.FindAll(e => var unlockPathPrefabsForBiome = unlockPathPrefabs.FindAll(e =>
@@ -166,11 +166,14 @@ namespace Barotrauma
void AddChildEvents(EventSet eventSet) void AddChildEvents(EventSet eventSet)
{ {
if (eventSet == null) { return; } if (eventSet == null) { return; }
foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First)) if (eventSet.OncePerOutpost)
{ {
if (!level.LevelData.NonRepeatableEvents.Contains(ep)) foreach (EventPrefab ep in eventSet.EventPrefabs.Select(e => e.First))
{ {
level.LevelData.NonRepeatableEvents.Add(ep); if (!level.LevelData.NonRepeatableEvents.Contains(ep))
{
level.LevelData.NonRepeatableEvents.Add(ep);
}
} }
} }
foreach (EventSet childSet in eventSet.ChildSets) foreach (EventSet childSet in eventSet.ChildSets)
@@ -373,6 +376,8 @@ namespace Barotrauma
private void CreateEvents(EventSet eventSet, Random rand) private void CreateEvents(EventSet eventSet, Random rand)
{ {
if (level == null) { return; } if (level == null) { return; }
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
int applyCount = 1; int applyCount = 1;
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>(); List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
if (eventSet.PerRuin) if (eventSet.PerRuin)
@@ -86,6 +86,7 @@ namespace Barotrauma
public readonly bool IgnoreCoolDown; public readonly bool IgnoreCoolDown;
public readonly bool PerRuin, PerCave, PerWreck; public readonly bool PerRuin, PerCave, PerWreck;
public readonly bool DisableInHuntingGrounds;
public readonly bool OncePerOutpost; public readonly bool OncePerOutpost;
@@ -142,9 +143,10 @@ namespace Barotrauma
PerRuin = element.GetAttributeBool("perruin", false); PerRuin = element.GetAttributeBool("perruin", false);
PerCave = element.GetAttributeBool("percave", false); PerCave = element.GetAttributeBool("percave", false);
PerWreck = element.GetAttributeBool("perwreck", false); PerWreck = element.GetAttributeBool("perwreck", false);
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck)); IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck); DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
OncePerOutpost = element.GetAttributeBool("perwreck", false); OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true); TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
Commonness[""] = 1.0f; Commonness[""] = 1.0f;
@@ -54,10 +54,6 @@ namespace Barotrauma
requireRescue.Clear(); requireRescue.Clear();
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub; var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
if (submarine.Info.Type == SubmarineType.Outpost)
{
submarine.TeamID = CharacterTeamType.None;
}
if (!IsClient) if (!IsClient)
{ {
InitCharacters(submarine); InitCharacters(submarine);
@@ -148,6 +144,10 @@ namespace Barotrauma
} }
humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos); humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos);
humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false); humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false);
if (spawnPos is WayPoint wp)
{
spawnedCharacter.GiveIdCardTags(wp);
}
if (element.GetAttributeBool("requirekill", false)) if (element.GetAttributeBool("requirekill", false))
{ {
requireKill.Add(spawnedCharacter); requireKill.Add(spawnedCharacter);
@@ -175,6 +175,15 @@ namespace Barotrauma
{ {
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true)); characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
} }
if (submarine != null && spawnedCharacter.AIController is EnemyAIController enemyAi)
{
enemyAi.UnattackableSubmarines.Add(submarine);
enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
foreach (Submarine sub in Submarine.MainSub.DockedTo)
{
enemyAi.UnattackableSubmarines.Add(sub);
}
}
} }
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic; using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
@@ -99,18 +100,18 @@ namespace Barotrauma
} }
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] }; subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
subs[0].TeamID = CharacterTeamType.Team1; subs[1].TeamID = CharacterTeamType.Team2;
subs[0].NeutralizeBallast(); subs[1].NeutralizeBallast(); subs[0].NeutralizeBallast();
subs[0].TeamID = CharacterTeamType.Team1;
subs[0].DockedTo.ForEach(s => s.TeamID = CharacterTeamType.Team1);
subs[1].NeutralizeBallast();
subs[1].TeamID = CharacterTeamType.Team2;
subs[1].DockedTo.ForEach(s => s.TeamID = CharacterTeamType.Team2);
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition)); subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
subs[1].FlipX(); subs[1].FlipX();
crews = new List<Character>[] { new List<Character>(), new List<Character>() }; crews = new List<Character>[] { new List<Character>(), new List<Character>() };
foreach (Submarine submarine in Submarine.Loaded)
{
//hide all subs from sonar to make sneak attacks possible
submarine.ShowSonarMarker = false;
}
} }
public override void End() public override void End()
@@ -107,6 +107,11 @@ namespace Barotrauma
} }
public readonly Location[] Locations; public readonly Location[] Locations;
public int? Difficulty
{
get { return Prefab.Difficulty; }
}
public Mission(MissionPrefab prefab, Location[] locations) public Mission(MissionPrefab prefab, Location[] locations)
{ {
@@ -186,6 +191,9 @@ namespace Barotrauma
public void Start(Level level) public void Start(Level level)
{ {
#if CLIENT
shownMessages.Clear();
#endif
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories) foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
{ {
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false)) foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false))
@@ -72,6 +72,8 @@ namespace Barotrauma
public readonly List<Tuple<string, object, SetDataAction.OperationType>> DataRewards = new List<Tuple<string, object, SetDataAction.OperationType>>(); public readonly List<Tuple<string, object, SetDataAction.OperationType>> DataRewards = new List<Tuple<string, object, SetDataAction.OperationType>>();
public readonly int Commonness; public readonly int Commonness;
public readonly int? Difficulty;
public const int MinDifficulty = 1, MaxDifficulty = 4;
public readonly int Reward; public readonly int Reward;
@@ -156,6 +158,11 @@ namespace Barotrauma
AllowRetry = element.GetAttributeBool("allowretry", false); AllowRetry = element.GetAttributeBool("allowretry", false);
IsSideObjective = element.GetAttributeBool("sideobjective", false); IsSideObjective = element.GetAttributeBool("sideobjective", false);
Commonness = element.GetAttributeInt("commonness", 1); Commonness = element.GetAttributeInt("commonness", 1);
if (element.GetAttribute("difficulty") != null)
{
int difficulty = element.GetAttributeInt("difficulty", MinDifficulty);
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
}
SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully"); SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully");
FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? ""; FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? "";
@@ -228,7 +228,7 @@ namespace Barotrauma
} }
GiveReward(); GiveReward();
completed = true; completed = true;
if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase))) if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase) || t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)))
{ {
level.LevelData.HasHuntingGrounds = false; level.LevelData.HasHuntingGrounds = false;
} }
@@ -310,7 +310,7 @@ namespace Barotrauma
{ {
foreach (Character npc in Character.CharacterList) foreach (Character npc in Character.CharacterList)
{ {
if (npc.TeamID != CharacterTeamType.FriendlyNPC || npc.CurrentHull == null || npc.IsIncapacitated) { continue; } if ((npc.TeamID != CharacterTeamType.FriendlyNPC && npc.TeamID != CharacterTeamType.None) || npc.CurrentHull == null || npc.IsIncapacitated) { continue; }
if (npc.AIController is HumanAIController humanAI && (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>())) if (npc.AIController is HumanAIController humanAI && (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()))
{ {
continue; continue;
@@ -327,10 +327,12 @@ namespace Barotrauma
{ {
if (npc.TeamID == CharacterTeamType.None) if (npc.TeamID == CharacterTeamType.None)
{ {
dialogFlags.Remove("OutpostNPC");
dialogFlags.Add("Bandit"); dialogFlags.Add("Bandit");
} }
else if (npc.TeamID == CharacterTeamType.FriendlyNPC) else if (npc.TeamID == CharacterTeamType.FriendlyNPC)
{ {
dialogFlags.Remove("OutpostNPC");
dialogFlags.Add("Hostage"); dialogFlags.Add("Hostage");
} }
} }
@@ -144,7 +144,7 @@ namespace Barotrauma
new XAttribute("value", valueStr), new XAttribute("value", valueStr),
new XAttribute("type", value?.GetType()))); new XAttribute("type", value?.GetType())));
} }
#if DEBUG || UNSTABLE #if DEBUG
DebugConsole.Log(element.ToString()); DebugConsole.Log(element.ToString());
#endif #endif
modeElement.Add(element); modeElement.Add(element);
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -258,22 +259,32 @@ namespace Barotrauma
if (levelData.HasBeaconStation && !levelData.IsBeaconActive) if (levelData.HasBeaconStation && !levelData.IsBeaconActive)
{ {
var beaconMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase))); var beaconMissionPrefabs = MissionPrefab.List.FindAll(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase)));
if (beaconMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type)) if (beaconMissionPrefabs.Any())
{ {
extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations)); Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
var beaconMissionPrefab = beaconMissionPrefabs.GetRandom(rand);
if (!Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type))
{
extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
}
} }
} }
if (levelData.HasHuntingGrounds) if (levelData.HasHuntingGrounds)
{ {
var huntingGroundsMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase))); var huntingGroundsMissionPrefabs = MissionPrefab.List.FindAll(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase)));
if (huntingGroundsMissionPrefab == null) if (!huntingGroundsMissionPrefabs.Any())
{ {
DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggroundsnoreward\" found."); DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggroundsnoreward\" found.");
} }
else if (!Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type)) else
{ {
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations)); Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
var huntingGroundsMissionPrefab = huntingGroundsMissionPrefabs.GetRandom(rand);
if (!Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type))
{
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
}
} }
} }
} }
@@ -524,11 +535,13 @@ namespace Barotrauma
takenItems.Add(item); takenItems.Add(item);
} }
} }
map.CurrentLocation.RegisterTakenItems(takenItems); if (map != null && CargoManager != null)
{
map.CurrentLocation.AddToStock(CargoManager.SoldItems); map.CurrentLocation.RegisterTakenItems(takenItems);
CargoManager.ClearSoldItemsProjSpecific(); map.CurrentLocation.AddToStock(CargoManager.SoldItems);
map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems); CargoManager.ClearSoldItemsProjSpecific();
map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems);
}
if (GameMain.NetworkMember == null) if (GameMain.NetworkMember == null)
{ {
CargoManager.ClearItemsInBuyCrate(); CargoManager.ClearItemsInBuyCrate();
@@ -538,11 +551,11 @@ namespace Barotrauma
{ {
if (GameMain.NetworkMember.IsServer) if (GameMain.NetworkMember.IsServer)
{ {
CargoManager.ClearItemsInBuyCrate(); CargoManager?.ClearItemsInBuyCrate();
} }
else if (GameMain.NetworkMember.IsClient) else if (GameMain.NetworkMember.IsClient)
{ {
CargoManager.ClearItemsInSellCrate(); CargoManager?.ClearItemsInSellCrate();
} }
} }
@@ -601,7 +614,11 @@ namespace Barotrauma
} }
foreach (Location location in Map.Locations) foreach (Location location in Map.Locations)
{ {
location.ChangeType(location.OriginalType); if (location.Type != location.OriginalType)
{
location.ChangeType(location.OriginalType);
location.PendingLocationTypeChange = null;
}
location.CreateStore(force: true); location.CreateStore(force: true);
location.ClearMissions(); location.ClearMissions();
location.Discovered = false; location.Discovered = false;

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