From 58c50a235d3c6472f098acb16226d6bf2bc8b9d7 Mon Sep 17 00:00:00 2001 From: Markus Isberg <3e849f2e5c@pm.me> Date: Thu, 25 Mar 2021 15:40:24 +0200 Subject: [PATCH] Unstable 0.1300.0.3 --- .../BarotraumaClient/ClientSource/Camera.cs | 122 +++++---- .../ClientSource/Characters/Character.cs | 32 ++- .../ClientSource/Characters/CharacterInfo.cs | 17 +- .../Characters/CharacterNetworking.cs | 3 + .../Characters/Health/CharacterHealth.cs | 2 +- .../ClientSource/DebugConsole.cs | 1 + .../Missions/AbandonedOutpostMission.cs | 14 +- .../ClientSource/Events/Missions/Mission.cs | 15 ++ .../Events/Missions/MissionPrefab.cs | 2 - .../BarotraumaClient/ClientSource/GUI/GUI.cs | 38 ++- .../ClientSource/GUI/GUIListBox.cs | 4 +- .../ClientSource/GUI/GUITextBlock.cs | 6 +- .../ClientSource/GUI/RectTransform.cs | 4 +- .../ClientSource/GUI/TabMenu.cs | 238 +++++++++++++----- .../ClientSource/GUI/UpgradeStore.cs | 148 +++++++---- .../BarotraumaClient/ClientSource/GameMain.cs | 12 +- .../ClientSource/GameSession/CrewManager.cs | 34 +-- .../GameSession/GameModes/CampaignMode.cs | 13 +- .../GameModes/MultiPlayerCampaign.cs | 22 +- .../GameModes/SinglePlayerCampaign.cs | 31 +-- .../GameModes/Tutorials/BasicTutorial.cs | 2 +- .../GameModes/Tutorials/ScenarioTutorial.cs | 2 +- .../ClientSource/GameSession/GameSession.cs | 158 +++++++----- .../ClientSource/GameSession/HintManager.cs | 191 +++++++------- .../ClientSource/GameSession/RoundSummary.cs | 27 +- .../ClientSource/Items/CharacterInventory.cs | 7 - .../Items/Components/Machines/Fabricator.cs | 18 +- .../Items/Components/Machines/Sonar.cs | 71 +++++- .../Items/Components/Machines/Steering.cs | 14 +- .../Items/Components/Signal/Connection.cs | 148 ++++++++--- .../Components/Signal/ConnectionPanel.cs | 36 ++- .../Items/Components/Signal/Terminal.cs | 20 +- .../Items/Components/Signal/Wire.cs | 3 +- .../ClientSource/Items/Inventory.cs | 28 +-- .../ClientSource/Items/Item.cs | 1 + .../BarotraumaClient/ClientSource/Map/Hull.cs | 5 +- .../ClientSource/Map/Lights/LightManager.cs | 9 +- .../ClientSource/Map/Map/Map.cs | 11 +- .../ClientSource/Map/MapEntity.cs | 5 - .../ClientSource/Map/Structure.cs | 5 +- .../ClientSource/Map/SubmarineInfo.cs | 95 ++++--- .../ClientSource/Map/SubmarinePreview.cs | 89 ++++++- .../ClientSource/Networking/ChatMessage.cs | 12 +- .../ClientSource/Networking/GameClient.cs | 66 +++-- .../ClientSource/Screens/CampaignUI.cs | 35 ++- .../ClientSource/Screens/GameScreen.cs | 8 +- .../ClientSource/Screens/MainMenuScreen.cs | 10 + .../ClientSource/Screens/NetLobbyScreen.cs | 4 +- .../ClientSource/Screens/SubEditorScreen.cs | 119 +++++---- .../Serialization/SerializableEntityEditor.cs | 2 +- .../ClientSource/Sounds/OggSound.cs | 5 +- .../ClientSource/Sounds/Sound.cs | 34 +-- .../ClientSource/Sounds/SoundPlayer.cs | 15 +- .../ClientSource/Sounds/VoipSound.cs | 4 +- .../Content/Effects/losshader.xnb | Bin 1397 -> 1470 bytes .../Content/Effects/losshader_opengl.xnb | Bin 1307 -> 1398 bytes .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/Shaders/losshader.fx | 4 +- .../Shaders/losshader_opengl.fx | 4 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../Items/Components/Signal/Terminal.cs | 11 +- .../ServerSource/Networking/Client.cs | 1 + .../ServerSource/Networking/GameServer.cs | 38 ++- .../ServerSource/Networking/RespawnManager.cs | 34 ++- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../SharedSource/CameraTransition.cs | 34 ++- .../Characters/AI/EnemyAIController.cs | 39 ++- .../Characters/AI/HumanAIController.cs | 28 ++- .../Characters/AI/NPCConversation.cs | 7 +- .../AI/Objectives/AIObjectiveCleanupItem.cs | 13 + .../AI/Objectives/AIObjectiveCleanupItems.cs | 37 ++- .../AI/Objectives/AIObjectiveCombat.cs | 36 ++- .../AI/Objectives/AIObjectiveFixLeak.cs | 2 +- .../AI/Objectives/AIObjectiveGetItem.cs | 62 +++-- .../AI/Objectives/AIObjectiveGoTo.cs | 56 +++-- .../AI/Objectives/AIObjectiveIdle.cs | 14 +- .../AI/Objectives/AIObjectiveManager.cs | 2 +- .../AI/Objectives/AIObjectiveOperateItem.cs | 10 +- .../SharedSource/Characters/Character.cs | 52 ++-- .../SharedSource/Characters/CharacterInfo.cs | 14 ++ .../Health/Afflictions/Affliction.cs | 75 ++++-- .../Health/Afflictions/AfflictionPrefab.cs | 11 +- .../Characters/Health/CharacterHealth.cs | 48 +++- .../Characters/Params/CharacterParams.cs | 3 + .../SharedSource/DebugConsole.cs | 113 ++++++++- .../Events/EventActions/SpawnAction.cs | 8 +- .../Events/EventActions/TagAction.cs | 33 ++- .../SharedSource/Events/EventManager.cs | 13 +- .../SharedSource/Events/EventSet.cs | 4 +- .../Missions/AbandonedOutpostMission.cs | 17 +- .../Events/Missions/CombatMission.cs | 19 +- .../SharedSource/Events/Missions/Mission.cs | 8 + .../Events/Missions/MissionPrefab.cs | 7 + .../Events/Missions/MonsterMission.cs | 2 +- .../SharedSource/GameSession/CrewManager.cs | 4 +- .../GameSession/Data/CampaignMetadata.cs | 2 +- .../GameSession/GameModes/CampaignMode.cs | 47 ++-- .../SharedSource/GameSession/GameSession.cs | 1 + .../GameSession/UpgradeManager.cs | 12 +- .../SharedSource/Items/CharacterInventory.cs | 51 ++++ .../Items/Components/Holdable/MeleeWeapon.cs | 1 + .../Items/Components/Machines/Engine.cs | 2 +- .../Items/Components/Machines/Fabricator.cs | 5 +- .../Items/Components/Machines/Reactor.cs | 3 +- .../Items/Components/Power/PowerTransfer.cs | 2 +- .../Components/Signal/ConnectionPanel.cs | 4 +- .../Components/Signal/SignalCheckComponent.cs | 2 +- .../Items/Components/Signal/SmokeDetector.cs | 3 +- .../Items/Components/Signal/Terminal.cs | 11 +- .../Items/Components/Signal/Wire.cs | 7 + .../SharedSource/Items/Components/Turret.cs | 37 ++- .../SharedSource/Items/Inventory.cs | 18 +- .../SharedSource/Items/Item.cs | 9 +- .../SharedSource/Items/ItemPrefab.cs | 3 + .../Map/Creatures/BallastFloraBehavior.cs | 2 +- .../Map/Creatures/State/GrowIdleState.cs | 4 +- .../SharedSource/Map/Explosion.cs | 6 +- .../SharedSource/Map/Levels/Level.cs | 155 +++++++++--- .../SharedSource/Map/Levels/LevelData.cs | 5 +- .../Map/Levels/LevelGenerationParams.cs | 7 + .../SharedSource/Map/Map/Location.cs | 18 +- .../SharedSource/Map/Map/LocationType.cs | 5 + .../SharedSource/Map/Map/Map.cs | 16 +- .../SharedSource/Map/Submarine.cs | 7 +- .../SharedSource/Map/SubmarineBody.cs | 23 +- .../SharedSource/Networking/NetworkMember.cs | 3 +- .../SharedSource/Networking/RespawnManager.cs | 2 +- .../SharedSource/Physics/PhysicsBody.cs | 6 +- .../StatusEffects/StatusEffect.cs | 13 +- .../BarotraumaShared/Submarines/Humpback.sub | Bin 206186 -> 41624 bytes Barotrauma/BarotraumaShared/changelog.txt | 104 +++++++- Barotrauma/BarotraumaShared/hintmanager.xml | 5 +- test.xml | 1 + 136 files changed, 2486 insertions(+), 1008 deletions(-) create mode 100644 test.xml 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 b17c38ef3a9dbe6c9888f48bff8a4923c48f1162..7f09d5fe633c65a2edafa7e0eeb973a1442cc946 100644 GIT binary patch delta 356 zcmey$wU3)U!q2Ikm0{mR_Lc;FRt5%Ncee-*#)A8aPZ${#7?>Cs7#IbB)BzyNCBn)1 z+V-L&Y1hr|t}Vati($vg?~FhNAUPHwZ2-gqKmijVzXymN^Ybik0L4IB-eh8k|7m8SH?XJSXpEv6=jWDU_Xwfe|RkyxE2M eGNU*P12boZPkymuPC-T@P#H)$5KNX}jRgRw$wS%z delta 260 zcmdnT{gsP7!q2Ikm7#PZdrRyy76t}icee-*#%G`3IWaN_FfcMOFfcJpU}9i!iEwhB z@vmXj?lD(C`biwyKC6tuyrjr1yjhH^p#LGmCLNTn87+F)`olM!n%}<$bFp9G3%yITYYgOMN0A4Y+K;`q|M%(VQX+~WAM)MOI@ z21W)31||k^rpX4(QWM{BOuXeGY8Y$H%axp;S6re1QlkLWlx#TJoH2Uxc}B6xrx`Da z8R(TO*ed7(C5jC6DipM#N-i-eF&a$%%B0D|$iT?RzzUQvHe}kY#e9R2k$JKtYb*c( CJuHj> 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 9f783fe8329b085c7c4770ac8a81bc75a002d474..65e1861f521ff82ee3d09e1fcf4737f2d975bcbb 100644 GIT binary patch literal 41624 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 z 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%0A7lFjJs``tzj(TF2G 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%0D>MMnD(|y0Zu~XU`ejjGJ1TFB>G%fJ9 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
    5;)n(U=H0F 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