diff --git a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs index 4bef949b7..5d855a04a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Camera.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Camera.cs @@ -37,7 +37,7 @@ namespace Barotrauma public float 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; @@ -51,8 +51,6 @@ namespace Barotrauma private float zoom; - private float offsetAmount; - private Matrix transform, shaderTransform, viewMatrix; private Vector2 position; private float rotation; @@ -67,16 +65,9 @@ namespace Barotrauma public float Shake; private Vector2 shakePosition; private float shakeTimer; - - //the area of the world inside the camera view - private Rectangle worldView; private float globalZoomScale = 1.0f; - private Point resolution; - - private Vector2 targetPos; - //used to smooth out the movement when in freecam private float targetZoom; private Vector2 velocity; @@ -89,10 +80,10 @@ namespace Barotrauma zoom = MathHelper.Clamp(value, GameMain.DebugDraw ? 0.01f : MinZoom, MaxZoom); Vector2 center = WorldViewCenter; - float newWidth = resolution.X / zoom; - float newHeight = resolution.Y / zoom; + float newWidth = Resolution.X / zoom; + float newHeight = Resolution.Y / zoom; - worldView = new Rectangle( + WorldView = new Rectangle( (int)(center.X - newWidth / 2.0f), (int)(center.Y + newHeight / 2.0f), (int)newWidth, @@ -122,29 +113,20 @@ namespace Barotrauma } } - public float OffsetAmount - { - get { return offsetAmount; } - set { offsetAmount = value; } - } + public float OffsetAmount { get; set; } - public Point Resolution - { - get { return resolution; } - } + public Point Resolution { get; private set; } - public Rectangle WorldView - { - get { return worldView; } - } + //the area of the world inside the camera view + public Rectangle WorldView { get; private set; } public Vector2 WorldViewCenter { get { return new Vector2( - worldView.X + worldView.Width / 2.0f, - worldView.Y - worldView.Height / 2.0f); + WorldView.X + WorldView.Width / 2.0f, + WorldView.Y - WorldView.Height / 2.0f); } } @@ -171,12 +153,13 @@ namespace Barotrauma UpdateTransform(false); } - public Vector2 TargetPos + ~Camera() { - get { return targetPos; } - set { targetPos = value; } + GameMain.Instance.ResolutionChanged -= CreateMatrices; } + public Vector2 TargetPos { get; set; } + public Vector2 GetPosition() { return position; @@ -204,21 +187,29 @@ namespace Barotrauma 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)); - 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; float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom; - worldView.X = (int)(interpolatedPosition.X - worldView.Width / 2.0); - worldView.Y = (int)(interpolatedPosition.Y + worldView.Height / 2.0); + WorldView = new Rectangle((int)(interpolatedPosition.X - WorldView.Width / 2.0), + (int)(interpolatedPosition.Y + WorldView.Height / 2.0), + WorldView.Width, WorldView.Height); transform = Matrix.CreateTranslation( new Vector3(-interpolatedPosition.X, interpolatedPosition.Y, 0)) * @@ -227,19 +218,22 @@ namespace Barotrauma shaderTransform = Matrix.CreateTranslation( new Vector3( - -interpolatedPosition.X - resolution.X / interpolatedZoom / 2.0f, - -interpolatedPosition.Y - resolution.Y / interpolatedZoom / 2.0f, 0)) * + -interpolatedPosition.X - Resolution.X / interpolatedZoom / 2.0f, + -interpolatedPosition.Y - Resolution.Y / interpolatedZoom / 2.0f, 0)) * Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) * viewMatrix * Matrix.CreateRotationZ(-rotation); - if (Character.Controlled == null) + if (updateListener) { - GameMain.SoundManager.ListenerPosition = new Vector3(WorldViewCenter.X, WorldViewCenter.Y, -(100.0f / zoom)); - } - else - { - GameMain.SoundManager.ListenerPosition = new Vector3(Character.Controlled.WorldPosition.X, Character.Controlled.WorldPosition.Y, -(100.0f / zoom)); + if (Character.Controlled == null) + { + GameMain.SoundManager.ListenerPosition = new Vector3(WorldViewCenter.X, WorldViewCenter.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 /// 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; prevZoom = zoom; @@ -265,7 +259,7 @@ namespace Barotrauma float moveSpeed = 20.0f / zoom; Vector2 moveCam = Vector2.Zero; - if (targetPos == Vector2.Zero) + if (TargetPos == Vector2.Zero) { Vector2 moveInput = Vector2.Zero; 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 diffViewCenter; @@ -318,14 +312,14 @@ namespace Barotrauma else if (allowMove) { Vector2 mousePos = PlayerInput.MousePosition; - Vector2 offset = mousePos - resolution.ToVector2() / 2; - offset.X = offset.X / (resolution.X * 0.4f); - offset.Y = -offset.Y / (resolution.Y * 0.3f); + Vector2 offset = mousePos - Resolution.ToVector2() / 2; + offset.X = offset.X / (Resolution.X * 0.6f); + offset.Y = -offset.Y / (Resolution.Y * 0.6f); 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. // 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; } @@ -343,25 +337,21 @@ namespace Barotrauma previousOffset = offset; } - //how much to zoom out (zoom completely out when offset is 1000) - float zoomOutAmount = GetZoomAmount(offset); - //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; + //TODO: remove magic numbers + float minMultiplier = OffsetAmount > 0f ? ((DefaultZoom * 8f * 250f) / OffsetAmount) : 15f; - //an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution, - //while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling - //on lower resolutions, which doesn't look that good) - float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom, - (GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(zoomOutAmount) : 0.3f); + //how much to zoom out (0.0 = Default zoom, 1.0 = zoom completely out) + float zoomOutAmount = GetZoomAmount(offset); + float newZoom = MathHelper.Lerp(DefaultZoom, MinZoom * minMultiplier, zoomOutAmount) * globalZoomScale; + //zoom in further if zoomOutAmount is low and resolution is lower than reference + newZoom *= MathHelper.Lerp(0.5f * (1f - Math.Min(globalZoomScale, 1f)), 0f, zoomOutAmount) + 1f; Zoom += (newZoom - zoom) / ZoomSmoothness; //force targetzoom to the current zoom value, so the camera stays at the same zoom when switching to freecam targetZoom = Zoom; - Vector2 diff = (targetPos + offset) - position; + Vector2 diff = (TargetPos + offset) - position; moveCam = diff / MoveSmoothness; } @@ -426,7 +416,7 @@ namespace Barotrauma 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() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 9cc51ecc1..bbf7d80fc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -224,8 +224,7 @@ namespace Barotrauma float targetOffsetAmount = 0.0f; if (moveCam) { - if (NeedsAir && - pressureProtection < 80.0f && + if (NeedsAir && !IsProtectedFromPressure() && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f)) { 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) { + HintManager.OnCharacterKilled(this); + if (GameMain.NetworkMember != null && controlled == this) { string chatMessage = CauseOfDeath.Type == CauseOfDeathType.Affliction ? CauseOfDeath.Affliction.SelfCauseOfDeathDescription : 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.LightManager.LosEnabled = false; controlled = null; } - + PlaySound(CharacterSound.SoundType.Die); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 66c981347..1d2892d72 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -100,7 +100,22 @@ namespace Barotrauma 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 }; - 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 67b51da47..3f1b518ac 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -330,6 +330,8 @@ namespace Barotrauma GameMain.Client.HasSpawned = true; GameMain.Client.Character = this; GameMain.LightManager.LosEnabled = true; + GameMain.LightManager.LosAlpha = 1f; + GameMain.Client.WaitForNextRoundRespawn = null; } else { @@ -516,6 +518,7 @@ namespace Barotrauma if (!character.IsDead) { Controlled = character; } GameMain.LightManager.LosEnabled = true; + GameMain.LightManager.LosAlpha = 1f; character.memInput.Clear(); character.memState.Clear(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 3a7201e4c..7c97a7dc0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -997,8 +997,8 @@ namespace Barotrauma cprButton.Visible = Character == Character.Controlled?.SelectedCharacter - && (Character.IsUnconscious || Character.Stun > 0.0f) && !Character.IsDead + && Character.IsKnockedDown && openHealthWindow == this; cprButton.IgnoreLayoutGroups = !cprButton.Visible; cprButton.Selected = diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index e914344f7..e5dda0c13 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1247,6 +1247,7 @@ namespace Barotrauma GameMain.DebugDraw = false; GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LosEnabled = true; + GameMain.LightManager.LosAlpha = 1f; } NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White); }); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs index 5c58ea83e..d4d0afdd6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs @@ -14,10 +14,7 @@ namespace Barotrauma base.State = value; if (state == HostagesKilledState && !string.IsNullOrEmpty(hostagesKilledMessage)) { - new GUIMessageBox(string.Empty, hostagesKilledMessage, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon, parseRichText: true) - { - IconColor = Prefab.IconColor - }; + CreateMessageBox(string.Empty, hostagesKilledMessage); } } } @@ -44,6 +41,15 @@ namespace Barotrauma { 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)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs index d7a663e72..56e2e95f8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/Mission.cs @@ -1,4 +1,5 @@ using Barotrauma.Networking; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Globalization; @@ -7,6 +8,19 @@ namespace Barotrauma { abstract partial class Mission { + private readonly List shownMessages = new List(); + public IEnumerable ShownMessages + { + get { return shownMessages; } + } + + public Color GetDifficultyColor() + { + int v = Difficulty ?? MissionPrefab.MinDifficulty; + float t = MathUtils.InverseLerp(MissionPrefab.MinDifficulty, MissionPrefab.MaxDifficulty, v); + return ToolBox.GradientLerp(t, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red); + } + public string GetMissionRewardText() { string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Reward)); @@ -71,6 +85,7 @@ namespace Barotrauma protected void CreateMessageBox(string header, string message) { + shownMessages.Add(message); new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon, parseRichText: true) { IconColor = Prefab.IconColor diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs index 878c814b7..5bc64d50f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/MissionPrefab.cs @@ -1,7 +1,5 @@ using Microsoft.Xna.Framework; using System; -using System.Collections.Generic; -using System.Text; using System.Xml.Linq; namespace Barotrauma diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index fdbbd65d6..7be3d7ae9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -565,34 +565,26 @@ namespace Barotrauma } else { - string guiScaleString = $"GUI.Scale: {Scale}"; - string guixScaleString = $"GUI.xScale: {xScale}"; - string guiyScaleString = $"GUI.yScale: {yScale}"; - string relativeHorizontalAspectRatioString = $"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}"; - string relativeVerticalAspectRatioString = $"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}"; - Vector2 guiScaleStringSize = SmallFont.MeasureString(guiScaleString); - Vector2 guixScaleStringSize = SmallFont.MeasureString(guixScaleString); - Vector2 guiyScaleStringSize = SmallFont.MeasureString(guiyScaleString); - Vector2 relativeHorizontalAspectRatioStringSize = SmallFont.MeasureString(relativeHorizontalAspectRatioString); - Vector2 relativeVerticalAspectRatioStringSize = SmallFont.MeasureString(relativeVerticalAspectRatioString); + string[] strings = new string[] + { + $"GUI.Scale: {Scale}", + $"GUI.xScale: {xScale}", + $"GUI.yScale: {yScale}", + $"RelativeHorizontalAspectRatio: {RelativeHorizontalAspectRatio}", + $"RelativeVerticalAspectRatio: {RelativeVerticalAspectRatio}", + $"Cam.Zoom: {Screen.Selected.Cam?.Zoom ?? 0f}", + }; int padding = IntScale(10); int yPos = padding; - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guiScaleStringSize.X - padding, yPos), guiScaleString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)guiScaleStringSize.Y + padding / 2; + foreach (string str in strings) + { + Vector2 stringSize = SmallFont.MeasureString(str); - DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)guixScaleStringSize.X - padding, yPos), guixScaleString, Color.LightGreen, Color.Black, 0, SmallFont); - yPos += (int)guixScaleStringSize.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; + DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)stringSize.X - padding, yPos), str, Color.LightGreen, Color.Black, 0, SmallFont); + yPos += (int)stringSize.Y + padding / 2; + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 7d5233b38..a6867eb60 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -194,6 +194,8 @@ namespace Barotrauma public bool ScrollBarEnabled { get; set; } = true; public bool KeepSpaceForScrollBar { get; set; } + public bool CanTakeKeyBoardFocus { get; set; } = true; + public bool ScrollBarVisible { 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 (takeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) + if (takeKeyBoardFocus && CanTakeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) { Selected = true; GUI.KeyboardDispatcher.Subscriber = this; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs index 6bfe37323..1e30def57 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs @@ -261,7 +261,7 @@ namespace Barotrauma public readonly List RichTextData = null; - private readonly bool hasColorHighlight = false; + public bool HasColorHighlight => RichTextData != null; public struct ClickableArea { @@ -295,7 +295,6 @@ namespace Barotrauma if (parseRichText) { 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, @@ -326,7 +325,6 @@ namespace Barotrauma : this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput) { this.RichTextData = richTextData; - hasColorHighlight = richTextData != null; } public void CalculateHeightFromText(int padding = 0, bool removeExtraSpacing = false) @@ -634,7 +632,7 @@ namespace Barotrauma currentTextColor = selectedTextColor; } - if (!hasColorHighlight) + if (!HasColorHighlight) { string textToShow = Censor ? censoredText : (Wrap ? wrappedText : text); Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs index 7355cdbc9..4bb0dbc7a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/RectTransform.cs @@ -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; /// @@ -103,7 +103,7 @@ namespace Barotrauma /// public Point MaxSize { - get { return maxSize ?? maxPoint; } + get { return maxSize ?? MaxPoint; } set { if (maxSize == value) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index fecc51016..a9a72df2e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -18,7 +18,7 @@ namespace Barotrauma private static UISprite spectateIcon, disconnectedIcon; 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 GUIFrame infoFrame, contentFrame; @@ -181,77 +181,79 @@ namespace Barotrauma 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"); - 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), - _ => new Vector2(0.33f, 0.667f) - }; + case InfoFrameTab.MyCharacter: + 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) }); - 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)) }) - { - RelativeSpacing = 0.01f, - Stretch = true - }; - var buttonArea = new GUILayoutGroup(new RectTransform(new Point(innerLayoutGroup.Rect.Width, GUI.IntScale(25f)), parent: innerLayoutGroup.RectTransform), isHorizontal: true) + 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 }; + 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; 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); - 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, - OnClicked = SelectInfoFrameTab - }; - tabButtons.Add(crewButton); + var newButton = new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: $"InfoFrameTabButton.{tab}") + { + UserData = tab, + 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") - { - UserData = InfoFrameTab.Mission, - OnClicked = SelectInfoFrameTab - }; - tabButtons.Add(missionButton); + var crewButton = createTabButton(InfoFrameTab.Crew, "crew"); + + var missionButton = createTabButton(InfoFrameTab.Mission, "mission"); 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") - { - UserData = InfoFrameTab.Reputation, - OnClicked = SelectInfoFrameTab - }; - tabButtons.Add(reputationButton); + var reputationButton = createTabButton(InfoFrameTab.Reputation, "reputation"); - 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) { TextGetter = () => TextManager.GetWithVariable("campaignmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", campaignMode.Money)) }; } - - bool isTraitor = GameMain.Client?.Character?.IsTraitor ?? false; - if (isTraitor && GameMain.Client.TraitorMission != null) + else { - 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, - OnClicked = SelectInfoFrameTab - }; - tabButtons.Add(traitorButton); + var traitorButton = createTabButton(InfoFrameTab.Traitor, "tabmenu.traitor"); + } } if (GameMain.NetworkMember != null) { - var myCharacterButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("tabmenu.character"), style: "GUITabButton") - { - UserData = InfoFrameTab.MyCharacter, - OnClicked = SelectInfoFrameTab - }; - tabButtons.Add(myCharacterButton); + var myCharacterButton = createTabButton(InfoFrameTab.MyCharacter, "tabmenu.character"); } } @@ -288,6 +290,9 @@ namespace Barotrauma if (GameMain.NetworkMember == null) { return false; } GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder); break; + case InfoFrameTab.Submarine: + CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); + break; } return true; @@ -879,13 +884,16 @@ namespace Barotrauma UserData = line }; 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, - OnClick = GameMain.NetLobbyScreen.SelectPlayer - }); + textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() + { + Data = data, + OnClick = GameMain.NetLobbyScreen.SelectPlayer + }); + } } } } @@ -930,27 +938,38 @@ namespace Barotrauma foreach (Mission mission in GameMain.GameSession.Missions) { 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 reputationText = mission.GetReputationRewardText(mission.Locations[0]); 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 missionReputationRichTextData = RichTextData.GetRichTextData(reputationText, out string missionReputationString); + var missionDescriptionRichTextData = RichTextData.GetRichTextData(descriptionText, out string missionDescriptionString); 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); missionReputationString = ToolBox.WrapText(missionReputationString, missionTextGroup.Rect.Width, GUI.Font); + missionDescriptionString = ToolBox.WrapText(missionDescriptionString, missionTextGroup.Rect.Width, GUI.Font); Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString); Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString); Vector2 missionRewardSize = GUI.Font.MeasureString(missionRewardString); Vector2 missionReputationSize = GUI.Font.MeasureString(missionReputationString); - 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); if (mission.Prefab.Icon != null) @@ -960,10 +979,38 @@ namespace Barotrauma int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); Point iconSize = new Point(iconWidth, iconHeight); - new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor, 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), 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), 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), 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; + }; + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index c64af21de..3b3d5ceff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -15,7 +15,7 @@ namespace Barotrauma internal class UpgradeStore { - private readonly struct CategoryData + public readonly struct CategoryData { public readonly UpgradeCategory Category; public readonly List Prefabs; @@ -125,7 +125,7 @@ namespace Barotrauma { 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 if (currentStoreLayout?.Parent != null) { - foreach (GUIComponent component in currentStoreLayout.Content.Children) - { - if (!(component.UserData is CategoryData data)) { continue; } - if (component.FindChild("indicators", true) is { } indicators) - { - UpdateCategoryIndicators(indicators, component, data.Prefabs, data.Category); - } - } + UpdateCategoryList(currentStoreLayout, Campaign, drawnSubmarine, applicableCategories); + } + } - // reset the order first - foreach (UpgradeCategory category in UpgradeCategory.Categories) + //TODO: move this somewhere else + public static void UpdateCategoryList(GUIListBox categoryList, CampaignMode campaign, Submarine drawnSubmarine, IEnumerable 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); - component?.SetAsLastChild(); + UpdateCategoryIndicators(indicators, component, data.Prefabs, data.Category, campaign, drawnSubmarine, applicableCategories); } + } - // send the disabled components to the bottom - List lastChilds = currentStoreLayout.Content.Children.Where(component => !component.Enabled).ToList(); + // reset the order first + 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) - { - lastChild.SetAsLastChild(); - } + // send the disabled components to the bottom + List lastChilds = categoryList.Content.Children.Where(component => !component.Enabled).ToList(); + + 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, ScrollBarVisible = false, @@ -548,7 +555,7 @@ namespace Barotrauma 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), GlowOnSelect = true @@ -565,9 +572,9 @@ namespace Barotrauma * |-----------------------------|--------------------------| */ 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 }; - GUILayoutGroup indicatorLayout = new GUILayoutGroup(rectT(0.5f, 0.25f, contentLayout, Anchor.BottomRight), isHorizontal: true, childAnchor: Anchor.TopRight) { UserData = "indicators", IgnoreLayoutGroups = true, RelativeSpacing = 0.01f }; - + 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 }; + foreach (var prefab in prefabs) { 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(); } + 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 }; RefreshUpgradeList(); @@ -637,7 +651,7 @@ namespace Barotrauma } } - private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List itemsOnSubmarine) + public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true) { /* UPGRADE PREFAB ENTRY * |------------------------------------------------------------------| @@ -648,8 +662,8 @@ namespace Barotrauma * | | progress bar | x / y | | * |------------------------------------------------------------------| */ - GUIFrame prefabFrame = new GUIFrame(rectT(1f, 0.25f, parent), style: "ListBoxElement") { SelectedColor = Color.Transparent, UserData = new CategoryData(category, prefab) }; - GUILayoutGroup prefabLayout = new GUILayoutGroup(rectT(0.98f,0.95f, prefabFrame, Anchor.Center), isHorizontal: true); + 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) { Stretch = true }; 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 }; 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" }; 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 }; - GUILayoutGroup 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 }; + GUILayoutGroup buyButtonLayout = null; + 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 }; + } description.CalculateHeightFromText(); // cut the description if it overflows and add a tooltip to it @@ -677,14 +695,33 @@ namespace Barotrauma } // Recalculate everything to prevent jumping - if (parent is GUILayoutGroup group) { group.Recalculate(); } + if (rectTransform.Parent.GUIComponent is GUILayoutGroup group) { group.Recalculate(); } descriptionLayout.Recalculate(); prefabLayout.Recalculate(); imageLayout.Recalculate(); textLayout.Recalculate(); progressLayout.Recalculate(); - buyButtonLayout.Recalculate(); + buyButtonLayout?.Recalculate(); + + return prefabFrame; + } + + private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List itemsOnSubmarine) + { + GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent)); + var prefabLayout = prefabFrame.GetChild(); + GUILayoutGroup[] childLayouts = prefabLayout.GetAllChildren().ToArray(); + var imageLayout = childLayouts[0]; + var icon = imageLayout.GetChild(); + var textLayout = childLayouts[1]; + var name = textLayout.GetChild(); + GUILayoutGroup[] textChildLayouts = textLayout.GetAllChildren().ToArray(); + var descriptionLayout = textChildLayouts[0]; + var description = descriptionLayout.GetChild(); + var progressLayout = textChildLayouts[1]; + var buyButtonLayout = childLayouts[2]; + var buyButton = buyButtonLayout.GetChild(); if (!HasPermission || itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))) { @@ -713,7 +750,7 @@ namespace Barotrauma return true; }; - UpdateUpgradeEntry(prefabFrame, prefab, category); + UpdateUpgradeEntry(prefabFrame, prefab, category, Campaign); } 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}" }); } + public static IEnumerable 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) { 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); CreateHullBorderVerticies(drawnSubmarine, parent); - List entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToList(); applicableCategories.Clear(); - - foreach (UpgradeCategory category in UpgradeCategory.Categories) - { - if (entitiesOnSub.Any(item => category.CanBeApplied(item, null))) - { - applicableCategories.Add(category); - } - } + applicableCategories.AddRange(GetApplicableCategories(drawnSubmarine)); } 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() }); if (prefabFrame.FindChild("progressbar", true) is { } progressParent) @@ -1023,7 +1064,7 @@ namespace Barotrauma if (prefabFrame.FindChild("buybutton", true) is { } buttonParent) { GUITextBlock priceLabel = buttonParent.GetChild(); - 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) { @@ -1038,7 +1079,7 @@ namespace Barotrauma if (button != null) { button.Enabled = currentLevel < prefab.MaxLevel; - if (WaitForServerUpdate || !HasPermission || price > Campaign.Money) + if (WaitForServerUpdate || !campaign.AllowedToManageCampaign() || price > campaign.Money) { button.Enabled = false; } @@ -1046,7 +1087,14 @@ namespace Barotrauma } } - private void UpdateCategoryIndicators(GUIComponent indicators, GUIComponent parent, List prefabs, UpgradeCategory category) + private static void UpdateCategoryIndicators( + GUIComponent indicators, + GUIComponent parent, + List prefabs, + UpgradeCategory category, + CampaignMode campaign, + Submarine drawnSubmarine, + IEnumerable applicableCategories) { // Disables the parent and only re-enables if the submarine contains valid items if (!category.IsWallUpgrade && drawnSubmarine != null) @@ -1078,13 +1126,13 @@ namespace Barotrauma GUIComponentStyle dimStyle = styles["upgradeindicatordim"]; 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 if (image.Style == onStyle) { continue; } image.ApplyStyle(onStyle); } - else if (Campaign.UpgradeManager.GetUpgradeLevel(prefab, category) > 0) + else if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) > 0) { if (image.Style == dimStyle) { continue; } image.ApplyStyle(dimStyle); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index f5e4cb3e4..ea2024401 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -1229,13 +1229,19 @@ namespace Barotrauma base.OnExiting(sender, args); } - public void ShowOpenUrlInWebBrowserPrompt(string url) + public void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null) { if (string.IsNullOrEmpty(url)) { return; } if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; } - var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url), - new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) + string text = TextManager.GetWithVariable("openlinkinbrowserprompt", "[link]", url); + 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" }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index 7da3bb369..b3a5380c8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -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") { + ToolTip = TextManager.Get("chat"), ClampMouseRectToParent = false }; 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), style: "CrewListBackground") { - 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; - } + UserData = character }; var iconRelativeWidth = (float)crewListEntrySize.Y / background.Rect.Width; @@ -379,7 +368,17 @@ namespace Barotrauma background.RectTransform), 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); @@ -389,7 +388,6 @@ namespace Barotrauma } else { - characterButton.CanBeFocused = false; characterButton.CanBeSelected = false; } @@ -1089,7 +1087,8 @@ namespace Barotrauma public void CreateModerationContextMenu(Point mousePos, Client client) { 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, canKick = GameMain.Client.HasPermission(ClientPermissions.Kick), @@ -1638,7 +1637,8 @@ namespace Barotrauma #if DEBUG if (Character.Controlled == null) { return true; } #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() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs index cb91ff9c9..11b414dd7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs @@ -124,22 +124,22 @@ namespace Barotrauma { var backgroundSprite = GUI.Style.GetComponentStyle("CommandBackground").GetDefaultSprite(); 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, centerPos, Color.White * (overlayTextColor.A / 255.0f), origin: backgroundSprite.size / 2, 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, wrappedText, overlayTextColor); 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, overlayTextBottom, overlayTextColor); } @@ -152,7 +152,7 @@ namespace Barotrauma if (ReadyCheckButton != null) { ReadyCheckButton.Visible = false; } return; } - if (Submarine.MainSub == null) { return; } + if (Submarine.MainSub == null || Level.Loaded == null) { return; } endRoundButton.Visible = false; var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub); @@ -211,6 +211,7 @@ namespace Barotrauma HintManager.OnAvailableTransition(availableTransition); //opening the campaign map pauses the game and prevents HintManager from running -> update it manually to get the hint to show up immediately HintManager.Update(); + Map.SelectLocation(-1); endRoundButton.OnClicked(EndRoundButton, null); prevCampaignUIAutoOpenType = availableTransition; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index 2a60355a2..4e228d2d9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -239,11 +239,20 @@ namespace Barotrauma timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); 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, null, null, fadeOut: false, - duration: 5, - startZoom: 1.5f, endZoom: 1.0f) + losFadeIn: true, + waitDuration: 1, + panDuration: 5, + startZoom: startZoom, endZoom: 1.0f) { AllowInterrupt = true, RemoveControlFromCharacter = false @@ -274,7 +283,8 @@ namespace Barotrauma var transition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, null, fadeOut: false, - duration: 5, + losFadeIn: true, + panDuration: 5, startZoom: 0.5f, endZoom: 1.0f) { AllowInterrupt = true, @@ -327,7 +337,7 @@ namespace Barotrauma var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, fadeOut: false, - duration: EndTransitionDuration); + panDuration: EndTransitionDuration); GameMain.Client.EndCinematic = endTransition; Location portraitLocation = Map?.SelectedLocation ?? Map?.CurrentLocation ?? Level.Loaded?.StartLocation; @@ -335,7 +345,7 @@ namespace Barotrauma { overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); } - float fadeOutDuration = endTransition.Duration; + float fadeOutDuration = endTransition.PanDuration; float t = 0.0f; while (t < fadeOutDuration || endTransition.Running) { @@ -498,7 +508,7 @@ namespace Barotrauma var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, fadeOut: true, - duration: 10, + panDuration: 10, startZoom: null, endZoom: 0.2f); while (transition.Running) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index 61598905a..847b21361 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -298,11 +298,20 @@ namespace Barotrauma timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); 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, null, null, fadeOut: false, - duration: 5, - startZoom: 1.5f, endZoom: 1.0f) + losFadeIn: true, + waitDuration: 1, + panDuration: 5, + startZoom: startZoom, endZoom: 1.0f) { AllowInterrupt = true, RemoveControlFromCharacter = false @@ -327,19 +336,13 @@ namespace Barotrauma else { ISpatialEntity transitionTarget; - if (prevControlled != null) - { - transitionTarget = prevControlled; - } - else - { - transitionTarget = Submarine.MainSub; - } + transitionTarget = (ISpatialEntity)prevControlled ?? Submarine.MainSub; var transition = new CameraTransition(transitionTarget, GameMain.GameScreen.Cam, null, null, fadeOut: false, - duration: 5, + losFadeIn: prevControlled != null, + panDuration: 5, startZoom: 0.5f, endZoom: 1.0f) { AllowInterrupt = true, @@ -409,13 +412,13 @@ namespace Barotrauma var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center, fadeOut: false, - duration: EndTransitionDuration); + panDuration: EndTransitionDuration); GUI.ClearMessages(); Location portraitLocation = Map.SelectedLocation ?? Map.CurrentLocation; overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId); - float fadeOutDuration = endTransition.Duration; + float fadeOutDuration = endTransition.PanDuration; float t = 0.0f; while (t < fadeOutDuration || endTransition.Running) { @@ -509,7 +512,7 @@ namespace Barotrauma var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, fadeOut: true, - duration: 10, + panDuration: 10, startZoom: null, endZoom: 0.2f); while (transition.Running) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs index a98a699a6..7372def70 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/BasicTutorial.cs @@ -614,7 +614,7 @@ namespace Barotrauma.Tutorials GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; 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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs index dd779c17e..6f90823d7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/ScenarioTutorial.cs @@ -288,7 +288,7 @@ namespace Barotrauma.Tutorials 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; while (endCinematic.Running) yield return null; Stop(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs index 405c7e13d..90aae527a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -40,56 +40,82 @@ namespace Barotrauma private GUILayoutGroup topLeftButtonGroup; 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); - Vector2 buttonSpriteSize = GUI.Style.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size; - int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X); - Point buttonSize = new Point(buttonWidth, buttonHeight); - // Crew list button - 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; - } - }; - // 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; + private GUIComponent respawnInfoFrame, respawnButtonContainer; + private GUITextBlock respawnInfoText; + private GUITickBox respawnTickBox; + private GUILayoutGroup TopLeftButtonGroup; + private void CreateTopLeftButtons() + { + if (topLeftButtonGroup != null) + { + topLeftButtonGroup.RectTransform.Parent = null; + topLeftButtonGroup = null; + crewListButton = commandButton = tabMenuButton = null; } + topLeftButtonGroup = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUI.Canvas), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + AbsoluteSpacing = HUDLayoutSettings.Padding, + CanBeFocused = false + }; + topLeftButtonGroup.RectTransform.ParentChanged += (_) => + { + GameMain.Instance.ResolutionChanged -= CreateTopLeftButtons; + }; + int buttonHeight = GUI.IntScale(40); + Vector2 buttonSpriteSize = GUI.Style.GetComponentStyle("CrewListToggleButton").GetDefaultSprite().size; + int buttonWidth = (int)((buttonHeight / buttonSpriteSize.Y) * buttonSpriteSize.X); + Point buttonSize = new Point(buttonWidth, buttonHeight); + crewListButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "CrewListToggleButton") + { + ToolTip = TextManager.GetWithVariable("hudbutton.crewlist", "[key]", GameMain.Config.KeyBindText(InputType.CrewOrders)), + OnClicked = (GUIButton btn, object userdata) => + { + if (CrewManager == null) { return false; } + CrewManager.IsCrewMenuOpen = !CrewManager.IsCrewMenuOpen; + return true; + } + }; + commandButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "CommandButton") + { + ToolTip = TextManager.GetWithVariable("hudbutton.commandinterface", "[key]", GameMain.Config.KeyBindText(InputType.Command)), + OnClicked = (button, userData) => + { + if (CrewManager == null) { return false; } + CrewManager.ToggleCommandUI(); + return true; + } + }; + tabMenuButton = new GUIButton(new RectTransform(buttonSize, parent: topLeftButtonGroup.RectTransform), style: "TabMenuButton") + { + ToolTip = TextManager.GetWithVariable("hudbutton.tabmenu", "[key]", GameMain.Config.KeyBindText(InputType.InfoTab)), + OnClicked = (button, userData) => + { + return ToggleTabMenu(); + } + }; + GameMain.Instance.ResolutionChanged += CreateTopLeftButtons; + + respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform) + { MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null) + { + Visible = false + }; + respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform), "", wrap: true); + respawnButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft) + { + AbsoluteSpacing = HUDLayoutSettings.Padding, + Stretch = true + }; + respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn")) + { + ToolTip = TextManager.Get("respawnquestionprompt"), + OnSelected = (tickbox) => + { + GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: !tickbox.Selected); + return true; + } + }; } public void AddToGUIUpdateList() @@ -101,20 +127,15 @@ namespace Barotrauma if ((!(GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) && !CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition")) { - if (crewListButton != null) + if (topLeftButtonGroup == null) { - crewListButton.Selected = CrewManager != null && CrewManager.IsCrewMenuOpen; + CreateTopLeftButtons(); } - if (commandButton != null) - { - commandButton.Selected = CrewManager.IsCommandInterfaceOpen; - commandButton.Enabled = CrewManager.CanIssueOrders; - } - if (tabMenuButton != null) - { - tabMenuButton.Selected = IsTabMenuOpen; - } - TopLeftButtonGroup.AddToGUIUpdateList(); + crewListButton.Selected = CrewManager != null && CrewManager.IsCrewMenuOpen; + commandButton.Selected = CrewManager.IsCommandInterfaceOpen; + commandButton.Enabled = CrewManager.CanIssueOrders; + tabMenuButton.Selected = IsTabMenuOpen; + topLeftButtonGroup.AddToGUIUpdateList(); } if (GameMain.NetworkMember != null) @@ -130,7 +151,7 @@ namespace Barotrauma 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(); } @@ -138,8 +159,8 @@ namespace Barotrauma else { tabMenu.Update(); - - if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) + if ((PlayerInput.KeyHit(InputType.InfoTab) || PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) && + !(GUI.KeyboardDispatcher.Subscriber is GUITextBox)) { ToggleTabMenu(); } @@ -167,6 +188,17 @@ namespace Barotrauma HintManager.Update(); } + public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn) + { + if (topLeftButtonGroup == null) { return; } + respawnInfoFrame.Visible = visible; + if (!visible) { return; } + respawnInfoText.Text = text; + respawnInfoText.TextColor = textColor; + respawnButtonContainer.Visible = buttonsVisible; + respawnTickBox.Selected = !waitForNextRoundRespawn; + } + public void Draw(SpriteBatch spriteBatch) { GameMode?.Draw(spriteBatch); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index d33b76cf3..943608350 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -1,4 +1,5 @@ -using Barotrauma.IO; +using Barotrauma.Extensions; +using Barotrauma.IO; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; @@ -21,41 +22,16 @@ namespace Barotrauma private static GUIMessageBox ActiveHintMessageBox { get; set; } private static Action OnUpdate { get; set; } private static double TimeStoppedInteracting { get; set; } - + private static double TimeRoundStarted { get; set; } /// /// Seconds before any reminders can be shown /// private static int TimeBeforeReminders { get; set; } - /// /// Seconds before another reminder can be shown /// private static int ReminderCooldown { get; set; } - private static double TimeReminderLastDisplayed { get; set; } - - private static Queue HintQueue { get; } = new Queue(); - - 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 BallastHulls { get; } = new HashSet(); public static void Init() @@ -127,15 +103,6 @@ namespace Barotrauma 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(); CheckIfDivingGearOutOfOxygen(); @@ -166,21 +133,21 @@ namespace Barotrauma // onstartedinteracting.brokenitem if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold)) { - if (EnqueueHint($"{hintIdentifierBase}.brokenitem")) { return; } + if (DisplayHint($"{hintIdentifierBase}.brokenitem")) { return; } } // onstartedinteracting.lootingisstealing if (item.Submarine?.Info?.Type == SubmarineType.Outpost && item.ContainedItems.Any(i => i.SpawnedInOutpost)) { - if (EnqueueHint($"{hintIdentifierBase}.lootingisstealing")) { return; } + if (DisplayHint($"{hintIdentifierBase}.lootingisstealing")) { return; } } // onstartedinteracting.turretperiscope if (item.HasTag("periscope") && item.GetConnectedComponents().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret) { - if (EnqueueHint($"{hintIdentifierBase}.turretperiscope", + if (DisplayHint($"{hintIdentifierBase}.turretperiscope", variableTags: new string[] { "[shootkey]", "[deselectkey]", }, variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect) })) { return; } @@ -193,7 +160,7 @@ namespace Barotrauma if (!hintIdentifier.StartsWith(hintIdentifierBase)) { continue; } if (!HintTags.TryGetValue(hintIdentifier, out var 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 containedItems && 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 Reset(); + TimeRoundStarted = GameMain.GameScreen.GameTime; var initRoundHandle = CoroutineManager.StartCoroutine(InitRound(), "HintManager.InitRound"); if (!CanDisplayHints(requireGameScreen: false, requireControllingCharacter: false)) { return; } @@ -252,10 +220,15 @@ namespace Barotrauma OnStartedControlling(); + while (ActiveHintMessageBox != null) + { + yield return CoroutineStatus.Running; + } + if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) { - EnqueueHint("onroundstarted.voipdisabled", onUpdate: () => + DisplayHint("onroundstarted.voipdisabled", onUpdate: () => { if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) { return; } ActiveHintMessageBox.Close(); @@ -281,7 +254,6 @@ namespace Barotrauma ActiveHintMessageBox = null; } OnUpdate = null; - HintQueue.Clear(); HintsIgnoredThisRound.Clear(); } @@ -292,7 +264,7 @@ namespace Barotrauma if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; } if (Character.Controlled.SelectedConstruction != sonar) { return; } if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; } - EnqueueHint("onsonarspottedenemy"); + DisplayHint("onsonarspottedenemy"); } public static void OnAfflictionDisplayed(Character character, List displayedAfflictions) @@ -305,7 +277,8 @@ namespace Barotrauma if (affliction.Prefab.IsBuff) { continue; } if (affliction.Prefab == AfflictionPrefab.OxygenLow) { 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]" }, variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Health) }, icon: affliction.Prefab.Icon, @@ -325,12 +298,13 @@ namespace Barotrauma if (character != Character.Controlled) { return; } if (character.SelectedConstruction != null || character.FocusedItem != null) { return; } if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; } - if (GUI.MouseOn != null) { return; } if (TimeStoppedInteracting + 1 > Timing.TotalTime) { return; } + if (GUI.MouseOn != null) { return; } + if (Character.Controlled.Inventory?.visualSlots != null && Character.Controlled.Inventory.visualSlots.Any(s => s.InteractRect.Contains(PlayerInput.MousePosition))) { return; } string hintIdentifier = "onshootwithoutaiming"; if (!HintTags.TryGetValue(hintIdentifier, out var tags)) { return; } if (!item.HasTag(tags)) { return; } - EnqueueHint(hintIdentifier, + DisplayHint(hintIdentifier, variableTags: new string[1] { "[key]" }, variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Aim) }, onUpdate: () => @@ -347,14 +321,14 @@ namespace Barotrauma if (!CanDisplayHints()) { return; } if (character != Character.Controlled) { return; } if (door == null || door.Stuck < 20.0f) { return; } - EnqueueHint("onweldingdoor"); + DisplayHint("onweldingdoor"); } public static void OnTryOpenStuckDoor(Character character) { if (!CanDisplayHints()) { return; } if (character != Character.Controlled) { return; } - EnqueueHint("ontryopenstuckdoor"); + DisplayHint("ontryopenstuckdoor"); } public static void OnShowCampaignInterface(CampaignMode.InteractionType interactionType) @@ -362,36 +336,34 @@ namespace Barotrauma if (!CanDisplayHints()) { return; } if (interactionType == CampaignMode.InteractionType.None) { return; } string hintIdentifier = $"onshowcampaigninterface.{interactionType.ToString().ToLowerInvariant()}"; - EnqueueHint(hintIdentifier, - onUpdate: () => - { + DisplayHint(hintIdentifier, onUpdate: () => + { - if (!(GameMain.GameSession?.Campaign is CampaignMode campaign) || - (!campaign.ShowCampaignUI && !campaign.ForceMapUI) || - campaign.CampaignUI?.SelectedTab != CampaignMode.InteractionType.Map) - { - ActiveHintMessageBox.Close(); - } - }); + if (!(GameMain.GameSession?.Campaign is CampaignMode campaign) || + (!campaign.ShowCampaignUI && !campaign.ForceMapUI) || + campaign.CampaignUI?.SelectedTab != CampaignMode.InteractionType.Map) + { + ActiveHintMessageBox.Close(); + } + }); } public static void OnShowCommandInterface() { IgnoreReminder("commandinterface"); if (!CanDisplayHints()) { return; } - EnqueueHint("onshowcommandinterface", - onUpdate: () => - { - if (CrewManager.IsCommandInterfaceOpen) { return; } - ActiveHintMessageBox.Close(); - }); + DisplayHint("onshowcommandinterface", onUpdate: () => + { + if (CrewManager.IsCommandInterfaceOpen) { return; } + ActiveHintMessageBox.Close(); + }); } public static void OnShowHealthInterface() { if (!CanDisplayHints()) { return; } if (CharacterHealth.OpenHealthWindow == null) { return; } - EnqueueHint("onshowhealthinterface", onUpdate: () => + DisplayHint("onshowhealthinterface", onUpdate: () => { if (CharacterHealth.OpenHealthWindow != null) { return; } ActiveHintMessageBox.Close(); @@ -408,7 +380,7 @@ namespace Barotrauma if (!CanDisplayHints()) { return; } if (character != Character.Controlled) { return; } if (item == null || !item.SpawnedInOutpost || !item.StolenDuringRound) { return; } - EnqueueHint("onstoleitem", onUpdate: () => + DisplayHint("onstoleitem", onUpdate: () => { if (item == null || item.Removed || item.GetRootInventoryOwner() != character) { @@ -421,7 +393,7 @@ namespace Barotrauma { if (!CanDisplayHints()) { return; } if (character != Character.Controlled || !character.LockHands) { return; } - EnqueueHint("onhandcuffed", onUpdate: () => + DisplayHint("onhandcuffed", onUpdate: () => { if (character != null && !character.Removed && character.LockHands) { return; } ActiveHintMessageBox.Close(); @@ -434,7 +406,7 @@ namespace Barotrauma if (reactor == null) { return; } if (reactor.Item.Submarine?.Info?.Type != SubmarineType.Player || reactor.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; } if (!HasValidJob("engineer")) { return; } - EnqueueHint("onreactoroutoffuel", onUpdate: () => + DisplayHint("onreactoroutoffuel", onUpdate: () => { if (reactor?.Item != null && !reactor.Item.Removed && reactor.AvailableFuel < 1) { return; } ActiveHintMessageBox.Close(); @@ -445,7 +417,7 @@ namespace Barotrauma { if (!CanDisplayHints()) { return; } if (transitionType == CampaignMode.TransitionType.None) { return; } - EnqueueHint($"onavailabletransition.{transitionType.ToString().ToLowerInvariant()}"); + DisplayHint($"onavailabletransition.{transitionType.ToString().ToLowerInvariant()}"); } public static void OnShowSubInventory(Item item) @@ -462,12 +434,31 @@ namespace Barotrauma IgnoreReminder("characterchange"); } + public static void OnCharacterUnconscious(Character character) + { + if (!CanDisplayHints()) { return; } + if (character != Character.Controlled) { return; } + if (character.IsDead) { return; } + if (character.CharacterHealth != null && character.Vitality < character.CharacterHealth.MinVitality) { return; } + DisplayHint("oncharacterunconscious"); + } + + public static void OnCharacterKilled(Character character) + { + if (!CanDisplayHints()) { return; } + if (character != Character.Controlled) { return; } + if (GameMain.IsMultiplayer) { return; } + if (GameMain.GameSession?.CrewManager == null) { return; } + if (GameMain.GameSession.CrewManager.GetCharacters().None(c => !c.IsDead)) { return; } + DisplayHint("oncharacterkilled"); + } + private static void OnStartedControlling() { if (Level.IsLoadedOutpost) { return; } if (Character.Controlled?.Info?.Job?.Prefab == null) { return; } string hintIdentifier = $"onstartedcontrolling.job.{Character.Controlled.Info.Job.Prefab.Identifier}"; - EnqueueHint(hintIdentifier, + DisplayHint(hintIdentifier, icon: Character.Controlled.Info.Job.Prefab.Icon, iconColor: Character.Controlled.Info.Job.Prefab.UIColor, onDisplay: () => @@ -500,7 +491,7 @@ namespace Barotrauma if (!steering.SteeringPath.Finished && steering.SteeringPath.NextNode != null) { return; } if (steering.LevelStartSelected && (Level.Loaded.StartOutpost == null || !steering.Item.Submarine.AtStartExit)) { return; } if (steering.LevelEndSelected && (Level.Loaded.EndOutpost == null || !steering.Item.Submarine.AtEndExit)) { return; } - EnqueueHint("onautopilotreachedoutpost"); + DisplayHint("onautopilotreachedoutpost"); } public static void OnStatusEffectApplied(ItemComponent component, ActionType actionType, Character character) @@ -509,7 +500,7 @@ namespace Barotrauma if (character != Character.Controlled) { return; } // Could make this more generic if there will ever be any other status effect related hints if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; } - EnqueueHint("onrepairfailed"); + DisplayHint("onrepairfailed"); } private static void CheckIfDivingGearOutOfOxygen() @@ -517,12 +508,12 @@ namespace Barotrauma if (!CanDisplayHints()) { return; } var divingGear = Character.Controlled.GetEquippedItem("diving"); if (divingGear?.OwnInventory == null) { return; } - if (divingGear.GetContainedItemConditionPercentage() > 0.05f) { return; } - EnqueueHint("ondivinggearoutofoxygen", onUpdate: () => + if (divingGear.GetContainedItemConditionPercentage() > 0.0f) { return; } + DisplayHint("ondivinggearoutofoxygen", onUpdate: () => { if (divingGear == null || divingGear.Removed || Character.Controlled == null || !Character.Controlled.HasEquippedItem(divingGear) || - divingGear.GetContainedItemConditionPercentage() > 0.05f) + divingGear.GetContainedItemConditionPercentage() > 0.0f) { ActiveHintMessageBox.Close(); } @@ -535,15 +526,21 @@ namespace Barotrauma if (Character.Controlled.CurrentHull == null) { return; } foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps) { - if (!gap.IsRoomToRoom) { continue; } if (gap.ConnectedDoor == null || gap.ConnectedDoor.Impassable) { continue; } if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; } + if (!gap.IsRoomToRoom) + { + if (!(Character.Controlled.GetEquippedItem("deepdiving") is Item)) { continue; } + if (Character.Controlled.IsProtectedFromPressure()) { continue; } + if (DisplayHint("divingsuitwarning", extendTextTag: false)) { return; } + continue; + } foreach (var me in gap.linkedTo) { if (me == Character.Controlled.CurrentHull) { continue; } if (!(me is Hull adjacentHull)) { continue; } - if (adjacentHull.LethalPressure > 5.0f && EnqueueHint("onadjacenthull.highpressure")) { return; } - if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && EnqueueHint("onadjacenthull.highwaterpercentage")) { return; } + if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure")) { return; } + if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && DisplayHint("onadjacenthull.highwaterpercentage")) { return; } } } } @@ -552,23 +549,23 @@ namespace Barotrauma { if (!CanDisplayHints()) { return; } if (Level.Loaded == null) { return; } - if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + TimeBeforeReminders) { return; } - if (Timing.TotalTime < TimeReminderLastDisplayed + ReminderCooldown) { return; } + if (GameMain.GameScreen.GameTime < TimeRoundStarted + TimeBeforeReminders) { return; } + if (GameMain.GameScreen.GameTime < TimeReminderLastDisplayed + ReminderCooldown) { return; } string hintIdentifierBase = "reminder"; if (GameMain.GameSession.GameMode.IsSinglePlayer) { - if (EnqueueHint($"{hintIdentifierBase}.characterchange")) + if (DisplayHint($"{hintIdentifierBase}.characterchange")) { - TimeReminderLastDisplayed = Timing.TotalTime; + TimeReminderLastDisplayed = GameMain.GameScreen.GameTime; return; } } if (Level.Loaded.Type != LevelData.LevelType.Outpost) { - if (EnqueueHint($"{hintIdentifierBase}.commandinterface", + if (DisplayHint($"{hintIdentifierBase}.commandinterface", variableTags: new string[] { "[commandkey]" }, variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Command) }, onUpdate: () => @@ -577,12 +574,12 @@ namespace Barotrauma ActiveHintMessageBox.Close(); })) { - TimeReminderLastDisplayed = Timing.TotalTime; + TimeReminderLastDisplayed = GameMain.GameScreen.GameTime; return; } } - if (EnqueueHint($"{hintIdentifierBase}.tabmenu", + if (DisplayHint($"{hintIdentifierBase}.tabmenu", variableTags: new string[] { "[infotabkey]" }, variableValues: new string[] { GameMain.Config.KeyBindText(InputType.InfoTab) }, onUpdate: () => @@ -591,21 +588,21 @@ namespace Barotrauma ActiveHintMessageBox.Close(); })) { - TimeReminderLastDisplayed = Timing.TotalTime; + TimeReminderLastDisplayed = GameMain.GameScreen.GameTime; return; } 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; } } } - 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 (!HintIdentifiers.Contains(hintIdentifier)) { return false; } @@ -613,7 +610,7 @@ namespace Barotrauma if (HintsIgnoredThisRound.Contains(hintIdentifier)) { return false; } string text; - string textTag = $"hint.{hintIdentifier}"; + string textTag = extendTextTag ? $"hint.{hintIdentifier}" : hintIdentifier; if (variableTags != null && variableTags != null && variableTags.Length > 0 && variableTags.Length == variableValues.Length) { text = TextManager.GetWithVariables(textTag, variableTags, variableValues, returnNull: true); @@ -631,9 +628,16 @@ namespace Barotrauma return false; } - var hint = new HintInfo(hintIdentifier, text, icon, iconColor, onDisplay, onUpdate); - HintQueue.Enqueue(hint); 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; } @@ -678,6 +682,7 @@ namespace Barotrauma { if (HintIdentifiers == null) { return false; } if (GameMain.Config.DisableInGameHints) { return false; } + if (ActiveHintMessageBox != null) { return false; } if (requireControllingCharacter && Character.Controlled == null) { return false; } var gameMode = GameMain.GameSession?.GameMode; if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs index cdda918e2..f58158c45 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs @@ -266,7 +266,9 @@ namespace Barotrauma displayedMission.Description; 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)); if (selectedMissions.Contains(displayedMission)) @@ -278,8 +280,27 @@ namespace Barotrauma { 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); + 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), missionMessage, wrap: true, parseRichText: true); if (selectedMissions.Contains(displayedMission) && displayedMission.Completed && displayedMission.Reward > 0) @@ -430,7 +451,7 @@ namespace Barotrauma Faction unlockFaction = null; 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; } float normalizedUnlockReputation = MathUtils.InverseLerp(unlockReputation.MinReputation, unlockReputation.MaxReputation, unlockEvent.UnlockPathReputation); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 1e4ff0d59..35a8c3919 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -160,13 +160,6 @@ namespace Barotrauma CreateSlots(); } - public override void RemoveItem(Item item) - { - if (!Contains(item)) { return; } - base.RemoveItem(item); - CreateSlots(); - } - public override void CreateSlots() { if (visualSlots == null) { visualSlots = new VisualSlot[capacity]; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index 23558e14c..ecc4b8b83 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -381,15 +381,11 @@ namespace Barotrauma.Items.Components private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) { - if (!outputContainer.Inventory.IsEmpty()) { return; } - overlayComponent.RectTransform.SetAsLastChild(); FabricationRecipe targetItem = fabricatedItem ?? selectedItem; if (targetItem != null) { - var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.sprite; - Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect; if (fabricatedItem != null) @@ -402,11 +398,15 @@ namespace Barotrauma.Items.Components GUI.Style.Green * 0.5f, isFilled: true); } - itemIcon.Draw( - 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 (outputContainer.Inventory.IsEmpty()) + { + var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.sprite; + itemIcon.Draw( + 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index fe54e26fa..f7bc9181b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -16,7 +16,8 @@ namespace Barotrauma.Items.Components { Default, Disruption, - Destructible + Destructible, + LongRange } private PathFinder pathFinder; @@ -69,6 +70,9 @@ namespace Barotrauma.Items.Components private const float DisruptionUpdateInterval = 0.2f; private float disruptionUpdateTimer; + private const float LongRangeUpdateInterval = 10.0f; + private float longRangeUpdateTimer; + private float showDirectionalIndicatorTimer; private readonly List nearbyObjects = new List(); @@ -122,6 +126,10 @@ namespace Barotrauma.Items.Components { 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 } + }, + { + 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; - for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex) { var activePing = activePings[pingIndex]; @@ -684,12 +691,44 @@ namespace Barotrauma.Items.Components pingRadius, activePing.PrevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f); activePing.PrevPingRadius = pingRadius; } - if (disruptionUpdateTimer <= 0.0f) { 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) { return; @@ -946,6 +985,19 @@ namespace Barotrauma.Items.Components if (connectedSubs.Contains(sub)) { 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, sub.Info.DisplayName, sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine", @@ -1045,15 +1097,17 @@ namespace Barotrauma.Items.Components foreach (DockingPort dockingPort in DockingPort.List) { if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } - if (dockingPort.Item.Submarine == null) { continue; } if (dockingPort.Item.Submarine.Info.IsWreck) { continue; } //don't show the docking ports of the opposing team on the sonar if (item.Submarine != null) { - if ((dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team1 && item.Submarine.TeamID == CharacterTeamType.Team2) || - (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team2 && item.Submarine.TeamID == CharacterTeamType.Team1)) + if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team1 && (item.Submarine.TeamID == CharacterTeamType.Team2 || Character.Controlled?.TeamID == CharacterTeamType.Team2)) + { + continue; + } + else if (dockingPort.Item.Submarine.TeamID == CharacterTeamType.Team2 && (item.Submarine.TeamID == CharacterTeamType.Team1 || Character.Controlled?.TeamID == CharacterTeamType.Team1)) { continue; } @@ -1555,12 +1609,12 @@ namespace Barotrauma.Items.Components float scale = (strength + 3.0f) * blip.Scale * blipScale; 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); 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, @@ -1778,6 +1832,7 @@ namespace Barotrauma.Items.Components public float? Rotation; public Vector2 Size; public Sonar.BlipType BlipType; + public float Alpha = 1.0f; public SonarBlip(Vector2 pos, float fadeTimer, float scale, Sonar.BlipType blipType = Sonar.BlipType.Default) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index 9fdb845a1..1f32930b3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -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; if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs index 18d7f5650..403e6d15b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Connection.cs @@ -1,5 +1,4 @@ -using Barotrauma.Networking; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; @@ -28,15 +27,7 @@ namespace Barotrauma.Items.Components int x = panelRect.X, y = panelRect.Y; int width = panelRect.Width, height = panelRect.Height; - Vector2 scale = new Vector2(GUI.Scale); - 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; - } + Vector2 scale = GetScale(panel.GuiFrame.RectTransform.MaxSize, panel.GuiFrame.Rect.Size); 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 for (int i = 0; i < 2; i++) { - Vector2 rightPos = new Vector2(x + width - 80 * scale.X, y + 60 * scale.Y); - Vector2 leftPos = new Vector2(x + 80 * scale.X, y + 60 * scale.Y); + Vector2 rightPos = GetRightPos(x, y, width, scale); + Vector2 leftPos = GetLeftPos(x, y, scale); 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); 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 connectorIntervalRight = (height - (int)(100 * scale.Y)) / Math.Max(panel.Connections.Count(c => !c.IsOutput), 1); + int connectorIntervalLeft = GetConnectorIntervalLeft(height, scale, panel); + int connectorIntervalRight = GetConnectorIntervalRight(height, scale, panel); foreach (Connection c in panel.Connections) { @@ -101,15 +92,12 @@ namespace Barotrauma.Items.Components { if (i == 0) { - c.DrawConnection(spriteBatch, panel, rightPos, - new Vector2(rightPos.X - GUI.SmallFont.MeasureString(c.DisplayName.ToUpper()).X - 25 * panel.Scale, rightPos.Y + 5 * panel.Scale), - scale); + c.DrawConnection(spriteBatch, panel, rightPos, GetOutputLabelPosition(rightPos, panel, c), scale); } else { c.DrawWires(spriteBatch, panel, rightPos, rightWirePos, mouseInRect, equippedWire, wireInterval); } - rightPos.Y += connectorIntervalLeft; rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval; } @@ -117,15 +105,12 @@ namespace Barotrauma.Items.Components { if (i == 0) { - c.DrawConnection(spriteBatch, panel, leftPos, - new Vector2(leftPos.X + 25 * panel.Scale, leftPos.Y - 5 * panel.Scale - GUI.SmallFont.MeasureString(c.DisplayName.ToUpper()).Y), - scale); + c.DrawConnection(spriteBatch, panel, leftPos, GetInputLabelPosition(leftPos, panel, c), scale); } else { c.DrawWires(spriteBatch, panel, leftPos, leftWirePos, mouseInRect, equippedWire, wireInterval); } - leftPos.Y += connectorIntervalRight; 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) { string text = DisplayName.ToUpper(); - Vector2 textSize = GUI.SmallFont.MeasureString(text); //nasty - var labelSprite = GUI.Style.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First(); - if (labelSprite != null) + if (GUI.Style.GetComponentStyle("ConnectionPanelLabel")?.Sprites.Values.First().First() is UISprite labelSprite) { - Rectangle labelArea = new Rectangle(labelPos.ToPoint(), textSize.ToPoint()); - labelArea.Inflate(10 * scale.X, 3 * scale.Y); + Rectangle labelArea = GetLabelArea(labelPos, text, scale); 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(); + 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); + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs index 2886e0e74..1162e4bff 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs @@ -24,9 +24,15 @@ namespace Barotrauma.Items.Components 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; } + originalMaxSize = GuiFrame.RectTransform.MaxSize; + originalRelativeSize = GuiFrame.RectTransform.RelativeSize; + CheckForLabelOverlap(); new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null) { UserData = this @@ -40,7 +46,7 @@ namespace Barotrauma.Items.Components partial void UpdateProjSpecific(float deltaTime) { - foreach (Wire wire in DisconnectedWires) + foreach (var _ in DisconnectedWires) { 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) { if (GameMain.Client.MidRoundSyncing) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs index 06dc96fbc..353e4550b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Terminal.cs @@ -67,17 +67,19 @@ namespace Barotrauma.Items.Components item.SendSignal(input, "signal_out"); } - partial void ShowOnDisplay(string input) + partial void ShowOnDisplay(string input, bool addToHistory = true) { - messageHistory.Add(input); - while (messageHistory.Count > MaxMessages) + if (addToHistory) { - messageHistory.RemoveAt(0); - } - - while (historyBox.Content.CountChildren > MaxMessages) - { - historyBox.RemoveChild(historyBox.Content.Children.First()); + messageHistory.Add(input); + while (messageHistory.Count > MaxMessages) + { + messageHistory.RemoveAt(0); + } + while (historyBox.Content.CountChildren > MaxMessages) + { + historyBox.RemoveChild(historyBox.Content.Children.First()); + } } GUITextBlock newBlock = new GUITextBlock( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index d1bec4b9f..ce86894a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -156,7 +156,8 @@ namespace Barotrauma.Items.Components 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) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 1c9003891..e538b8a63 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -302,7 +302,7 @@ namespace Barotrauma } if (!string.IsNullOrEmpty(description)) { toolTip += '\n' + description; } } - if (itemsInSlot.Count() > 2) + if (itemsInSlot.Count() > 1) { string colorStr = XMLExtensions.ColorToString(GUI.Style.Blue); 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 (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)) { - DraggingItems.AddRange(slots[slotIndex].Items.Skip(slots[slotIndex].ItemCount / 2)); + DraggingItems.AddRange(interactableItems.Skip(interactableItems.Count() / 2)); } else if (PlayerInput.KeyDown(InputType.TakeOneFromInventorySlot)) { - DraggingItems.Add(slots[slotIndex].First()); + DraggingItems.Add(interactableItems.First()); } else { - DraggingItems.AddRange(slots[slotIndex].Items); - } - if (Screen.Selected == GameMain.GameScreen) - { - DraggingItems.RemoveAll(it => it.NonInteractable || it.NonPlayerTeamInteractable); + DraggingItems.AddRange(interactableItems); } DraggingSlot = slot; } } 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(); 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)) { - doubleClickedItems.Add(slots[slotIndex].First()); + doubleClickedItems.Add(interactableItems.First()); } else { - doubleClickedItems.AddRange(slots[slotIndex].Items); - } - if (Screen.Selected == GameMain.GameScreen) - { - doubleClickedItems.RemoveAll(it => it.NonInteractable || it.NonPlayerTeamInteractable); + doubleClickedItems.AddRange(interactableItems); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 383a3cd48..3ddfdf3f5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -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 }; 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) }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 82627664c..2d8875d30 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -90,7 +90,10 @@ namespace Barotrauma 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 }; - 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); PositionEditingHUD(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index 6565af460..c1161dfc3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -48,6 +48,7 @@ namespace Barotrauma.Lights private readonly List lights; public bool LosEnabled = true; + public float LosAlpha = 1f; public LosMode LosMode = LosMode.Transparent; public bool LightingEnabled = true; @@ -495,9 +496,10 @@ namespace Barotrauma.Lights if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) return; if (ViewTarget == null) return; + if (Character.Controlled == null) { DebugConsole.NewMessage("aaa", Color.Orange); } + graphics.SetRenderTarget(LosTexture); - spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cam.Transform * Matrix.CreateScale(new Vector3(GameMain.Config.LightMapScale, GameMain.Config.LightMapScale, 1.0f))); if (ObstructVision) { graphics.Clear(Color.Black); @@ -507,16 +509,17 @@ namespace Barotrauma.Lights float rotation = MathUtils.VectorToAngle(losOffset); 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, new Vector2(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f); + spriteBatch.End(); } else { graphics.Clear(Color.White); } - spriteBatch.End(); //-------------------------------------- diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs index 225ea8724..38b1db50b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs @@ -465,7 +465,7 @@ namespace Barotrauma Rectangle rect = mapContainer.Rect; 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.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"); repLabelSize = GUI.Font.MeasureString(repLabelText); - size.X = Math.Max(size.X, repLabelSize.X); - repBarSize = new Vector2(Math.Max(0.75f * size.X, 100), repLabelSize.Y); - size.X = Math.Max(size.X, (4.0f / 3.0f) * repBarSize.X); + repBarSize = new Vector2(GUI.IntScale(200), repLabelSize.Y); size.Y += 2 * repLabelSize.Y + GUI.IntScale(5) + repBarSize.Y; 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( 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)); 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); - 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; 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; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index ba189ab1f..03649abd1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -20,9 +20,6 @@ namespace Barotrauma public static Vector2 StartMovingPos => startMovingPos; - // Quick undo/redo for size and movement only. TODO: Remove if we do a more general implementation. - private Memento rectMemento; - public event Action Resized; private static bool resizing; @@ -65,8 +62,6 @@ namespace Barotrauma } } - //protected bool isSelected; - private static bool disableSelect; public static bool DisableSelect { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index 7d359ea23..9d4200691 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -95,7 +95,10 @@ namespace Barotrauma { 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 }; - 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 }; if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs index 050a5337b..3ad06be3d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarineInfo.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; using System.Linq; namespace Barotrauma @@ -32,24 +33,45 @@ namespace Barotrauma } } - 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 { - var submarinePreviewBackground = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), style: null) { Color = Color.Black }; - 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); + 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 descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter)) { UserData = "descriptionbox", @@ -59,36 +81,25 @@ namespace Barotrauma ScalableFont font = parent.Rect.Width < 350 ? GUI.SmallFont : GUI.Font; - CreateSpecsWindow(descriptionBox, font); - - //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 - }; + CreateSpecsWindow(descriptionBox, font, includesDescription: true); } - 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 rightPanelWidth = 0.4f / leftPanelWidth; + float rightPanelWidth = 0.4f; 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 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 }; - submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y); + GUITextBlock submarineNameText = null; + 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 }; 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); } - submarineNameText.AutoScaleHorizontal = true; - GUITextBlock.AutoScaleAndNormalize(parent.Content.Children.Where(c => c is GUITextBlock && c != submarineNameText).Cast()); + if (submarineNameText != null) + { + 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().Where(c => c != submarineNameText && c != descBlock)); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs index 35714282a..044b4f444 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/SubmarinePreview.cs @@ -1,4 +1,5 @@ -using Barotrauma.IO; +using Barotrauma.Extensions; +using Barotrauma.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -83,28 +84,94 @@ namespace Barotrauma }; 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); - var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), verticalLayout.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft); - - 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")) + int innerPadding = GUI.IntScale(100f); + var innerPadded = new GUIFrame(new RectTransform(new Point(innerFrame.Rect.Width - innerPadding, innerFrame.Rect.Height - innerPadding), previewFrame.RectTransform, Anchor.Center), style: null) { - OnClicked = (btn, obj) => { Dispose(); return false; } + OutlineColor = Color.Black, + OutlineThickness = 2 }; - new GUICustomComponent(new RectTransform(new Vector2(1f, 0.9f), verticalLayout.RectTransform, Anchor.Center), - (spriteBatch, component) => { camera.UpdateTransform(true); RenderSubmarine(spriteBatch, component.Rect); }, + GUITextBlock titleText = null; + GUIListBox specsContainer = null; + + new GUICustomComponent(new RectTransform(Vector2.One, innerPadded.RectTransform, Anchor.Center), + (spriteBatch, component) => { + camera.UpdateTransform(interpolate: true, updateListener: false); + Rectangle drawRect = new Rectangle(component.Rect.X + 1, component.Rect.Y + 1, component.Rect.Width - 2, component.Rect.Height - 2); + RenderSubmarine(spriteBatch, drawRect); + }, (deltaTime, component) => { - camera.MoveCamera(deltaTime, overrideMouseOn: component.Rect); - if (component.Rect.Contains(PlayerInput.MousePosition) && + bool isMouseOnComponent = GUI.MouseOn == component; + camera.MoveCamera(deltaTime, allowZoom: isMouseOnComponent); + if (isMouseOnComponent && (PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld())) { Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom; moveSpeed.X = -moveSpeed.X; camera.Position += moveSpeed; } + + if (titleText != null && specsContainer != null) + { + specsContainer.Visible = GUI.IsMouseOn(titleText); + } }); + var topContainer = new GUIFrame(new RectTransform(new Vector2(1f, 0.07f), innerPadded.RectTransform, Anchor.TopLeft), style: null) + { + Color = Color.Black * 0.65f + }; + var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 5f / 7f), topContainer.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft); + + titleText = new GUITextBlock(new RectTransform(new Vector2(0.95f, 1f), topLayout.RectTransform), subInfo.DisplayName, font: GUI.LargeFont); + new GUIButton(new RectTransform(new Vector2(0.05f, 1f), topLayout.RectTransform), TextManager.Get("Close")) + { + OnClicked = (btn, obj) => { Dispose(); return false; } + }; + + specsContainer = new GUIListBox(new RectTransform(new Vector2(0.4f, 1f), innerPadded.RectTransform, Anchor.TopLeft) { RelativeOffset = new Vector2(0.015f, 0.07f) }) + { + Color = Color.Black * 0.65f, + ScrollBarEnabled = false, + ScrollBarVisible = false, + Spacing = 5 + }; + subInfo.CreateSpecsWindow(specsContainer, GUI.Font, includeTitle: false, 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().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(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs index 75073f197..157ed497a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ChatMessage.cs @@ -15,7 +15,7 @@ namespace Barotrauma.Networking public static void ClientRead(IReadMessage msg) { - UInt16 ID = msg.ReadUInt16(); + UInt16 id = msg.ReadUInt16(); ChatMessageType type = (ChatMessageType)msg.ReadByte(); PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None; string txt = ""; @@ -114,7 +114,7 @@ namespace Barotrauma.Networking if (orderIndex < 0 || orderIndex >= Order.PrefabList.Count) { 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; } else @@ -155,11 +155,11 @@ namespace Barotrauma.Networking } } - if (NetIdUtils.IdMoreRecent(ID, LastID)) + if (NetIdUtils.IdMoreRecent(id, LastID)) { GameMain.Client.AddChatMessage( new OrderChatMessage(orderPrefab, orderOption, orderPriority, txt, orderTargetPosition ?? targetEntity as ISpatialEntity, targetCharacter, senderCharacter)); - LastID = ID; + LastID = id; } return; case ChatMessageType.ServerMessageBox: @@ -171,7 +171,7 @@ namespace Barotrauma.Networking break; } - if (NetIdUtils.IdMoreRecent(ID, LastID)) + if (NetIdUtils.IdMoreRecent(id, LastID)) { switch (type) { @@ -200,7 +200,7 @@ namespace Barotrauma.Networking GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter, changeType); break; } - LastID = ID; + LastID = id; } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 9d57c8329..39be674c2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -159,6 +159,12 @@ namespace Barotrauma.Networking get { return entityEventManager; } } + public bool? WaitForNextRoundRespawn + { + get; + set; + } + private readonly object serverEndpoint; private readonly int ownerKey; private readonly bool steamP2POwner; @@ -185,10 +191,10 @@ namespace Barotrauma.Networking 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), - MaxSize = new Point(25, 25) + AbsoluteOffset = new Point(0, HUDLayoutSettings.ButtonAreaTop.Y + HUDLayoutSettings.ButtonAreaTop.Height / 2), + MaxSize = new Point(GUI.IntScale(25)) }, TextManager.Get("CamFollowSubmarine")) { Selected = Camera.FollowSub, @@ -1424,6 +1430,8 @@ namespace Barotrauma.Networking EndVoteTickBox.Selected = false; + WaitForNextRoundRespawn = null; + roundInitStatus = RoundInitStatus.Starting; int seed = inc.ReadInt32(); @@ -1690,13 +1698,15 @@ namespace Barotrauma.Networking } foreach (Submarine sub in Submarine.MainSubs[i].DockedTo) { + if (sub.Info.Type == SubmarineType.Outpost) { continue; } sub.TeamID = teamID; } } - if (respawnAllowed) - { - respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle && gameMode != GameModePreset.MultiPlayerCampaign ? GameMain.NetLobbyScreen.SelectedShuttle : null); + if (respawnAllowed) + { + 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; @@ -1739,6 +1749,7 @@ namespace Barotrauma.Networking gameStarted = false; Character.Controlled = null; + WaitForNextRoundRespawn = null; SpawnAsTraitor = false; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.LightManager.LosEnabled = false; @@ -1965,14 +1976,19 @@ namespace Barotrauma.Networking } 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; } @@ -2452,6 +2468,15 @@ namespace Barotrauma.Networking 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) { IWriteMessage msg = new WriteOnlyMessage(); @@ -3143,11 +3168,11 @@ namespace Barotrauma.Networking 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.LightManager.LosEnabled = false; - } + }*/ } //tab doesn't autoselect the chatbox when debug console is open, @@ -3255,9 +3280,10 @@ namespace Barotrauma.Networking if (respawnManager != null) { - string respawnText = ""; + string respawnText = string.Empty; float textScale = 1.0f; 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 && respawnManager.RespawnCountdownStarted) { @@ -3275,18 +3301,18 @@ namespace Barotrauma.Networking { //oscillate between 0-1 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); } + canChooseRespawn = false; } - - if (!string.IsNullOrEmpty(respawnText)) - { - GUI.SmallFont.DrawString(spriteBatch, respawnText, new Vector2(120.0f, 10), textColor, 0.0f, Vector2.Zero, textScale, Microsoft.Xna.Framework.Graphics.SpriteEffects.None, 0.0f); - } + + GameMain.GameSession?.SetRespawnInfo( + visible: !string.IsNullOrEmpty(respawnText) || canChooseRespawn, text: respawnText, textColor: textColor, + buttonsVisible: canChooseRespawn, waitForNextRoundRespawn: (WaitForNextRoundRespawn ?? true)); } - if (!ShowNetStats) return; + if (!ShowNetStats) { return; } NetStats.Draw(spriteBatch, new Rectangle(300, 10, 300, 150)); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index f14563ffe..2210f080d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -473,7 +473,36 @@ namespace Barotrauma SelectedColor = MapGenerationParams.Instance.IndicatorColor, 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); @@ -494,6 +523,10 @@ namespace Barotrauma missionPanel.OnAddedToGUIUpdateList = (c) => { 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()) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index 8498aaffb..636daf0f3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -171,10 +171,7 @@ namespace Barotrauma Character.Controlled.ObstructVision && (Character.Controlled.ViewTarget == Character.Controlled || Character.Controlled.ViewTarget == null); - if (Character.Controlled != null) - { - GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled.CursorWorldPosition); - } + GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled?.CursorWorldPosition ?? Vector2.Zero); //------------------------------------------------------------------------ graphics.SetRenderTarget(renderTarget); @@ -334,12 +331,13 @@ namespace Barotrauma } 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.Parameters["xTexture"].SetValue(renderTargetBackground); GameMain.LightManager.LosEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.LosTexture); + GameMain.LightManager.LosEffect.Parameters["xLosAlpha"].SetValue(GameMain.LightManager.LosAlpha); Color losColor; if (GameMain.LightManager.LosMode == LosMode.Transparent) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index 439b4994e..b7745a745 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -348,6 +348,16 @@ namespace Barotrauma 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") { ForceUpperCase = true, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs index 7c08835df..c8e7ca975 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/NetLobbyScreen.cs @@ -1305,9 +1305,9 @@ namespace Barotrauma StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !CampaignSetupFrame.Visible && !CampaignFrame.Visible; ServerName.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)); - 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); LogButtons.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 318acacae..b1d8f054f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -533,20 +533,6 @@ namespace Barotrauma OnSelected = (GUITickBox obj) => { 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.Light.Color = item.Container != null || (item.body != null && !item.body.Enabled) ? - Color.Transparent : - lightComponent.LightColor; - lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects; - } - } - } return true; } }; @@ -2704,16 +2690,6 @@ namespace Barotrauma cam.Position = Submarine.MainSub.Position + Submarine.MainSub.HiddenSubPosition; loadFrame = null; - - //turn off lights that are inside an inventory (cabinet for example) - foreach (Item item in Item.ItemList) - { - var lightComponent = item.GetComponent(); - if (lightComponent != null) - { - lightComponent.Light.Enabled = item.ParentInventory == null; - } - } } private bool LoadSub(GUIButton button, object obj) @@ -2748,16 +2724,6 @@ namespace Barotrauma loadFrame = null; - //turn off lights that are inside an inventory (cabinet for example) - foreach (Item item in Item.ItemList) - { - var lightComponent = item.GetComponent(); - if (lightComponent != null) - { - lightComponent.Light.Enabled = item.ParentInventory == null; - } - } - if (selectedSub.Info.GameVersion < new Version("0.8.9.0")) { var adjustLightsPrompt = new GUIMessageBox(TextManager.Get("Warning"), TextManager.Get("AdjustLightsPrompt"), @@ -2857,8 +2823,8 @@ namespace Barotrauma categorizedEntityList.UpdateScrollBarSize(); categorizedEntityList.BarScroll = 0.0f; - categorizedEntityList.Visible = true; - allEntityList.Visible = false; + // categorizedEntityList.Visible = true; + // allEntityList.Visible = false; } private void FilterEntities(string filter) @@ -3047,6 +3013,37 @@ namespace Barotrauma 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()) + { + 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; object sliderMutex = new object(), sliderTextMutex = new object(), @@ -3142,9 +3139,22 @@ namespace Barotrauma colorPicker.DisposeTextures(); msgBox.Close(); - if (entity is MapEntity { Removed: true } me) { return true; } Color newColor = SetColor(null); - StoreCommand(new PropertyCommand(entity, property.Name, newColor, originalColor)); + + Dictionary> oldProperties = new Dictionary>(); + + foreach (var (sEntity, color, _) in entities) + { + if (sEntity is MapEntity { Removed: true }) { continue; } + if (!oldProperties.ContainsKey(color)) + { + oldProperties.Add(color, new List()); + } + oldProperties[color].Add(sEntity); + } + + List 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))) { @@ -3170,8 +3180,12 @@ namespace Barotrauma { colorPicker.DisposeTextures(); 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; }; @@ -3218,8 +3232,12 @@ namespace Barotrauma } Color color = ToolBox.HSVToRGB(colorPicker.SelectedHue, colorPicker.SelectedSaturation, colorPicker.SelectedValue); - color.A = originalColor.A; - property.TrySetValue(entity, color); + foreach (var (e, origColor, prop) in entities) + { + if (e is MapEntity { Removed: true }) { continue; } + color.A = origColor.A; + prop.TrySetValue(e, color); + } return color; void SetSliders(Vector3 hsv) @@ -3238,9 +3256,11 @@ namespace Barotrauma void SetColorPicker(Vector3 hsv) { + bool hueChanged = !MathUtils.NearlyEqual(colorPicker.SelectedHue, hsv.X); colorPicker.SelectedHue = hsv.X; colorPicker.SelectedSaturation = hsv.Y; colorPicker.SelectedValue = hsv.Z; + if (hueChanged) { colorPicker.RefreshHue(); } } void SetHex(Vector3 hsv) @@ -3496,6 +3516,8 @@ namespace Barotrauma private bool SelectPrefab(GUIComponent component, object obj) { + allEntityList.Deselect(); + categorizedEntityList.Deselect(); if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) { return false; } AddPreviouslyUsed(obj as MapEntityPrefab); @@ -3586,7 +3608,7 @@ namespace Barotrauma SoundPlayer.PlayUISound(GUISoundType.PickItem); MapEntityPrefab.SelectPrefab(obj); } - + return false; } @@ -4332,6 +4354,17 @@ namespace Barotrauma if (lightingEnabled) { + //turn off lights that are inside containers + foreach (Item item in Item.ItemList) + { + foreach (LightComponent lightComponent in item.GetComponents()) + { + 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); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 3b9d3d2d1..dff1e31da 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -1208,7 +1208,7 @@ namespace Barotrauma SafeAdd((ISerializableEntity) entity, property); 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; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs index 78cc554d5..8f1f89246 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/OggSound.cs @@ -30,6 +30,7 @@ namespace Barotrauma.Sounds public override int FillStreamBuffer(int samplePos, short[] buffer) { 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; @@ -73,6 +74,8 @@ namespace Barotrauma.Sounds { if (!Stream) { + reader ??= new VorbisReader(Filename); + reader.DecodedPosition = 0; int bufferSize = (int)reader.TotalSamples * reader.Channels; @@ -126,7 +129,7 @@ namespace Barotrauma.Sounds { if (Stream) { - reader.Dispose(); + reader?.Dispose(); } base.Dispose(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs index 46a8b977d..fbcca4b32 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/Sound.cs @@ -14,35 +14,15 @@ namespace Barotrauma.Sounds get { return disposed; } } - public SoundManager Owner - { - get; - protected set; - } + public readonly SoundManager Owner; - public string Filename - { - get; - protected set; - } + public readonly string Filename; - public XElement XElement - { - get; - protected set; - } + public readonly XElement XElement; - public bool Stream - { - get; - protected set; - } + public readonly bool Stream; - public bool StreamsReliably - { - get; - protected set; - } + public readonly bool StreamsReliably; public virtual SoundManager.SourcePoolIndex SourcePoolIndex { @@ -79,10 +59,10 @@ namespace Barotrauma.Sounds public float BaseNear; 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; - Filename = Path.GetFullPath(filename.CleanUpPath()).CleanUpPath(); + Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename; Stream = stream; StreamsReliably = streamsReliably; XElement = xElement; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs index 4b52362e6..158de9924 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundPlayer.cs @@ -21,12 +21,14 @@ namespace Barotrauma 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.damageRange = damageRange; this.damageType = damageType; - + this.ignoreMuffling = ignoreMuffling; this.requiredTag = requiredTag; } } @@ -269,6 +271,7 @@ namespace Barotrauma damageSound, soundElement.GetAttributeVector2("damagerange", Vector2.Zero), damageSoundType, + soundElement.GetAttributeBool("ignoremuffling", false), soundElement.GetAttributeString("requiredtag", ""))); break; @@ -531,7 +534,7 @@ namespace Barotrauma Vector2 diff = gap.WorldPosition - listenerPos; 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; if (!gap.IsRoomToRoom) { gapFlow *= 2.0f; } if (gapFlow < 10.0f) { continue; } @@ -1123,7 +1126,11 @@ namespace Barotrauma 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) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs index 35d39dc91..89800a93f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/VoipSound.cs @@ -63,10 +63,8 @@ namespace Barotrauma.Sounds 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(); ALFormat = Al.FormatMono16; diff --git a/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb b/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb index b17c38ef3..7f09d5fe6 100644 Binary files a/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb and b/Barotrauma/BarotraumaClient/Content/Effects/losshader.xnb differ diff --git a/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb b/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb index bd4516465..f8f1b3f6a 100644 Binary files a/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb and b/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.xnb differ diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index d5627f7cc..55ef308e9 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1300.0.2 + 0.1300.0.3 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index cab25d1e7..b10d5ec10 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1300.0.2 + 0.1300.0.3 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/Shaders/losshader.fx b/Barotrauma/BarotraumaClient/Shaders/losshader.fx index d40f91c49..f761aa1bc 100644 --- a/Barotrauma/BarotraumaClient/Shaders/losshader.fx +++ b/Barotrauma/BarotraumaClient/Shaders/losshader.fx @@ -26,6 +26,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = ; } Texture2D xLosTexture; sampler LosSampler = sampler_state { Texture = ; }; +float xLosAlpha; + float4 xColor; float4 mainPS(VertexShaderOutput input) : COLOR0 @@ -39,7 +41,7 @@ float4 mainPS(VertexShaderOutput input) : COLOR0 sampleColor.r * xColor.r, sampleColor.g * xColor.g, sampleColor.b * xColor.b, - obscureAmount); + obscureAmount * xLosAlpha); return outColor; } diff --git a/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx index 23a48e4d2..a799c320a 100644 --- a/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx +++ b/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx @@ -26,6 +26,8 @@ sampler TextureSampler : register (s0) = sampler_state { Texture = ; } Texture xLosTexture; sampler LosSampler = sampler_state { Texture = ; }; +float xLosAlpha; + float4 xColor; float4 mainPS(VertexShaderOutput input) : COLOR0 @@ -39,7 +41,7 @@ float4 mainPS(VertexShaderOutput input) : COLOR0 sampleColor.r * xColor.r, sampleColor.g * xColor.g, sampleColor.b * xColor.b, - obscureAmount); + obscureAmount * xLosAlpha); return outColor; } diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index a803b5be5..4f28bac29 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.1300.0.2 + 0.1300.0.3 Copyright © FakeFish 2018-2020 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index b8215ac30..1d613f26e 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1300.0.2 + 0.1300.0.3 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index fe0fdf117..b8e7c5a84 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1300.0.2 + 0.1300.0.3 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs index b3d291101..1382bdc27 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Signal/Terminal.cs @@ -25,12 +25,15 @@ namespace Barotrauma.Items.Components } } - partial void ShowOnDisplay(string input) + partial void ShowOnDisplay(string input, bool addToHistory = true) { - messageHistory.Add(input); - while (messageHistory.Count > MaxMessages) + if (addToHistory) { - messageHistory.RemoveAt(0); + messageHistory.Add(input); + while (messageHistory.Count > MaxMessages) + { + messageHistory.RemoveAt(0); + } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs index 4c2661d1a..8c435e979 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs @@ -73,6 +73,7 @@ namespace Barotrauma.Networking public NetworkConnection Connection { get; set; } public bool SpectateOnly; + public bool? WaitForNextRoundRespawn; public int KarmaKickCount; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index fca297450..c0cef8f02 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -486,6 +486,11 @@ namespace Barotrauma.Networking endRoundTimer += deltaTime; #endif } + else if (isCrewDead && (GameMain.GameSession?.GameMode is CampaignMode)) + { + endRoundDelay = 1.0f; + endRoundTimer += deltaTime; + } else { endRoundTimer = 0.0f; @@ -505,10 +510,14 @@ namespace Barotrauma.Networking { 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); } + else + { + Log("Ending round (no living players left)", ServerLog.MessageType.ServerMessage); + } EndGame(); return; } @@ -819,6 +828,9 @@ namespace Barotrauma.Networking case ClientPacketHeader.READY_CHECK: ReadyCheck.ServerRead(inc, connectedClient); break; + case ClientPacketHeader.READY_TO_SPAWN: + ReadReadyToSpawnMessage(inc, connectedClient); + break; case ClientPacketHeader.FILE_REQUEST: if (serverSettings.AllowFileTransfers) { @@ -1191,6 +1203,15 @@ namespace Barotrauma.Networking 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) { @@ -2145,12 +2166,12 @@ namespace Barotrauma.Networking } MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; - bool missionAllowRespawn = GameMain.GameSession.Campaign == null && (missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn)); - bool outpostAllowRespawn = GameMain.GameSession.Campaign != null && Level.Loaded?.Type == LevelData.LevelType.Outpost; + bool missionAllowRespawn = missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn); + 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(); @@ -2400,9 +2421,8 @@ namespace Barotrauma.Networking msg.Write((byte)ServerPacketHeader.STARTGAME); msg.Write(seed); msg.Write(gameSession.GameMode.Preset.Identifier); - bool missionAllowRespawn = campaign == null && (missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn)); - bool outpostAllowRespawn = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; - msg.Write(serverSettings.AllowRespawn && (missionAllowRespawn || outpostAllowRespawn)); + bool missionAllowRespawn = missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn); + msg.Write(serverSettings.AllowRespawn && missionAllowRespawn); msg.Write(serverSettings.AllowDisguises); msg.Write(serverSettings.AllowRewiring); msg.Write(serverSettings.LockAllDefaultWires); @@ -2563,6 +2583,7 @@ namespace Barotrauma.Networking client.Character = null; client.HasSpawned = false; client.InGame = false; + client.WaitForNextRoundRespawn = null; } } @@ -2798,6 +2819,7 @@ namespace Barotrauma.Networking client.Character = null; client.HasSpawned = false; + client.WaitForNextRoundRespawn = null; client.InGame = false; if (string.IsNullOrWhiteSpace(msg)) { msg = $"ServerMessage.ClientLeftServer~[client]={ClientLogName(client)}"; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index d2d533985..ceb57cfe6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -18,13 +18,12 @@ namespace Barotrauma.Networking if (!c.InGame) { continue; } if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { 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 var matchingData = campaign?.GetClientCharacterData(c); - if (matchingData != null && matchingData.HasSpawned && - Character.CharacterList.Any(c => c.Info == matchingData.CharacterInfo && c.CauseOfDeath?.Type == CauseOfDeathType.Disconnected)) + if (matchingData != null && matchingData.HasSpawned) { - continue; + if (!c.WaitForNextRoundRespawn.HasValue || c.WaitForNextRoundRespawn.Value) { continue; } } yield return c; @@ -77,25 +76,24 @@ namespace Barotrauma.Networking partial void UpdateWaiting(float deltaTime) { + if (RespawnShuttle != null) + { + RespawnShuttle.Velocity = Vector2.Zero; + } + bool respawnPending = RespawnPending(); if (respawnPending != RespawnCountdownStarted) { 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); } - if (!RespawnCountdownStarted) { return; } - - if (DateTime.Now > RespawnTime) + if (RespawnCountdownStarted && DateTime.Now > RespawnTime) { DispatchShuttle(); RespawnCountdownStarted = false; } - - if (RespawnShuttle == null) { return; } - - RespawnShuttle.Velocity = Vector2.Zero; } private void DispatchShuttle() @@ -183,7 +181,6 @@ namespace Barotrauma.Networking partial void UpdateTransportingProjSpecific(float deltaTime) { - if (!ReturnCountdownStarted) { //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 c.Character?.DespawnNow(); + c.WaitForNextRoundRespawn = null; + var matchingData = campaign?.GetClientCharacterData(c); if (matchingData != null && !matchingData.HasSpawned) { @@ -332,6 +331,15 @@ namespace Barotrauma.Networking } 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) { //give the character the items they would've gotten if they had spawned in the main sub diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index db3e39241..ca4eeec0d 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.1300.0.2 + 0.1300.0.3 Copyright © FakeFish 2018-2020 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs index 1a578ebf4..234ae434e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/CameraTransition.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using NLog.Targets; +using System; using System.Collections.Generic; using System.Linq; @@ -18,8 +19,11 @@ namespace Barotrauma private readonly Alignment? cameraEndPos; private readonly float? startZoom; private readonly float? endZoom; - public readonly float Duration; + + public readonly float WaitDuration; + public readonly float PanDuration; public readonly bool FadeOut; + public readonly bool LosFadeIn; private readonly CoroutineHandle updateCoroutine; @@ -28,10 +32,12 @@ namespace Barotrauma public bool AllowInterrupt = false; 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; + LosFadeIn = losFadeIn; this.cameraStartPos = cameraStartPos; this.cameraEndPos = cameraEndPos; this.startZoom = startZoom; @@ -77,9 +83,12 @@ namespace Barotrauma Vector2 initialCameraPos = cam.Position; Vector2? initialTargetPos = targetEntity?.WorldPosition; - float timer = 0.0f; - while (timer < Duration) + float timer = -WaitDuration; + + while (timer < PanDuration) { + float clampedTimer = Math.Max(timer, 0f); + if (Screen.Selected != GameMain.GameScreen) { 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)) : 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); #if CLIENT - cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, timer / Duration); - if (timer / Duration > 0.9f) + cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, clampedTimer / PanDuration); + 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 timer += CoroutineManager.UnscaledDeltaTime; @@ -158,6 +173,7 @@ namespace Barotrauma #if CLIENT GUI.ScreenOverlayColor = Color.TransparentBlack; GameMain.LightManager.LosEnabled = true; + GameMain.LightManager.LosAlpha = 1f; #endif if (prevControlled != null && !prevControlled.Removed) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index ec9917338..7119b258b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -172,6 +172,15 @@ namespace Barotrauma } } + /// + /// The monster won't try to damage these submarines + /// + public HashSet UnattackableSubmarines + { + get; + private set; + } = new HashSet(); + 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); @@ -2150,7 +2159,13 @@ namespace Barotrauma else { // 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) { // 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 _)) - { - continue; - } + case AIState.Escape: + case AIState.Avoid: + 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); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 4d95e9afc..eea5366db 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1012,11 +1012,6 @@ namespace Barotrauma } 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 if (cumulativeDamage < 10) { @@ -1027,7 +1022,7 @@ namespace Barotrauma } 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; } bool isWitnessing = otherHumanAI.VisibleHulls.Contains(Character.CurrentHull) || otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull); 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); otherHumanAI.AddCombatObjective(combatMode, attacker, delay); } @@ -1099,6 +1094,15 @@ namespace Barotrauma } else { + if (c.AIController is HumanAIController humanAI && humanAI.ObjectiveManager.GetActiveObjective()?.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 (c.IsSecurity) @@ -1154,7 +1158,7 @@ namespace Barotrauma HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman" || Character.CurrentHull == null || - Character.IsOnPlayerTeam && ObjectiveManager.GetActiveObjective()?.Target is Character followTarget && followTarget.IsPlayer, + Character.IsOnPlayerTeam && !target.IsPlayer && ObjectiveManager.GetActiveObjective()?.Target is Character followTarget && followTarget.IsPlayer, abortCondition = abortCondition, allowHoldFire = allowHoldFire, }; @@ -1841,15 +1845,14 @@ namespace Barotrauma if (character == null) { continue; } if (c == character) { continue; } if (c.IsDead || c.IsIncapacitated) { continue; } - if (c.SelectedConstruction != target.Item) { continue; } if (!IsFriendly(character, c, onlySameTeam: true)) { continue; } operatingCharacter = c; - // If the other character is player, don't try to operate - if (c.IsPlayer) { return true; } if (c.AIController is HumanAIController controllingHumanAi) { Item otherTarget = controllingHumanAi.objectiveManager.GetActiveObjective()?.Component.Item ?? c.SelectedConstruction; 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 (controllingHumanAi.ObjectiveManager.IsCurrentOrder()) { @@ -1874,8 +1877,7 @@ namespace Barotrauma } else { - // Shouldn't go here, unless we allow non-humans to operate items - return false; + return c.SelectedConstruction == target.Item; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs index 8cd4fb016..76b45c261 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs @@ -178,7 +178,7 @@ namespace Barotrauma { if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f && 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)) { currentFlags.Add("EnterOutpost"); @@ -188,6 +188,11 @@ namespace Barotrauma { currentFlags.Add("Casual"); } + + if (GameMain.GameSession.IsCurrentLocationRadiated()) + { + currentFlags.Add("InRadiation"); + } } if (speaker != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs index 919eb6f0a..09ad0a897 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItem.cs @@ -50,6 +50,11 @@ namespace Barotrauma float reduction = IsPriority ? 1 : isSelected ? 2 : 3; float max = AIObjectiveManager.LowestOrderPriority - reduction; 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; } @@ -126,5 +131,13 @@ namespace Barotrauma itemIndex = 0; decontainObjective = null; } + + public void DropTarget() + { + if (item != null && character.HasItem(item)) + { + item.Drop(character); + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index 73bb788a6..cb1e3f299 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -2,6 +2,7 @@ using Barotrauma.Items.Components; using System.Collections.Generic; using System.Linq; +using System; namespace Barotrauma { @@ -29,7 +30,21 @@ namespace Barotrauma 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) { @@ -65,10 +80,10 @@ namespace Barotrauma return true; } - public static bool IsValidContainer(Item item, Character character) => - !item.IgnoreByAI && item.IsInteractable(character) && item.HasTag("allowcleanup") && item.ParentInventory == null && item.OwnInventory != null && item.OwnInventory.AllItems.Any() && IsItemInsideValidSubmarine(item, character); + public static bool IsValidContainer(Item item, Character character, bool allowUnloading = true) => + !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.IgnoreByAI) { return false; } @@ -76,7 +91,7 @@ namespace Barotrauma if (item.SpawnedInOutpost) { return false; } 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; } var pickable = item.GetComponent(); @@ -127,5 +142,17 @@ namespace Barotrauma } return canEquip; } + + public override void OnDeselected() + { + base.OnDeselected(); + foreach (var subObjective in SubObjectives) + { + if (subObjective is AIObjectiveCleanupItem cleanUpObjective) + { + cleanUpObjective.DropTarget(); + } + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 336c01ede..4859536a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -457,7 +457,7 @@ namespace Barotrauma priority /= 2; } } - if (Enemy.Stun > 1) + if (Enemy.IsKnockedDown) { // Enemy is stunned, reduce the priority of stunner weapons. Attack attack = GetAttackDefinition(weapon); @@ -730,11 +730,12 @@ namespace Barotrauma { IgnoreIfTargetDead = true, DialogueIdentifier = "dialogcannotreachtarget", - TargetName = Enemy.DisplayName + TargetName = Enemy.DisplayName, + AlwaysUseEuclideanDistance = false }, onAbandon: () => Abandon = true); 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 _)) { @@ -742,8 +743,8 @@ namespace Barotrauma { arrestingRegistered = true; followTargetObjective.Completed += OnArrestTargetReached; + followTargetObjective.CloseEnough = 100; } - followTargetObjective.CloseEnough = 100; } else { @@ -759,7 +760,7 @@ namespace Barotrauma SteeringManager.Reset(); } } - if (followTargetObjective != null) + if (!arrestingRegistered && followTargetObjective != null) { followTargetObjective.CloseEnough = WeaponComponent is RangedWeapon ? 1000 : @@ -782,7 +783,7 @@ namespace Barotrauma private void OnArrestTargetReached() { - if (HumanAIController.HasItem(character, "handlocker", out IEnumerable matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy)) + if (HumanAIController.HasItem(character, "handlocker", out IEnumerable matchingItems) && !Enemy.IsUnconscious && Enemy.IsKnockedDown && character.CanInteractWith(Enemy)) { var handCuffs = matchingItems.First(); 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); - IsCompleted = true; } + IsCompleted = true; } /// @@ -937,7 +938,14 @@ namespace Barotrauma 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; } float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position); if (WeaponComponent is MeleeWeapon meleeWeapon) @@ -1017,6 +1025,8 @@ namespace Barotrauma 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; if (WeaponComponent is RangedWeapon rangedWeapon) { @@ -1038,10 +1048,16 @@ namespace Barotrauma 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() { base.OnCompleted(); - if (Weapon != null) + if (ShouldUnequipWeapon) { Unequip(); } @@ -1051,7 +1067,7 @@ namespace Barotrauma protected override void OnAbandon() { base.OnAbandon(); - if (Weapon != null) + if (ShouldUnequipWeapon) { Unequip(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 86257f0b6..f3ede74d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -38,7 +38,7 @@ namespace Barotrauma Priority = 0; Abandon = true; } - else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective()?.Leak == Leak)) + else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective()?.Leak == Leak)) { Priority = 0; Abandon = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index 224bb6ed5..4490d4f1a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -10,6 +10,8 @@ namespace Barotrauma { public override string DebugTag => "get item"; + public override bool AbandonWhenCannotCompleteSubjectives => false; + private readonly bool equip; public HashSet ignoredItems = new HashSet(); @@ -225,18 +227,8 @@ namespace Barotrauma 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) - abortCondition = obj => - { - 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() + abortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget, + SpeakIfFails = false }; }, onAbandon: () => @@ -263,13 +255,18 @@ namespace Barotrauma if (targetItem == null) { #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 Abandon = true; } 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() || 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++; var item = Item.ItemList[currSearchIndex]; @@ -310,8 +307,18 @@ namespace Barotrauma float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, 10000, dist)); itemPriority *= distanceFactor; 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 (!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; targetItem = 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 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 Abandon = true; } @@ -345,7 +352,7 @@ namespace Barotrauma else { #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 Abandon = true; } @@ -393,11 +400,30 @@ namespace Barotrauma /// private void ResetInternal() { - goToObjective = null; + RemoveSubObjective(ref goToObjective); targetItem = originalTarget; moveToTarget = targetItem?.GetRootInventoryOwner(); isDoneSeeking = false; 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); + } + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index fd2571d9e..ebc959fee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -30,7 +30,7 @@ namespace Barotrauma public bool followControlledCharacter; public bool mimic; - public bool speakIfFails = true; + public bool SpeakIfFails { get; set; } = true; public float extraDistanceWhileSwimming; public float extraDistanceOutsideSub; @@ -67,6 +67,8 @@ namespace Barotrauma public bool IgnoreIfTargetDead { get; set; } public bool AllowGoingOutside { get; set; } + public bool AlwaysUseEuclideanDistance { get; set; } = true; + public override bool AbandonWhenCannotCompleteSubjectives => !repeat; public override bool AllowOutsideSubmarine => AllowGoingOutside; @@ -88,12 +90,7 @@ namespace Barotrauma Abandon = !isOrder; return Priority; } - if (followControlledCharacter && Character.Controlled == null) - { - Priority = 0; - Abandon = !isOrder; - } - if (Target is Entity e && e.Removed) + if (Target == null || Target is Entity e && e.Removed) { Priority = 0; Abandon = !isOrder; @@ -147,11 +144,10 @@ namespace Barotrauma private void SpeakCannotReach() { - if (!character.IsOnPlayerTeam) { return; } #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow); #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)); if (msg != null) @@ -165,12 +161,15 @@ namespace Barotrauma { 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; return; } - Target = Character.Controlled; } if (Target == character || character.SelectedBy != null && HumanAIController.IsFriendly(character.SelectedBy)) { @@ -260,6 +259,7 @@ namespace Barotrauma } if (needsEquipment) { + SteeringManager.Reset(); if (findDivingGear != null && !findDivingGear.CanBeCompleted) { 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.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) { @@ -298,7 +303,7 @@ namespace Barotrauma } else { - SeekGaps(maxDistance: 500); + SeekGaps(maxGapDistance); seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f); if (TargetGap != null) { @@ -327,7 +332,7 @@ namespace Barotrauma } 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); return; @@ -347,7 +352,7 @@ namespace Barotrauma float closeEnough = 250; float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition); 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 equippedScooters, recursive: false, requireEquipped: true)) { // Currently equipped scooter @@ -528,17 +533,24 @@ namespace Barotrauma { Gap selectedGap = null; float selectedDistance = -1; + Vector2 toTargetNormalized = Vector2.Normalize(Target.WorldPosition - character.WorldPosition); foreach (Gap gap in Gap.GapList) { if (gap.Open < 1) { continue; } - if (gap.FlowTargetHull == null) { continue; } - if (gap.Submarine != Target.Submarine) { continue; } - float distance = Vector2.DistanceSquared(character.WorldPosition, gap.WorldPosition); - if (distance > maxDistance * maxDistance) { continue; } - if (selectedGap == null || distance < selectedDistance) + if (gap.Submarine == null) { continue; } + if (!followControlledCharacter) + { + if (gap.FlowTargetHull == null) { continue; } + 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; - selectedDistance = distance; + selectedDistance = squaredDistance; } } 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 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); if (yDiff > CloseEnough) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index 665639722..9ec668204 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -495,7 +495,7 @@ namespace Barotrauma foreach (Item item in Item.ItemList) { 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); } @@ -540,5 +540,17 @@ namespace Barotrauma ignoredItems.Clear(); autonomousObjectiveRetryTimer = 10; } + + public override void OnDeselected() + { + base.OnDeselected(); + foreach (var subObjective in SubObjectives) + { + if (subObjective is AIObjectiveCleanupItem cleanUpObjective) + { + cleanUpObjective.DropTarget(); + } + } + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 391461cec..90b22484a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -445,7 +445,7 @@ namespace Barotrauma extraDistanceWhileSwimming = 100, AllowGoingOutside = true, IgnoreIfTargetDead = true, - followControlledCharacter = orderGiver == character, + followControlledCharacter = true, mimic = true, DialogueIdentifier = "dialogcannotreachplace" }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 9a6824696..7312c2945 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -69,10 +69,9 @@ namespace Barotrauma { if (!isOrder) { - if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC || - HumanAIController.IsTrueForAnyCrewMember(c => - c.ObjectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder.GetTarget() == target)) + if (reactor.LastUserWasPlayer && character.TeamID != CharacterTeamType.FriendlyNPC) { + // The reactor was previously operated by a player -> ignore. Priority = 0; return Priority; } @@ -89,7 +88,6 @@ namespace Barotrauma case "powerup": // 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. - if (IsAnotherOrderTargetingSameItem(objectiveManager.ForcedOrder) || objectiveManager.CurrentOrders.Any(o => IsAnotherOrderTargetingSameItem(o.Objective))) { Priority = 0; @@ -177,9 +175,9 @@ namespace Barotrauma } 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; return; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 02f1d40c3..188707322 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -471,13 +471,15 @@ namespace Barotrauma } } + private double pressureProtectionLastSet; private float pressureProtection; public float PressureProtection { get { return pressureProtection; } 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 { - get { return IsRagdolled ? 1.0f : CharacterHealth.StunTimer; } + get { return IsRagdolled ? 1.0f : CharacterHealth.Stun; } set { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return; - + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } SetStun(value, true); } } @@ -697,7 +698,7 @@ namespace Barotrauma { if (!canBeDragged) { return false; } if (Removed || !AnimController.Draggable) { return false; } - return IsDead || Stun > 0.0f || LockHands || IsIncapacitated || IsPet; + return IsKnockedDown || LockHands || IsPet; } set { canBeDragged = value; } } @@ -715,7 +716,7 @@ namespace Barotrauma } else { - return IsDead || Stun > 0.0f || LockHands || IsIncapacitated; + return IsKnockedDown || LockHands; } } set { canInventoryBeAccessed = value; } @@ -1016,7 +1017,7 @@ namespace Barotrauma { // Get the non husked name and find the ragdoll with it var matchingAffliction = AfflictionPrefab.List - .Where(p => p.AfflictionType == "huskinfection") + .Where(p => p is AfflictionPrefabHusk) .Select(p => p as AfflictionPrefabHusk) .FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.OrdinalIgnoreCase))); string nonHuskedSpeciesName = string.Empty; @@ -1052,7 +1053,7 @@ namespace Barotrauma else { AnimController = new FishAnimController(this, seed, ragdollParams as FishRagdollParams); - PressureProtection = 100.0f; + PressureProtection = int.MaxValue; } AnimController.SetPosition(ConvertUnits.ToSimUnits(position)); @@ -1271,7 +1272,13 @@ namespace Barotrauma 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 @@ -2466,11 +2473,8 @@ namespace Barotrauma 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 - if (!protectedFromPressure && + if (!IsProtectedFromPressure() && (AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure >= 80.0f)) { if (CharacterHealth.PressureKillDelay <= 0.0f) @@ -2656,7 +2660,10 @@ namespace Barotrauma { if (NeedsAir) { - PressureProtection -= deltaTime * 100.0f; + if (Timing.TotalTime > pressureProtectionLastSet + 0.1) + { + PressureProtection = 0.0f; + } } if (NeedsWater) { @@ -3281,7 +3288,7 @@ namespace Barotrauma GameMain.Config.RecentlyEncounteredCreatures.Add(other.SpeciesName); } - public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1) + public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, float attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true) { if (Removed) { return new AttackResult(); } @@ -3327,7 +3334,7 @@ namespace Barotrauma bool wasDead = IsDead; Vector2 simPos = hitLimb.SimPosition + ConvertUnits.ToSimUnits(dir); AttackResult attackResult = hitLimb.AddDamage(simPos, afflictions, playSound, damageMultiplier: damageMultiplier); - CharacterHealth.ApplyDamage(hitLimb, attackResult); + CharacterHealth.ApplyDamage(hitLimb, attackResult, allowStacking); if (attacker != this) { OnAttacked?.Invoke(attacker, attackResult); @@ -3382,6 +3389,12 @@ namespace Barotrauma } } + /// + /// 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. + /// + public bool IsKnockedDown => IsDead || IsIncapacitated || CharacterHealth.StunTimer > 0.5f; + public void SetStun(float newStun, bool allowStunDecrease = false, bool isNetworkMessage = false) { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; } @@ -3391,7 +3404,7 @@ namespace Barotrauma { AnimController.ResetPullJoints(); } - CharacterHealth.StunTimer = newStun; + CharacterHealth.Stun = newStun; if (newStun > 0.0f) { SelectedConstruction = null; @@ -3964,5 +3977,10 @@ namespace Barotrauma public bool IsWatchman => HasJob("watchman"); public bool HasJob(string identifier) => Info?.Job?.Prefab.Identifier == identifier; + + public bool IsProtectedFromPressure() + { + return PressureProtection >= (Level.Loaded?.GetRealWorldDepth(WorldPosition.Y) ?? 0.0f); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 818bcdd8b..ff478bab3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -961,6 +961,20 @@ namespace Barotrauma public void Rename(string newName) { 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 061ab4a14..8d7ae53f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -14,6 +14,9 @@ namespace Barotrauma public Dictionary SerializableProperties { get; set; } + public float PendingAdditionStrenght { get; set; } + public float AdditionStrength { get; set; } + protected float _strength; [Serialize(0f, true), Editable] @@ -26,7 +29,12 @@ namespace Barotrauma { _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) { Prefab = prefab; + PendingAdditionStrenght = Prefab.GrainBurst; _strength = strength; Identifier = prefab?.Identifier; @@ -109,18 +118,25 @@ namespace Barotrauma if (currentEffect == null) { return 0.0f; } if (MathUtils.NearlyEqual(currentEffect.MaxGrainStrength, 0f)) { return 0.0f; } - return MathHelper.Lerp( + float amount = MathHelper.Lerp( currentEffect.MinGrainStrength, currentEffect.MaxGrainStrength, (Strength - currentEffect.MinStrength) / (currentEffect.MaxStrength - currentEffect.MinStrength)); + + if (Prefab.GrainBurst > 0 && AdditionStrength > amount) + { + return AdditionStrength; + } + + return amount; } public float GetScreenDistortStrength() { - if (Strength < Prefab.ActivationThreshold) return 0.0f; + if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); - if (currentEffect == null) return 0.0f; - if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength <= 0.0f) return 0.0f; + if (currentEffect == null) { return 0.0f; } + if (currentEffect.MaxScreenDistortStrength - currentEffect.MinScreenDistortStrength < 0.0f) { return 0.0f; } return MathHelper.Lerp( currentEffect.MinScreenDistortStrength, @@ -130,10 +146,10 @@ namespace Barotrauma public float GetRadialDistortStrength() { - if (Strength < Prefab.ActivationThreshold) return 0.0f; + if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); - if (currentEffect == null) return 0.0f; - if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength <= 0.0f) return 0.0f; + if (currentEffect == null) { return 0.0f; } + if (currentEffect.MaxRadialDistortStrength - currentEffect.MinRadialDistortStrength < 0.0f) { return 0.0f; } return MathHelper.Lerp( currentEffect.MinRadialDistortStrength, @@ -143,10 +159,10 @@ namespace Barotrauma public float GetChromaticAberrationStrength() { - if (Strength < Prefab.ActivationThreshold) return 0.0f; + if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); - if (currentEffect == null) return 0.0f; - if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength <= 0.0f) return 0.0f; + if (currentEffect == null) { return 0.0f; } + if (currentEffect.MaxChromaticAberrationStrength - currentEffect.MinChromaticAberrationStrength < 0.0f) { return 0.0f; } return MathHelper.Lerp( currentEffect.MinChromaticAberrationStrength, @@ -156,10 +172,10 @@ namespace Barotrauma public float GetScreenBlurStrength() { - if (Strength < Prefab.ActivationThreshold) return 0.0f; + if (Strength < Prefab.ActivationThreshold) { return 0.0f; } AfflictionPrefab.Effect currentEffect = Prefab.GetActiveEffect(Strength); - if (currentEffect == null) return 0.0f; - if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength <= 0.0f) return 0.0f; + if (currentEffect == null) { return 0.0f; } + if (currentEffect.MaxScreenBlurStrength - currentEffect.MinScreenBlurStrength < 0.0f) { return 0.0f; } return MathHelper.Lerp( currentEffect.MinScreenBlurStrength, @@ -167,6 +183,20 @@ namespace Barotrauma (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) { DamagePerSecond = Math.Max(DamagePerSecond, currentVitalityDecrease - PreviousVitalityDecrease); @@ -245,6 +275,21 @@ namespace Barotrauma { 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) @@ -267,7 +312,7 @@ namespace Barotrauma { var targets = new List(); statusEffect.GetNearbyTargets(characterHealth.Character.WorldPosition, targets); - statusEffect.Apply(ActionType.OnActive, deltaTime, targetLimb.character, targets); + statusEffect.Apply(ActionType.OnActive, deltaTime, characterHealth.Character, targets); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index 22c4f2fd2..4076e1c6d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -134,6 +134,8 @@ namespace Barotrauma public float MinSpeedMultiplier, MaxSpeedMultiplier; public float MinBuffMultiplier, MaxBuffMultiplier; + public float MinSkillMultiplier, MaxSkillMultiplier; + public float MinResistance, MaxResistance; public string ResistanceFor; public string DialogFlag; @@ -172,6 +174,9 @@ namespace Barotrauma MaxScreenBlurStrength = element.GetAttributeFloat("maxscreenblur", 0.0f); MaxScreenBlurStrength = Math.Max(MinScreenBlurStrength, MaxScreenBlurStrength); + MinSkillMultiplier = element.GetAttributeFloat("minskillmultiplier", 1.0f); + MaxSkillMultiplier = element.GetAttributeFloat("maxskillmultiplier", 1.0f); + ResistanceFor = element.GetAttributeString("resistancefor", ""); MinResistance = element.GetAttributeFloat("minresistance", 0.0f); MaxResistance = element.GetAttributeFloat("maxresistance", 0.0f); @@ -292,6 +297,8 @@ namespace Barotrauma public readonly float ShowIconToOthersThreshold = 0.05f; 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 public readonly float ShowInHealthScannerThreshold = 0.05f; @@ -451,6 +458,7 @@ namespace Barotrauma prefab = new AfflictionPrefab(sourceElement, file.Path, typeof(AfflictionBleeding)); break; case "huskinfection": + case "alieninfection": prefab = new AfflictionPrefabHusk(sourceElement, file.Path, typeof(AfflictionHusk)); break; case "cprsettings": @@ -521,7 +529,7 @@ namespace Barotrauma if (prefab != null) { - loadedAfflictions.Add((prefab, element)); + loadedAfflictions.Add((prefab, sourceElement)); Prefabs.Add(prefab, isOverride); prefab.CalculatePrefabUIntIdentifier(Prefabs); } @@ -582,6 +590,7 @@ namespace Barotrauma ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f)); ShowIconToOthersThreshold = element.GetAttributeFloat("showicontoothersthreshold", ShowIconThreshold); MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f); + GrainBurst = element.GetAttributeFloat(nameof(GrainBurst).ToLower(), 0.0f); ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f)); TreatmentThreshold = element.GetAttributeFloat("treatmentthreshold", Math.Max(ActivationThreshold, 5.0f)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 02d4da315..fd7e4e4ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -190,12 +190,14 @@ namespace Barotrauma set { bloodlossAffliction.Strength = MathHelper.Clamp(value, 0.0f, 100.0f); } } - public float StunTimer + public float Stun { get { return stunAffliction.Strength; } set { stunAffliction.Strength = MathHelper.Clamp(value, 0.0f, stunAffliction.Prefab.MaxStrength); } } + public float StunTimer { get; private set; } + public Affliction PressureAffliction { get { return pressureAffliction; } @@ -488,7 +490,7 @@ namespace Barotrauma CalculateVitality(); } - public void ApplyDamage(Limb hitLimb, AttackResult attackResult) + public void ApplyDamage(Limb hitLimb, AttackResult attackResult, bool allowStacking = true) { if (Unkillable || Character.GodMode) { return; } if (hitLimb.HealthIndex < 0 || hitLimb.HealthIndex >= limbHealths.Count) @@ -502,11 +504,11 @@ namespace Barotrauma { if (newAffliction.Prefab.LimbSpecific) { - AddLimbAffliction(hitLimb, newAffliction); + AddLimbAffliction(hitLimb, newAffliction, allowStacking); } else { - AddAffliction(newAffliction); + AddAffliction(newAffliction, allowStacking); } } } @@ -573,7 +575,7 @@ namespace Barotrauma 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 (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); 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 (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; } @@ -594,7 +596,15 @@ namespace Barotrauma { 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; CalculateVitality(); if (Vitality <= MinVitality) @@ -624,13 +634,12 @@ namespace Barotrauma #endif } - private void AddAffliction(Affliction newAffliction) + private void AddAffliction(Affliction newAffliction, bool allowStacking = true) { if (!DoesBleed && newAffliction is AfflictionBleeding) { 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))) { return; @@ -640,7 +649,13 @@ namespace Barotrauma { 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); } affliction.Strength = newStrength; affliction.Source = newAffliction.Source; @@ -676,6 +691,8 @@ namespace Barotrauma { UpdateOxygen(deltaTime); + StunTimer = Stun > 0 ? StunTimer + deltaTime : 0; + for (int i = 0; i < limbHealths.Count; i++) { for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--) @@ -795,6 +812,13 @@ namespace Barotrauma Vitality -= vitalityDecrease; affliction.CalculateDamagePerSecond(vitalityDecrease); } + +#if CLIENT + if (IsUnconscious) + { + HintManager.OnCharacterUnconscious(Character); + } +#endif } private void Kill() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 5cecd6e4d..988de5809 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -79,6 +79,9 @@ namespace Barotrauma [Serialize(0f, true), Editable] 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)] public float DisableDistance { get; set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 6a1bc30e0..602937ec9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -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) => { - if (args.Length < 2) return; + if (args.Length < 2) { return; } AfflictionPrefab afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(a => a.Name.Equals(args[0], StringComparison.OrdinalIgnoreCase) || @@ -626,9 +626,19 @@ namespace Barotrauma 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 (relativeStrength) + { + afflictionStrength *= targetCharacter.MaxVitality / afflictionPrefab.MaxStrength; + } targetCharacter.CharacterHealth.ApplyAffliction(targetCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(afflictionStrength)); } }, @@ -734,19 +744,19 @@ namespace Barotrauma List eventPrefabs = EventSet.GetAllEventPrefabs().Where(prefab => !string.IsNullOrWhiteSpace(prefab.Identifier)).ToList(); 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) { NewMessage($"Could not initialize event {args[0]} because level did not meet requirements"); return; } - GameMain.GameSession.EventManager.ActiveEvents.Add(@event); - @event.Init(true); - NewMessage($"Initialized event {newEvent.Identifier}", Color.Aqua); + GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent); + newEvent.Init(true); + NewMessage($"Initialized event {eventPrefab.Identifier}", Color.Aqua); return; } @@ -1002,7 +1012,7 @@ namespace Barotrauma } 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 @@ -1010,6 +1020,41 @@ namespace Barotrauma ThrowError("Could not set location reputation (no active campaign)."); } }, 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) => { @@ -1133,6 +1178,56 @@ namespace Barotrauma UpgradePrefab.Prefabs.Select(c => c.Identifier).Distinct().ToArray() }; }, 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) => { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 1efea1e0c..4b92ac0b8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -253,8 +253,8 @@ namespace Barotrauma }; 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()) { List spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList(); @@ -303,6 +303,12 @@ namespace Barotrauma 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 (spawnpointTags == null || !spawnpointTags.Any()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs index 3a2c82772..090a19fb5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TagAction.cs @@ -6,12 +6,17 @@ namespace Barotrauma { class TagAction : EventAction { + public enum SubType { Any= 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 } + [Serialize("", true)] public string Criteria { get; set; } [Serialize("", true)] public string Tag { get; set; } + [Serialize(SubType.Any, true)] + public SubType SubmarineType { get; set; } + [Serialize(true, true)] public bool IgnoreIncapacitatedCharacters { get; set; } @@ -63,17 +68,37 @@ namespace Barotrauma 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) { - 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) { - 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) @@ -113,7 +138,7 @@ namespace Barotrauma 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()})"; } } } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index e9975f456..c5b6cecdd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -134,7 +134,7 @@ namespace Barotrauma if (level?.LevelData?.Type == LevelData.LevelType.Outpost) { //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 unlockPathPrefabsForBiome = unlockPathPrefabs.FindAll(e => @@ -166,11 +166,14 @@ namespace Barotrauma void AddChildEvents(EventSet eventSet) { 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) @@ -373,6 +376,8 @@ namespace Barotrauma private void CreateEvents(EventSet eventSet, Random rand) { if (level == null) { return; } + if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; } + int applyCount = 1; List> spawnPosFilter = new List>(); if (eventSet.PerRuin) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 476c1e173..16170a3b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -86,6 +86,7 @@ namespace Barotrauma public readonly bool IgnoreCoolDown; public readonly bool PerRuin, PerCave, PerWreck; + public readonly bool DisableInHuntingGrounds; public readonly bool OncePerOutpost; @@ -142,9 +143,10 @@ namespace Barotrauma PerRuin = element.GetAttributeBool("perruin", false); PerCave = element.GetAttributeBool("percave", false); PerWreck = element.GetAttributeBool("perwreck", false); + DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false); IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (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); Commonness[""] = 1.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 187355154..0fa581532 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -54,10 +54,6 @@ namespace Barotrauma requireRescue.Clear(); 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) { InitCharacters(submarine); @@ -148,6 +144,10 @@ namespace Barotrauma } humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos); humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false); + if (spawnPos is WayPoint wp) + { + spawnedCharacter.GiveIdCardTags(wp); + } if (element.GetAttributeBool("requirekill", false)) { requireKill.Add(spawnedCharacter); @@ -175,6 +175,15 @@ namespace Barotrauma { 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); + } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs index 2b50cd61e..054436bfd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs @@ -1,4 +1,5 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; using System.Collections.Generic; namespace Barotrauma @@ -99,18 +100,18 @@ namespace Barotrauma } 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].FlipX(); crews = new List[] { new List(), new List() }; - - foreach (Submarine submarine in Submarine.Loaded) - { - //hide all subs from sonar to make sneak attacks possible - submarine.ShowSonarMarker = false; - } } public override void End() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 45ab834c0..fa3d252da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -107,6 +107,11 @@ namespace Barotrauma } public readonly Location[] Locations; + + public int? Difficulty + { + get { return Prefab.Difficulty; } + } public Mission(MissionPrefab prefab, Location[] locations) { @@ -186,6 +191,9 @@ namespace Barotrauma public void Start(Level level) { +#if CLIENT + shownMessages.Clear(); +#endif foreach (string categoryToShow in Prefab.UnhideEntitySubCategories) { foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index 9d48ab4c3..274f041cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -72,6 +72,8 @@ namespace Barotrauma public readonly List> DataRewards = new List>(); public readonly int Commonness; + public readonly int? Difficulty; + public const int MinDifficulty = 1, MaxDifficulty = 4; public readonly int Reward; @@ -156,6 +158,11 @@ namespace Barotrauma AllowRetry = element.GetAttributeBool("allowretry", false); IsSideObjective = element.GetAttributeBool("sideobjective", false); 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"); FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? ""; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index 29641f40d..bf83c9e61 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -228,7 +228,7 @@ namespace Barotrauma } GiveReward(); 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; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs index 0c471748b..5167bce56 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CrewManager.cs @@ -310,7 +310,7 @@ namespace Barotrauma { 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() || humanAI.ObjectiveManager.IsCurrentObjective())) { continue; @@ -327,10 +327,12 @@ namespace Barotrauma { if (npc.TeamID == CharacterTeamType.None) { + dialogFlags.Remove("OutpostNPC"); dialogFlags.Add("Bandit"); } else if (npc.TeamID == CharacterTeamType.FriendlyNPC) { + dialogFlags.Remove("OutpostNPC"); dialogFlags.Add("Hostage"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs index 55977e124..ba5a9912b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs @@ -144,7 +144,7 @@ namespace Barotrauma new XAttribute("value", valueStr), new XAttribute("type", value?.GetType()))); } -#if DEBUG || UNSTABLE +#if DEBUG DebugConsole.Log(element.ToString()); #endif modeElement.Add(element); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 6bcf8d278..daaa46986 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Barotrauma.Networking; +using Barotrauma.Extensions; namespace Barotrauma { @@ -258,22 +259,32 @@ namespace Barotrauma if (levelData.HasBeaconStation && !levelData.IsBeaconActive) { - var beaconMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase))); - if (beaconMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type)) + var beaconMissionPrefabs = MissionPrefab.List.FindAll(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase))); + 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) { - var huntingGroundsMissionPrefab = MissionPrefab.List.Find(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase))); - if (huntingGroundsMissionPrefab == null) + var huntingGroundsMissionPrefabs = MissionPrefab.List.FindAll(m => m.Tags.Any(t => t.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase))); + if (!huntingGroundsMissionPrefabs.Any()) { 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); } } - map.CurrentLocation.RegisterTakenItems(takenItems); - - map.CurrentLocation.AddToStock(CargoManager.SoldItems); - CargoManager.ClearSoldItemsProjSpecific(); - map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems); + if (map != null && CargoManager != null) + { + map.CurrentLocation.RegisterTakenItems(takenItems); + map.CurrentLocation.AddToStock(CargoManager.SoldItems); + CargoManager.ClearSoldItemsProjSpecific(); + map.CurrentLocation.RemoveFromStock(CargoManager.PurchasedItems); + } if (GameMain.NetworkMember == null) { CargoManager.ClearItemsInBuyCrate(); @@ -538,11 +551,11 @@ namespace Barotrauma { if (GameMain.NetworkMember.IsServer) { - CargoManager.ClearItemsInBuyCrate(); + CargoManager?.ClearItemsInBuyCrate(); } else if (GameMain.NetworkMember.IsClient) { - CargoManager.ClearItemsInSellCrate(); + CargoManager?.ClearItemsInSellCrate(); } } @@ -601,7 +614,11 @@ namespace Barotrauma } 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.ClearMissions(); location.Discovered = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index cc077eb08..b5c48766e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -425,6 +425,7 @@ namespace Barotrauma #if CLIENT GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null; + if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; } if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode; #endif LevelData = level?.LevelData; diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index 182a5ef40..fae47582a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -104,7 +104,8 @@ namespace Barotrauma /// /// /// - public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category) + /// + public void PurchaseUpgrade(UpgradePrefab prefab, UpgradeCategory category, bool force = false) { if (!CanUpgradeSub()) { @@ -136,6 +137,11 @@ namespace Barotrauma }); } + if (force) + { + price = 0; + } + if (Campaign.Money > price) { if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) @@ -154,7 +160,7 @@ namespace Barotrauma PurchasedUpgrade? upgrade = FindMatchingUpgrade(prefab, category); #if CLIENT - DebugLog($"CLIENT: Purchased level {GetUpgradeLevel(prefab, category) + 1} {category.Name}.{prefab.Name} for ${price}", GUI.Style.Orange); + DebugLog($"CLIENT: Purchased level {GetUpgradeLevel(prefab, category) + 1} {category.Name}.{prefab.Name} for {price}", GUI.Style.Orange); #endif if (upgrade == null) @@ -689,7 +695,7 @@ namespace Barotrauma public static void DebugLog(string msg, Color? color = null) { -#if UNSTABLE || DEBUG +#if DEBUG DebugConsole.NewMessage(msg, color ?? Color.GreenYellow); #else DebugConsole.Log(msg); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 7647cf0b9..2f616c89d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -150,6 +150,44 @@ namespace Barotrauma return false; } + public override void RemoveItem(Item item) + { + RemoveItem(item, tryEquipFromSameStack: false); + } + + public void RemoveItem(Item item, bool tryEquipFromSameStack) + { + if (!Contains(item)) { return; } + + bool wasEquipped = character.HasEquippedItem(item); + var indices = FindIndices(item); + + base.RemoveItem(item); +#if CLIENT + CreateSlots(); +#endif + //if the item was equipped and there are more items in the same stack, equip one of those items + if (tryEquipFromSameStack && wasEquipped) + { + int limbSlot = indices.Find(j => SlotTypes[j] != InvSlotType.Any); + foreach (int i in indices) + { + var itemInSameSlot = GetItemAt(i); + if (itemInSameSlot != null) + { + if (TryPutItem(itemInSameSlot, limbSlot, allowSwapping: false, allowCombine: false, character)) + { +#if CLIENT + visualSlots[i].ShowBorderHighlight(GUI.Style.Green, 0.1f, 0.412f); +#endif + } + break; + } + } + } + } + + /// /// If there is no room in the generic inventory (InvSlotType.Any), check if the item can be auto-equipped into its respective limbslot /// @@ -165,6 +203,19 @@ namespace Barotrauma } } + if (allowedSlots != null && !allowedSlots.Contains(InvSlotType.Any)) + { + int slot = FindLimbSlot(allowedSlots.First()); + if (slot > -1 && slots[slot].Items.Any(it => it != item) && slots[slot].First().Prefab.AllowDroppingOnSwap) + { + foreach (Item existingItem in slots[slot].Items.ToList()) + { + existingItem.Drop(user); + if (existingItem.ParentInventory != null) { existingItem.ParentInventory.RemoveItem(existingItem); } + } + } + } + return TryPutItem(item, user, allowedSlots, createNetworkEvent); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index abae924a3..a8b797122 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -62,6 +62,7 @@ namespace Barotrauma.Items.Components { if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; } Attack = new Attack(subElement, item.Name + ", MeleeWeapon"); + Attack.DamageRange = item.body == null ? 10.0f : ConvertUnits.ToDisplayUnits(item.body.GetMaxExtent()); } item.IsShootable = true; // TODO: should define this in xml if we have melee weapons that don't require aim to use diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs index 4208b58b0..e4502d68c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Engine.cs @@ -175,7 +175,7 @@ namespace Barotrauma.Items.Components Vector2 propellerWorldPos = item.WorldPosition + PropellerPos * item.Scale; foreach (Character character in Character.CharacterList) { - if (character.Submarine != null || !character.Enabled || character.Removed) { continue; } + if (!character.Enabled || character.Removed) { continue; } float distSqr = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos); if (distSqr > scaledDamageRange * scaledDamageRange) { continue; } character.LastDamageSource = item; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index ba43293af..7e19a9248 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -121,11 +121,10 @@ namespace Barotrauma.Items.Components inputContainer = containers[0]; outputContainer = containers[1]; - + foreach (var recipe in fabricationRecipes) { - int ingredientCount = recipe.RequiredItems.Sum(it => it.Amount); - if (ingredientCount > inputContainer.Capacity) + if (recipe.RequiredItems.Count > inputContainer.Capacity) { DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + recipe.TargetItem.Name + "\"!"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 70c93a0a9..592a01cc9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -581,10 +581,9 @@ namespace Barotrauma.Items.Components return false; } aiUpdateTimer = AIUpdateInterval; - // load more fuel if the current maximum output is only 50% of the current load // or if the fuel rod is (almost) deplenished - float minCondition = fuelConsumptionRate * MathUtils.Pow((degreeOfSuccess - refuelLimit) * 2, 2); + float minCondition = fuelConsumptionRate * MathUtils.Pow2((degreeOfSuccess - refuelLimit) * 2); if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition)) { bool outOfFuel = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs index b37e346b2..dfb522af9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerTransfer.cs @@ -360,7 +360,7 @@ namespace Barotrauma.Items.Components //other junction boxes don't need to receive the signal in the pass-through signal connections //because we relay it straight to the connected items without going through the whole chain of junction boxes if (ic is PowerTransfer && !(ic is RelayComponent) && connection.Name.Contains("signal")) { continue; } - ic.ReceiveSignal(signal, connection); + ic.ReceiveSignal(signal, recipient); } foreach (StatusEffect effect in recipient.Effects) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index 82b0b5e75..89543f080 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -65,10 +65,10 @@ namespace Barotrauma.Items.Components } base.IsActive = true; - InitProjSpecific(element); + InitProjSpecific(); } - partial void InitProjSpecific(XElement element); + partial void InitProjSpecific(); private bool linksInitialized; public override void OnMapLoaded() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs index 285742337..bd35f1731 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SignalCheckComponent.cs @@ -62,7 +62,7 @@ namespace Barotrauma.Items.Components { case "signal_in": string signalOut = (signal.value == TargetSignal) ? Output : FalseOutput; - if (string.IsNullOrWhiteSpace(signalOut)) { return; } + if (string.IsNullOrEmpty(signalOut)) { return; } signal.value = signalOut; item.SendSignal(signal, "signal_out"); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs index 3318c0123..122c056bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/SmokeDetector.cs @@ -83,7 +83,8 @@ namespace Barotrauma.Items.Components fireInRange = IsFireInRange(); fireCheckTimer = FireCheckInterval; } - item.SendSignal(fireInRange ? Output : FalseOutput, "signal_out"); + string signalOut = fireInRange ? Output : FalseOutput; + if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs index de25ec0d4..ea713c3f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -42,7 +42,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(XElement element); - partial void ShowOnDisplay(string input); + partial void ShowOnDisplay(string input, bool addToHistory = true); public override void ReceiveSignal(Signal signal, Connection connection) { @@ -58,13 +58,18 @@ namespace Barotrauma.Items.Components public override void OnItemLoaded() { + bool isSubEditor = false; +#if CLIENT + isSubEditor = Screen.Selected != GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode; +#endif + base.OnItemLoaded(); if (!string.IsNullOrEmpty(DisplayedWelcomeMessage)) { - ShowOnDisplay(DisplayedWelcomeMessage); + ShowOnDisplay(DisplayedWelcomeMessage, addToHistory: !isSubEditor); DisplayedWelcomeMessage = ""; //remove welcome message if a game session is running so it doesn't reappear on successive rounds - if (GameMain.GameSession != null) + if (GameMain.GameSession != null && !isSubEditor) { welcomeMessage = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index 0f2096201..d0cce2fa9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -102,6 +102,13 @@ namespace Barotrauma.Items.Components set; } + [Editable, Serialize(false, true, "If enabled, this wire will use the sprite depth instead of a constant depth.")] + public bool UseSpriteDepth + { + get; + set; + } + public Wire(Item item, XElement element) : base(item, element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index 9a9232669..66742a085 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -436,23 +436,28 @@ namespace Barotrauma.Items.Components Projectile launchedProjectile = null; for (int i = 0; i < ProjectileCount; i++) { - foreach (MapEntity e in item.linkedTo) + var projectiles = GetLoadedProjectiles(true); + if (projectiles.Any()) { - //use linked projectile containers in case they have to react to the turret being launched somehow - //(play a sound, spawn more projectiles) - if (!(e is Item linkedItem)) { continue; } - ItemContainer projectileContainer = linkedItem.GetComponent(); - if (projectileContainer != null) + ItemContainer projectileContainer = projectiles.First().Item.Container?.GetComponent(); + projectileContainer?.Item.Use(deltaTime, null); + } + else + { + foreach (MapEntity e in item.linkedTo) { - linkedItem.Use(deltaTime, null); - var repairable = linkedItem.GetComponent(); - if (repairable != null && failedLaunchAttempts < 2) + //use linked projectile containers in case they have to react to the turret being launched somehow + //(play a sound, spawn more projectiles) + if (!(e is Item linkedItem)) { continue; } + ItemContainer projectileContainer = linkedItem.GetComponent(); + if (projectileContainer != null) { - repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f; + linkedItem.Use(deltaTime, null); + projectiles = GetLoadedProjectiles(true); + if (projectiles.Any()) { break; } } } } - var projectiles = GetLoadedProjectiles(true); if (projectiles.Count == 0 && !LaunchWithoutProjectile) { //coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched, @@ -471,7 +476,6 @@ namespace Barotrauma.Items.Components } failedLaunchAttempts = 0; launchedProjectile = projectiles.FirstOrDefault(); - if (!ignorePower) { var batteries = item.GetConnectedComponents(); @@ -492,6 +496,15 @@ namespace Barotrauma.Items.Components } } + if (launchedProjectile?.Item.Container != null) + { + var repairable = launchedProjectile?.Item.Container.GetComponent(); + if (repairable != null) + { + repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f; + } + } + if (launchedProjectile != null || LaunchWithoutProjectile) { Launch(launchedProjectile?.Item, character); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index be7a89c9d..4701fafc1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -663,13 +663,29 @@ namespace Barotrauma existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(),user, CharacterInventory.anySlot, createNetworkEvent)) && stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent)); + + if (!swapSuccessful && existingItems.Count == 1 && existingItems[0].Prefab.AllowDroppingOnSwap) + { + existingItems[0].Drop(user, createNetworkEvent); + swapSuccessful = stackedItems.Distinct().Any(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent)); +#if CLIENT + if (swapSuccessful) + { + SoundPlayer.PlayUISound(GUISoundType.DropItem); + if (otherInventory.visualSlots != null && otherIndex > -1) + { + otherInventory.visualSlots[otherIndex].ShowBorderHighlight(Color.Transparent, 0.1f, 0.1f); + } + } +#endif + } } //if the item in the slot can be moved to the slot of the moved item if (swapSuccessful) { System.Diagnostics.Debug.Assert(slots[index].Contains(item), "Something when wrong when swapping items, item is not present in the inventory."); - System.Diagnostics.Debug.Assert(otherInventory.Contains(existingItems.FirstOrDefault()), "Something when wrong when swapping items, item is not present in the other inventory."); + System.Diagnostics.Debug.Assert(!existingItems.Any(it => !it.Prefab.AllowDroppingOnSwap && !otherInventory.Contains(it)), "Something when wrong when swapping items, item is not present in the other inventory."); #if CLIENT if (visualSlots != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index f8629021b..a9623d15d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -2807,7 +2807,14 @@ namespace Barotrauma if (parentInventory != null) { - parentInventory.RemoveItem(this); + if (parentInventory is CharacterInventory characterInventory) + { + characterInventory.RemoveItem(this, tryEquipFromSameStack: true); + } + else + { + parentInventory.RemoveItem(this); + } parentInventory = null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index aeffbd4a6..51bab9e1c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -517,6 +517,9 @@ namespace Barotrauma set { maxStackSize = MathHelper.Clamp(value, 1, Inventory.MaxStackSize); } } + [Serialize(false, false)] + public bool AllowDroppingOnSwap { get; private set; } + public Vector2 Size => size; public bool CanBeBought => (DefaultPrice != null && DefaultPrice.CanBeBought) || (locationPrices != null && locationPrices.Any(p => p.Value.CanBeBought)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs index ffead791e..26d1a03c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/BallastFloraBehavior.cs @@ -87,7 +87,7 @@ namespace Barotrauma.MapCreatures.Behavior internal partial class BallastFloraBehavior : ISerializableEntity { -#if DEBUG || UNSTABLE +#if DEBUG public List> debugSearchLines = new List>(); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/State/GrowIdleState.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/State/GrowIdleState.cs index 69a69b806..ff3ac93bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/State/GrowIdleState.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Creatures/State/GrowIdleState.cs @@ -60,7 +60,7 @@ namespace Barotrauma.MapCreatures.Behavior protected virtual void Grow() { List newTiles = GrowRandomly(); -#if DEBUG || UNSTABLE +#if DEBUG Behavior.debugSearchLines.Clear(); #endif if (newTiles.Any(TryScanTargets)) { return; } @@ -135,7 +135,7 @@ namespace Barotrauma.MapCreatures.Behavior Vector2 itemSimPos = ConvertUnits.ToSimUnits(item.Position); -#if DEBUG || UNSTABLE +#if DEBUG Tuple debugLine1 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(topLeft), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos - diameter)); Tuple debugLine2 = Tuple.Create(parent.Position - ConvertUnits.ToDisplayUnits(bottomRight), parent.Position - ConvertUnits.ToDisplayUnits(itemSimPos + diameter)); Behavior.debugSearchLines.Add(debugLine2); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs index e3f4357c2..4f0fa15ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Explosion.cs @@ -27,6 +27,7 @@ namespace Barotrauma private bool sparks, shockwave, flames, smoke, flash, underwaterBubble; private bool playTinnitus; private bool applyFireEffects; + private string[] ignoreFireEffectsForTags; private bool ignoreCover; private bool onlyInside; private bool onlyOutside; @@ -53,6 +54,7 @@ namespace Barotrauma smoke = true; flames = true; underwaterBubble = true; + ignoreFireEffectsForTags = new string[0]; } public Explosion(XElement element, string parentDebugName) @@ -70,6 +72,8 @@ namespace Barotrauma playTinnitus = element.GetAttributeBool("playtinnitus", true); applyFireEffects = element.GetAttributeBool("applyfireeffects", flames); + ignoreFireEffectsForTags = element.GetAttributeStringArray("ignorefireeffectsfortags", new string[0], convertToLowerInvariant: true); + ignoreCover = element.GetAttributeBool("ignorecover", false); onlyInside = element.GetAttributeBool("onlyinside", false); onlyOutside = element.GetAttributeBool("onlyoutside", false); @@ -192,7 +196,7 @@ namespace Barotrauma dist = Math.Max(0.0f, dist - ConvertUnits.ToDisplayUnits(itemRadius)); if (dist > Attack.Range) { continue; } - if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof) + if (dist < Attack.Range * 0.5f && applyFireEffects && !item.FireProof && ignoreFireEffectsForTags.None(t => item.HasTag(t))) { //don't apply OnFire effects if the item is inside a fireproof container //(or if it's inside a container that's inside a fireproof container, etc) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index f4417aff4..162900bde 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -479,8 +479,8 @@ namespace Barotrauma minMainPathWidth, parentTunnel: null); Tunnels.Add(mainPath); - Tunnel startPath = null, endPath = null; - if (Mirrored ? !HasEndOutpost() : !HasStartOutpost()) + Tunnel startPath = null, endPath = null, endHole = null; + if (GenerationParams.StartPosition.Y < 0.5f && (Mirrored ? !HasEndOutpost() : !HasStartOutpost())) { startPath = new Tunnel( TunnelType.SidePath, @@ -488,7 +488,11 @@ namespace Barotrauma minWidth / 2, parentTunnel: mainPath); Tunnels.Add(startPath); } - if (Mirrored ? !HasStartOutpost() : !HasEndOutpost()) + else + { + startExitPosition = StartPosition; + } + if (GenerationParams.EndPosition.Y < 0.5f && (Mirrored ? !HasStartOutpost() : !HasEndOutpost())) { endPath = new Tunnel( TunnelType.SidePath, @@ -496,12 +500,51 @@ namespace Barotrauma minWidth / 2, parentTunnel: mainPath); Tunnels.Add(endPath); } + else + { + endExitPosition = EndPosition; + } + + if (GenerationParams.CreateHoleNextToEnd) + { + if (Mirrored) + { + endHole = new Tunnel( + TunnelType.SidePath, + new List() { startPosition, startExitPosition.ToPoint(), new Point(0, Size.Y) }, + minWidth / 2, parentTunnel: mainPath); + } + else + { + endHole = new Tunnel( + TunnelType.SidePath, + new List() { endPosition, endExitPosition.ToPoint(), Size }, + minWidth / 2, parentTunnel: mainPath); + } + Tunnels.Add(endHole); + } + + //create a tunnel from the lowest point in the main path to the abyss + //to ensure there's a way to the abyss in all levels + if (GenerationParams.CreateHoleToAbyss) + { + Point lowestPoint = mainPath.Nodes.First(); + foreach (var pathNode in mainPath.Nodes) + { + if (pathNode.Y < lowestPoint.Y) { lowestPoint = pathNode; } + } + var abyssTunnel = new Tunnel( + TunnelType.SidePath, + new List() { lowestPoint, new Point(lowestPoint.X, 0) }, + minWidth / 2, parentTunnel: mainPath); + Tunnels.Add(abyssTunnel); + } int sideTunnelCount = Rand.Range(GenerationParams.SideTunnelCount.X, GenerationParams.SideTunnelCount.Y + 1, Rand.RandSync.Server); for (int j = 0; j < sideTunnelCount; j++) { if (mainPath.Nodes.Count < 4) { break; } - var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath); + var validTunnels = Tunnels.FindAll(t => t.Type != TunnelType.Cave && t != startPath && t != endPath && t != endHole); Tunnel tunnelToBranchOff = validTunnels[Rand.Int(validTunnels.Count, Rand.RandSync.Server)]; if (tunnelToBranchOff == null) { tunnelToBranchOff = mainPath; } @@ -634,13 +677,16 @@ namespace Barotrauma CaveGenerator.GeneratePath(tunnel, this); if (tunnel.Type == TunnelType.MainPath || tunnel.Type == TunnelType.SidePath) { - var distinctCells = tunnel.Cells.Distinct().ToList(); - for (int i = 2; i < distinctCells.Count; i += 3) + if (tunnel != startPath && tunnel != endPath && tunnel != endHole) { - PositionsOfInterest.Add(new InterestingPosition( - new Point((int)distinctCells[i].Site.Coord.X, (int)distinctCells[i].Site.Coord.Y), - tunnel.Type == TunnelType.MainPath ? PositionType.MainPath : PositionType.SidePath, - Caves.Find(cave => cave.Tunnels.Contains(tunnel)))); + var distinctCells = tunnel.Cells.Distinct().ToList(); + for (int i = 2; i < distinctCells.Count; i += 3) + { + PositionsOfInterest.Add(new InterestingPosition( + new Point((int)distinctCells[i].Site.Coord.X, (int)distinctCells[i].Site.Coord.Y), + tunnel.Type == TunnelType.MainPath ? PositionType.MainPath : PositionType.SidePath, + Caves.Find(cave => cave.Tunnels.Contains(tunnel)))); + } } } GenerateWaypoints(tunnel, parentTunnel: tunnel.ParentTunnel); @@ -685,7 +731,10 @@ namespace Barotrauma var potentialIslands = new List(); foreach (var cell in pathCells) { - if (GetDistToTunnel(cell.Center, mainPath) < minMainPathWidth) { continue; } + if (GetDistToTunnel(cell.Center, mainPath) < minMainPathWidth || + (startPath != null && GetDistToTunnel(cell.Center, startPath) < minMainPathWidth) || + (endPath != null && GetDistToTunnel(cell.Center, endPath) < minMainPathWidth) || + (endHole != null && GetDistToTunnel(cell.Center, endHole) < minMainPathWidth)) { continue; } if (cell.Edges.Any(e => e.AdjacentCell(cell)?.CellType != CellType.Path || e.NextToCave)) { continue; } potentialIslands.Add(cell); } @@ -1230,18 +1279,6 @@ namespace Barotrauma List toBeRemoved = new List(); foreach (VoronoiCell cell in cells) { - if (GenerationParams.CreateHoleNextToEnd) - { - if ((!Mirrored && cell.Center.X > endPosition.X) || (Mirrored && cell.Center.X < StartPosition.X)) - { - if (cell.Edges.Any(e => e.Point1.Y > Size.Y - submarineSize || e.Point2.Y > Size.Y - submarineSize)) - { - toBeRemoved.Add(cell); - continue; - } - } - } - if (cell.Edges.Any(e => e.NextToCave)) { continue; } if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > holeProbability) { continue; } if (!limits.Contains(cell.Site.Coord.X, cell.Site.Coord.Y)) { continue; } @@ -1644,7 +1681,7 @@ namespace Barotrauma Rectangle allowedArea = new Rectangle(padding, padding, Size.X - padding * 2, Size.Y - padding * 2); int radius = Math.Max(caveSize.X, caveSize.Y) / 2; - var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.2f, asCloseAsPossible: true, allowedArea); + var cavePos = FindPosAwayFromMainPath((parentTunnel.MinWidth + radius) * 1.5f, asCloseAsPossible: true, allowedArea); GenerateCave(caveParams, parentTunnel, cavePos, caveSize); @@ -2633,7 +2670,7 @@ namespace Barotrauma if (Submarine.PickBody( ConvertUnits.ToSimUnits(startPos), ConvertUnits.ToSimUnits(endPos), - ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body), + ExtraWalls.Where(w => w.Body?.BodyType == BodyType.Dynamic || w is DestructibleLevelWall).Select(w => w.Body).Union(Submarine.Loaded.Where(s => s.Info.Type == SubmarineType.Player).Select(s => s.PhysicsBody.FarseerBody)), Physics.CollisionLevel | Physics.CollisionWall) != null) { position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos) * offsetFromWall; @@ -3027,6 +3064,7 @@ namespace Barotrauma else if (type == SubmarineType.BeaconStation) { sub.ShowSonarMarker = false; + sub.DockedTo.ForEach(s => s.ShowSonarMarker = false); sub.PhysicsBody.FarseerBody.BodyType = BodyType.Static; sub.TeamID = CharacterTeamType.None; } @@ -3472,13 +3510,22 @@ namespace Barotrauma if ((i == 0) == !Mirrored) { StartOutpost = outpost; - if (StartLocation != null) { outpost.Info.Name = StartLocation.Name; } + if (StartLocation != null) + { + outpost.TeamID = StartLocation.Type.OutpostTeam; + outpost.Info.Name = StartLocation.Name; + } } else { EndOutpost = outpost; - if (EndLocation != null) { outpost.Info.Name = EndLocation.Name; } + if (EndLocation != null) + { + outpost.TeamID = EndLocation.Type.OutpostTeam; + outpost.Info.Name = EndLocation.Name; + } } + } } @@ -3514,20 +3561,28 @@ namespace Barotrauma List beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation); Item reactorItem = beaconItems.Find(it => it.GetComponent() != null); - Reactor reactorComponent = reactorItem.GetComponent(); - ItemContainer reactorContainer = reactorItem.GetComponent(); - Repairable repairable = reactorItem.GetComponent(); - reactorComponent.FuelConsumptionRate = 0.0f; - if (repairable != null) + Reactor reactorComponent = null; + ItemContainer reactorContainer = null; + if (reactorItem != null) { - repairable.DeteriorationSpeed = 0.0f; + reactorComponent = reactorItem.GetComponent(); + reactorComponent.FuelConsumptionRate = 0.0f; + reactorContainer = reactorItem.GetComponent(); + Repairable repairable = reactorItem.GetComponent(); + if (repairable != null) + { + if (repairable != null) + { + repairable.DeteriorationSpeed = 0.0f; + } + } } if (LevelData.IsBeaconActive) { - if (reactorContainer.Inventory.IsEmpty()) + if (reactorContainer != null && reactorContainer.Inventory.IsEmpty()) { ItemPrefab fuelPrefab = ItemPrefab.Prefabs[reactorContainer.ContainableItems[0].Identifiers[0]]; - Entity.Spawner.AddToSpawnQueue( + Spawner.AddToSpawnQueue( fuelPrefab, reactorContainer.Inventory, onSpawned: (it) => reactorComponent.PowerUpImmediately()); } @@ -3544,7 +3599,7 @@ namespace Barotrauma foreach (Item item in reactorContainer.Inventory.AllItems) { if (item.NonInteractable) { continue; } - Entity.Spawner.AddToRemoveQueue(item); + Spawner.AddToRemoveQueue(item); } //remove wires @@ -3563,7 +3618,18 @@ namespace Barotrauma } if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.25f) { - Entity.Spawner.AddToRemoveQueue(item); + foreach (Connection connection in wire.Connections) + { + if (connection != null) + { + connection.ConnectionPanel.DisconnectedWires.Add(wire); + wire.RemoveConnection(connection.Item); +#if SERVER + connection.ConnectionPanel.Item.CreateServerEvent(connection.ConnectionPanel); + wire.CreateNetworkEvent(); +#endif + } + } } } @@ -3573,7 +3639,7 @@ namespace Barotrauma if (item.NonInteractable) { continue; } if (Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < 0.5f) { - item.Condition *= Rand.Range(0.2f, 0.6f, Rand.RandSync.Unsynced); + item.Condition *= Rand.Range(0.6f, 0.8f, Rand.RandSync.Unsynced); } } @@ -3617,6 +3683,9 @@ namespace Barotrauma pathPoints.Shuffle(Rand.RandSync.Unsynced); var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse); corpsePoints.Shuffle(Rand.RandSync.Unsynced); + + if (!corpsePoints.Any() && !pathPoints.Any()) { continue; } + int spawnCounter = 0; for (int j = 0; j < corpseCount; j++) { @@ -3706,7 +3775,15 @@ namespace Barotrauma /// public float GetRealWorldDepth(float worldPositionY) { - return (-(worldPositionY - GenerationParams.Height) + LevelData.InitialDepth) * Physics.DisplayToRealWorldRatio; + if (GameMain.GameSession?.Campaign == null) + { + //ensure the levels aren't too deep to traverse in non-campaign modes where you don't have the option to upgrade/switch the sub + return (-(worldPositionY - GenerationParams.Height) + 80000.0f) * Physics.DisplayToRealWorldRatio; + } + else + { + return (-(worldPositionY - GenerationParams.Height) + LevelData.InitialDepth) * Physics.DisplayToRealWorldRatio; + } } public void DebugSetStartLocation(Location newStartLocation) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs index 56f7b6008..8123a7409 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -145,8 +145,11 @@ namespace Barotrauma var rand = new MTRandom(ToolBox.StringToInt(Seed)); InitialDepth = (int)MathHelper.Lerp(GenerationParams.InitialDepthMin, GenerationParams.InitialDepthMax, (float)rand.NextDouble()); + //minimum difficulty of the level before hunting grounds can appear + float huntingGroundsDifficultyThreshold = 25; + //probability of hunting grounds appearing in 100% difficulty levels float maxHuntingGroundsProbability = 0.3f; - HasHuntingGrounds = rand.NextDouble() < Difficulty / 100.0f * maxHuntingGroundsProbability; + HasHuntingGrounds = OriginallyHadHuntingGrounds = rand.NextDouble() < MathUtils.InverseLerp(huntingGroundsDifficultyThreshold, 100.0f, Difficulty) * maxHuntingGroundsProbability; HasBeaconStation = !HasHuntingGrounds && rand.NextDouble() < locationConnection.Locations.Select(l => l.Type.BeaconStationChance).Max(); IsBeaconActive = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs index 943dc6768..3905348a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelGenerationParams.cs @@ -181,6 +181,13 @@ namespace Barotrauma set; } + [Serialize(true, true, "Should the generator force a hole to the bottom of the level to ensure there's a way to the abyss."), Editable] + public bool CreateHoleToAbyss + { + get; + set; + } + [Serialize(1000, true, description: "The total number of level objects (vegetation, vents, etc) in the level."), Editable(MinValueInt = 0, MaxValueInt = 100000)] public int LevelObjectAmount { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs index dcbbb246e..c17029a9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs @@ -361,6 +361,11 @@ namespace Barotrauma if (subElement.Attribute("index") != null) { int locationTypeChangeIndex = subElement.GetAttributeInt("index", 0); + if (locationTypeChangeIndex < 0 || locationTypeChangeIndex >= Type.CanChangeTo.Count) + { + DebugConsole.AddWarning($"Failed to activate a location type change in the location \"{Name}\". Location index out of bounds ({locationTypeChangeIndex})."); + continue; + } PendingLocationTypeChange = (Type.CanChangeTo[locationTypeChangeIndex], timer, null); } else @@ -1059,12 +1064,21 @@ namespace Barotrauma if (PendingLocationTypeChange.Value.parentMission != null) { changeElement.Add(new XAttribute("missionidentifier", PendingLocationTypeChange.Value.parentMission.Identifier)); + locationElement.Add(changeElement); } else { - changeElement.Add(new XAttribute("index", Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange))); + int index = Type.CanChangeTo.IndexOf(PendingLocationTypeChange.Value.typeChange); + changeElement.Add(new XAttribute("index", index)); + if (index == -1) + { + DebugConsole.AddWarning($"Invalid location type change in the location \"{Name}\". Unknown type change ({PendingLocationTypeChange.Value.typeChange.ChangeToType})."); + } + else + { + locationElement.Add(changeElement); + } } - locationElement.Add(changeElement); } if (LocationTypeChangeCooldown > 0) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs index d4a2d57f0..88fe4d6f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs @@ -27,6 +27,8 @@ namespace Barotrauma public readonly float BeaconStationChance; + public readonly CharacterTeamType OutpostTeam; + public readonly List CanChangeTo = new List(); public readonly List MissionIdentifiers = new List(); @@ -90,6 +92,9 @@ namespace Barotrauma ReplaceInRadiation = element.GetAttributeString(nameof(ReplaceInRadiation).ToLower(), ""); + string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC"); + Enum.TryParse(teamStr, out OutpostTeam); + string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt"); try { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index 7530ed96f..efe2c9318 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -316,20 +316,13 @@ namespace Barotrauma if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; } } } - - HashSet connectedLocations = new HashSet(); + foreach (LocationConnection connection in Connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); - - connectedLocations.Add(connection.Locations[0]); - connectedLocations.Add(connection.Locations[1]); } - //remove orphans - Locations.RemoveAll(c => !connectedLocations.Contains(c)); - //remove locations that are too close to each other float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance; for (int i = Locations.Count - 1; i >= 0; i--) @@ -443,6 +436,9 @@ namespace Barotrauma } } + //remove orphans + Locations.RemoveAll(l => !Connections.Any(c => c.Locations.Contains(l))); + foreach (LocationConnection connection in Connections) { connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); @@ -654,10 +650,12 @@ namespace Barotrauma CurrentLocation.CreateStore(); OnLocationChanged?.Invoke(prevLocation, CurrentLocation); - if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.CampaignMetadata is { } metadata) + if (GameMain.GameSession is { Campaign: { CampaignMetadata: { } metadata } }) { metadata.SetValue("campaign.location.id", CurrentLocationIndex); metadata.SetValue("campaign.location.name", CurrentLocation.Name); + metadata.SetValue("campaign.location.biome", CurrentLocation.Biome?.Identifier ?? "null"); + metadata.SetValue("campaign.location.type", CurrentLocation.Type?.Identifier ?? "null"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index d8fa578ea..93077aefb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -185,10 +185,6 @@ namespace Barotrauma { return -WorldPosition.Y * Physics.DisplayToRealWorldRatio; } - else if (GameMain.GameSession?.Campaign == null) - { - return (-(WorldPosition.Y - Level.Loaded.GenerationParams.Height) + 80000.0f) * Physics.DisplayToRealWorldRatio; - } return Level.Loaded.GetRealWorldDepth(WorldPosition.Y); } } @@ -254,7 +250,7 @@ namespace Barotrauma get { if (Level.Loaded == null || subBody == null) { return false; } - return RealWorldDepth > Level.Loaded.RealWorldCrushDepth & RealWorldDepth > RealWorldCrushDepth; + return RealWorldDepth > Level.Loaded.RealWorldCrushDepth && RealWorldDepth > RealWorldCrushDepth; } } @@ -324,6 +320,7 @@ namespace Barotrauma { Info.Type = SubmarineType.Wreck; ShowSonarMarker = false; + DockedTo.ForEach(s => s.ShowSonarMarker = false); PhysicsBody.FarseerBody.BodyType = BodyType.Static; TeamID = CharacterTeamType.None; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 43efe44fc..cb4cb0e85 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -450,12 +450,21 @@ namespace Barotrauma if (GameMain.GameSession?.GameMode is TestGameMode) { return; } #endif if (Level.Loaded == null) { return; } - float submarineDepth = submarine.RealWorldDepth; - if (!Submarine.AtDamageDepth) { return; } + + //camera shake and sounds start playing 500 meters before crush depth + float depthEffectThreshold = 500.0f; + if (Submarine.RealWorldDepth < Level.Loaded.RealWorldCrushDepth - depthEffectThreshold && Submarine.RealWorldDepth < Submarine.RealWorldCrushDepth - depthEffectThreshold) + { + return; + } depthDamageTimer -= deltaTime; if (depthDamageTimer > 0.0f) { return; } +#if CLIENT + SoundPlayer.PlayDamageSound("pressure", Rand.Range(0.0f, 100.0f), submarine.WorldPosition + Rand.Vector(Rand.Range(0.0f, Math.Min(submarine.Borders.Width, submarine.Borders.Height))), 20000.0f); +#endif + foreach (Structure wall in Structure.WallList) { if (wall.Submarine != submarine) { continue; } @@ -463,12 +472,14 @@ namespace Barotrauma float wallCrushDepth = wall.CrushDepth; if (submarine.Info.SubmarineClass == SubmarineClass.DeepDiver) { wallCrushDepth *= 1.2f; } float pastCrushDepth = submarine.RealWorldDepth - wallCrushDepth; - if (pastCrushDepth < 0) { return; } - Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f); + if (pastCrushDepth > 0) + { + Explosion.RangedStructureDamage(wall.WorldPosition, 100.0f, pastCrushDepth * 0.1f, levelWallDamage: 0.0f); + } if (Character.Controlled != null && Character.Controlled.Submarine == submarine) { - GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, Math.Min(pastCrushDepth * 0.001f, 50.0f)); - } + GameMain.GameScreen.Cam.Shake = Math.Max(GameMain.GameScreen.Cam.Shake, MathHelper.Clamp(pastCrushDepth * 0.001f, 1.0f, 50.0f)); + } } depthDamageTimer = 10.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs index 709c4d002..327c43e7f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs @@ -30,7 +30,8 @@ namespace Barotrauma.Networking ERROR, //tell the server that an error occurred CREW, - READY_CHECK + READY_CHECK, + READY_TO_SPAWN } enum ClientNetObject diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index 183c48e38..c3a1ab7a1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -24,7 +24,7 @@ namespace Barotrauma.Networking //items created during respawn //any respawn items left in the shuttle are removed when the shuttle despawns - private List respawnItems = new List(); + private readonly List respawnItems = new List(); public bool UsingShuttle { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index aeb5d17ed..d09effa0e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -713,7 +713,7 @@ namespace Barotrauma public void SetPrevTransform(Vector2 simPosition, float rotation) { -#if DEBUG || UNSTABLE +#if DEBUG if (!IsValidValue(simPosition, "position", -1e10f, 1e10f)) { return; } if (!IsValidValue(rotation, "rotation")) { return; } #endif @@ -756,12 +756,12 @@ namespace Barotrauma Vector2 vel = FarseerBody.LinearVelocity; Vector2 deltaPos = simPosition - (Vector2)pullPos; +#if DEBUG if (deltaPos.LengthSquared() > 100.0f * 100.0f) { -#if DEBUG || UNSTABLE DebugConsole.ThrowError("Attempted to move a physics body to an invalid position.\n" + Environment.StackTrace.CleanupStackTrace()); -#endif } +#endif deltaPos *= force; ApplyLinearImpulse((deltaPos - vel * 0.5f) * FarseerBody.Mass, (Vector2)pullPos); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index e9749833e..174be3390 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1057,12 +1057,11 @@ namespace Barotrauma foreach (Affliction affliction in Afflictions) { if (Rand.Value(Rand.RandSync.Unsynced) > affliction.Probability) { continue; } - Affliction multipliedAffliction = affliction; - if (!disableDeltaTime) + Affliction newAffliction = affliction; + if (!disableDeltaTime && !setValue) { - multipliedAffliction = affliction.CreateMultiplied(deltaTime); + newAffliction = affliction.CreateMultiplied(deltaTime); } - if (target is Character character) { if (character.Removed) { continue; } @@ -1072,7 +1071,7 @@ namespace Barotrauma if (limb.Removed) { continue; } if (limb.IsSevered) { continue; } if (targetLimbs != null && !targetLimbs.Contains(limb.type)) { continue; } - AttackResult result = limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source); + AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); //only apply non-limb-specific afflictions to the first limb if (!affliction.Prefab.LimbSpecific) { break; } @@ -1082,7 +1081,7 @@ namespace Barotrauma { if (limb.IsSevered) { continue; } if (limb.character.Removed || limb.Removed) { continue; } - AttackResult result = limb.character.DamageLimb(position, limb, multipliedAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source); + AttackResult result = limb.character.DamageLimb(position, limb, newAffliction.ToEnumerable(), stun: 0.0f, playSound: false, attackImpulse: 0.0f, attacker: affliction.Source, allowStacking: !setValue); limb.character.TrySeverLimbJoints(limb, SeverLimbsProbability, disableDeltaTime ? result.Damage : result.Damage / deltaTime, allowBeheading: true); } } @@ -1383,7 +1382,7 @@ namespace Barotrauma foreach (Affliction affliction in element.Parent.Afflictions) { Affliction multipliedAffliction = affliction; - if (!element.Parent.disableDeltaTime) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); } + if (!element.Parent.disableDeltaTime && !element.Parent.setValue) { multipliedAffliction = affliction.CreateMultiplied(deltaTime); } if (target is Character character) { diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 9f783fe83..65e1861f5 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index ed317f899..7b67a9531 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,99 @@ +--------------------------------------------------------------------------------------------------------- +v0.1300.0.3 (unstable) +--------------------------------------------------------------------------------------------------------- + +Changes: +- Added respawning mid-round in the multiplayer campaign mode. Respawning gives you an affliction that can be healed for a cost at outposts. You can also choose not to respawn mid-round, and instead wait for the next round to spawn normally without the affliction, just like before. +- Added a distant sonar effect for the abyss monsters. +- Abyss diving suits protect from high pressure, consume oxygen tanks more slowly and reduce movement speed. +- Combat diving suits are less resistant to pressure, protect from damage and reduce movement speed less than the other types of suits. +- Make characters grabbable and the inventories accessible after a half a second delay. Fixes abusing the toy hammer to access NPC inventories. +- Adjustments on incremental stun, stun baton, and stun gun. +- Disabled beacon mission events (beacon missions are now purely optional side objectives). +- Made abandoned outposts a bit more common. +- Hunting grounds don't appear in the first quarter of the campaign map. +- No additional abyss monsters spawn in levels with a hunting grounds mission. +- Secure cabinets in abandoned outposts can now be accessed with the outpost NPCs' ID cards. +- Trying to pick up a diving suit when you're already wearing one swaps the suits. +- Monsters in abandoned outposts don't target the items/structures in the outpost or the sub. +- Made monster eggs glow to make them easier to spot in caves and abandoned outposts. +- Exploding oxygen/fuel tanks don't make other tanks explode. +- Added a delay to oxygen/fuel tank explosions. +- Show a warning on the sonar if the sub is 500 meters or less from crush depth. +- Disconnect wires from beacon stations instead of deleting the wires completely. +- The mid-round mission messages (e.g. "the monster is dead, navigate out...") are shown in the tab menu's mission tab. +- The tab menu can be closed with esc. +- When using an equipped item from a stack (e.g. a medical item), another item from the same stack is automatically equipped. +- Abyss monsters are now less aggressive in hunting other creatures: they shouldn't any longer attack outside of the abyss, unless they are chasing a target that was selected when it was in the abyss. Addresses #5163. +- Display the mission difficulty in the available missions list, the info tab, and the round summary. +- Added hints for falling unconscious, dying, and leaving the sub with an insufficient diving suit. +- Disabled hint stacking: hints are now ignored when there is an active hint on the screen, instead of being queued up when triggered. +- Added a link to the wiki to the main menu. +- Changed the camera animation at the start of the campaign to show the entire outpost. +- Drop empty/used oxygen tank from diving suit when trying to swap in a new one when the inventory is full. +- Added a submarine tab to the in-game tab menu. +- Changed the layout of the in-game tab menu: now the tabs are icons on the left side. + +Fixes: +- Removed duplicate damage modifiers from diving suit. #5204. +- Fixed a draw order issue in Endworm's armor plates (drawn over the head sprite). +- Fixed status effects with "set value" not actually setting the affliction strength but adding to the strength instead. +- Fixed affliction visual effects not working right when the effect's min and max strengths were equal (for example both 1). +- Fixed path unlock tooltips after the 1st biome displaying location reputation instead of coalition reputation. +- Fixed hunting grounds disappearing from the map if you complete the campaign without saving and loading (e.g. by teleporting straight to the end with console commands). +- Fixed path unlocking requiring a reputation _greater_ than the specified value, as opposed to greater or equal. +- Changed how the hole next to the end outpost is generated to prevent the outpost from blocking the hole. +- Fixed islands sometimes blocking the exit tunnels. +- Fixed bugged exit tunnel in the final level. +- Fixed junction boxes being unable to pass signals. +- Fixed locations with no connections sometimes generating on the campaign map. +- Fixed completing a hunting grounds mission not removing the hunting grounds from the map. +- Fixed fabricator's green progress indicator not being displayed when there's already an item in the output slot. +- Fixed fabricator displaying the "not enough room in the input inventory" error even if the items fit in the input inventory by stacking. +- Fixed signal check components being unable to output whitespace. +- Fixed inability to edit smoke detector's parameters in-game. +- Fixed smoke detectors outputting empty signals. +- Fixed "engineers are special" outpost event not giving a price discount. +- Fixed afflictions caused by status effects being multiplied by deltatime even when setvalue="true". The only vanilla statuseffect affected by this was incremental stun, which would stun the player for 1 frame instead of 1 second, but this may have affected some mods as well. +- Fixed errors when trying to load a beacon station with no reactor. +- Fixed terminal's welcome message getting cleared when using the test mode in the sub editor. +- Fixed inability to cut alien walls/hatches. +- Fixed non-ruin artifacts sometimes spawning on top of the sub. +- Fixed melee weapons failing to damage walls if the item is not inside the wall when the impact is registered. +- Allow engine propellers to do damage inside the sub. +- Fixed new lights being displayed as off in the sub editor until you toggle the lighting. +- Fixed stacking keybinds not being shown in the tooltip when hovering over a stack with just 2 items. +- Fixed water flow sounds activating before the particles. +- Fixed shuttles that are a part of a wreck or beacon station being visible on the sonar. +- Fixed all subs/shuttles being hidden on the sonar in PvP (as opposed to just the subs/shuttles of the enemy team). +- Fixed ID cards and duffel bags not being updated when a crew member is renamed. +- Fixed wiring interface label overlapping when using a high text scale. +- Fixed dismissing a player order in the crew list opening the moderation menu in multiplayer. +- Fixed top left buttons (crew list, command interface, info tab) not scaling when adjusting the HUD scale. +- Fixed issues with the hints for running out of oxygen and trying to shoot without aiming. +- Fixed maximum camera zoom increasing on lower resolutions. +- Fixed coilguns consuming ammo faster than they should when linked to multiple loaders. + +Bots: +- Fixed bots failing to fix leaks when they were not swimming (caused by a change in the previous unstable version). #5205. +- Fixed security officers standing idle next to the stunned target and doing nothing when they don't have handcuffs. Only happened while trying to arrest the target. +- Fixed multiple bots occasionally trying to operate the reactor at the same time (#5259). +- Fixed bots "stealing" stuff while cleaning up. Happened when the objective changed (= they decided or were ordered to do something else while cleaning up). Idle bots are no longer allowed to take items from purchased containers. #5138. +- Fixed bots running headlessly around while trying to find the container for items while following the clean up order. Now they should walk around instead and only run when the target container is found. +- Fixes and adjustments to security officer reactions for damage done by friendlies. +- Fixed bots sometimes not moving while targeting the player (caused by a change in v0.1300.0.1, doesn't happen in the release build). +- Bots no longer automatically unequip weapons after combat if they are outside of a friendly submarine. +- Fixed bots having difficulties in leaving the ruins via gaps (#4717). +- Allow bots to use gaps also to exit the sub, but only when following a player character. +- Fixed bots always trying to reach the closest item even when it's unreachable (#3332). +- Make bots find items faster. +- Fixed bots sometimes continuing their movement (e.g. walking towards a wall) when they fail to find the diving gear they need to continue with the go to objective (e.g. follow). + +Modding: +- Changed husk affliction's type to "alieninfection" because using "huskinfection" as both the identifier and type makes it impossible to add custom husk infections and reference just the custom one in conditionals or statuseffects. +- Fixed affliction statuseffects targeting NearbyItems or NearbyCharacters causing a crash. +- Corpses don't spawn in wrecks that contain no waypoints/spawnpoints. + --------------------------------------------------------------------------------------------------------- v0.1300.0.2 (unstable) --------------------------------------------------------------------------------------------------------- @@ -34,6 +130,12 @@ Fixes: - Fixed repair icons appearing in abandoned outposts in multiplayer. - Fixed players getting prompted to download subs when the server has disabled file transfers. - Fixed mineral scanner not working in the abyss. +- Fixed bots sometimes failing to reach targets that are on the ground level. For example when trying to handcuff targets (This change causes the bots not to be able to reach and fix leaks while standing on ground. Now fixed in v0.1300.0.3.) +- Fixed unarmed bots with the fight intruders objective not reacting when attacked. +- Fixed a crash in the monster AI in the sub editor test mode. +- Monsters now ignore beacons. +- Fixed some waypoint issues in abandoned outposts. +- Added missing waypoints to BeaconStation1. There was no waypoints generated, which caused navigation issues with all AI controlled characters. --------------------------------------------------------------------------------------------------------- v0.1300.0.1 (unstable) @@ -54,8 +156,6 @@ Changes: - Fabricators now display remaining time when fabricating. - Endworm now groans when the tail is cut. - Beacon stations' reactors don't deteriorate or consuming fuel to prevent the station from going back down when the player is travelling out from the level. -- Irradiated outposts become abandoned instead of simply disappearing. -- Fabricator displays the time until ready when fabrication is in progress. - The output length of all signal components is now restricted to 200 characters by default to prevent performance and networking issues if the output is set to an excessively long value (such as the entire dialog of the Bee Movie). The limit can be increased in the sub editor. - Stunning still works on friendly characters even if friendly fire has been disabled on a server. - Made radiation sickness's icon appear before it starts causing burns (instead of working the other way around). diff --git a/Barotrauma/BarotraumaShared/hintmanager.xml b/Barotrauma/BarotraumaShared/hintmanager.xml index 43ef39410..422212c3e 100644 --- a/Barotrauma/BarotraumaShared/hintmanager.xml +++ b/Barotrauma/BarotraumaShared/hintmanager.xml @@ -1,6 +1,7 @@  - + + @@ -11,6 +12,8 @@ + + diff --git a/test.xml b/test.xml new file mode 100644 index 000000000..744d86d15 --- /dev/null +++ b/test.xml @@ -0,0 +1 @@ +0.1300.0.2