From 6b55adcdd9c735f310ed05ae5a7a53c92983c0af Mon Sep 17 00:00:00 2001 From: Regalis11 Date: Wed, 20 Jul 2022 18:47:07 +0300 Subject: [PATCH] v0.19.0.0 (unstable) --- .../ClientSource/CameraTransition.cs | 2 +- .../Characters/AI/EnemyAIController.cs | 5 + .../Characters/Animation/Ragdoll.cs | 91 +++-- .../ClientSource/Characters/Character.cs | 38 +- .../ClientSource/Characters/CharacterHUD.cs | 22 +- .../ClientSource/Characters/CharacterInfo.cs | 36 +- .../Characters/CharacterNetworking.cs | 37 +- .../Characters/Health/CharacterHealth.cs | 24 +- .../ClientSource/Characters/Limb.cs | 2 +- .../ClientSource/DebugConsole.cs | 63 +++- .../Events/EventActions/ConversationAction.cs | 4 +- .../ClientSource/GUI/CrewManagement.cs | 2 +- .../BarotraumaClient/ClientSource/GUI/GUI.cs | 27 +- .../ClientSource/GUI/GUIContextMenu.cs | 25 +- .../ClientSource/GUI/GUIDragHandle.cs | 75 ++++ .../ClientSource/GUI/GUIListBox.cs | 6 +- .../ClientSource/GUI/GUIMessageBox.cs | 3 +- .../ClientSource/GUI/HUDLayoutSettings.cs | 8 + .../ClientSource/GUI/TabMenu.cs | 235 ++++++------ .../ClientSource/GUI/UpgradeStore.cs | 24 +- .../BarotraumaClient/ClientSource/GameMain.cs | 10 +- .../ClientSource/GameSession/CrewManager.cs | 4 +- .../ClientSource/GameSession/GameMode.cs | 6 +- .../GameModes/MultiPlayerCampaign.cs | 21 +- .../GameModes/SinglePlayerCampaign.cs | 30 +- .../GameModes/Tutorials/CaptainTutorial.cs | 12 +- .../GameModes/Tutorials/DoctorTutorial.cs | 14 +- .../GameModes/Tutorials/EngineerTutorial.cs | 6 +- .../GameModes/Tutorials/MechanicTutorial.cs | 4 +- .../GameModes/Tutorials/OfficerTutorial.cs | 2 +- .../ClientSource/GameSession/GameSession.cs | 8 + .../ClientSource/GameSession/HintManager.cs | 26 +- .../ClientSource/Items/CharacterInventory.cs | 19 +- .../Items/Components/Holdable/IdCard.cs | 5 +- .../Items/Components/ItemComponent.cs | 25 +- .../Items/Components/ItemContainer.cs | 13 +- .../Components/Machines/Deconstructor.cs | 227 ++++++++++-- .../Items/Components/Machines/MiniMap.cs | 1 + .../Components/Machines/OutpostTerminal.cs | 2 +- .../Items/Components/Machines/Steering.cs | 8 +- .../Items/Components/Repairable.cs | 5 +- .../Components/Signal/ConnectionPanel.cs | 14 +- .../Items/Components/Signal/Wire.cs | 2 +- .../ClientSource/Items/Inventory.cs | 49 +-- .../ClientSource/Items/Item.cs | 15 +- .../ClientSource/Items/ItemInventory.cs | 5 +- .../ClientSource/Map/MapEntity.cs | 18 +- .../ClientSource/Networking/GameClient.cs | 6 +- .../Networking/Voip/VoipCapture.cs | 7 +- .../Networking/Voip/VoipClient.cs | 7 +- .../ClientSource/Particles/ParticleManager.cs | 6 +- .../ClientSource/PlayerInput.cs | 4 +- .../ClientSource/Screens/CampaignUI.cs | 186 ---------- .../CharacterEditor/CharacterEditorScreen.cs | 4 +- .../ClientSource/Screens/GameScreen.cs | 49 ++- .../ClientSource/Screens/MainMenuScreen.cs | 9 +- .../ClientSource/Screens/Screen.cs | 4 +- .../ClientSource/Screens/SubEditorScreen.cs | 67 ++-- .../ClientSource/Screens/TestScreen.cs | 57 +-- .../Serialization/SerializableEntityEditor.cs | 4 +- .../ClientSource/Settings/SettingsMenu.cs | 55 ++- .../ClientSource/Sounds/SoundManager.cs | 2 +- .../ClientSource/Sprite/Sprite.cs | 10 +- .../ClientSource/Steam/Lobby.cs | 4 +- .../WorkshopMenu/Mutable/InstalledTab.cs | 5 +- .../Steam/WorkshopMenu/Mutable/ItemList.cs | 3 + .../Mutable/MutableWorkshopMenu.cs | 6 +- .../Utils/LocalizationCSVtoXML.cs | 264 +++++++++----- .../BarotraumaClient/LinuxClient.csproj | 4 +- Barotrauma/BarotraumaClient/MacClient.csproj | 4 +- .../BarotraumaClient/WindowsClient.csproj | 4 +- .../BarotraumaServer/LinuxServer.csproj | 4 +- Barotrauma/BarotraumaServer/MacServer.csproj | 4 +- .../ServerSource/Characters/CharacterInfo.cs | 12 +- .../Characters/CharacterNetworking.cs | 11 +- .../Events/EventActions/ConversationAction.cs | 10 +- .../ServerSource/Events/EventManager.cs | 18 +- .../BarotraumaServer/ServerSource/GameMain.cs | 2 +- .../ServerSource/GameSession/Data/Wallet.cs | 11 + .../GameModes/MultiPlayerCampaign.cs | 21 +- .../ServerSource/Networking/Client.cs | 4 + .../ServerSource/Networking/GameServer.cs | 13 +- .../ServerSource/Networking/KarmaManager.cs | 4 +- .../ServerSource/Networking/RespawnManager.cs | 16 +- .../ServerSource/Networking/Voting.cs | 20 +- .../BarotraumaServer/ServerSource/Program.cs | 6 +- .../BarotraumaServer/WindowsServer.csproj | 4 +- .../ModLists/Release checklist mods.xml | 16 + .../Characters/AI/HumanAIController.cs | 8 +- .../Characters/AI/IndoorsSteeringManager.cs | 98 ++--- .../Characters/AI/NPCConversation.cs | 16 +- .../AI/Objectives/AIObjectiveCombat.cs | 17 + .../AI/Objectives/AIObjectiveGoTo.cs | 9 +- .../AI/Objectives/AIObjectiveIdle.cs | 9 +- .../AI/Objectives/AIObjectiveOperateItem.cs | 2 +- .../AI/Objectives/AIObjectiveRepairItem.cs | 10 +- .../AI/Objectives/AIObjectiveRepairItems.cs | 4 +- .../SharedSource/Characters/AI/PathFinder.cs | 66 +--- .../Characters/AI/SteeringPath.cs | 2 + .../Characters/Animation/AnimController.cs | 56 ++- .../Animation/HumanoidAnimController.cs | 83 +++-- .../Characters/Animation/Ragdoll.cs | 5 +- .../SharedSource/Characters/Character.cs | 342 +++++++++++------- .../Characters/CharacterEventData.cs | 9 + .../SharedSource/Characters/CharacterInfo.cs | 56 ++- .../Characters/CharacterNetworking.cs | 13 +- .../SharedSource/Characters/CorpsePrefab.cs | 2 +- .../Characters/Health/CharacterHealth.cs | 18 +- .../SharedSource/Characters/HumanPrefab.cs | 58 ++- .../SharedSource/Characters/Jobs/Job.cs | 11 +- .../SharedSource/Characters/Jobs/JobPrefab.cs | 2 +- .../SharedSource/Characters/Limb.cs | 11 + .../Characters/NPCPersonalityTrait.cs | 40 +- .../CharacterAbilityTandemFire.cs | 8 +- .../ContentFile/NPCPersonalityTraitsFile.cs | 12 + .../SharedSource/CoroutineManager.cs | 20 +- .../SharedSource/DebugConsole.cs | 53 ++- .../Events/EventActions/ConversationAction.cs | 12 +- .../Events/EventActions/SpawnAction.cs | 2 +- .../Events/EventActions/TriggerAction.cs | 5 +- .../SharedSource/Events/EventManager.cs | 119 +++--- .../SharedSource/Events/EventSet.cs | 26 +- .../Events/Missions/EscortMission.cs | 31 +- .../SharedSource/Events/Missions/Mission.cs | 2 +- .../Events/Missions/MonsterMission.cs | 26 +- .../SharedSource/Events/MonsterEvent.cs | 5 +- .../GameAnalytics/GameAnalyticsManager.cs | 21 +- .../SharedSource/GameSession/CargoManager.cs | 7 +- .../GameSession/Data/CampaignMetadata.cs | 21 +- .../GameSession/GameModes/CampaignMode.cs | 42 ++- .../SharedSource/GameSession/GameSession.cs | 2 +- .../GameSession/UpgradeManager.cs | 16 +- .../SharedSource/InputType.cs | 2 +- .../Items/Components/Holdable/Holdable.cs | 18 + .../Items/Components/Holdable/MeleeWeapon.cs | 18 +- .../Items/Components/Holdable/Pickable.cs | 6 +- .../Items/Components/Holdable/RepairTool.cs | 14 +- .../Items/Components/ItemContainer.cs | 16 +- .../SharedSource/Items/Components/Ladder.cs | 3 +- .../Items/Components/Machines/Controller.cs | 104 +++--- .../Components/Machines/Deconstructor.cs | 9 + .../Items/Components/Machines/Fabricator.cs | 14 +- .../Items/Components/Machines/Reactor.cs | 6 +- .../Items/Components/Machines/Steering.cs | 8 +- .../Items/Components/Power/PowerContainer.cs | 3 - .../Items/Components/Projectile.cs | 15 +- .../SharedSource/Items/Components/Quality.cs | 2 +- .../Items/Components/Repairable.cs | 6 +- .../Components/Signal/ConnectionPanel.cs | 15 +- .../Items/Components/Signal/Terminal.cs | 2 +- .../Items/Components/Signal/Wire.cs | 6 +- .../SharedSource/Items/Components/Turret.cs | 2 +- .../SharedSource/Items/Item.cs | 38 +- .../SharedSource/Map/Levels/Level.cs | 15 +- .../SharedSource/Map/Levels/LevelData.cs | 7 +- .../SharedSource/Map/Map/Map.cs | 7 +- .../SharedSource/Map/Outposts/NPCSet.cs | 2 +- .../Map/Outposts/OutpostGenerationParams.cs | 2 +- .../Map/Outposts/OutpostGenerator.cs | 6 +- .../SharedSource/Map/RoundEndCinematic.cs | 126 ------- .../SharedSource/Map/Submarine.cs | 8 - .../SharedSource/Map/SubmarineBody.cs | 14 +- .../SharedSource/Map/WayPoint.cs | 71 ++-- .../SharedSource/Networking/ServerSettings.cs | 7 + .../SharedSource/Screens/GameScreen.cs | 6 +- .../SharedSource/Screens/Screen.cs | 1 + .../SharedSource/Settings/GameSettings.cs | 27 +- .../StatusEffects/StatusEffect.cs | 84 ++++- Barotrauma/BarotraumaShared/changelog.txt | 107 +++++- .../BarotraumaShared/serversettings.xml | 2 +- 170 files changed, 2769 insertions(+), 1634 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs create mode 100644 Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml create mode 100644 Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCPersonalityTraitsFile.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Map/RoundEndCinematic.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/CameraTransition.cs b/Barotrauma/BarotraumaClient/ClientSource/CameraTransition.cs index 77339f0b1..ed7ff67d7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/CameraTransition.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/CameraTransition.cs @@ -159,7 +159,7 @@ namespace Barotrauma GameMain.LightManager.LosEnabled = true; } #endif - timer += CoroutineManager.UnscaledDeltaTime; + timer += CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs index bfb8b7202..c43d3f26b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs @@ -73,6 +73,11 @@ namespace Barotrauma } GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black); + if (State == AIState.Attack && selectedTargetingParams != null && selectedTargetingParams.AttackPattern == AttackPattern.Circle) + { + GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 100.0f, CirclePhase.ToString(), stateColor, Color.Black); + } + if (LatchOntoAI != null && (State == AIState.Idle || LatchOntoAI.IsAttachedToSub)) { foreach (Joint attachJoint in LatchOntoAI.AttachJoints) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index 5e315ee8f..dc50608c8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -1,15 +1,14 @@ -using Barotrauma.Items.Components; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Barotrauma.Particles; using Barotrauma.SpriteDeformations; -using Barotrauma.Extensions; using FarseerPhysics; using FarseerPhysics.Dynamics; -using FarseerPhysics.Dynamics.Joints; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; -using System.Linq; using System.Collections.Generic; -using Barotrauma.Particles; +using System.Linq; namespace Barotrauma { @@ -55,21 +54,34 @@ namespace Barotrauma if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed) { - character.SelectedConstruction = null; + character.SelectedItem = null; } - else + else if (character.SelectedItem != character.MemState[0].SelectedItem) { - if (character.SelectedConstruction != character.MemState[0].SelectedItem) + foreach (var ic in character.MemState[0].SelectedItem.Components) { - foreach (var ic in character.MemState[0].SelectedItem.Components) + if (ic.CanBeSelected) { - if (ic.CanBeSelected) - { - ic.Select(character); - } + ic.Select(character); } } - character.SelectedConstruction = character.MemState[0].SelectedItem; + character.SelectedItem = character.MemState[0].SelectedItem; + } + + if (character.MemState[0].SelectedSecondaryItem == null || character.MemState[0].SelectedSecondaryItem.Removed) + { + character.SelectedSecondaryItem = null; + } + else if (character.SelectedSecondaryItem != character.MemState[0].SelectedSecondaryItem) + { + foreach (var ic in character.MemState[0].SelectedSecondaryItem.Components) + { + if (ic.CanBeSelected) + { + ic.Select(character); + } + } + character.SelectedSecondaryItem = character.MemState[0].SelectedSecondaryItem; } if (character.MemState[0].Animation == AnimController.Animation.CPR) @@ -201,15 +213,24 @@ namespace Barotrauma { if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed) { - character.SelectedConstruction = null; + character.SelectedItem = null; } - else if (serverPos.SelectedItem != null) + else if (character.SelectedItem != serverPos.SelectedItem) { - if (character.SelectedConstruction != serverPos.SelectedItem) - { - serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true); - } - character.SelectedConstruction = serverPos.SelectedItem; + serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true); + character.SelectedItem = serverPos.SelectedItem; + } + } + if (localPos.SelectedSecondaryItem != serverPos.SelectedSecondaryItem) + { + if (serverPos.SelectedSecondaryItem == null || serverPos.SelectedSecondaryItem.Removed) + { + character.SelectedSecondaryItem = null; + } + else if (character.SelectedSecondaryItem != serverPos.SelectedSecondaryItem) + { + serverPos.SelectedSecondaryItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true); + character.SelectedSecondaryItem = serverPos.SelectedSecondaryItem; } } @@ -496,12 +517,11 @@ namespace Barotrauma float maxDepth = 0.0f; float minDepth = 1.0f; float depthOffset = 0.0f; - var ladder = character.SelectedConstruction?.GetComponent(); - - if (ladder != null) + + if (character.SelectedSecondaryItem?.GetComponent() is Ladder ladder) { CalculateLimbDepths(); - if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X) + if (character.WorldPosition.X < character.SelectedSecondaryItem.WorldPosition.X) { //at the left side of the ladder, needs to be drawn in front of the rungs if (maxDepth > ladder.BackgroundSpriteDepth) @@ -522,16 +542,21 @@ namespace Barotrauma else { CalculateLimbDepths(); - var controller = character.SelectedConstruction?.GetComponent(); - if (controller != null && controller.ControlCharacterPose && controller.User == character && controller.UserInCorrectPosition) + AdjustDepthOffset(character.SelectedItem); + AdjustDepthOffset(character.SelectedSecondaryItem); + + void AdjustDepthOffset(Item item) { - if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind) + if (item?.GetComponent() is { ControlCharacterPose: true, UserInCorrectPosition: true } controller && controller.User == character) { - depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth); - } - else - { - depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f); + if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind) + { + depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth); + } + else + { + depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f); + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 3c015ee76..99557594c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -9,7 +9,6 @@ using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; using Barotrauma.Extensions; namespace Barotrauma @@ -323,8 +322,8 @@ namespace Barotrauma { cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected * item.OffsetOnSelectedMultiplier; } - else if (SelectedConstruction != null && ViewTarget == null && - SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this))) + else if (SelectedItem != null && ViewTarget == null && + SelectedItem.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this))) { cam.OffsetAmount = targetOffsetAmount = 0.0f; cursorPosition = @@ -368,21 +367,20 @@ namespace Barotrauma if (!GUI.InputBlockingMenuOpen) { - if (SelectedConstruction != null && - (SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) || + if (SelectedItem != null && + (SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) || ((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))) { if (GameMain.Client != null) { - //emulate a Select input to get the character to deselect the item server-side - //keys[(int)InputType.Select].Hit = true; - keys[(int)InputType.Deselect].Hit = true; + //emulate a Deselect input to get the character to deselect the item server-side + EmulateInput(InputType.Deselect); } //reset focus to prevent us from accidentally interacting with another entity focusedItem = null; FocusedCharacter = null; findFocusedTimer = 0.2f; - SelectedConstruction = null; + SelectedItem = null; } } @@ -425,6 +423,11 @@ namespace Barotrauma } } } + + public void EmulateInput(InputType input) + { + keys[(int)input].Hit = true; + } partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun) { @@ -518,7 +521,7 @@ namespace Barotrauma //reduce the amount of aim assist if an item has been selected //= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item //otherwise it would be too easy to accidentally switch the selected item when rewiring items - float aimAssistAmount = SelectedConstruction == null ? 100.0f * aimAssistModifier : 1.0f; + float aimAssistAmount = SelectedItem == null ? 100.0f * aimAssistModifier : 1.0f; Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition); @@ -623,12 +626,12 @@ namespace Barotrauma { if (this != controlled) { return false; } if (GameMain.GameSession?.Campaign != null && GameMain.GameSession.Campaign.ShowCampaignUI) { return true; } - var controller = SelectedConstruction?.GetComponent(); + var controller = SelectedItem?.GetComponent(); //lock if using a controller, except if we're also using a connection panel in the same item return - SelectedConstruction != null && + SelectedItem != null && controller?.User == this && controller.HideHUD && - SelectedConstruction?.GetComponent()?.User != this; + SelectedItem?.GetComponent()?.User != this; } @@ -900,7 +903,14 @@ namespace Barotrauma if (info != null) { LocalizedString name = Info.DisplayName; - if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); } + if (controlled == null && name != Info.Name) + { + name += " " + TextManager.Get("Disguised"); + } + else if (Info.Title != null) + { + name += '\n' + Info.Title; + } Vector2 nameSize = GUIStyle.Font.MeasureString(name); Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs index 02e09c587..323184f04 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs @@ -108,7 +108,7 @@ namespace Barotrauma private static bool ShouldDrawInventory(Character character) { - var controller = character.SelectedConstruction?.GetComponent(); + var controller = character.SelectedItem?.GetComponent(); return character?.Inventory != null && @@ -417,13 +417,17 @@ namespace Barotrauma visibleRange = new Range(-100f, 500f); } + float dist = Vector2.Distance(character.WorldPosition, npc.WorldPosition); + float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist); + float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor); GUI.DrawIndicator( spriteBatch, npc.WorldPosition, cam, visibleRange, iconStyle.GetDefaultSprite(), - iconStyle.Color); + iconStyle.Color * alpha, + label: npc.Info?.Title); } foreach (Item item in Item.ItemList) @@ -436,10 +440,10 @@ namespace Barotrauma } } - if (character.SelectedConstruction != null && - (character.CanInteractWith(character.SelectedConstruction) || Screen.Selected == GameMain.SubEditorScreen)) + if (character.SelectedItem != null && + (character.CanInteractWith(character.SelectedItem) || Screen.Selected == GameMain.SubEditorScreen)) { - character.SelectedConstruction.DrawHUD(spriteBatch, cam, character); + character.SelectedItem.DrawHUD(spriteBatch, cam, character); } if (character.Inventory != null) { @@ -561,9 +565,15 @@ namespace Barotrauma Color nameColor = character.FocusedCharacter.GetNameColor(); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); - textPos.X += 10.0f * GUI.Scale; textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y; + if (character.FocusedCharacter.Info?.Title != null && !character.FocusedCharacter.Info.Title.IsNullOrEmpty()) + { + GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); + textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y; + } + textPos.X += 10.0f * GUI.Scale; + if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet) { GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use), diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs index 305031f47..290746f93 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterInfo.cs @@ -102,7 +102,7 @@ namespace Barotrauma if (PersonalityTrait != null) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), - TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + PersonalityTrait.Name.Replace(" ".ToIdentifier(), "".ToIdentifier()))), + TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName), font: font) { Padding = Vector4.Zero @@ -523,22 +523,20 @@ namespace Barotrauma Color facialHairColor = inc.ReadColorR8G8B8(); string ragdollFile = inc.ReadString(); - string jobIdentifier = inc.ReadString(); + uint jobIdentifier = inc.ReadUInt32(); int variant = inc.ReadByte(); JobPrefab jobPrefab = null; Dictionary skillLevels = new Dictionary(); - if (!string.IsNullOrEmpty(jobIdentifier)) - { - jobPrefab = JobPrefab.Get(jobIdentifier); - byte skillCount = inc.ReadByte(); - for (int i = 0; i < skillCount; i++) + if (jobIdentifier > 0) + { + jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier); + foreach (SkillPrefab skillPrefab in jobPrefab.Skills.OrderBy(s => s.Identifier)) { - Identifier skillIdentifier = inc.ReadIdentifier(); float skillLevel = inc.ReadSingle(); - skillLevels.Add(skillIdentifier, skillLevel); - } - } + skillLevels.Add(skillPrefab.Identifier, skillLevel); + } + } // TODO: animations CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant) @@ -777,7 +775,21 @@ namespace Barotrauma createColorSelector($"Customization.{nameof(info.Head.SkinColor)}".ToIdentifier(), info.SkinColors, () => info.Head.SkinColor, (color) => info.Head.SkinColor = color); - +#if DEBUG + new GUIButton(new RectTransform(Vector2.One * 0.12f, + parentComponent.RectTransform, + anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest) + { RelativeOffset = new Vector2(0.01f, 0.005f) }, style: "SaveButton", color: Color.Magenta) + { + ToolTip = "DEBUG ONLY: copy the character info XML to clipboard", + OnClicked = (button, o) => + { + XElement element = info.Save(null); + Clipboard.SetText(element.ToString()); + return false; + } + }; +#endif RandomizeButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, parentComponent.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs index 49a1eb6d8..49292f426 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs @@ -1,5 +1,4 @@ -using Barotrauma.Extensions; -using Barotrauma.Items.Components; +using Barotrauma.Items.Components; using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; @@ -44,7 +43,8 @@ namespace Barotrauma LastNetworkUpdateID, AnimController.TargetDir, SelectedCharacter, - SelectedConstruction, + SelectedItem, + SelectedSecondaryItem, AnimController.Anim); memLocalState.Add(posInfo); @@ -219,15 +219,17 @@ namespace Barotrauma bool entitySelected = msg.ReadBoolean(); Character selectedCharacter = null; - Item selectedItem = null; + Item selectedItem = null, selectedSecondaryItem = null; AnimController.Animation animation = AnimController.Animation.None; if (entitySelected) { ushort characterID = msg.ReadUInt16(); ushort itemID = msg.ReadUInt16(); + ushort secondaryItemID = msg.ReadUInt16(); selectedCharacter = FindEntityByID(characterID) as Character; selectedItem = FindEntityByID(itemID) as Item; + selectedSecondaryItem = FindEntityByID(secondaryItemID) as Item; if (characterID != NullEntityID) { bool doingCpr = msg.ReadBoolean(); @@ -274,7 +276,7 @@ namespace Barotrauma pos, rotation, networkUpdateID, facingRight ? Direction.Right : Direction.Left, - selectedCharacter, selectedItem, animation); + selectedCharacter, selectedItem, selectedSecondaryItem, animation); while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID)) index++; @@ -286,7 +288,7 @@ namespace Barotrauma pos, rotation, linearVelocity, angularVelocity, sendingTime, facingRight ? Direction.Right : Direction.Left, - selectedCharacter, selectedItem, animation); + selectedCharacter, selectedItem, selectedSecondaryItem, animation); while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp) index++; @@ -375,9 +377,15 @@ namespace Barotrauma if (attackLimbIndex == 255 || Removed) { break; } if (attackLimbIndex >= AnimController.Limbs.Length) { - string errorMsg = $"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})"; - DebugConsole.ThrowError(errorMsg); - GameAnalyticsManager.AddErrorEventOnce("Character.ClientEventRead:AttackLimbOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + //it's possible to get these errors when mid-round syncing, as the client may not + //yet know about afflictions that have given the character extra limbs (e.g. spineling genes) + //ignoring the error should be safe though, not executing the attack should not cause any further issues + if (!GameMain.Client.MidRoundSyncing) + { + string errorMsg = $"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})"; + DebugConsole.ThrowError(errorMsg); + GameAnalyticsManager.AddErrorEventOnce("Character.ClientEventRead:AttackLimbOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + } break; } Limb attackLimb = AnimController.Limbs[attackLimbIndex]; @@ -673,16 +681,17 @@ namespace Barotrauma AfflictionPrefab causeOfDeathAffliction = null; if (causeOfDeathType == CauseOfDeathType.Affliction) { - string afflictionName = msg.ReadString(); - if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionName)) + uint afflictionId = msg.ReadUInt32(); + AfflictionPrefab afflictionPrefab = AfflictionPrefab.Prefabs.Find(p => p.UintIdentifier == afflictionId); + if (afflictionPrefab == null) { - string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})"; + string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found (id {afflictionId})"; causeOfDeathType = CauseOfDeathType.Unknown; - GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); } else { - causeOfDeathAffliction = AfflictionPrefab.Prefabs[afflictionName]; + causeOfDeathAffliction = afflictionPrefab; } } bool containsAfflictionData = msg.ReadBoolean(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs index 48e87f489..19dbd141f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Health/CharacterHealth.cs @@ -162,11 +162,7 @@ namespace Barotrauma openHealthWindow.characterName.Text = value.Character.Info.DisplayName; value.Character.Info.CheckDisguiseStatus(false); } - - if (Character.Controlled.SelectedConstruction != null && Character.Controlled.SelectedConstruction.GetComponent() == null) - { - Character.Controlled.SelectedConstruction = null; - } + Character.Controlled.SelectedItem = null; } HintManager.OnShowHealthInterface(); @@ -724,7 +720,7 @@ namespace Barotrauma //emulate a Health input to get the character to deselect the item server-side if (GameMain.Client != null) { - Character.Controlled.Keys[(int)InputType.Health].Hit = true; + Character.Controlled.EmulateInput(InputType.Health); } OpenHealthWindow = null; } @@ -2014,7 +2010,7 @@ namespace Barotrauma FaceTint = DefaultFaceTint; BodyTint = Color.TransparentBlack; - if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; } + if (!Character.Params.Health.ApplyAfflictionColors) { return; } foreach (KeyValuePair kvp in afflictions) { @@ -2031,15 +2027,21 @@ namespace Barotrauma foreach (Limb limb in Character.AnimController.Limbs) { if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; } - limb.BurnOverlayStrength = 0.0f; limb.DamageOverlayStrength = 0.0f; foreach (KeyValuePair kvp in afflictions) { - if (kvp.Value != limbHealths[limb.HealthIndex]) { continue; } var affliction = kvp.Key; - limb.BurnOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha; - limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha; + float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha; + if (kvp.Value == limbHealths[limb.HealthIndex]) + { + limb.BurnOverlayStrength += burnStrength; + limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha; + } + else + { + limb.BurnOverlayStrength += burnStrength / 2; + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index 7fbcef8fe..4a8c36df7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -481,7 +481,7 @@ namespace Barotrauma { ContentPath texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage) - ?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath()); + ?? ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath()); path = GetSpritePath(texturePath); } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 1bee53b41..2eee53707 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -1,21 +1,18 @@ -using Barotrauma.Items.Components; +using Barotrauma.ClientSource.Settings; +using Barotrauma.Extensions; +using Barotrauma.IO; +using Barotrauma.Items.Components; +using Barotrauma.MapCreatures.Behavior; using Barotrauma.Networking; +using Barotrauma.Steam; using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; -using Barotrauma.IO; +using System.Globalization; using System.Linq; using System.Text; using System.Xml.Linq; -using System.Globalization; -using FarseerPhysics; -using Barotrauma.Extensions; -using Barotrauma.Steam; -using System.Threading.Tasks; -using Barotrauma.ClientSource.Settings; -using Barotrauma.MapCreatures.Behavior; using static Barotrauma.FabricationRecipe; namespace Barotrauma @@ -1299,7 +1296,7 @@ namespace Barotrauma int? fabricationCost = null; int? deconstructProductCost = null; - var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab); + var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab && f.RequiredItems.Any()); if (fabricationRecipe != null) { foreach (var ingredient in fabricationRecipe.RequiredItems) @@ -1334,6 +1331,21 @@ namespace Barotrauma if (fabricationRecipe != null) { var ingredient = fabricationRecipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem)); + + if (ingredient == null) + { + foreach (var requiredItem in fabricationRecipe.RequiredItems) + { + foreach (var itemPrefab2 in requiredItem.ItemPrefabs) + { + foreach (var recipe in itemPrefab2.FabricationRecipes.Values) + { + ingredient ??= recipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem)); + } + } + } + } + if (ingredient == null) { NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red); @@ -2077,7 +2089,17 @@ namespace Barotrauma var prefab = MapEntityPrefab.Find(null, args[0]); if (prefab != null) { - DebugConsole.NewMessage(prefab.Name + " " + prefab.Identifier + " " + prefab.GetType().ToString()); + NewMessage(prefab.Name + " " + prefab.Identifier + " " + prefab.GetType().ToString()); + } + })); + + commands.Add(new Command("copycharacterinfotoclipboard", "", (string[] args) => + { + if (Character.Controlled?.Info != null) + { + XElement element = Character.Controlled?.Info.Save(null); + Clipboard.SetText(element.ToString()); + DebugConsole.NewMessage($"Copied the characterinfo of {Character.Controlled.Name} to clipboard."); } })); @@ -2507,7 +2529,7 @@ namespace Barotrauma commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) => { - if (args.Length != 1) return; + if (args.Length != 1) { return; } TextManager.CheckForDuplicates(args[0].ToIdentifier().ToLanguageIdentifier()); })); @@ -2517,9 +2539,20 @@ namespace Barotrauma NPCConversation.WriteToCSV(); })); - commands.Add(new Command("csvtoxml", "csvtoxml [language] -> Converts .csv localization files in Content/NPCConversations & Content/Texts to .xml for use in-game.", (string[] args) => + commands.Add(new Command("csvtoxml", "csvtoxml -> Converts .csv localization files Content/Texts/Texts.csv and Content/Texts/NPCConversations.csv to .xml for use in-game.", (string[] args) => { - LocalizationCSVtoXML.Convert(); + ShowQuestionPrompt("Do you want to save the text files to the project folder (../../../BarotraumaShared/Content/Texts/)? If not, they are saved in the current working directory. Y/N", + (option1) => + { + ShowQuestionPrompt("Do you want to convert the NPC conversations as well? Y/N", + (option2) => + { + LocalizationCSVtoXML.ConvertMasterLocalizationKit( + option1.ToLowerInvariant() == "y" ? "../../../BarotraumaShared/Content/Texts/" : "Content/Texts", + option1.ToLowerInvariant() == "y" ? "../../../BarotraumaShared/Content/NPCConversations/" : "Content/NPCConversations", + convertConversations: option2.ToLowerInvariant() == "y"); + }); + }); })); commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) => diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index d9dd3a9cc..f216984a4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -32,12 +32,12 @@ namespace Barotrauma private static bool shouldFadeToBlack; - private bool IsBlockedByAnotherConversation(IEnumerable _) + private bool IsBlockedByAnotherConversation(IEnumerable _, float duration) { return lastActiveAction != null && lastActiveAction.ParentEvent != ParentEvent && - Timing.TotalTime < lastActiveAction.lastActiveTime + BlockOtherConversationsDuration; + Timing.TotalTime < lastActiveAction.lastActiveTime + duration; } partial void ShowDialog(Character speaker, Character targetCharacter) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs index 5760ecefd..c4ec84e16 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs @@ -528,7 +528,7 @@ namespace Barotrauma if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait) { new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait")); - new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get("personalitytrait." + trait.Name.Replace(" ".ToIdentifier(), Identifier.Empty))); + new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), trait.DisplayName); } infoLabelGroup.Recalculate(); infoValueGroup.Recalculate(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs index 49f027c19..63021ce65 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs @@ -958,7 +958,7 @@ namespace Barotrauma // Wire cursors if (Character.Controlled != null) { - if (Character.Controlled.SelectedConstruction?.GetComponent() != null) + if (Character.Controlled.SelectedItem?.GetComponent() != null) { if (Connection.DraggingConnected != null) { @@ -1344,7 +1344,7 @@ namespace Barotrauma /// Should the indicator move based on the camera position? /// Override the distance-based alpha value with the specified alpha value public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Range visibleRange, Sprite sprite, in Color color, - bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null) + bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null, LocalizedString label = null) { Vector2 diff = worldPosition - cam.WorldViewCenter; float dist = diff.Length(); @@ -1394,10 +1394,6 @@ namespace Barotrauma angle = MathHelper.Lerp(originalAngle, angle, MathHelper.Clamp(((screenDist + 10f) - iconDiff.Length()) / 10f, 0f, 1f)); - /*Vector2 unclampedDiff = new Vector2( - (float)Math.Cos(angle) * screenDist, - (float)-Math.Sin(angle) * screenDist);*/ - iconDiff = new Vector2( (float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist), (float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist)); @@ -1405,7 +1401,20 @@ namespace Barotrauma Vector2 iconPos = cam.WorldToScreen(cam.WorldViewCenter) + iconDiff; sprite.Draw(spriteBatch, iconPos, color * alpha, rotate: 0.0f, scale: symbolScale); - if (/*unclampedDiff.Length()*/ screenDist - 10 > iconDiff.Length()) + if (label != null) + { + float cursorDist = Vector2.Distance(PlayerInput.MousePosition, iconPos); + if (cursorDist < sprite.size.X * symbolScale) + { + Vector2 textSize = GUIStyle.Font.MeasureString(label); + Vector2 textPos = iconPos + new Vector2(sprite.size.X * symbolScale * 0.7f * Math.Sign(-iconDiff.X), -textSize.Y / 2); + if (iconDiff.X > 0) { textPos.X -= textSize.X; } + DrawString(spriteBatch, textPos + Vector2.One, label, Color.Black); + DrawString(spriteBatch, textPos, label, color); + } + } + + if (screenDist - 10 > iconDiff.Length()) { Vector2 normalizedDiff = Vector2.Normalize(targetScreenPos - iconPos); Vector2 arrowOffset = normalizedDiff * sprite.size.X * symbolScale * 0.7f; @@ -1465,9 +1474,9 @@ namespace Barotrauma depth); } - public static void DrawString(SpriteBatch sb, Vector2 pos, LocalizedString text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null) + public static void DrawString(SpriteBatch sb, Vector2 pos, LocalizedString text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null, ForceUpperCase forceUpperCase = ForceUpperCase.Inherit) { - DrawString(sb, pos, text.Value, color, backgroundColor, backgroundPadding, font); + DrawString(sb, pos, text.Value, color, backgroundColor, backgroundPadding, font, forceUpperCase); } public static void DrawString(SpriteBatch sb, Vector2 pos, string text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null, ForceUpperCase forceUpperCase = ForceUpperCase.Inherit) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIContextMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIContextMenu.cs index 40eae2afa..3e9166fcd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIContextMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIContextMenu.cs @@ -62,7 +62,7 @@ namespace Barotrauma GUIFont headerFont = GUIStyle.SubHeadingFont; GUIFont font = GUIStyle.SmallFont; // font the context menu options use Vector4 padding = new Vector4(4), headerPadding = new Vector4(8); - int horizontalPadding = (int) (padding.X + padding.Z), verticalPadding = (int) (padding.Y + padding.W); + int horizontalPadding = (int)(padding.X + padding.Z), verticalPadding = (int)(padding.Y + padding.W); bool hasHeader = !header.IsNullOrWhiteSpace(); //---------------------------------------------------------------------------------- @@ -131,9 +131,15 @@ namespace Barotrauma optionElement.ToolTip = option.Tooltip; } - if (!option.IsEnabled) + //option doesn't do anything, make it a label + if (option.OnSelected == null) { - optionElement.TextColor *= 0.5f; + optionElement.TextAlignment = Alignment.BottomLeft; + optionElement.TextColor = optionElement.DisabledTextColor = GUIStyle.Green; + } + else if (!option.IsEnabled) + { + optionElement.TextColor *= 0.5f; } } @@ -146,7 +152,10 @@ namespace Barotrauma // Resize all children to the size of their text foreach (GUITextBlock block in children.Where(c => c is GUITextBlock).Cast()) { - block.RectTransform.NonScaledSize = new Point((int) (block.TextSize.X + (block.Padding.X + block.Padding.Z)), (int) (18 * GUI.Scale)); + bool isLabel = block.UserData is ContextMenuOption option && option.OnSelected == null; + block.RectTransform.NonScaledSize = new Point( + (int)(block.TextSize.X + (block.Padding.X + block.Padding.Z)), + (int)Math.Max(block.TextSize.Y * 1.2f, 18 * GUI.Scale)); } int largestWidth = children.Max(c => c.Rect.Width + horizontalPadding); @@ -155,7 +164,7 @@ namespace Barotrauma if (HeaderLabel != null) { RectTransform headerTransform = HeaderLabel.RectTransform; - headerTransform.MinSize = new Point((int) (HeaderLabel.TextSize.X + (headerPadding.X + headerPadding.Z)), headerTransform.NonScaledSize.Y); + headerTransform.MinSize = new Point((int)(HeaderLabel.TextSize.X + (headerPadding.X + headerPadding.Z)), headerTransform.NonScaledSize.Y); if (largestWidth < headerTransform.MinSize.X) { largestWidth = headerTransform.MinSize.X; @@ -171,7 +180,7 @@ namespace Barotrauma // the cropped size of the option list Point newSize = new Point(largestWidth, children.Sum(c => c.Rect.Height) + verticalPadding); // resize the menu itself taking into account the option menus relative Y size - RectTransform.NonScaledSize = new Point(newSize.X, (int) (newSize.Y / optionList.RectTransform.RelativeSize.Y)); + RectTransform.NonScaledSize = new Point(newSize.X, (int)(newSize.Y / optionList.RectTransform.RelativeSize.Y)); optionList.RectTransform.NonScaledSize = newSize; // move the context menu if it would go outside of screen @@ -227,8 +236,8 @@ namespace Barotrauma private Vector2 InflateSize(ref Point size, LocalizedString label, ScalableFont font) { Vector2 textSize = font.MeasureString(label); - size.X = Math.Max((int) Math.Ceiling(textSize.X), size.X); - size.Y += (int) Math.Ceiling(textSize.Y); + size.X = Math.Max((int)Math.Ceiling(textSize.X), size.X); + size.Y += (int)Math.Ceiling(textSize.Y); return textSize; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs new file mode 100644 index 000000000..b4987a87d --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDragHandle.cs @@ -0,0 +1,75 @@ +using Microsoft.Xna.Framework; +using System; + +namespace Barotrauma +{ + public class GUIDragHandle : GUIComponent + { + private readonly RectTransform elementToMove; + + private Vector2 dragStart; + private bool dragStarted; + + public Rectangle DragArea; + + public GUIDragHandle(RectTransform rectT, RectTransform elementToMove, string style = "GUIDragIndicator") + : base(style, rectT) + { + this.elementToMove = elementToMove; + DragArea = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight); + } + + protected override void Update(float deltaTime) + { + if (!Visible) return; + base.Update(deltaTime); + Enabled = true; + if (dragStarted) + { + Point moveAmount = (PlayerInput.MousePosition - dragStart).ToPoint() - elementToMove.ScreenSpaceOffset; + Rectangle rect = elementToMove.Rect; + rect.Location += moveAmount; + + moveAmount.X += Math.Max(DragArea.X - rect.X, 0); + moveAmount.X -= Math.Max(rect.Right - DragArea.Right, 0); + moveAmount.Y += Math.Max(DragArea.Y - rect.Y, 0); + moveAmount.Y -= Math.Max(rect.Bottom - DragArea.Bottom, 0); + + if (moveAmount != Point.Zero) + { + elementToMove.ScreenSpaceOffset += moveAmount; + } + + if (!PlayerInput.PrimaryMouseButtonHeld()) + { + dragStarted = false; + } + } + else if (Rect.Contains(PlayerInput.MousePosition) && CanBeFocused && Enabled && GUI.IsMouseOn(this)) + { + State = Selected ? ComponentState.HoverSelected : ComponentState.Hover; + if (PlayerInput.PrimaryMouseButtonDown()) + { + dragStart = PlayerInput.MousePosition - elementToMove.ScreenSpaceOffset.ToVector2(); + dragStarted = true; + } + } + else + { + if (!ExternalHighlight) + { + State = Selected ? ComponentState.Selected : ComponentState.None; + } + else + { + State = ComponentState.Hover; + } + } + + foreach (GUIComponent child in Children) + { + child.State = State; + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs index 8e43f9c6f..579e492c2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIListBox.cs @@ -49,7 +49,7 @@ namespace Barotrauma //scaling the bar linearly with the resolution tends to make them too large on large resolutions float desiredSize = 25.0f; float scaledSize = desiredSize * GUI.Scale; - return (int)((desiredSize + scaledSize) / 2.0f); + return (int)Math.Min((desiredSize + scaledSize) / 2.0f, Rect.Height / 3); } } @@ -73,6 +73,8 @@ namespace Barotrauma public bool HideChildrenOutsideFrame = true; + public bool ResizeContentToMakeSpaceForScrollBar = true; + private bool useGridLayout; private GUIComponent scrollToElement; @@ -419,7 +421,7 @@ namespace Barotrauma { dimensionsNeedsRecalculation = false; ContentBackground.RectTransform.Resize(Rect.Size); - bool reduceScrollbarSize = KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible; + bool reduceScrollbarSize = ResizeContentToMakeSpaceForScrollBar && (KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible); Point contentSize = reduceScrollbarSize ? CalculateFrameSize(ScrollBar.IsHorizontal, ScrollBarSize) : Rect.Size; Content.RectTransform.Resize(new Point((int)(contentSize.X - Padding.X - Padding.Z), (int)(contentSize.Y - Padding.Y - Padding.W))); if (!IsScrollBarOnDefaultSide) { Content.RectTransform.SetPosition(Anchor.BottomRight); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs index fb9756c54..8d8a563b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIMessageBox.cs @@ -471,6 +471,7 @@ namespace Barotrauma public void SetBackgroundIcon(Sprite icon) { if (icon == null) { return; } + if (icon == BackgroundIcon.Sprite) { return; } GUIImage newIcon = new GUIImage(new RectTransform(icon.size.ToPoint(), RectTransform), icon) { IgnoreLayoutGroups = true, @@ -593,7 +594,7 @@ namespace Barotrauma { newBackgroundIcon.SetAsFirstChild(); newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int)(newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2); - newBackgroundIcon.Color = ToolBox.GradientLerp(iconState, Color.Transparent, Color.White); + newBackgroundIcon.Color = Color.Lerp(Color.Transparent, Color.White, iconState); if (newBackgroundIcon.Color.A == 255) { BackgroundIcon = newBackgroundIcon; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs index 1c2e869b0..0552340d0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/HUDLayoutSettings.cs @@ -81,6 +81,11 @@ namespace Barotrauma get; private set; } + public static Rectangle ItemHUDArea + { + get; private set; + } + public static int Padding { get; private set; @@ -168,6 +173,8 @@ namespace Barotrauma // Height is based on text content VotingArea = new Rectangle(votingAreaX, votingAreaY, votingAreaWidth, 0); + + ItemHUDArea = new Rectangle(0, ButtonAreaTop.Bottom, GameMain.GraphicsWidth, GameMain.GraphicsHeight - ButtonAreaTop.Bottom - InventoryAreaLower.Height); } public static void Draw(SpriteBatch spriteBatch) @@ -181,6 +188,7 @@ namespace Barotrauma GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f); GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f); GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f); + GUI.DrawRectangle(spriteBatch, ItemHUDArea, Color.Magenta * 0.3f); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs index 6bd80ccc1..aded19429 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs @@ -403,7 +403,7 @@ namespace Barotrauma CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); break; case InfoFrameTab.Talents: - CreateTalentInfo(infoFrameHolder); + CreateCharacterInfo(infoFrameHolder); break; } } @@ -1803,161 +1803,121 @@ namespace Barotrauma { TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) }, }.ToImmutableDictionary(); - private void CreateTalentInfo(GUIFrame infoFrame) + private void CreateCharacterInfo(GUIFrame infoFrame) { infoFrame.ClearChildren(); talentButtons.Clear(); talentCornerIcons.Clear(); - GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); + GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); int padding = GUI.IntScale(15); - GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); + GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); - GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); - - GUIFrame talentFrameMain = new GUIFrame(new RectTransform(Vector2.One, paddedTalentFrame.RectTransform), style: null); + GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null); GUIFrame characterSettingsFrame = null; GUILayoutGroup characterLayout = null; if (!(GameMain.NetworkMember is null)) { - characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrameContent.RectTransform), style: null) { Visible = false }; + characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: null) { Visible = false }; characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform)); GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null); GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null); GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false); } - /*Character controlledCharacter = Character.Controlled; - if (controlledCharacter == null) { return; } - - if (controlledCharacter.Info is null) - { - DebugConsole.ThrowError("No character info found for talent UI"); - return; - }*/ - Character controlledCharacter = Character.Controlled; CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo; if (info == null) { return; } Job job = info.Job; - GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) + GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) { - AbsoluteSpacing = GUI.IntScale(5) + AbsoluteSpacing = GUI.IntScale(10), + Stretch = true }; - GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: true); + GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), contentLayout.RectTransform, Anchor.Center), isHorizontal: true); - - new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) => + new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) => { float posY = component.Rect.Center.Y - component.Rect.Width / 2; info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false); }); - GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f }; + GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform)) + { + AbsoluteSpacing = GUI.IntScale(5), + CanBeFocused = true + }; - Vector2 nameSize = GUIStyle.SubHeadingFont.MeasureString(info.Name); - GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont); - nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint(); + GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont); if (!info.OmitJobInMenus) { nameBlock.TextColor = job.Prefab.UIColor; - Vector2 jobSize = GUIStyle.SmallFont.MeasureString(job.Name); - GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor }; - jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint(); + GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor }; } - LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", ""))); + LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName); Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString); GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont); traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); - GUIFrame talentsOutsideTreeFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null); - - if (!(GameMain.NetworkMember is null)) + IEnumerable talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); + if (talentsOutsideTree.Count() > 0) { - GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), talentsOutsideTreeFrame.RectTransform, Anchor.TopLeft), - text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) + //spacing + new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null); + + GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter); + + talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont); + talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y); + + var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true) { - IgnoreLayoutGroups = true + AutoHideScrollBar = false, + ResizeContentToMakeSpaceForScrollBar = false }; - newCharacterBox.TextBlock.AutoScaleHorizontal = true; + extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter); + extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65)); + extraTalentLayout.Recalculate(); + extraTalentList.ForceLayoutRecalculation(); - newCharacterBox.OnClicked = (button, o) => + foreach (var extraTalent in talentsOutsideTree) { - if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded) + var img = new GUIImage(new RectTransform(new Point(extraTalentList.Content.Rect.Height), extraTalentList.Content.RectTransform), sprite: extraTalent.Icon, scaleToFit: true) { - GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() => - { - newCharacterBox.Text = TextManager.Get("settings"); - - if (pendingChangesFrame != null) - { - NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame); - } - OpenMenu(); - }); - return true; - } - - OpenMenu(); - return true; - - void OpenMenu() - { - characterSettingsFrame!.Visible = true; - talentFrameMain.Visible = false; - } - }; - - if (!(characterLayout is null)) - { - GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomRight); - new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages? - { - OnClicked = (button, o) => - { - GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName); - characterSettingsFrame!.Visible = false; - talentFrameMain.Visible = true; - return true; - } + ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description), + Color = GUIStyle.Green }; + img.RectTransform.SizeChanged += () => + { + img.RectTransform.MaxSize = new Point(img.Rect.Height); + }; } } - IEnumerable talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); + GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight) + { + AbsoluteSpacing = GUI.IntScale(5), + Stretch = true + }; - if (talentsOutsideTree.Count() > 0) - { - //TODO: replace with something more generic - GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), talentsOutsideTreeFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") - { - ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", talentsOutsideTree.Select(e => e.DisplayName))}" - }; - } - - GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true }; - - LocalizedString skillString = TextManager.Get("skills"); - Vector2 skillSize = GUIStyle.SubHeadingFont.MeasureString(skillString); - GUITextBlock skillBlock = new GUITextBlock(new RectTransform(Vector2.One, skillLayout.RectTransform), skillString, font: GUIStyle.SubHeadingFont); - skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint(); + GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont); skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); - CreateTalentSkillList(controlledCharacter, info, skillListBox); + CreateSkillList(controlledCharacter, info, skillListBox); if (controlledCharacter != null) { if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; } - new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine"); + new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine"); - GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); + GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null); selectedTalents = info.GetUnlockedTalentsInTree().ToList(); @@ -2008,10 +1968,9 @@ namespace Barotrauma }; GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null); - GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null) { - ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description), + ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description), UserData = talent.Identifier, PressedColor = pressedColor, Enabled = controlledCharacter != null, @@ -2078,9 +2037,13 @@ namespace Barotrauma } GUITextBlock.AutoScaleAndNormalize(subTreeNames); - GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; + GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true) + { + RelativeSpacing = 0.01f, + Stretch = true + }; - GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), talentBottomFrame.RectTransform)); + GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform)); GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null); experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), @@ -2097,30 +2060,84 @@ namespace Barotrauma talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; - talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale") + talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale") { OnClicked = ResetTalentSelection }; - talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale") + talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale") { OnClicked = ApplyTalentSelection, }; GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock); } + if (!(GameMain.NetworkMember is null)) + { + GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight), + text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall") + { + IgnoreLayoutGroups = false + }; + newCharacterBox.TextBlock.AutoScaleHorizontal = true; + + newCharacterBox.OnClicked = (button, o) => + { + if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded) + { + GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() => + { + newCharacterBox.Text = TextManager.Get("settings"); + if (pendingChangesFrame != null) + { + NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame); + } + OpenMenu(); + }); + return true; + } + + OpenMenu(); + return true; + + void OpenMenu() + { + characterSettingsFrame!.Visible = true; + content.Visible = false; + } + }; + + if (!(characterLayout is null)) + { + GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter); + new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages? + { + OnClicked = (button, o) => + { + GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName); + characterSettingsFrame!.Visible = false; + content.Visible = true; + return true; + } + }; + } + } + UpdateTalentInfo(); } - private void CreateTalentSkillList(Character character, CharacterInfo info, GUIListBox parent) + private void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent) { parent.Content.ClearChildren(); List skillNames = new List(); foreach (Skill skill in info.Job.GetSkills()) { - GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; - - skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value))); - new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.CenterRight) { Padding = new Vector4(0, 0, 4, 0) }; + GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.0f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = true }; + var skillName = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value)); + skillNames.Add(skillName); + skillName.RectTransform.MinSize = new Point(0, skillName.Rect.Height); + skillContainer.RectTransform.MinSize = new Point(0, skillName.Rect.Height); + + new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight); float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level; if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level))) @@ -2129,15 +2146,15 @@ namespace Barotrauma //TODO: if/when we upgrade to C# 9, do neater pattern matching here string stringColor = true switch { - true when skillChange > 0 => XMLExtensions.ColorToString(GUIStyle.Green), - true when skillChange < 0 => XMLExtensions.ColorToString(GUIStyle.Red), - _ => XMLExtensions.ColorToString(GUIStyle.TextColorNormal) + true when skillChange > 0 => XMLExtensions.ToStringHex(GUIStyle.Green), + true when skillChange < 0 => XMLExtensions.ToStringHex(GUIStyle.Red), + _ => XMLExtensions.ToStringHex(GUIStyle.TextColorNormal) }; RichString changeText = RichString.Rich($"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)"); new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText) { Padding = Vector4.Zero }; } - skillContainer.Recalculate(); + //skillContainer.Recalculate(); } parent.RecalculateChildren(); @@ -2216,7 +2233,7 @@ namespace Barotrauma talentButton.icon.HoverColor = hoverColor; } - CreateTalentSkillList(controlledCharacter, controlledCharacter.Info, skillListBox); + CreateSkillList(controlledCharacter, controlledCharacter.Info, skillListBox); } private void ApplyTalents(Character controlledCharacter) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs index 4d5f3f806..7887ea6db 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs @@ -432,13 +432,21 @@ namespace Barotrauma }; Location location = Campaign.Map.CurrentLocation; - int hullRepairCost = location?.GetAdjustedMechanicalCost(CampaignMode.HullRepairCost) ?? CampaignMode.HullRepairCost; - int itemRepairCost = location?.GetAdjustedMechanicalCost(CampaignMode.ItemRepairCost) ?? CampaignMode.ItemRepairCost; - int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(CampaignMode.ShuttleReplaceCost) ?? CampaignMode.ShuttleReplaceCost; + + int hullRepairCost = Campaign.GetHullRepairCost(); + int itemRepairCost = Campaign.GetItemRepairCost(); + int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost; + if (location != null) + { + hullRepairCost = location.GetAdjustedMechanicalCost(hullRepairCost); + itemRepairCost = location.GetAdjustedMechanicalCost(itemRepairCost); + shuttleRetrieveCost = location.GetAdjustedMechanicalCost(shuttleRetrieveCost); + } CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallwalls"), "RepairHullButton", hullRepairCost, (button, o) => { - if (Campaign.PurchasedHullRepairs) + //cost is zero = nothing to repair + if (Campaign.PurchasedHullRepairs || hullRepairCost <= 0) { button.Enabled = false; return false; @@ -471,7 +479,7 @@ namespace Barotrauma return false; } return true; - }, Campaign.PurchasedHullRepairs || !HasPermission, isHovered => + }, Campaign.PurchasedHullRepairs || !HasPermission || hullRepairCost <= 0, isHovered => { highlightWalls = isHovered; return true; @@ -479,7 +487,8 @@ namespace Barotrauma CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) => { - if (PlayerBalance >= itemRepairCost && !Campaign.PurchasedItemRepairs) + //cost is zero = nothing to repair + if (PlayerBalance >= itemRepairCost && !Campaign.PurchasedItemRepairs && itemRepairCost > 0) { LocalizedString body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString()); currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => @@ -505,9 +514,8 @@ namespace Barotrauma button.Enabled = false; return false; } - return true; - }, Campaign.PurchasedItemRepairs || !HasPermission, isHovered => + }, Campaign.PurchasedItemRepairs || !HasPermission || itemRepairCost <= 0, isHovered => { foreach (var (item, itemFrame) in itemPreviews) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index f3908eb62..f0930992a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -814,7 +814,7 @@ namespace Barotrauma else if ((Character.Controlled == null || !itemHudActive()) && CharacterHealth.OpenHealthWindow == null && !CrewManager.IsCommandInterfaceOpen - && !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedConstruction != null)) + && !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedItem != null)) { // Otherwise toggle pausing, unless another window/interface is open. GUI.TogglePauseMenu(); @@ -822,9 +822,9 @@ namespace Barotrauma static bool itemHudActive() { - if (Character.Controlled?.SelectedConstruction == null) { return false; } + if (Character.Controlled?.SelectedItem == null) { return false; } return - Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || + Character.Controlled.SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null) || ((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false); } } @@ -919,7 +919,7 @@ namespace Barotrauma #endif } - CoroutineManager.Update((float)Timing.Step, Paused ? 0.0f : (float)Timing.Step); + CoroutineManager.Update(Paused, (float)Timing.Step); SteamManager.Update((float)Timing.Step); @@ -1098,7 +1098,7 @@ namespace Barotrauma GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity); foreach (var activeEvent in GameSession.EventManager.ActiveEvents) { - GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.ToString()); + GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.Prefab.Identifier); } GameSession.LogEndRoundStats(eventId); if (GameSession.GameMode is TutorialMode tutorialMode) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs index a4f756646..73ea45844 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/CrewManager.cs @@ -2412,7 +2412,7 @@ namespace Barotrauma float reactorOutput = -reactor.CurrPowerConsumption; // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // --> Create shortcut node for "Operate Reactor" order's "Power Up" option - if (ShouldDelegateOrder("operatereactor") && reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) + if (ShouldDelegateOrder("operatereactor") && reactorOutput < float.Epsilon && characters.None(c => c.SelectedItem == reactor.Item)) { var orderPrefab = OrderPrefab.Prefabs["operatereactor"]; var order = new Order(orderPrefab, orderPrefab.Options[0], reactor.Item, reactor); @@ -2426,7 +2426,7 @@ namespace Barotrauma // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up // --> Create shortcut node for Steer order if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) && - subItems.Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && + subItems.Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) && nav.GetComponent() is Steering steering && steering.Voltage > steering.MinVoltage) { var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering); diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameMode.cs index 83b87c5ce..a3c45a280 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameMode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameMode.cs @@ -3,8 +3,8 @@ namespace Barotrauma { partial class GameMode { - public virtual void Draw(SpriteBatch spriteBatch) - { - } + public virtual void HUDScaleChanged() { } + + public virtual void Draw(SpriteBatch spriteBatch) { } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs index c7d279a2d..a757aa34a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -115,12 +115,16 @@ namespace Barotrauma partial void InitProjSpecific() { - var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUI.Canvas), - isHorizontal: true, childAnchor: Anchor.CenterRight) - { - CanBeFocused = false - }; + CreateButtons(); + } + public override void HUDScaleChanged() + { + CreateButtons(); + } + + private void CreateButtons() + { int buttonHeight = (int) (GUI.Scale * 40), buttonWidth = GUI.IntScale(450), buttonCenter = buttonHeight / 2, @@ -166,8 +170,6 @@ namespace Barotrauma }, UserData = "ReadyCheckButton" }; - - buttonContainer.Recalculate(); } private void InitCampaignUI() @@ -311,7 +313,7 @@ namespace Barotrauma if (prevControlled != null) { - prevControlled.SelectedConstruction = null; + prevControlled.SelectedItem = prevControlled.SelectedSecondaryItem = null; if (prevControlled.AIController != null) { prevControlled.AIController.Enabled = true; @@ -362,7 +364,7 @@ namespace Barotrauma float t = 0.0f; while (t < fadeOutDuration || endTransition.Running) { - t += CoroutineManager.UnscaledDeltaTime; + t += CoroutineManager.DeltaTime; overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration); yield return CoroutineStatus.Running; } @@ -469,7 +471,6 @@ namespace Barotrauma { base.End(transitionType); ForceMapUI = ShowCampaignUI = false; - UpgradeManager.CanUpgrade = true; // remove all event dialogue boxes GUIMessageBox.MessageBoxes.ForEachMod(mb => diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs index fc3f8f71c..60814bd1c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs @@ -169,10 +169,20 @@ namespace Barotrauma public static SinglePlayerCampaign Load(XElement element) => new SinglePlayerCampaign(element); private void InitUI() + { + CreateEndRoundButton(); + + campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black); + CampaignUI = new CampaignUI(this, campaignUIContainer) + { + StartRound = () => { TryEndRound(); } + }; + } + + private void CreateEndRoundButton() { int buttonHeight = (int)(GUI.Scale * 40); int buttonWidth = GUI.IntScale(450); - endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUI.Canvas), TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") { @@ -190,12 +200,11 @@ namespace Barotrauma return true; } }; + } - campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black); - CampaignUI = new CampaignUI(this, campaignUIContainer) - { - StartRound = () => { TryEndRound(); } - }; + public override void HUDScaleChanged() + { + CreateEndRoundButton(); } #endregion @@ -292,7 +301,7 @@ namespace Barotrauma yield return CoroutineStatus.Success; } overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); - timer = Math.Min(timer + CoroutineManager.UnscaledDeltaTime, textDuration); + timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); yield return CoroutineStatus.Running; } var outpost = GameMain.GameSession.Level.StartOutpost; @@ -320,7 +329,7 @@ namespace Barotrauma while (timer < fadeInDuration) { overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration); - timer += CoroutineManager.UnscaledDeltaTime; + timer += CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } overlayColor = Color.Transparent; @@ -353,7 +362,7 @@ namespace Barotrauma if (prevControlled != null) { - prevControlled.SelectedConstruction = null; + prevControlled.SelectedItem = prevControlled.SelectedSecondaryItem = null; if (prevControlled.AIController != null) { prevControlled.AIController.Enabled = true; @@ -424,7 +433,7 @@ namespace Barotrauma float t = 0.0f; while (t < fadeOutDuration || endTransition.Running) { - t += CoroutineManager.UnscaledDeltaTime; + t += CoroutineManager.DeltaTime; overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration); yield return CoroutineStatus.Running; } @@ -436,6 +445,7 @@ namespace Barotrauma if (success) { GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); + GameMain.GameSession.EventManager.RegisterEventHistory(); SaveUtil.SaveGame(GameMain.GameSession.SavePath); } else diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs index 76cb87ce0..beb99fcb6 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -124,7 +124,7 @@ namespace Barotrauma.Tutorials captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition; tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent(); tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent(); - var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("medicaldoctor")) + var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("medicaldoctor".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; @@ -148,21 +148,21 @@ namespace Barotrauma.Tutorials SetDoorAccess(tutorial_lockedDoor_1, null, false); SetDoorAccess(tutorial_lockedDoor_2, null, false); - var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("mechanic")) + var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("mechanic".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic"); captain_mechanic.GiveJobItems(); - var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer")) + var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer"); captain_security.GiveJobItems(); - var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer")) + var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; @@ -339,8 +339,8 @@ namespace Barotrauma.Tutorials private bool IsSelectedItem(Item item) { return - captain?.SelectedConstruction == item || - (captain?.SelectedConstruction?.linkedTo?.Contains(item) ?? false); + captain?.SelectedItem == item || + (captain?.SelectedItem?.linkedTo?.Contains(item) ?? false); } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs index 8d9ef63b1..442f36b07 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/DoctorTutorial.cs @@ -119,7 +119,7 @@ namespace Barotrauma.Tutorials var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull; medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull; - var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant")) + var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; @@ -130,7 +130,7 @@ namespace Barotrauma.Tutorials patient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false); patient1.AIController.Enabled = false; - assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant")) + assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; @@ -139,7 +139,7 @@ namespace Barotrauma.Tutorials patient2.CanSpeak = false; patient2.AIController.Enabled = false; - var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer")) + var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; @@ -148,13 +148,13 @@ namespace Barotrauma.Tutorials subPatient1.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient1); - var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer")); + var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer".ToIdentifier())); var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3"); subPatient2.TeamID = CharacterTeamType.Team1; subPatient2.AddDamage(patient1.WorldPosition, new List() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatients.Add(subPatient2); - var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer")) + var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer".ToIdentifier())) { TeamID = CharacterTeamType.Team1 }; @@ -262,7 +262,7 @@ namespace Barotrauma.Tutorials HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); } } - if (doctor.SelectedConstruction == doctor_suppliesCabinet.Item) + if (doctor.SelectedItem == doctor_suppliesCabinet.Item) { for (int i = 0; i < doctor.Inventory.Capacity; i++) { @@ -373,7 +373,7 @@ namespace Barotrauma.Tutorials HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f); } } - if (doctor.SelectedConstruction == doctor_medBayCabinet.Item) + if (doctor.SelectedItem == doctor_medBayCabinet.Item) { for (int i = 0; i < doctor.Inventory.Capacity; i++) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs index b95fa6f31..3e30b7672 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/EngineerTutorial.cs @@ -400,7 +400,7 @@ namespace Barotrauma.Tutorials wait -= 0.1f; engineer_reactor.AutoTemp = true; } while (wait > 0.0f); - engineer.SelectedConstruction = null; + engineer.SelectedItem = null; engineer_reactor.CanBeSelected = false; RemoveCompletedObjective(2); GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective2"); @@ -513,7 +513,7 @@ namespace Barotrauma.Tutorials private bool IsSelectedItem(Item item) { - return engineer?.SelectedConstruction == item; + return engineer?.SelectedItem == item; } private IEnumerable ReactorOperatedProperly() @@ -568,7 +568,7 @@ namespace Barotrauma.Tutorials private void HandleJunctionBoxWiringHighlights() { - Item selected = engineer.SelectedConstruction; + Item selected = engineer.SelectedItem; if (!engineer.HasEquippedItem("screwdriver".ToIdentifier())) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs index 06d05b708..e96dfc137 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/MechanicTutorial.cs @@ -440,7 +440,7 @@ namespace Barotrauma.Tutorials bool gotSodium = false; do { - if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item) + if (mechanic.SelectedItem == mechanic_craftingCabinet.Item) { for (int i = 0; i < mechanic.Inventory.Capacity; i++) { @@ -702,7 +702,7 @@ namespace Barotrauma.Tutorials private bool IsSelectedItem(Item item) { - return mechanic?.SelectedConstruction == item; + return mechanic?.SelectedItem == item; } private bool WallHasDamagedSections(Structure wall) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs index dd0f49fe5..3cfb4551f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/OfficerTutorial.cs @@ -510,7 +510,7 @@ namespace Barotrauma.Tutorials private bool IsSelectedItem(Item item) { - return officer?.SelectedConstruction == item; + return officer?.SelectedItem == item; } private Character SpawnMonster(string speciesName, Vector2 pos) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs index 627f79236..dd566ee34 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameSession.cs @@ -14,6 +14,7 @@ namespace Barotrauma public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null; public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu; + private float prevHudScale; private TabMenu tabMenu; @@ -119,6 +120,7 @@ namespace Barotrauma return true; } }; + prevHudScale = GameSettings.CurrentConfig.Graphics.HUDScale; } public void AddToGUIUpdateList() @@ -178,6 +180,12 @@ namespace Barotrauma } } + public void HUDScaleChanged() + { + CreateTopLeftButtons(); + GameMode?.HUDScaleChanged(); + } + partial void UpdateProjSpecific(float deltaTime) { if (GUI.DisableHUD) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs index a7ee9cc4c..0dfc29ed7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/HintManager.cs @@ -112,19 +112,19 @@ namespace Barotrauma CheckReminders(); } - public static void OnSetSelectedConstruction(Character character, Item oldConstruction, Item newConstruction) + public static void OnSetSelectedItem(Character character, Item oldItem, Item newItem) { - if (oldConstruction == newConstruction) { return; } + if (oldItem == newItem) { return; } - if (Character.Controlled != null && Character.Controlled == character && oldConstruction != null && oldConstruction.GetComponent() == null) + if (Character.Controlled != null && Character.Controlled == character && oldItem != null && !oldItem.IsLadder) { TimeStoppedInteracting = Timing.TotalTime; } - if (newConstruction == null) { return; } - if (newConstruction.GetComponent() != null) { return; } - if (newConstruction.GetComponent() is ConnectionPanel cp && cp.User == character) { return; } - OnStartedInteracting(character, newConstruction); + if (newItem == null) { return; } + if (newItem.IsLadder) { return; } + if (newItem.GetComponent() is ConnectionPanel cp && cp.User == character) { return; } + OnStartedInteracting(character, newItem); } private static void OnStartedInteracting(Character character, Item item) @@ -177,10 +177,10 @@ namespace Barotrauma private static void CheckIsInteracting() { if (!CanDisplayHints()) { return; } - if (Character.Controlled?.SelectedConstruction == null) { return; } + if (Character.Controlled?.SelectedItem == null) { return; } - if (Character.Controlled.SelectedConstruction.GetComponent() is Reactor reactor && reactor.PowerOn && - Character.Controlled.SelectedConstruction.OwnInventory?.AllItems is IEnumerable containedItems && + if (Character.Controlled.SelectedItem.GetComponent() is Reactor reactor && reactor.PowerOn && + Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable containedItems && containedItems.Count(i => i.HasTag("reactorfuel")) > 1) { if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; } @@ -272,7 +272,7 @@ namespace Barotrauma if (!CanDisplayHints()) { return; } if (sonar == null || sonar.Removed) { return; } if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; } - if (Character.Controlled.SelectedConstruction != sonar) { return; } + if (Character.Controlled.SelectedItem != sonar) { return; } if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; } DisplayHint("onsonarspottedenemy".ToIdentifier()); } @@ -305,7 +305,7 @@ namespace Barotrauma { if (!CanDisplayHints()) { return; } if (character != Character.Controlled) { return; } - if (character.SelectedConstruction != null || character.FocusedItem != null) { return; } + if (character.HasSelectedAnyItem || character.FocusedItem != null) { return; } if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; } if (TimeStoppedInteracting + 1 > Timing.TotalTime) { return; } if (GUI.MouseOn != null) { return; } @@ -317,7 +317,7 @@ namespace Barotrauma variables: new[] { ("[key]".ToIdentifier(), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim)) }, onUpdate: () => { - if (character.SelectedConstruction == null && GUI.MouseOn == null && PlayerInput.KeyDown(InputType.Aim)) + if (character.SelectedItem == null && GUI.MouseOn == null && PlayerInput.KeyDown(InputType.Aim)) { ActiveHintMessageBox.Close(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs index 490516616..1645e1be1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs @@ -562,7 +562,7 @@ namespace Barotrauma break; } //if putting an item to a container with a max stack size of 1, only put one item from the stack - if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedConstruction?.GetComponent()?.MaxStackSize ?? 0) <= 1) + if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedItem?.GetComponent()?.MaxStackSize ?? 0) <= 1) { break; } @@ -595,14 +595,14 @@ namespace Barotrauma if (rootInventory != null && rootInventory.Owner != Character.Controlled && - rootInventory.Owner != Character.Controlled.SelectedConstruction && + rootInventory.Owner != Character.Controlled.SelectedItem && rootInventory.Owner != Character.Controlled.SelectedCharacter) { //allow interacting if the container is linked to the item the character is interacting with if (!(rootContainer != null && rootContainer.DisplaySideBySideWhenLinked && - Character.Controlled.SelectedConstruction != null && - rootContainer.linkedTo.Contains(Character.Controlled.SelectedConstruction))) + Character.Controlled.SelectedItem != null && + rootContainer.linkedTo.Contains(Character.Controlled.SelectedItem))) { DraggingItems.Clear(); } @@ -756,7 +756,7 @@ namespace Barotrauma } else { - var selectedContainer = character.SelectedConstruction?.GetComponent(); + var selectedContainer = character.SelectedItem?.GetComponent(); if (selectedContainer != null && selectedContainer.Inventory != null && !selectedContainer.Inventory.Locked) @@ -775,7 +775,8 @@ namespace Barotrauma else { bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any); - var selectedContainer = character.SelectedConstruction?.GetComponent(); + var selectedContainer = character.SelectedItem?.GetComponent(); + if (selectedContainer != null && selectedContainer.Inventory != null && !selectedContainer.Inventory.Locked && @@ -930,7 +931,7 @@ namespace Barotrauma } break; case QuickUseAction.PutToContainer: - var selectedContainer = character.SelectedConstruction?.GetComponent(); + var selectedContainer = character.SelectedItem?.GetComponent(); if (selectedContainer != null && selectedContainer.Inventory != null) { //player has selected the inventory of another item -> attempt to move the item there @@ -965,8 +966,8 @@ namespace Barotrauma } break; case QuickUseAction.PutToEquippedItem: - - foreach (Item heldItem in character.HeldItems) + //order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank + foreach (Item heldItem in character.HeldItems.OrderBy(it => it.ContainedItems.FirstOrDefault()?.Condition ?? 0.0f)) { if (heldItem.OwnInventory == null) { continue; } //don't allow swapping if we're moving items into an item with 1 slot holding a stack of items diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs index bd2b6da47..e19b1dd82 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/IdCard.cs @@ -23,11 +23,10 @@ namespace Barotrauma.Items.Components public void ExtractJobPrefab(IReadOnlyDictionary tags) { - if (!tags.TryGetValue("jobid".ToIdentifier(), out string jobId)) { return; } - + if (!tags.TryGetValue("jobid".ToIdentifier(), out string jobId)) { return; } if (!jobId.IsNullOrEmpty()) { - JobPrefab = JobPrefab.Get(jobId); + JobPrefab = JobPrefab.Get(jobId.ToIdentifier()); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index d331fc8a7..c60171bfe 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -522,13 +522,11 @@ namespace Barotrauma.Items.Components if (soundSelectionModes == null) soundSelectionModes = new Dictionary(); if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random) { - SoundSelectionMode selectionMode = SoundSelectionMode.Random; - Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out selectionMode); + Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out SoundSelectionMode selectionMode); soundSelectionModes[type] = selectionMode; } - List soundList = null; - if (!sounds.TryGetValue(itemSound.Type, out soundList)) + if (!sounds.TryGetValue(itemSound.Type, out List soundList)) { soundList = new List(); sounds.Add(itemSound.Type, soundList); @@ -566,6 +564,9 @@ namespace Barotrauma.Items.Components } string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", ""); GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color); + + TryCreateDragHandle(); + DefaultLayout = GUILayoutSettings.Load(GuiFrameSource); if (GuiFrame != null) { @@ -574,6 +575,22 @@ namespace Barotrauma.Items.Components GameMain.Instance.ResolutionChanged += OnResolutionChanged; } + protected void TryCreateDragHandle() + { + if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true)) + { + var handle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center), + GuiFrame.RectTransform, style: null) + { + DragArea = HUDLayoutSettings.ItemHUDArea + }; + + int iconHeight = GUIStyle.ItemFrameMargin.Y / 4; + new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), handle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) }, + style: "GUIDragIndicatorHorizontal"); + } + } + /// /// Overload this method and implement. The method is automatically called when the resolution changes. /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index b1d894203..5fe21e897 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -189,7 +189,7 @@ namespace Barotrauma.Items.Components onDraw: (SpriteBatch spriteBatch, GUICustomComponent component) => { Inventory.Draw(spriteBatch); }, onUpdate: null) { - CanBeFocused = false + CanBeFocused = true }; // Expand the frame vertically if it's too small to fit the text @@ -381,10 +381,15 @@ namespace Barotrauma.Items.Components guiCustomComponent.RectTransform.Parent = Inventory.RectTransform; } + if (item.ParentInventory?.Owner == character && character.SelectedItem == item) + { + character.SelectedItem = null; + } + //if the item is in the character's inventory, no need to update the item's inventory - //because the player can see it by hovering the cursor over the item - guiCustomComponent.Visible = item.ParentInventory?.Owner != character && DrawInventory; - if (!guiCustomComponent.Visible) { return; } + //because the player can see it by hovering the cursor over the item + guiCustomComponent.Visible = DrawInventory && item.ParentInventory?.Owner != character; + if (!guiCustomComponent.Visible) { return; } Inventory.Update(deltaTime, cam); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs index 52c211c18..1eeedca4b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs @@ -1,4 +1,5 @@ -using Barotrauma.Extensions; +using System.Collections.Generic; +using System.Collections.Immutable; using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -15,6 +16,7 @@ namespace Barotrauma.Items.Components } private GUIButton activateButton; private GUIComponent inputInventoryHolder, outputInventoryHolder; + private GUIListBox outputDisplayListBox; private GUIComponent inSufficientPowerWarning; @@ -44,32 +46,43 @@ namespace Barotrauma.Items.Components protected override void CreateGUI() { - var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.90f, 0.80f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) + var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.88f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) { - Stretch = true, + Stretch = true, RelativeSpacing = 0.08f }; - new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform), item.Name, font: GUIStyle.SubHeadingFont) + new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform) { MinSize = new Point(0, GUI.IntScale(25)) }, item.Name, font: GUIStyle.SubHeadingFont) { TextAlignment = Alignment.Center, AutoScaleHorizontal = true }; - var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); - + var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null); + // === INPUT LABEL === // - var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true) - { - Stretch = true, - RelativeSpacing = 0.05f - }; - var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelArea.RectTransform), TextManager.Get("deconstructor.input", "uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; - inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height)); - new GUIFrame(new RectTransform(Vector2.One, inputLabelArea.RectTransform), style: "HorizontalLine"); + var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true); + + var queueLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.43f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + var queueLabel = new GUITextBlock(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), TextManager.Get("deconstructor.inputqueue"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; + queueLabel.RectTransform.Resize(new Point((int) queueLabel.Font.MeasureString(queueLabel.Text).X, queueLabel.RectTransform.Rect.Height)); + new GUIFrame(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), style: "HorizontalLine"); + + var inputLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.57f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true) + { + Stretch = true, + RelativeSpacing = 0.05f + }; + var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), TextManager.Get("deconstructor.input", "uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; + inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height)); + new GUIFrame(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), style: "HorizontalLine"); var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; - + // === INPUT SLOTS === // inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null); new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false }; @@ -92,8 +105,8 @@ namespace Barotrauma.Items.Components }; // === OUTPUT AREA === // - var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); - + var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null); + // === OUTPUT LABEL === // var outputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true) { @@ -104,10 +117,16 @@ namespace Barotrauma.Items.Components outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height)); new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine"); - var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; + var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; - // === OUTPUT SLOTS === // - outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); + // === OUTPUT SLOTS === // + outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); + + GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter); + GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true); + GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; + GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true); + outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null); if (InfoAreaWidth >= 0.0f) { @@ -181,7 +200,7 @@ namespace Barotrauma.Items.Components { if (!(linkedTo is Item { DisplaySideBySideWhenLinked: true } linkedItem)) { continue; } if (!linkedItem.Components.Any()) { continue; } - + var itemContainer = linkedItem.GetComponent(); if (itemContainer?.GuiFrame == null || itemContainer.AllowUIOverlap) { continue; } @@ -195,6 +214,170 @@ namespace Barotrauma.Items.Components return base.Select(character); } + partial void OnItemSlotsChanged(ItemContainer container) + { + if (container.Inventory is null) { return; } + RefreshOutputDisplay(container.Inventory.AllItems.ToImmutableArray()); + } + + private void RefreshOutputDisplay(ImmutableArray items) + { + const string outputItemCountUserData = "OutputItemCount"; + const string questionMarkUserData = "UnknownItemOutput"; + + if (outputDisplayListBox is null || inputContainer.Inventory is null) { return; } + + Dictionary itemCounts = new Dictionary(); + Dictionary children = new Dictionary(); + + bool addQuestionMark = false; + + foreach (GUIComponent child in outputDisplayListBox.Content.Children) + { + if (child.UserData is Identifier it) + { + children.Add(it, child); + } + } + + if (outputDisplayListBox.Content.FindChild(questionMarkUserData) is { } foundChild) + { + outputDisplayListBox.RemoveChild(foundChild); + } + + foreach (Item it in items) + { + if (it.Prefab.RandomDeconstructionOutput) + { + addQuestionMark = true; + continue; + } + + foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems) + { + RegisterItem(deconstructItem.ItemIdentifier); + } + + if (it.OwnInventory is { } inventory) + { + foreach (Item inventoryItems in inventory.AllItems) + { + RegisterItem(inventoryItems.Prefab.Identifier); + } + } + + void RegisterItem(Identifier identifier) + { + if (itemCounts.ContainsKey(identifier)) + { + itemCounts[identifier]++; + return; + } + + itemCounts.Add(identifier, 1); + } + } + + foreach (var (it, child) in children) + { + if (!itemCounts.ContainsKey(it)) + { + outputDisplayListBox.RemoveChild(child); + } + } + + foreach (var (it, amount) in itemCounts) + { + if (!children.TryGetValue(it, out GUIComponent child)) + { + child = CreateOutputDisplayItem(it, outputDisplayListBox.Content); + } + + if (child is null) { continue; } + UpdateOutputDisplayItemCount(child, amount); + } + + if (addQuestionMark) + { + CreateQuestionMark(outputDisplayListBox.Content); + } + + static void CreateQuestionMark(GUIComponent parent) + { + GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null) + { + UserData = questionMarkUserData, + ToolTip = TextManager.Get("deconstructor.unknownitemsoutput") + }; + + GUIFrame questionMarkFrame = new GUIFrame(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), style: "GUIFrameListBox") + { + CanBeFocused = false, + }; + + // question mark text + new GUITextBlock(new RectTransform(Vector2.One, questionMarkFrame.RectTransform, anchor: Anchor.Center), text: "?", textAlignment: Alignment.Center, font: GUIStyle.LargeFont) + { + CanBeFocused = false + }; + } + + static GUIComponent CreateOutputDisplayItem(Identifier identifier, GUIComponent parent) + { + ItemPrefab prefab = ItemPrefab.Find(null, identifier); + if (prefab is null) { return null; } + + GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null) + { + UserData = identifier, + ToolTip = GetTooltip(prefab) + }; + + Sprite icon = prefab.InventoryIcon ?? prefab.Sprite; + Color iconColor = prefab.InventoryIcon is null ? prefab.SpriteColor : prefab.InventoryIconColor; + + GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true) + { + Color = iconColor, + CanBeFocused = false + }; + + // item count text + new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), itemIcon.RectTransform, anchor: Anchor.BottomRight), "", font: GUIStyle.Font, textAlignment: Alignment.BottomRight) + { + UserData = outputItemCountUserData, + Shadow = true, + CanBeFocused = false, + Padding = Vector4.Zero, + TextColor = Color.White, + }; + + return itemFrame; + } + + static void UpdateOutputDisplayItemCount(GUIComponent component, int count) + { + if (!(component.FindChild(outputItemCountUserData, recursive: true) is GUITextBlock textBlock)) { return; } + + textBlock.Text = TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString()); + } + + static RichString GetTooltip(ItemPrefab prefab) + { + LocalizedString toolTip = $"‖color:{Color.White.ToStringHex()}‖{prefab.Name}‖color:end‖"; + + LocalizedString description = prefab.Description; + if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; } + + if (prefab.ContentPackage != GameMain.VanillaContent && prefab.ContentPackage != null) + { + toolTip += $"\n‖color:{Color.MediumPurple.ToStringHex()}‖{prefab.ContentPackage.Name}‖color:end‖"; + } + + return RichString.Rich(toolTip); + } + } + partial void OnItemLoadedProjSpecific() { inputContainer.AllowUIOverlap = true; @@ -208,7 +391,7 @@ namespace Barotrauma.Items.Components overlayComponent.RectTransform.SetAsLastChild(); if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; } - + if (DeconstructItemsSimultaneously) { for (int i = 0; i < InputContainer.Inventory.Capacity; i++) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 371d3882b..5a917c369 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -246,6 +246,7 @@ namespace Barotrauma.Items.Components protected override void CreateGUI() { GuiFrame.ClearChildren(); + TryCreateDragHandle(); GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.CanBeFocused = true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs index 2798830ae..8c09ef322 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OutpostTerminal.cs @@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components public override void Update(float deltaTime, Camera cam) { - if (Character.Controlled?.SelectedConstruction != item) + if (Character.Controlled?.SelectedItem != item) { IsActive = false; return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs index d85bca980..8eed52986 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs @@ -871,13 +871,11 @@ namespace Barotrauma.Items.Components posToMaintain = item.Submarine.WorldPosition; } MaintainPos = true; - if (userdata is Vector2) + if (userdata is Vector2 nudgeAmount) { - Sonar sonar = item.GetComponent(); - Vector2 nudgeAmount = (Vector2)userdata; - if (sonar != null) + if (item.GetComponent() is Sonar sonar) { - nudgeAmount *= sonar == null ? 500.0f : 500.0f / sonar.Zoom; + nudgeAmount *= 500.0f / sonar.Zoom; } PosToMaintain += nudgeAmount; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index 156e1afc1..20fc3437e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -59,7 +59,7 @@ namespace Barotrauma.Items.Components public override bool ShouldDrawHUD(Character character) { if (item.HiddenInGame) { return false; } - if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; } + if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; } if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; } float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier; @@ -110,6 +110,7 @@ namespace Barotrauma.Items.Components if (GuiFrame != null) { GuiFrame.ClearChildren(); + TryCreateDragHandle(); CreateGUI(); } } @@ -265,7 +266,7 @@ namespace Barotrauma.Items.Components } } - if (CurrentFixer != null && CurrentFixer.SelectedConstruction == item) + if (CurrentFixer != null && CurrentFixer.SelectedItem == item) { if (repairSoundChannel == null || !repairSoundChannel.IsPlaying) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs index 541c54a63..c0d624511 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs @@ -33,10 +33,16 @@ namespace Barotrauma.Items.Components originalMaxSize = GuiFrame.RectTransform.MaxSize; originalRelativeSize = GuiFrame.RectTransform.RelativeSize; CheckForLabelOverlap(); - new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null) + var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null) { UserData = this }; + content.RectTransform.SetAsFirstChild(); + + //prevents inputs from going through the GUICustomComponent to the drag handle + var blocker = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) + { AbsoluteOffset = GUIStyle.ItemFrameOffset }, + style: null); } public void TriggerRewiringSound() @@ -62,7 +68,7 @@ namespace Barotrauma.Items.Components } rewireSoundTimer -= deltaTime; - if (user != null && user.SelectedConstruction == item && rewireSoundTimer > 0.0f) + if (user != null && user.SelectedItem == item && rewireSoundTimer > 0.0f) { if (rewireSoundChannel == null || !rewireSoundChannel.IsPlaying) { @@ -85,12 +91,12 @@ namespace Barotrauma.Items.Components public override bool ShouldDrawHUD(Character character) { - return character == Character.Controlled && character == user && character.SelectedConstruction == item; + return character == Character.Controlled && character == user && character.SelectedItem == item; } public override void UpdateHUD(Character character, float deltaTime, Camera cam) { - if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; } + if (character != Character.Controlled || character != user || character.SelectedItem != item) { return; } if (HighlightedWire != null) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs index e0b3a75f0..4c1ac0f07 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/Wire.cs @@ -311,7 +311,7 @@ namespace Barotrauma.Items.Components Wire equippedWire = Character.Controlled.HeldItems.FirstOrDefault(it => it.GetComponent() != null)?.GetComponent(); if (equippedWire != null && GUI.MouseOn == null) { - if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedConstruction == null) + if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedItem == null) { equippedWire.Use(1.0f, Character.Controlled); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index d4ad96681..b64fda77a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -364,7 +364,7 @@ namespace Barotrauma get { return Character.Controlled != null && - Character.Controlled.SelectedConstruction == null && + !Character.Controlled.HasSelectedAnyItem && CharacterHealth.OpenHealthWindow == null && DraggingItems.Any(); } @@ -924,9 +924,9 @@ namespace Barotrauma } } - if (Character.Controlled.SelectedConstruction != null) + if (Character.Controlled.SelectedItem != null) { - foreach (var ic in Character.Controlled.SelectedConstruction.ActiveHUDs) + foreach (var ic in Character.Controlled.SelectedItem.ActiveHUDs) { var itemContainer = ic as ItemContainer; if (itemContainer?.Inventory?.visualSlots == null) { continue; } @@ -1003,9 +1003,9 @@ namespace Barotrauma } } - if (character.SelectedConstruction != null) + if (character.SelectedItem != null) { - foreach (var ic in character.SelectedConstruction.ActiveHUDs) + foreach (var ic in character.SelectedItem.ActiveHUDs) { var itemContainer = ic as ItemContainer; if (itemContainer?.Inventory?.visualSlots == null) { continue; } @@ -1341,27 +1341,29 @@ namespace Barotrauma } else { - var rootOwner = (selectedSlot.ParentInventory?.Owner as Item)?.GetRootInventoryOwner(); - if (selectedSlot.ParentInventory?.Owner != Character.Controlled && - selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter && - selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction && - !(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) && - rootOwner != Character.Controlled && - rootOwner != Character.Controlled.SelectedCharacter && - rootOwner != Character.Controlled.SelectedConstruction && - !(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false)) + static bool OwnerInaccessible(Entity owner) => + owner != Character.Controlled && + owner != Character.Controlled.SelectedCharacter && + owner != Character.Controlled.SelectedItem && + (Character.Controlled.SelectedItem == null || !Character.Controlled.SelectedItem.linkedTo.Contains(owner)); + + Entity owner = selectedSlot.ParentInventory?.Owner; + Entity rootOwner = (owner as Item)?.GetRootInventoryOwner(); + if (OwnerInaccessible(owner) && (rootOwner == owner || OwnerInaccessible(rootOwner))) { return false; } - var parentItem = (selectedSlot?.ParentInventory?.Owner as Item) ?? selectedSlot?.Item; - if ((parentItem?.GetRootInventoryOwner() is Character ownerCharacter) && - ownerCharacter == Character.Controlled && - CharacterHealth.OpenHealthWindow?.Character != ownerCharacter && - ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface) && - Screen.Selected != GameMain.SubEditorScreen) + Item parentItem = (owner as Item) ?? selectedSlot?.Item; + if (parentItem?.GetRootInventoryOwner() is Character ownerCharacter) { - highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); - return false; + if (ownerCharacter == Character.Controlled && + CharacterHealth.OpenHealthWindow?.Character != ownerCharacter && + ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface) && + Screen.Selected != GameMain.SubEditorScreen) + { + highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); + return false; + } } } return true; @@ -1725,7 +1727,8 @@ namespace Barotrauma if (inventory != null && !inventory.Locked && Character.Controlled?.Inventory == inventory && - slot.InventoryKeyIndex != -1) + slot.InventoryKeyIndex != -1 && + slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length) { spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor); GUI.DrawString(spriteBatch, rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)), GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name, Color.Black, font: GUIStyle.HotkeyFont); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index 32610f313..7431e8dda 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -982,7 +982,7 @@ namespace Barotrauma List elementsToMove = new List(); if (editingHUD != null && editingHUD.UserData == this && - ((HasInGameEditableProperties && Character.Controlled?.SelectedConstruction == this) || Screen.Selected == GameMain.SubEditorScreen)) + ((HasInGameEditableProperties && Character.Controlled?.SelectedItem == this) || Screen.Selected == GameMain.SubEditorScreen)) { elementsToMove.Add(editingHUD); } @@ -1042,7 +1042,7 @@ namespace Barotrauma public void UpdateHUD(Camera cam, Character character, float deltaTime) { bool editingHUDCreated = false; - if ((HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) || + if ((HasInGameEditableProperties && (character.SelectedItem == this || EditableWhenEquipped)) || Screen.Selected == GameMain.SubEditorScreen) { GUIComponent prevEditingHUD = editingHUD; @@ -1126,7 +1126,7 @@ namespace Barotrauma foreach (Character otherCharacter in Character.CharacterList) { if (otherCharacter != character && - otherCharacter.SelectedConstruction == this) + otherCharacter.SelectedItem == this) { ItemInUseWarning.Visible = true; if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } @@ -1145,7 +1145,7 @@ namespace Barotrauma public void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character) { - if (HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) + if (HasInGameEditableProperties && (character.SelectedItem == this || EditableWhenEquipped)) { DrawEditing(spriteBatch, cam); } @@ -1215,6 +1215,7 @@ namespace Barotrauma if (ic.DisplayMsg.IsNullOrEmpty()) { continue; } if (!ic.CanBePicked && !ic.CanBeSelected) { continue; } if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; } + if (ic is ConnectionPanel connectionPanel && !connectionPanel.CanRewire()) { continue; } Color color = Color.Gray; if (ic.HasRequiredItems(character, false)) @@ -1246,15 +1247,15 @@ namespace Barotrauma } else { - if (HasInGameEditableProperties && Character.Controlled != null && (Character.Controlled.SelectedConstruction == this || EditableWhenEquipped)) + if (HasInGameEditableProperties && Character.Controlled != null && (Character.Controlled.SelectedItem == this || EditableWhenEquipped)) { if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(); } } } - if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this && GetComponent() == null) + if (Character.Controlled != null && Character.Controlled.SelectedItem != this && GetComponent() == null) { - if (Character.Controlled.SelectedConstruction?.GetComponent()?.TargetItem != this && + if (Character.Controlled.SelectedItem?.GetComponent()?.TargetItem != this && !Character.Controlled.HeldItems.Any(it => it.GetComponent()?.TargetItem == this)) { return; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs index 20ab00f8a..35e6f8385 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemInventory.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Linq; @@ -17,7 +16,7 @@ namespace Barotrauma { if (PlayerInput.KeyHit(InputType.Select)) { - Character.Controlled.SelectedConstruction = null; + Character.Controlled.SelectedItem = null; } }*/ } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs index 0b0bec78b..b07a40828 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/MapEntity.cs @@ -232,8 +232,7 @@ namespace Barotrauma { foreach (MapEntity e in mapEntityList) { - if (!e.SelectableInEditor) continue; - + if (!e.SelectableInEditor) { continue; } if (e.IsMouseOn(position)) { int i = 0; @@ -243,9 +242,7 @@ namespace Barotrauma { i++; } - highlightedEntities.Insert(i, e); - if (i == 0) highLightedEntity = e; } } @@ -741,7 +738,14 @@ namespace Barotrauma /// public static void DrawSelecting(SpriteBatch spriteBatch, Camera cam) { - if (GUI.MouseOn != null) return; + if (Screen.Selected is SubEditorScreen subEditor) + { + if (subEditor.IsMouseOnEditorGUI()) { return; } + } + else if (GUI.MouseOn != null) + { + return; + } Vector2 position = PlayerInput.MousePosition; position = cam.ScreenToWorld(position); @@ -1093,6 +1097,10 @@ namespace Barotrauma resizeDirY = y; resizing = true; startMovingPos = Vector2.Zero; + foreach (var mapEntity in mapEntityList) + { + if (mapEntity != this) { mapEntity.isHighlighted = false; } + } } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index e5a4c4b0d..532928c46 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -1202,6 +1202,8 @@ namespace Barotrauma.Networking connected = false; var prevContentPackages = clientPeer.ServerContentPackages; + //decrement lobby update ID to make sure we update the lobby when we reconnect + GameMain.NetLobbyScreen.LastUpdateID--; ConnectToServer(serverEndpoint, serverName); if (clientPeer != null) { @@ -3271,8 +3273,8 @@ namespace Barotrauma.Networking if (gameStarted && Screen.Selected == GameMain.GameScreen) { - var controller = Character.Controlled?.SelectedConstruction?.GetComponent(); - bool disableButtons = Character.Controlled != null && (controller != null && controller.HideHUD); + bool disableButtons = Character.Controlled?.SelectedItem?.GetComponent() is Controller c1 && c1.HideHUD || + Character.Controlled?.SelectedSecondaryItem?.GetComponent() is Controller c2 && c2.HideHUD; buttonContainer.Visible = !disableButtons; if (!GUI.DisableHUD && !GUI.DisableUpperHUD) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs index f1c611704..db7a406c7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipCapture.cs @@ -227,10 +227,13 @@ namespace Barotrauma.Networking bool allowEnqueue = overrideSound != null; if (GameMain.WindowActive && SettingsMenu.Instance is null) { - bool pttDown = PlayerInput.KeyDown(InputType.Voice) && GUI.KeyboardDispatcher.Subscriber == null; + bool usingActiveMode = PlayerInput.KeyDown(InputType.Voice); + bool usingLocalMode = PlayerInput.KeyDown(InputType.LocalVoice); + bool usingRadioMode = PlayerInput.KeyDown(InputType.RadioVoice); + bool pttDown = (usingActiveMode || usingLocalMode || usingRadioMode) && GUI.KeyboardDispatcher.Subscriber == null; if (pttDown || captureTimer <= 0) { - ForceLocal = GameMain.ActiveChatMode == ChatMode.Local; + ForceLocal = (usingActiveMode && GameMain.ActiveChatMode == ChatMode.Local) || usingLocalMode; } if (GameSettings.CurrentConfig.Audio.VoiceSetting == VoiceMode.Activity) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs index 8ce374562..eabc6004a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs @@ -121,10 +121,9 @@ namespace Barotrauma.Networking { client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange); } - if (messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters) - { - client.VoipSound.UseMuffleFilter = SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull); - } + client.VoipSound.UseMuffleFilter = + messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters && + SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull); } GameMain.NetLobbyScreen?.SetPlayerSpeaking(client); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs index 982106567..dfdd7342a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Particles/ParticleManager.cs @@ -3,7 +3,6 @@ using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Particles { @@ -217,6 +216,11 @@ namespace Barotrauma.Particles } } + public void ClearParticles() + { + particleCount = 0; + } + public void RemoveByPrefab(ParticlePrefab prefab) { if (particles == null) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs b/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs index 3aedb98d6..5b09b97aa 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs @@ -132,11 +132,11 @@ namespace Barotrauma } else if (a.MouseButton != MouseButton.None) { - return a.MouseButton == b.MouseButton; + return !(b is null) && a.MouseButton == b.MouseButton; } else { - return a.Key.Equals(b.Key); + return !(b is null) && a.Key.Equals(b.Key); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs index ba4d9eaa3..5fc6deb6c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CampaignUI.cs @@ -27,8 +27,6 @@ namespace Barotrauma private bool hasMaxMissions; - private GUIButton repairHullsButton, replaceShuttlesButton, repairItemsButton; - private SubmarineSelection submarineSelection; private Location selectedLocation; @@ -101,170 +99,6 @@ namespace Barotrauma tabs[(int)CampaignMode.InteractionType.Store] = storeTab; Store = new Store(this, storeTab); - // repair tab ------------------------------------------------------------------------- - - tabs[(int)CampaignMode.InteractionType.Repair] = CreateDefaultTabContainer(container, new Vector2(0.7f)); - var repairFrame = new GUIFrame(new RectTransform(Vector2.One, GetTabContainer(CampaignMode.InteractionType.Repair).RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f); - new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), repairFrame.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black * 0.7f) - { - UserData = "outerglow", - CanBeFocused = false - }; - - var repairContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), repairFrame.RectTransform, Anchor.Center)) - { - RelativeSpacing = 0.05f, - Stretch = true - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), repairContent.RectTransform), "", font: GUIStyle.LargeFont) - { - TextGetter = GetMoney - }; - - // repair hulls ----------------------------------------------- - - var repairHullsHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), repairContent.RectTransform), childAnchor: Anchor.TopRight) - { - RelativeSpacing = 0.05f, - Stretch = true - }; - new GUIImage(new RectTransform(new Vector2(0.3f, 1.0f), repairHullsHolder.RectTransform, Anchor.CenterLeft), "RepairHullButton") - { - IgnoreLayoutGroups = true, - CanBeFocused = false - }; - var repairHullsLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.3f), repairHullsHolder.RectTransform), TextManager.Get("RepairAllWalls"), textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont) - { - ForceUpperCase = ForceUpperCase.Yes - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), repairHullsHolder.RectTransform), CampaignMode.HullRepairCost.ToString(), textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont); - repairHullsButton = new GUIButton(new RectTransform(new Vector2(0.4f, 0.3f), repairHullsHolder.RectTransform) { MinSize = new Point(140, 0) }, TextManager.Get("Repair")) - { - OnClicked = (btn, userdata) => - { - if (Campaign.PurchasedHullRepairs) - { - Campaign.Wallet.Refund(CampaignMode.HullRepairCost); - Campaign.PurchasedHullRepairs = false; - } - else - { - if (Campaign.TryPurchase(null, CampaignMode.HullRepairCost)) - { - GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.HullRepairCost, GameAnalyticsManager.MoneySink.Service, "hullrepairs"); - Campaign.PurchasedHullRepairs = true; - } - } - GameMain.Client?.SendCampaignState(); - btn.GetChild().Selected = Campaign.PurchasedHullRepairs; - - return true; - } - }; - new GUITickBox(new RectTransform(new Vector2(0.65f), repairHullsButton.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(10, 0) }, "") - { - CanBeFocused = false - }; - - // repair items ------------------------------------------- - - var repairItemsHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), repairContent.RectTransform), childAnchor: Anchor.TopRight) - { - RelativeSpacing = 0.05f, - Stretch = true - }; - new GUIImage(new RectTransform(new Vector2(0.3f, 1.0f), repairItemsHolder.RectTransform, Anchor.CenterLeft), "RepairItemsButton") - { - IgnoreLayoutGroups = true, - CanBeFocused = false - }; - var repairItemsLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.3f), repairItemsHolder.RectTransform), TextManager.Get("RepairAllItems"), textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont) - { - ForceUpperCase = ForceUpperCase.Yes - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), repairItemsHolder.RectTransform), CampaignMode.ItemRepairCost.ToString(), textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont); - repairItemsButton = new GUIButton(new RectTransform(new Vector2(0.4f, 0.3f), repairItemsHolder.RectTransform) { MinSize = new Point(140, 0) }, TextManager.Get("Repair")) - { - OnClicked = (btn, userdata) => - { - if (Campaign.PurchasedItemRepairs) - { - Campaign.Wallet.Refund(CampaignMode.ItemRepairCost); - Campaign.PurchasedItemRepairs = false; - } - else - { - if (Campaign.TryPurchase(null, CampaignMode.ItemRepairCost)) - { - GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ItemRepairCost, GameAnalyticsManager.MoneySink.Service, "devicerepairs"); - Campaign.PurchasedItemRepairs = true; - } - } - GameMain.Client?.SendCampaignState(); - btn.GetChild().Selected = Campaign.PurchasedItemRepairs; - - return true; - } - }; - new GUITickBox(new RectTransform(new Vector2(0.65f), repairItemsButton.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(10, 0) }, "") - { - CanBeFocused = false - }; - - // replace lost shuttles ------------------------------------------- - - var replaceShuttlesHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), repairContent.RectTransform), childAnchor: Anchor.TopRight) - { - RelativeSpacing = 0.05f, - Stretch = true - }; - new GUIImage(new RectTransform(new Vector2(0.3f, 1.0f), replaceShuttlesHolder.RectTransform, Anchor.CenterLeft), "ReplaceShuttlesButton") - { - IgnoreLayoutGroups = true, - CanBeFocused = false - }; - var replaceShuttlesLabel = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.3f), replaceShuttlesHolder.RectTransform), TextManager.Get("ReplaceLostShuttles"), textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont) - { - ForceUpperCase = ForceUpperCase.Yes - }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), replaceShuttlesHolder.RectTransform), CampaignMode.ShuttleReplaceCost.ToString(), textAlignment: Alignment.Right, font: GUIStyle.SubHeadingFont); - replaceShuttlesButton = new GUIButton(new RectTransform(new Vector2(0.4f, 0.3f), replaceShuttlesHolder.RectTransform) { MinSize = new Point(140, 0) }, TextManager.Get("ReplaceShuttles")) - { - OnClicked = (btn, userdata) => - { - if (GameMain.GameSession?.SubmarineInfo != null && - GameMain.GameSession.SubmarineInfo.LeftBehindSubDockingPortOccupied) - { - new GUIMessageBox("", TextManager.Get("ReplaceShuttleDockingPortOccupied")); - return true; - } - - if (Campaign.PurchasedLostShuttles) - { - Campaign.Wallet.Refund(CampaignMode.ShuttleReplaceCost); - Campaign.PurchasedLostShuttles = false; - } - else - { - if (Campaign.TryPurchase(null, CampaignMode.ShuttleReplaceCost)) - { - GameAnalyticsManager.AddMoneySpentEvent(CampaignMode.ShuttleReplaceCost, GameAnalyticsManager.MoneySink.Service, "retrieveshuttle"); - Campaign.PurchasedLostShuttles = true; - } - } - GameMain.Client?.SendCampaignState(); - btn.GetChild().Selected = Campaign.PurchasedLostShuttles; - - return true; - } - }; - new GUITickBox(new RectTransform(new Vector2(0.65f), replaceShuttlesButton.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(10, 0) }, "") - { - CanBeFocused = false - }; - GUITextBlock.AutoScaleAndNormalize(repairHullsLabel, repairItemsLabel, replaceShuttlesLabel); - GUITextBlock.AutoScaleAndNormalize(repairHullsButton.GetChild().TextBlock, repairItemsButton.GetChild().TextBlock, replaceShuttlesButton.GetChild().TextBlock); - // upgrade tab ------------------------------------------------------------------------- tabs[(int)CampaignMode.InteractionType.Upgrade] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f); @@ -701,26 +535,6 @@ namespace Barotrauma switch (selectedTab) { - case CampaignMode.InteractionType.Repair: - repairHullsButton.Enabled = - (Campaign.PurchasedHullRepairs || Campaign.Wallet.CanAfford(CampaignMode.HullRepairCost)); - repairHullsButton.GetChild().Selected = Campaign.PurchasedHullRepairs; - repairItemsButton.Enabled = - (Campaign.PurchasedItemRepairs || Campaign.Wallet.CanAfford(CampaignMode.ItemRepairCost)); - repairItemsButton.GetChild().Selected = Campaign.PurchasedItemRepairs; - - if (GameMain.GameSession?.SubmarineInfo == null || !GameMain.GameSession.SubmarineInfo.SubsLeftBehind) - { - replaceShuttlesButton.Enabled = false; - replaceShuttlesButton.GetChild().Selected = false; - } - else - { - replaceShuttlesButton.Enabled = - (Campaign.PurchasedLostShuttles || Campaign.Wallet.CanAfford(CampaignMode.ShuttleReplaceCost)); - replaceShuttlesButton.GetChild().Selected = Campaign.PurchasedLostShuttles; - } - break; case CampaignMode.InteractionType.Store: Store.SelectStore(storeIdentifier); break; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs index 11105e73a..668226715 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/CharacterEditor/CharacterEditorScreen.cs @@ -2606,8 +2606,8 @@ namespace Barotrauma.CharacterEditor animationControls = new GUIFrame(new RectTransform(Vector2.One, centerArea.RectTransform), style: null) { CanBeFocused = false }; var layoutGroupAnimation = new GUILayoutGroup(new RectTransform(Vector2.One, animationControls.RectTransform), childAnchor: Anchor.TopLeft) { CanBeFocused = false }; var animationSelectionElement = new GUIFrame(new RectTransform(new Point(elementSize.X * 2 - (int)(5 * GUI.xScale), elementSize.Y), layoutGroupAnimation.RectTransform), style: null); - var animationSelectionText = new GUITextBlock(new RectTransform(new Point(elementSize.X, elementSize.Y), animationSelectionElement.RectTransform), GetCharacterEditorTranslation("SelectedAnimation") + ": ", Color.WhiteSmoke, textAlignment: Alignment.Center); - animSelection = new GUIDropDown(new RectTransform(new Point((int)(100 * GUI.xScale), elementSize.Y), animationSelectionElement.RectTransform, Anchor.TopRight), elementCount: 5); + var animationSelectionText = new GUITextBlock(new RectTransform(new Point(elementSize.X, elementSize.Y), animationSelectionElement.RectTransform), GetCharacterEditorTranslation("SelectedAnimation"), Color.WhiteSmoke, textAlignment: Alignment.CenterRight); + animSelection = new GUIDropDown(new RectTransform(new Point((int)(150 * GUI.xScale), elementSize.Y), animationSelectionElement.RectTransform, Anchor.Center, Pivot.CenterLeft), elementCount: 5); if (character.AnimController.CanWalk) { animSelection.AddItem(AnimationType.Walk.ToString(), AnimationType.Walk); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index f3783a78a..90f0e3f1b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -1,11 +1,10 @@ +using Barotrauma.Extensions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using System; -using FarseerPhysics; using System.Diagnostics; using System.Linq; -using Barotrauma.Extensions; namespace Barotrauma { @@ -79,21 +78,27 @@ namespace Barotrauma public override void AddToGUIUpdateList() { - if (Character.Controlled != null && Character.Controlled.SelectedConstruction != null && Character.Controlled.CanInteractWith(Character.Controlled.SelectedConstruction)) + if (Character.Controlled != null) { - Character.Controlled.SelectedConstruction.AddToGUIUpdateList(); - } - if (Character.Controlled?.Inventory != null) - { - foreach (Item item in Character.Controlled.Inventory.AllItems) + if (Character.Controlled.SelectedItem is { } selectedItem && Character.Controlled.CanInteractWith(selectedItem)) { - if (Character.Controlled.HasEquippedItem(item)) + selectedItem.AddToGUIUpdateList(); + } + if (Character.Controlled.SelectedSecondaryItem is { } selectedSecondaryItem && Character.Controlled.CanInteractWith(selectedSecondaryItem)) + { + selectedSecondaryItem.AddToGUIUpdateList(); + } + if (Character.Controlled.Inventory != null) + { + foreach (Item item in Character.Controlled.Inventory.AllItems) { - item.AddToGUIUpdateList(); + if (Character.Controlled.HasEquippedItem(item)) + { + item.AddToGUIUpdateList(); + } } } } - GameMain.GameSession?.AddToGUIUpdateList(); Character.AddAllToGUIUpdateList(); } @@ -260,11 +265,7 @@ namespace Barotrauma //Draw the rest of the structures, characters and front structures spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform); Submarine.DrawBack(spriteBatch, false, e => !(e is Structure) || e.SpriteDepth < 0.9f); - foreach (Character c in Character.CharacterList) - { - if (!c.IsVisible || c.AnimController.Limbs.Any(l => l.DeformSprite != null)) { continue; } - c.Draw(spriteBatch, Cam); - } + DrawCharacters(deformed: false, firstPass: true); spriteBatch.End(); sw.Stop(); @@ -272,11 +273,12 @@ namespace Barotrauma sw.Restart(); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform); - DrawDeformed(firstPass: true); - DrawDeformed(firstPass: false); + DrawCharacters(deformed: true, firstPass: true); + DrawCharacters(deformed: true, firstPass: false); + DrawCharacters(deformed: false, firstPass: false); spriteBatch.End(); - void DrawDeformed(bool firstPass) + void DrawCharacters(bool deformed, bool firstPass) { //backwards order to render the most recently spawned characters in front (characters spawned later have a larger sprite depth) for (int i = Character.CharacterList.Count - 1; i >= 0; i--) @@ -284,7 +286,14 @@ namespace Barotrauma Character c = Character.CharacterList[i]; if (!c.IsVisible) { continue; } if (c.Params.DrawLast == firstPass) { continue; } - if (c.AnimController.Limbs.All(l => l.DeformSprite == null)) { continue; } + if (deformed) + { + if (c.AnimController.Limbs.All(l => l.DeformSprite == null)) { continue; } + } + else + { + if (c.AnimController.Limbs.Any(l => l.DeformSprite != null)) { continue; } + } c.Draw(spriteBatch, Cam); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs index b61b535f0..303b9d94f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen.cs @@ -695,8 +695,13 @@ namespace Barotrauma gamesession.StartRound(fixedSeed ? "abcd" : ToolBox.RandomSeed(8), difficulty, levelGenerationParams); GameMain.GameScreen.Select(); // TODO: modding support - string[] jobIdentifiers = new string[] { "captain", "engineer", "mechanic", "securityofficer", "medicaldoctor" }; - foreach (string job in jobIdentifiers) + Identifier[] jobIdentifiers = new Identifier[] { + "captain".ToIdentifier(), + "engineer".ToIdentifier(), + "mechanic".ToIdentifier(), + "securityofficer".ToIdentifier(), + "medicaldoctor".ToIdentifier() }; + foreach (Identifier job in jobIdentifiers) { var jobPrefab = JobPrefab.Get(job); var variant = Rand.Range(0, jobPrefab.Variants); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs index 356b8878c..cdce5489e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/Screen.cs @@ -55,9 +55,7 @@ namespace Barotrauma while (timer < duration) { GUI.ScreenOverlayColor = Color.Lerp(from, to, Math.Min(timer / duration, 1.0f)); - - timer += CoroutineManager.UnscaledDeltaTime; - + timer += CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 66c404987..11ae9f941 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -813,8 +813,13 @@ namespace Barotrauma var itemCount = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1.0f), itemCountText.RectTransform, Anchor.TopRight, Pivot.TopLeft), "", textAlignment: Alignment.CenterRight); itemCount.TextGetter = () => { - itemCount.TextColor = Item.ItemList.Count > MaxItems ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, Item.ItemList.Count / (float)MaxItems); - return Item.ItemList.Count.ToString(); + int count = Item.ItemList.Count; + if (dummyCharacter?.Inventory != null) + { + count -= dummyCharacter.Inventory.AllItems.Count(); + } + itemCount.TextColor = count > MaxItems ? GUIStyle.Red : Color.Lerp(GUIStyle.Green, GUIStyle.Orange, count / (float)MaxItems); + return count.ToString(); }; var structureCountText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 0.0f), paddedEntityCountPanel.RectTransform), TextManager.Get("Structures"), @@ -1497,7 +1502,7 @@ namespace Barotrauma /// private static IEnumerable AutoSaveCoroutine() { - DateTime target = DateTime.Now.AddMinutes(GameSettings.CurrentConfig.AutoSaveIntervalSeconds); + DateTime target = DateTime.Now.AddSeconds(GameSettings.CurrentConfig.AutoSaveIntervalSeconds); DateTime tempTarget = DateTime.Now; bool wasPaused = false; @@ -1549,7 +1554,9 @@ namespace Barotrauma MapEntity.DeselectAll(); ClearUndoBuffer(); +#if !DEBUG DebugConsole.DeactivateCheats(); +#endif SetMode(Mode.Default); @@ -3337,23 +3344,21 @@ namespace Barotrauma { if (!(userData is XElement element)) { return; } - #warning TODO: revise +#warning TODO: revise string filePath = element.GetAttributeStringUnrestricted("file", ""); if (string.IsNullOrWhiteSpace(filePath)) { return; } var loadedSub = Submarine.Load(new SubmarineInfo(filePath), true); - // set the submarine file path to the "default" value - var unspecifiedFileName = TextManager.Get("UnspecifiedSubFileName"); - loadedSub.Info.FilePath = Path.Combine(ContentPackage.LocalModsDir, unspecifiedFileName.Value, $"{unspecifiedFileName}.sub"); - loadedSub.Info.Name = unspecifiedFileName.Value; try { - loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString("name", loadedSub.Info.Name); + loadedSub.Info.Name = loadedSub.Info.SubmarineElement.GetAttributeString("name", loadedSub.Info.Name); } catch (Exception e) { DebugConsole.ThrowError("Failed to find a name for the submarine.", e); + var unspecifiedFileName = TextManager.Get("UnspecifiedSubFileName"); + loadedSub.Info.Name = unspecifiedFileName.Value; } MainSub = loadedSub; MainSub.SetPrevTransform(MainSub.Position); @@ -3726,7 +3731,6 @@ namespace Barotrauma } else { - List availableLayerOptions = new List { new ContextMenuOption("editor.layer.nolayer", true, onSelected: () => { MoveToLayer(null, targets); }) @@ -3769,7 +3773,8 @@ namespace Barotrauma { if (!me.Removed) { me.Remove(); } } - })); + }), + new ContextMenuOption(TextManager.Get("editortip.shiftforextraoptions") + '\n' + TextManager.Get("editortip.altforruler"), isEnabled: false, onSelected: null)); } } @@ -4257,7 +4262,7 @@ namespace Barotrauma MapEntity.SelectedList.Clear(); MapEntity.FilteredSelectedList.Clear(); MapEntity.SelectEntity(itemContainer); - dummyCharacter.SelectedConstruction = itemContainer; + dummyCharacter.SelectedItem = itemContainer; FilterEntities(entityFilterBox.Text); } @@ -4268,9 +4273,9 @@ namespace Barotrauma { if (dummyCharacter == null) { return; } //nothing to close -> return - if (DraggedItemPrefab == null && dummyCharacter?.SelectedConstruction == null && OpenedItem == null) { return; } + if (DraggedItemPrefab == null && dummyCharacter?.SelectedItem == null && OpenedItem == null) { return; } DraggedItemPrefab = null; - dummyCharacter.SelectedConstruction = null; + dummyCharacter.SelectedItem = null; OpenedItem?.Drop(dummyCharacter); OpenedItem?.SetTransform(oldItemPosition, 0f); OpenedItem = null; @@ -4347,9 +4352,9 @@ namespace Barotrauma } } - if (dummyCharacter?.SelectedConstruction != null) + if (dummyCharacter?.SelectedItem != null) { - var inv = dummyCharacter?.SelectedConstruction?.OwnInventory; + var inv = dummyCharacter?.SelectedItem?.OwnInventory; if (inv != null) { switch (obj) @@ -4779,9 +4784,9 @@ namespace Barotrauma if (dummyCharacter != null) { CharacterHUD.AddToGUIUpdateList(dummyCharacter); - if (dummyCharacter.SelectedConstruction != null) + if (dummyCharacter.SelectedItem != null) { - dummyCharacter.SelectedConstruction.AddToGUIUpdateList(); + dummyCharacter.SelectedItem.AddToGUIUpdateList(); } else if (WiringMode && MapEntity.SelectedList.FirstOrDefault() is Item item && item.GetComponent() != null) { @@ -4801,7 +4806,7 @@ namespace Barotrauma /// /// GUI.MouseOn doesn't get updated while holding primary mouse and we need it to /// - private bool IsMouseOnEditorGUI() + public bool IsMouseOnEditorGUI() { if (GUI.MouseOn == null) { return false; } @@ -5143,7 +5148,7 @@ namespace Barotrauma { if (dummyCharacter != null) { - if (dummyCharacter.SelectedConstruction == null) + if (dummyCharacter.SelectedItem == null) { foreach (var entity in MapEntity.mapEntityList) { @@ -5285,7 +5290,7 @@ namespace Barotrauma me.IsHighlighted = false; } - if (dummyCharacter.SelectedConstruction == null) + if (dummyCharacter.SelectedItem == null) { List wires = new List(); foreach (Item item in Item.ItemList) @@ -5308,8 +5313,8 @@ namespace Barotrauma }); } - if (dummyCharacter.SelectedConstruction == null || - dummyCharacter.SelectedConstruction.GetComponent() != null) + if (dummyCharacter.SelectedItem == null || + dummyCharacter.SelectedItem.GetComponent() != null) { if (WiringMode && PlayerInput.IsShiftDown()) { @@ -5341,7 +5346,7 @@ namespace Barotrauma TeleportDummyCharacter(oldItemPosition); } - if (WiringMode && dummyCharacter?.SelectedConstruction == null) + if (WiringMode && dummyCharacter?.SelectedItem == null) { TeleportDummyCharacter(FarseerPhysics.ConvertUnits.ToSimUnits(dummyCharacter.CursorPosition)); } @@ -5358,7 +5363,7 @@ namespace Barotrauma } // Deposit item from our "infinite stack" into inventory slots - var inv = dummyCharacter?.SelectedConstruction?.OwnInventory; + var inv = dummyCharacter?.SelectedItem?.OwnInventory; if (inv?.visualSlots != null && !PlayerInput.IsCtrlDown()) { var dragginMouse = MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, MouseDragStart) >= GUI.Scale * 20; @@ -5524,7 +5529,7 @@ namespace Barotrauma } if (!saveAssemblyFrame.Rect.Contains(PlayerInput.MousePosition) && !snapToGridFrame.Rect.Contains(PlayerInput.MousePosition) && - dummyCharacter?.SelectedConstruction == null && !WiringMode && GUI.MouseOn == null) + dummyCharacter?.SelectedItem == null && !WiringMode && GUI.MouseOn == null) { if (layerList is { Visible: true } && GUI.KeyboardDispatcher.Subscriber == layerList) { @@ -5549,7 +5554,7 @@ namespace Barotrauma if (!WiringMode) { - bool shouldCloseHud = dummyCharacter?.SelectedConstruction != null && HUD.CloseHUD(dummyCharacter.SelectedConstruction.Rect) && DraggedItemPrefab == null; + bool shouldCloseHud = dummyCharacter?.SelectedItem != null && HUD.CloseHUD(dummyCharacter.SelectedItem.Rect) && DraggedItemPrefab == null; if (MapEntityPrefab.Selected != null && GUI.MouseOn == null) { @@ -5565,7 +5570,7 @@ namespace Barotrauma } else { - if (dummyCharacter?.SelectedConstruction == null) + if (dummyCharacter?.SelectedItem == null) { CreateContextMenu(); } @@ -5622,11 +5627,11 @@ namespace Barotrauma wire?.Update((float)deltaTime, cam); } - if (dummyCharacter.SelectedConstruction != null) + if (dummyCharacter.SelectedItem != null) { - if (MapEntity.SelectedList.Contains(dummyCharacter.SelectedConstruction) || WiringMode) + if (MapEntity.SelectedList.Contains(dummyCharacter.SelectedItem) || WiringMode) { - dummyCharacter.SelectedConstruction?.UpdateHUD(cam, dummyCharacter, (float)deltaTime); + dummyCharacter.SelectedItem?.UpdateHUD(cam, dummyCharacter, (float)deltaTime); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs index 3d95b1bbb..abfbb86e2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/TestScreen.cs @@ -1,5 +1,4 @@ #nullable enable -using System; using System.Linq; using Barotrauma.Extensions; using Barotrauma.Items.Components; @@ -14,18 +13,14 @@ using Microsoft.Xna.Framework.Graphics; */ namespace Barotrauma { - class TestScreen : EditorScreen + internal sealed class TestScreen : EditorScreen { public override Camera Cam { get; } private Item? miniMapItem; - private Submarine? submarine; public static Character? dummyCharacter; public static Effect? BlueprintEffect; - private GUIFrame? container; - - private TabMenu? tabMenu; public TestScreen() { @@ -43,14 +38,11 @@ namespace Barotrauma return true; } }; - } public override void Select() { base.Select(); - container = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black); - var tab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f); if (dummyCharacter is { Removed: false }) { dummyCharacter?.Remove(); @@ -61,30 +53,50 @@ namespace Barotrauma dummyCharacter.Info.Name = "Galldren"; dummyCharacter.Inventory.CreateSlots(); + miniMapItem = new Item(ItemPrefab.Find(null, "deconstructor".ToIdentifier()), Vector2.Zero, null, 1337, false); + + foreach (ItemComponent component in miniMapItem.Components) + { + component.OnItemLoaded(); + } Character.Controlled = dummyCharacter; GameMain.World.ProcessChanges(); - tabMenu = new TabMenu(); } public override void AddToGUIUpdateList() { Frame.AddToGUIUpdateList(); - container?.AddToGUIUpdateList(); - tabMenu?.AddToGUIUpdateList(); - // CharacterHUD.AddToGUIUpdateList(dummyCharacter); - // dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList(); + CharacterHUD.AddToGUIUpdateList(dummyCharacter); + dummyCharacter?.SelectedItem?.AddToGUIUpdateList(); } public override void Update(double deltaTime) { base.Update(deltaTime); - if (dummyCharacter is { } dummy) + if (dummyCharacter is { } dummy && miniMapItem is { } item) { + if (dummy.SelectedItem != item) + { + dummy.SelectedItem = item; + } + + dummy.SelectedItem?.UpdateHUD(Cam, dummy, (float)deltaTime); + Vector2 pos = FarseerPhysics.ConvertUnits.ToSimUnits(item.Position); + + foreach (Limb limb in dummy.AnimController.Limbs) + { + limb.body.SetTransform(pos, 0.0f); + } + + if (dummy.AnimController?.Collider is { } collider) + { + collider.SetTransform(pos, 0); + } + dummy.ControlLocalPlayer((float)deltaTime, Cam, false); dummy.Control((float)deltaTime, Cam); } - tabMenu?.Update((float)deltaTime); } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) @@ -93,12 +105,13 @@ namespace Barotrauma graphics.Clear(BackgroundColor); spriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: Cam.Transform); - // miniMapItem?.Draw(spriteBatch, false); - // if (dummyCharacter is { } dummy) - // { - // dummyCharacter.DrawFront(spriteBatch, Cam); - // dummyCharacter.Draw(spriteBatch, Cam); - // } + miniMapItem?.Draw(spriteBatch, false); + if (dummyCharacter is { } dummy) + { + dummyCharacter.DrawFront(spriteBatch, Cam); + dummyCharacter.Draw(spriteBatch, Cam); + } + spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 636331079..b7e2d7744 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -1448,7 +1448,9 @@ namespace Barotrauma var component = otherComponents[componentIndex]; Debug.Assert(component.GetType() == parentObject.GetType()); SafeAdd(component, property); - if (value is string stringValue && Enum.TryParse(property.PropertyType, stringValue, out var enumValue)) + if (value is string stringValue && + property.PropertyType.IsEnum && + Enum.TryParse(property.PropertyType, stringValue, out var enumValue)) { property.PropertyInfo.SetValue(component, enumValue); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs index 9cf545dc8..b11d5eaf1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs @@ -26,7 +26,9 @@ namespace Barotrauma Gameplay, Mods } - + + public Tab CurrentTab { get; private set; } + private GameSettings.Config unsavedConfig; private readonly GUIFrame mainFrame; @@ -37,7 +39,13 @@ namespace Barotrauma public readonly WorkshopMenu WorkshopMenu; - private static readonly ImmutableHashSet LegacyInputTypes = new List() { InputType.Chat, InputType.RadioChat }.ToImmutableHashSet(); + private static readonly ImmutableHashSet LegacyInputTypes = new List() + { + InputType.Chat, + InputType.RadioChat, + InputType.LocalVoice, + InputType.RadioVoice, + }.ToImmutableHashSet(); public static SettingsMenu Create(RectTransform mainParent) { @@ -97,6 +105,7 @@ namespace Barotrauma public void SelectTab(Tab tab) { + CurrentTab = tab; SwitchContent(tabContents[tab].Content); tabber.Children.ForEach(c => { @@ -764,27 +773,35 @@ namespace Barotrauma private void CreateBottomButtons() { - GUIButton cancelButton = - new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), bottom.RectTransform), text: TextManager.Get("Cancel")) + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), bottom.RectTransform), text: TextManager.Get("Cancel")) + { + OnClicked = (btn, obj) => { - OnClicked = (btn, obj) => - { - Close(); - return false; - } - }; - GUIButton applyButton = - new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), bottom.RectTransform), text: TextManager.Get("applysettingsbutton")) + Close(); + return false; + } + }; + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), bottom.RectTransform), text: TextManager.Get("applysettingsbutton")) + { + OnClicked = (btn, obj) => { - OnClicked = (btn, obj) => + GameSettings.SetCurrentConfig(unsavedConfig); + if (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu && + mutableWorkshopMenu.CurrentTab == MutableWorkshopMenu.Tab.InstalledMods) { - GameSettings.SetCurrentConfig(unsavedConfig); - if (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu) { mutableWorkshopMenu.Apply(); } - GameSettings.SaveCurrentConfig(); - mainFrame.Flash(color: GUIStyle.Green); - return false; + mutableWorkshopMenu.Apply(); } - }; + GameSettings.SaveCurrentConfig(); + mainFrame.Flash(color: GUIStyle.Green); + return false; + }, + OnAddedToGUIUpdateList = (GUIComponent component) => + { + component.Enabled = + CurrentTab != Tab.Mods || + (WorkshopMenu is MutableWorkshopMenu mutableWorkshopMenu && mutableWorkshopMenu.CurrentTab == MutableWorkshopMenu.Tab.InstalledMods && !mutableWorkshopMenu.ViewingItemDetails); + } + }; } public void Close() diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs index a2418f455..ec631166a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sounds/SoundManager.cs @@ -368,7 +368,7 @@ namespace Barotrauma.Sounds string filePath = overrideFilePath ?? element.GetAttributeContentPath("file")?.Value ?? ""; if (!File.Exists(filePath)) { - throw new System.IO.FileNotFoundException("Sound file \"" + filePath + "\" doesn't exist!"); + throw new System.IO.FileNotFoundException($"Sound file \"{filePath}\" doesn't exist! Content package \"{(element.ContentPackage?.Name ?? "Unknown")}\"."); } var newSound = new OggSound(this, filePath, stream, xElement: element); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs index d4e1648a3..1b1aae8c0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Sprite/Sprite.cs @@ -253,12 +253,18 @@ namespace Barotrauma if (flipHorizontal) { float diff = targetSize.X % (sourceRect.Width * scale.X); - flippedDrawOffset.X = (int)((sourceRect.Width * scale.X - diff) / scale.X); + flippedDrawOffset.X = (sourceRect.Width * scale.X - diff) / scale.X; + flippedDrawOffset.X = + MathUtils.NearlyEqual(flippedDrawOffset.X, MathF.Round(flippedDrawOffset.X)) ? + MathF.Round(flippedDrawOffset.X) : flippedDrawOffset.X; } if (flipVertical) { float diff = targetSize.Y % (sourceRect.Height * scale.Y); - flippedDrawOffset.Y = (int)((sourceRect.Height * scale.Y - diff) / scale.Y); + flippedDrawOffset.Y = (sourceRect.Height * scale.Y - diff) / scale.Y; + flippedDrawOffset.Y = + MathUtils.NearlyEqual(flippedDrawOffset.Y, MathF.Round(flippedDrawOffset.Y)) ? + MathF.Round(flippedDrawOffset.Y) : flippedDrawOffset.Y; } drawOffset += flippedDrawOffset; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs index 03760d937..1243e39d7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/Lobby.cs @@ -395,8 +395,8 @@ namespace Barotrauma.Steam if (rules.ContainsKey("allowspectating")) { serverInfo.AllowSpectating = rules["allowspectating"] == "True"; } if (rules.ContainsKey("allowrespawn")) { serverInfo.AllowRespawn = rules["allowrespawn"] == "True"; } if (rules.ContainsKey("voicechatenabled")) { serverInfo.VoipEnabled = rules["voicechatenabled"] == "True"; } - if (rules.ContainsKey("friendlyfireenabled")) { serverInfo.AllowRespawn = rules["friendlyfireenabled"] == "True"; } - if (rules.ContainsKey("karmaenabled")) { serverInfo.VoipEnabled = rules["karmaenabled"] == "True"; } + if (rules.ContainsKey("friendlyfireenabled")) { serverInfo.FriendlyFireEnabled = rules["friendlyfireenabled"] == "True"; } + if (rules.ContainsKey("karmaenabled")) { serverInfo.KarmaEnabled = rules["karmaenabled"] == "True"; } if (rules.ContainsKey("traitors")) { if (Enum.TryParse(rules["traitors"], out YesNoMaybe traitorsEnabled)) { serverInfo.TraitorsEnabled = traitorsEnabled; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/InstalledTab.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/InstalledTab.cs index 2e5953ae5..32482cc76 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/InstalledTab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/InstalledTab.cs @@ -13,6 +13,8 @@ namespace Barotrauma.Steam { private CorePackage EnabledCorePackage => enabledCoreDropdown.SelectedData as CorePackage ?? throw new Exception("Valid core package not selected"); + public bool ViewingItemDetails { get; private set; } + private readonly GUIDropDown enabledCoreDropdown; private readonly GUIListBox enabledRegularModsList; private readonly GUIListBox disabledRegularModsList; @@ -167,7 +169,7 @@ namespace Barotrauma.Steam swapSoundType = null; } } - + private void CreateInstalledModsTab( out GUIDropDown enabledCoreDropdown, out GUIListBox enabledRegularModsList, @@ -523,6 +525,7 @@ namespace Barotrauma.Steam public void PopulateInstalledModLists(bool forceRefreshEnabled = false, bool refreshDisabled = true) { + ViewingItemDetails = false; bulkUpdateButton.Enabled = false; bulkUpdateButton.ToolTip = ""; ContentPackageManager.UpdateContentPackageList(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs index e4e155950..351ebb833 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs @@ -283,6 +283,7 @@ namespace Barotrauma.Steam { CanBeFocused = false }; + unpublishedLayout.Recalculate(); } if (publishedGuiComponents.Any()) @@ -456,6 +457,7 @@ namespace Barotrauma.Steam { CreateSubscribeButton(workshopItem, new RectTransform(Vector2.One, itemLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), spriteScale: 0.4f); } + itemLayout.Recalculate(); } onFill?.Invoke(workshopItems); }); @@ -550,6 +552,7 @@ namespace Barotrauma.Steam private void PopulateFrameWithItemInfo(Steamworks.Ugc.Item workshopItem, GUIFrame parentFrame) { + ViewingItemDetails = true; taskCancelSrc = taskCancelSrc.IsCancellationRequested ? new CancellationTokenSource() : taskCancelSrc; var contentPackage diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs index c8563eb3c..1a24d958e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/MutableWorkshopMenu.cs @@ -1,12 +1,9 @@ #nullable enable using Barotrauma.Extensions; using Microsoft.Xna.Framework; -using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using System.Threading.Tasks; -using ItemOrPackage = Barotrauma.Either; namespace Barotrauma.Steam { @@ -28,6 +25,8 @@ namespace Barotrauma.Steam ShowOnlySubs, ShowOnlyItemAssemblies } + + public Tab CurrentTab { get; private set; } private readonly GUILayoutGroup tabber; private readonly Dictionary tabContents; @@ -78,6 +77,7 @@ namespace Barotrauma.Steam public void SelectTab(Tab tab) { + CurrentTab = tab; SwitchContent(tabContents[tab].Content); tabber.Children.ForEach(c => { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs b/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs index b4662d858..f1fc1456d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Utils/LocalizationCSVtoXML.cs @@ -1,30 +1,75 @@ #if DEBUG +using Barotrauma.IO; using System; using System.Collections.Generic; -using Barotrauma.IO; -using System.Text; -using System.Text.RegularExpressions; using System.Linq; -using System.Globalization; +using System.Text; namespace Barotrauma { class LocalizationCSVtoXML { - private static Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"])*\"|[^,]*)", RegexOptions.Compiled); // Handling commas inside data fields surrounded by "" - private static List conversationClosingIndent = new List(); - private static char[] separator = new char[1] { '|' }; + private static readonly List conversationClosingIndent = new List(); + private static readonly char[] separator = new char[1] { '|' }; private const string conversationsPath = "Content/NPCConversations"; private const string infoTextPath = "Content/Texts"; private const string xmlHeader = ""; - private static string[,] translatedLanguageNames = new string[13, 2] { { "English", "English" }, { "French", "Français" }, { "German", "Deutsch" }, + private static readonly string[,] translatedLanguageNames = new string[13, 2] { { "English", "English" }, { "French", "Français" }, { "German", "Deutsch" }, { "Russian", "Русский" }, { "Brazilian Portuguese", "Português brasileiro" }, { "Simplified Chinese", "中文(简体)" }, { "Traditional Chinese", "中文(繁體)" }, { "Castilian Spanish", "Castellano" }, { "Latinamerican Spanish", "Español Latinoamericano" }, { "Polish", "Polski" }, { "Turkish", "Türkçe" }, { "Japanese", "日本語" }, { "Korean", "한국어" } }; - public static void Convert() + public static void ConvertMasterLocalizationKit(string outputTextsDirectory, string outputConversationsDirectory, bool convertConversations) + { + string textFilePath = Path.Combine(infoTextPath, "Texts.csv"); + string conversationFilePath = Path.Combine(infoTextPath, "NPCConversations.csv"); + + Dictionary> xmlContent; + try + { + xmlContent = ConvertInfoTextToXML(File.ReadAllLines(textFilePath, Encoding.UTF8)); + } + catch (Exception e) + { + DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath, e); + return; + } + if (xmlContent == null) + { + DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + textFilePath); + return; + } + foreach (string language in xmlContent.Keys) + { + string languageNoWhitespace = language.Replace(" ", ""); + string xmlFileFullPath = Path.Combine(outputTextsDirectory, $"{languageNoWhitespace}/{languageNoWhitespace}Vanilla.xml"); + File.WriteAllLines(xmlFileFullPath, xmlContent[language], Encoding.UTF8); + DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath); + } + + if (convertConversations) + { + var conversationLinesAll = File.ReadAllLines(conversationFilePath, Encoding.UTF8); + foreach (string language in xmlContent.Keys) + { + List convXmlContent = ConvertConversationsToXML(conversationLinesAll, language); + if (convXmlContent == null) + { + DebugConsole.ThrowError("NPCConversation Localization .csv to .xml conversion failed for: " + language); + continue; + } + string languageNoWhitespace = language.Replace(" ", ""); + string xmlFileFullPath = Path.Combine(outputTextsDirectory, $"NpcConversations_{languageNoWhitespace}.xml"); + File.WriteAllLines(xmlFileFullPath, convXmlContent, Encoding.UTF8); + DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath); + } + } + } + + [Obsolete] + public static void ConvertIndividualFiles() { if (GameSettings.CurrentConfig.Language != TextManager.DefaultLanguage) { @@ -89,8 +134,7 @@ namespace Barotrauma for (int j = 0; j < infoTextFiles.Count; j++) { - - List xmlContent = null; + List xmlContent; try { xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[j], Encoding.UTF8), language); @@ -121,6 +165,109 @@ namespace Barotrauma } } + private static Dictionary> ConvertInfoTextToXML(string[] csvContent) + { + Dictionary> xmlContentByLanguage = new Dictionary>(); + + //get all the languages from the header row + string headerRow = csvContent[0]; + var headerContent = headerRow.Split(separator); + for (int i = 0; i < headerContent.Length; i++) + { + string languageName = headerContent[i]; + if (languageName.Equals("tag", StringComparison.OrdinalIgnoreCase) || + languageName.Equals("comments", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + string translatedName = GetTranslatedName(languageName); + bool nowhitespace = TextManager.IsCJK(translatedName); + List xmlContent = new List() + { + xmlHeader, + $"" + }; + xmlContentByLanguage.Add(headerContent[i], xmlContent); + } + + for (int row = 1; row < csvContent.Length; row++) // Start at one to ignore header + { + if (!xmlContentByLanguage.Values.All(values => values.Count == xmlContentByLanguage["English"].Count)) + { + throw new Exception($"Error while converting csv to xml: mismatching number of texts on line {row-1} ({csvContent[row - 1]}). Check that there's no extra newlines, separators or missing lines in the csv file."); + } + + if (csvContent[row].Length == 0) + { + AddToAllLanguages(string.Empty); + } + else + { + string[] split = csvContent[row].Split(separator); + + if (split.Length < xmlContentByLanguage.Count) + { + throw new Exception($"Error while converting csv to xml: not enough values on line {row} ({csvContent[row]}). Check that there's no extra newlines, separators or missing lines in the csv file."); + } + + if (split.Length > 1) // Localization data + { + //all values empty = an empty line + if (split.All(s => s.IsNullOrEmpty())) + { + AddToAllLanguages(string.Empty); + } + //value is empty in all languages + else if (!split[0].IsNullOrEmpty() && split.Skip(2).All(s => s.IsNullOrEmpty())) + { + //first line is all lower-case and contains dot, assume it's an empty value + if (split[0].Contains(".") && !split[0].Any(char.IsUpper)) + { + AddToAllLanguages($"<{split[0]}>"); + } + //otherwise assume it's a comment + else + { + AddToAllLanguages($""); + } + } + else + { + for (int j = 0; j < split.Length; j++) + { + string languageName = headerContent[j]; + if (languageName.Equals("tag", StringComparison.OrdinalIgnoreCase) || + languageName.Equals("comments", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + split[j] = split[j].Replace(" & ", " & "); + xmlContentByLanguage[languageName].Add($"<{split[0]}>{split[j]}"); + } + } + } + else // A header/comment + { + AddToAllLanguages($""); + } + } + } + + AddToAllLanguages(string.Empty); + AddToAllLanguages(""); + + void AddToAllLanguages(string str) + { + foreach (var xmlContent in xmlContentByLanguage.Values) + { + xmlContent.Add(str); + } + } + + return xmlContentByLanguage; + } + + [Obsolete] private static List ConvertInfoTextToXML(string[] csvContent, string language) { List xmlContent = new List @@ -147,12 +294,6 @@ namespace Barotrauma if (split.Length >= 2) // Localization data { - if (split.Length > 2 && !split[0].All(char.IsLower)) // Invalid header in line with localization data - { - split[0] = split[1]; - split[1] = split[2]; - split[2] = string.Empty; - } split[1] = split[1].Replace(" & ", " & "); xmlContent.Add($"<{split[0]}>{split[1]}"); } @@ -186,68 +327,39 @@ namespace Barotrauma private static List ConvertConversationsToXML(string[] csvContent, string language) { - List xmlContent = new List(); - xmlContent.Add(xmlHeader); + List xmlContent = new List + { + xmlHeader + }; string translatedName = GetTranslatedName(language); bool nowhitespace = TextManager.IsCJK(translatedName); + int languageColumn = -1; + string[] headerSplit = csvContent[0].Split(separator); + for (int i = 0; i < headerSplit.Length; i++) + { + if (headerSplit[i] == language) + { + languageColumn = i; + break; + } + } + xmlContent.Add($""); - xmlContent.Add(string.Empty); - xmlContent.Add(""); - int traitStart = -1; - for (int i = 0; i < csvContent.Length; i++) - { - if (csvContent[i].StartsWith("Personality")) - { - traitStart = i + 1; - break; - } - } - - int conversationStart = -1; - for (int i = 0; i < csvContent.Length; i++) - { - if (csvContent[i].StartsWith("Generic")) - { - conversationStart = i; - break; - } - } - - if (traitStart == -1) - { - DebugConsole.ThrowError("Invalid formatting of NPCConversations, no traits found!"); - return null; - } - - //DebugConsole.NewMessage("Count: " + NPCPersonalityTrait.List.Count); - var traits = NPCPersonalityTrait.GetAll(language.ToLanguageIdentifier()).ToArray(); - for (int i = 0; i < traits.Length; i++) // Traits - { - //string[] split = SplitCSV(csvContent[traitStart + i].Trim(separator)); - string[] split = csvContent[traitStart + i].Split(separator); - xmlContent.Add( - $""); - } + int conversationStart = 1; xmlContent.Add(string.Empty); for (int i = conversationStart; i < csvContent.Length; i++) // Conversations { string[] split = csvContent[i].Split(separator); - int emptyFields = 0; - for (int j = 0; j < split.Length; j++) { - if (split[j] == string.Empty) emptyFields++; + if (split[j] == string.Empty) { emptyFields++; } } - if (emptyFields == split.Length) // Empty line with only commas, indicates the end of the previous conversation { HandleClosingElements(xmlContent, 0); @@ -260,10 +372,10 @@ namespace Barotrauma continue; } - string speaker = split[1]; - int depthIndex = int.Parse(split[2]); + string line = split[languageColumn].Replace("\"", ""); + string speaker = split[2]; + int depthIndex = int.Parse(split[3]); // 3 = original line - string line = split[3].Replace("\"", ""); string flags = split[4].Replace("\"", ""); string allowedJobs = split[5].Replace("\"", ""); string speakerTags = split[6].Replace("\"", ""); @@ -317,7 +429,7 @@ namespace Barotrauma xmlContent.Add(""); return xmlContent; - } + } private static void HandleClosingElements(List xmlContent, int targetDepth) { @@ -332,24 +444,6 @@ namespace Barotrauma } } - private static string[] SplitCSV(string input) // Splits the .csv with regex, leaving commas inside quotation marks intact - { - List list = new List(); - string curr = null; - foreach (Match match in csvSplit.Matches(input)) - { - curr = match.Value; - if (0 == curr.Length) - { - list.Add(""); - } - - list.Add(curr.TrimStart(separator)); - } - - return list.ToArray(); - } - private static string GetIndenting(int depthIndex) { string indenting = string.Empty; diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 251b78f36..04e63b8ad 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.18.15.0 + 0.19.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma @@ -65,7 +65,7 @@ - + diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 065e6b693..0081ad967 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.18.15.0 + 0.19.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma @@ -56,7 +56,7 @@ - + diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index f684f83ae..dfa092086 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 0.18.15.0 + 0.19.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 Barotrauma @@ -62,7 +62,7 @@ - + diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 868528655..cf881db47 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.18.15.0 + 0.19.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer @@ -65,7 +65,7 @@ - + diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 523a650c6..b7101a67b 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.18.15.0 + 0.19.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer @@ -58,7 +58,7 @@ - + diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs index 197c22a26..5dc354ccb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterInfo.cs @@ -1,5 +1,4 @@ using Barotrauma.Networking; -using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -66,19 +65,16 @@ namespace Barotrauma if (Job != null) { - msg.Write(Job.Prefab.Identifier); + msg.Write(Job.Prefab.UintIdentifier); msg.Write((byte)Job.Variant); - var skills = Job.GetSkills(); - msg.Write((byte)skills.Count()); - foreach (Skill skill in skills) + foreach (SkillPrefab skillPrefab in Job.Prefab.Skills.OrderBy(s => s.Identifier)) { - msg.Write(skill.Identifier); - msg.Write(skill.Level); + msg.Write(Job.GetSkill(skillPrefab.Identifier).Level); } } else { - msg.Write(""); + msg.Write((uint)0); msg.Write((byte)0); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index f896b27c5..65f10e4c2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -359,11 +359,12 @@ namespace Barotrauma tempBuffer.Write(AnimController.Dir > 0.0f); } - if (SelectedCharacter != null || SelectedConstruction != null) + if (SelectedCharacter != null || HasSelectedAnyItem) { tempBuffer.Write(true); tempBuffer.Write(SelectedCharacter != null ? SelectedCharacter.ID : NullEntityID); - tempBuffer.Write(SelectedConstruction != null ? SelectedConstruction.ID : NullEntityID); + tempBuffer.Write(SelectedItem != null ? SelectedItem.ID : NullEntityID); + tempBuffer.Write(SelectedSecondaryItem != null ? SelectedSecondaryItem.ID : NullEntityID); if (SelectedCharacter != null) { tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR); @@ -424,8 +425,8 @@ namespace Barotrauma msg.Write(owner == c && owner.Character == this); msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0); break; - case CharacterStatusEventData _: - WriteStatus(msg); + case CharacterStatusEventData statusEventData: + WriteStatus(msg, statusEventData.ForceAfflictionData); break; case UpdateSkillsEventData _: if (Info?.Job == null) @@ -573,7 +574,7 @@ namespace Barotrauma msg.WriteRangedInteger((int)CauseOfDeath.Type, 0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1); if (CauseOfDeath.Type == CauseOfDeathType.Affliction) { - msg.Write(CauseOfDeath.Affliction.Identifier); + msg.Write(CauseOfDeath.Affliction.UintIdentifier); } msg.Write(forceAfflictionData); if (forceAfflictionData) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs index 718c588b9..0336b9ed0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventActions/ConversationAction.cs @@ -58,7 +58,7 @@ namespace Barotrauma Reset(); } - private bool IsBlockedByAnotherConversation(IEnumerable targets) + private bool IsBlockedByAnotherConversation(IEnumerable targets, float duration) { foreach (Entity e in targets) { @@ -68,7 +68,7 @@ namespace Barotrauma { if (lastActiveAction.ContainsKey(targetClient) && lastActiveAction[targetClient].ParentEvent != ParentEvent && - Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + BlockOtherConversationsDuration) + Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + duration) { return true; } @@ -91,7 +91,7 @@ namespace Barotrauma { targetClients.Add(targetClient); lastActiveAction[targetClient] = this; - ServerWrite(speaker, targetClient); + ServerWrite(speaker, targetClient, interrupt); } } } @@ -105,14 +105,14 @@ namespace Barotrauma { targetClients.Add(c); lastActiveAction[c] = this; - ServerWrite(speaker, c); + ServerWrite(speaker, c, interrupt); } } } } } - private void ServerWrite(Character speaker, Client client) + public void ServerWrite(Character speaker, Client client, bool interrupt) { IWriteMessage outmsg = new WriteOnlyMessage(); outmsg.Write((byte)ServerPacketHeader.EVENTACTION); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs index 7446dfd5d..dfa4bc4c0 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs @@ -27,16 +27,24 @@ namespace Barotrauma #endif continue; } - - if (selectedOption == byte.MaxValue) + + if (convAction.SelectedOption > -1) { - convAction.IgnoreClient(sender, 3f); + //someone else already chose an option for this conversation: interrupt for this client + convAction.ServerWrite(convAction.speaker, sender, interrupt: true); } else { - convAction.SelectedOption = selectedOption; + if (selectedOption == byte.MaxValue) + { + convAction.IgnoreClient(sender, 3f); + } + else + { + convAction.SelectedOption = selectedOption; + } } - return; + return; } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 4af9addf0..42b514127 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -329,7 +329,7 @@ namespace Barotrauma if (Server == null) { break; } SteamManager.Update((float)Timing.Step); TaskPool.Update(); - CoroutineManager.Update((float)Timing.Step, (float)Timing.Step); + CoroutineManager.Update(paused: false, (float)Timing.Step); Timing.Accumulator -= Timing.Step; updateCount++; diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/Data/Wallet.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/Data/Wallet.cs index 7c0767001..a4f06ea89 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/Data/Wallet.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/Data/Wallet.cs @@ -6,6 +6,8 @@ namespace Barotrauma { private readonly Queue transactions = new Queue(); + public bool ShouldForceUpdate; + partial void SettingsChanged(Option balanceChanged, Option rewardChanged) { transactions.Enqueue(new WalletChangedData @@ -15,6 +17,15 @@ namespace Barotrauma }); } + /// + /// Forces the server to sync the state of the wallet regardless if the balance/reward has changed + /// + public void ForceUpdate() + { + SettingsChanged(balanceChanged: Option.Some(0), rewardChanged: Option.None()); + ShouldForceUpdate = true; + } + public bool HasTransactions() => transactions.Count > 0; public NetWalletTransaction DequeueAndMergeTransactions(ushort id) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs index 6dbe8ff73..61caf6e9d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/MultiPlayerCampaign.cs @@ -354,6 +354,7 @@ namespace Barotrauma LeaveUnconnectedSubs(leavingSub); NextLevel = newLevel; GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); + GameMain.GameSession.EventManager.RegisterEventHistory(); SaveUtil.SaveGame(GameMain.GameSession.SavePath); } else @@ -530,8 +531,9 @@ namespace Barotrauma if (wallet.HasTransactions()) { NetWalletTransaction transaction = wallet.DequeueAndMergeTransactions(id); - if (transaction.ChangedData.BalanceChanged.IsNone() && transaction.ChangedData.RewardDistributionChanged.IsNone()) { continue; } + if (!wallet.ShouldForceUpdate && transaction.ChangedData.BalanceChanged.IsNone() && transaction.ChangedData.RewardDistributionChanged.IsNone()) { continue; } transactions.Add(transaction); + wallet.ShouldForceUpdate = false; } } @@ -790,11 +792,19 @@ namespace Barotrauma purchasedItemSwaps.Add(new PurchasedItemSwap(itemToRemove, itemToInstall)); } + int hullRepairCost = GetHullRepairCost(); + int itemRepairCost = GetItemRepairCost(); + int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost; Location location = Map.CurrentLocation; - int hullRepairCost = location?.GetAdjustedMechanicalCost(HullRepairCost) ?? HullRepairCost; - int itemRepairCost = location?.GetAdjustedMechanicalCost(ItemRepairCost) ?? ItemRepairCost; - int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(ShuttleReplaceCost) ?? ShuttleReplaceCost; + if (location != null) + { + hullRepairCost = location.GetAdjustedMechanicalCost(hullRepairCost); + itemRepairCost = location.GetAdjustedMechanicalCost(itemRepairCost); + shuttleRetrieveCost = location.GetAdjustedMechanicalCost(shuttleRetrieveCost); + } + Wallet personalWallet = GetWallet(sender); + personalWallet?.ForceUpdate(); if (purchasedHullRepairs != PurchasedHullRepairs) { @@ -875,6 +885,9 @@ namespace Barotrauma { foreach (var item in store.Value.ToList()) { + if (map?.CurrentLocation?.Stores == null || !map.CurrentLocation.Stores.ContainsKey(store.Key)) { continue; } + item.Quantity = Math.Min(map.CurrentLocation.Stores[store.Key].Stock.Find(s => s.ItemPrefab == item.ItemPrefab)?.Quantity ?? 0, item.Quantity); + if (item.Quantity <= 0) { continue; } CargoManager.ModifyItemQuantityInBuyCrate(store.Key, item.ItemPrefab, item.Quantity, sender); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs index fe720421c..f385efcde 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Client.cs @@ -62,6 +62,8 @@ namespace Barotrauma.Networking public float DeleteDisconnectedTimer; + public DateTime JoinTime; + private CharacterInfo characterInfo; public CharacterInfo CharacterInfo { @@ -114,6 +116,8 @@ namespace Barotrauma.Networking //initialize to infinity, gets set to a proper value when initializing midround syncing MidRoundSyncTimeOut = double.PositiveInfinity; + + JoinTime = DateTime.Now; } partial void DisposeProjSpecific() diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index e246d4549..74984923c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -2081,7 +2081,7 @@ namespace Barotrauma.Networking float waitForResponseTimer = 5.0f; while (connectedClients.Any(c => !c.ReadyToStart) && waitForResponseTimer > 0.0f) { - waitForResponseTimer -= CoroutineManager.UnscaledDeltaTime; + waitForResponseTimer -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } @@ -2090,7 +2090,7 @@ namespace Barotrauma.Networking float waitForTransfersTimer = 20.0f; while (FileSender.ActiveTransfers.Count > 0 && waitForTransfersTimer > 0.0f) { - waitForTransfersTimer -= CoroutineManager.UnscaledDeltaTime; + waitForTransfersTimer -= CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; } } @@ -3278,7 +3278,8 @@ namespace Barotrauma.Networking Client.UpdateKickVotes(connectedClients); - int minimumKickVotes = Math.Max(1, (int)(connectedClients.Count * serverSettings.KickVoteRequiredRatio)); + var kickVoteEligibleClients = connectedClients.Where(c => (DateTime.Now - c.JoinTime).TotalSeconds > ServerSettings.DisallowKickVoteTime); + int minimumKickVotes = Math.Max(2, (int)(kickVoteEligibleClients.Count() * serverSettings.KickVoteRequiredRatio)); var clientsToKick = connectedClients.FindAll(c => c.Connection != OwnerConnection && !c.HasPermission(ClientPermissions.Kick) && @@ -3581,14 +3582,14 @@ namespace Barotrauma.Networking List jobPreferences = new List(); int count = message.ReadByte(); - // TODO: modding support? for (int i = 0; i < Math.Min(count, 3); i++) { string jobIdentifier = message.ReadString(); int variant = message.ReadByte(); - if (JobPrefab.Prefabs.ContainsKey(jobIdentifier)) + if (JobPrefab.Prefabs.TryGet(jobIdentifier, out JobPrefab jobPrefab)) { - jobPreferences.Add(new JobVariant(JobPrefab.Prefabs[jobIdentifier], variant)); + if (jobPrefab.HiddenJob) { continue; } + jobPreferences.Add(new JobVariant(jobPrefab, variant)); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs index e86a858f5..e732ce117 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs @@ -188,9 +188,9 @@ namespace Barotrauma } } - if (client.Character?.Info?.Job.Prefab.Identifier == "captain" && client.Character.SelectedConstruction != null) + if (client.Character?.Info?.Job.Prefab.Identifier == "captain" && client.Character.SelectedItem != null) { - if (client.Character.SelectedConstruction.GetComponent() != null) + if (client.Character.SelectedItem.GetComponent() != null) { AdjustKarma(client.Character, SteerSubKarmaIncrease * deltaTime, "Steering the sub"); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index de685bebf..c65ba58d2 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -125,7 +125,7 @@ namespace Barotrauma.Networking return characterToRespawnCount >= GetMinCharactersToRespawn(); } - partial void UpdateWaiting(float deltaTime) + partial void UpdateWaiting(float _) { if (RespawnShuttle != null) { @@ -487,6 +487,20 @@ namespace Barotrauma.Networking { AutoItemPlacer.RegenerateLoot(RespawnShuttle, respawnContainer); } + + //try to put the items in containers in the shuttle + foreach (var respawnItem in respawnItems) + { + foreach (Item shuttleItem in RespawnShuttle.GetItems(alsoFromConnectedSubs: false)) + { + if (shuttleItem.NonInteractable || shuttleItem.NonPlayerTeamInteractable) { continue; } + var container = shuttleItem.GetComponent(); + if (container != null && container.Inventory.TryPutItem(respawnItem, user: null)) + { + break; + } + } + } } var characterData = campaign?.GetClientCharacterData(clients[i]); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs index 4724420b4..0ee0a0d73 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voting.cs @@ -254,15 +254,21 @@ namespace Barotrauma break; case VoteType.Kick: byte kickedClientID = inc.ReadByte(); - - Client kicked = GameMain.Server.ConnectedClients.Find(c => c.ID == kickedClientID); - if (kicked != null && kicked.Connection != GameMain.Server.OwnerConnection && !kicked.HasKickVoteFrom(sender)) + if ((DateTime.Now - sender.JoinTime).TotalSeconds > GameMain.Server.ServerSettings.DisallowKickVoteTime) { - kicked.AddKickVote(sender); - Client.UpdateKickVotes(GameMain.Server.ConnectedClients); - GameMain.Server.SendChatMessage($"ServerMessage.HasVotedToKick~[initiator]={sender.Name}~[target]={kicked.Name}", ChatMessageType.Server, null); - } + GameMain.Server.SendDirectChatMessage($"ServerMessage.kickvotedisallowed", sender); + } + else + { + Client kicked = GameMain.Server.ConnectedClients.Find(c => c.ID == kickedClientID); + if (kicked != null && kicked.Connection != GameMain.Server.OwnerConnection && !kicked.HasKickVoteFrom(sender)) + { + kicked.AddKickVote(sender); + Client.UpdateKickVotes(GameMain.Server.ConnectedClients); + GameMain.Server.SendChatMessage($"ServerMessage.HasVotedToKick~[initiator]={sender.Name}~[target]={kicked.Name}", ChatMessageType.Server, null); + } + } break; case VoteType.StartRound: bool ready = inc.ReadBoolean(); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Program.cs b/Barotrauma/BarotraumaServer/ServerSource/Program.cs index c2fccb428..993323acb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Program.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Program.cs @@ -55,6 +55,10 @@ namespace Barotrauma #if LINUX setLinuxEnv(); + AppDomain.CurrentDomain.ProcessExit += (s, e) => + { + GameMain.ShouldRun = false; + }; #endif Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")"); @@ -93,7 +97,7 @@ namespace Barotrauma private static void CrashHandler(object sender, UnhandledExceptionEventArgs args) { - void swallowExceptions(Action action) + static void swallowExceptions(Action action) { try { diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 43732773c..0b500d712 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 0.18.15.0 + 0.19.0.0 Copyright © FakeFish 2018-2022 AnyCPU;x64 DedicatedServer @@ -60,7 +60,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml b/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml new file mode 100644 index 000000000..8a2bef012 --- /dev/null +++ b/Barotrauma/BarotraumaShared/ModLists/Release checklist mods.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index 6727b9637..62f664d39 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -511,9 +511,9 @@ namespace Barotrauma { newDir = Direction.Left; } - if (Character.SelectedConstruction != null) + if (Character.SelectedItem != null) { - Character.SelectedConstruction.SecondaryUse(deltaTime, Character); + Character.SelectedItem.SecondaryUse(deltaTime, Character); } } else if (AutoFaceMovement && Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater) @@ -2148,7 +2148,7 @@ namespace Barotrauma if (c.Removed) { continue; } if (c.TeamID != team) { continue; } if (c.IsIncapacitated) { continue; } - if (c.SelectedConstruction == target.Item) + if (c.SelectedItem == target.Item) { operatingCharacter = c; return true; @@ -2185,7 +2185,7 @@ namespace Barotrauma if (c.IsIncapacitated) { continue; } if (c.IsPlayer) { - if (c.SelectedConstruction == target.Item) + if (c.SelectedItem == target.Item) { // If the other character is player, don't try to operate other = c; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 0c46fca5d..740c80b72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -79,7 +79,8 @@ namespace Barotrauma { pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true) { - GetNodePenalty = GetNodePenalty + GetNodePenalty = GetNodePenalty, + GetSingleNodePenalty = GetSingleNodePenalty }; this.canOpenDoors = canOpenDoors; @@ -360,7 +361,7 @@ namespace Barotrauma Ladder nextLadder = GetNextLadder(); var ladders = currentLadder ?? nextLadder; bool useLadders = canClimb && ladders != null && steering.LengthSquared() > 0.1f && (!isDiving || steering.Y > 1); - if (useLadders && character.SelectedConstruction != ladders.Item) + if (useLadders && character.SelectedSecondaryItem != ladders.Item) { if (character.CanInteractWith(ladders.Item)) { @@ -372,7 +373,7 @@ namespace Barotrauma // Try to select the previous ladder, unless it's already selected, unless the previous ladder is not adjacent to the current ladder. // The intention of this code is to prevent the bots from dropping from the "double ladders". var previousLadders = currentPath.PrevNode?.Ladders; - if (previousLadders != null && previousLadders != ladders && character.SelectedConstruction != previousLadders.Item && + if (previousLadders != null && previousLadders != ladders && character.SelectedSecondaryItem != previousLadders.Item && character.CanInteractWith(previousLadders.Item) && Math.Abs(previousLadders.Item.WorldPosition.X - ladders.Item.WorldPosition.X) < 5) { previousLadders.Item.TryInteract(character, forceSelectKey: true); @@ -382,8 +383,7 @@ namespace Barotrauma var collider = character.AnimController.Collider; if (character.IsClimbing && !useLadders) { - character.AnimController.Anim = AnimController.Animation.None; - character.SelectedConstruction = null; + character.StopClimbing(); } if (character.IsClimbing && useLadders) { @@ -402,15 +402,14 @@ namespace Barotrauma // We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative. bool isAboveFloor = heightFromFloor > -0.1f; // If the next waypoint is horizontally far, we don't want to keep holding the ladders - if (isAboveFloor && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50)) + if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50)) { - character.AnimController.Anim = AnimController.Animation.None; - character.SelectedConstruction = null; + character.StopClimbing(); } else if (nextLadder != null && !nextLadderSameAsCurrent) { // Try to change the ladder (hatches between two submarines) - if (character.SelectedConstruction != nextLadder.Item && character.CanInteractWith(nextLadder.Item)) + if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item)) { if (nextLadder.Item.TryInteract(character, forceSelectKey: true)) { @@ -418,7 +417,7 @@ namespace Barotrauma } } } - if (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10) + if (!currentPath.IsAtEndNode && (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10)) { NextNode(!doorsChecked); } @@ -756,42 +755,8 @@ namespace Barotrauma private float? GetNodePenalty(PathNode node, PathNode nextNode) { if (character == null) { return 0.0f; } - if (nextNode.Waypoint.isObstructed) { return null; } - float penalty = 0.0f; - if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f) - { - var door = nextNode.Waypoint.ConnectedDoor; - if (door == null) - { - penalty = 100.0f; - } - else - { - if (!CanAccessDoor(door, button => - { - // Ignore buttons that are on the wrong side of the door - if (door.IsHorizontal) - { - if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y)) - { - return false; - } - } - else - { - if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X)) - { - return false; - } - } - return true; - })) - { - return null; - } - } - } - + float? penalty = GetSingleNodePenalty(nextNode); + if (penalty == null) { return null; } bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y; //non-humanoids can't climb up ladders if (!(character.AnimController is HumanoidAnimController)) @@ -839,6 +804,47 @@ namespace Barotrauma return penalty; } + private float? GetSingleNodePenalty(PathNode node) + { + if (node.Waypoint.isObstructed) { return null; } + if (node.IsBlocked()) { return null; } + float penalty = 0.0f; + if (node.Waypoint.ConnectedGap != null && node.Waypoint.ConnectedGap.Open < 0.9f) + { + var door = node.Waypoint.ConnectedDoor; + if (door == null) + { + penalty = 100.0f; + } + else + { + if (!CanAccessDoor(door, button => + { + // Ignore buttons that are on the wrong side of the door + if (door.IsHorizontal) + { + if (Math.Sign(button.Item.WorldPosition.Y - door.Item.WorldPosition.Y) != Math.Sign(character.WorldPosition.Y - door.Item.WorldPosition.Y)) + { + return false; + } + } + else + { + if (Math.Sign(button.Item.WorldPosition.X - door.Item.WorldPosition.X) != Math.Sign(character.WorldPosition.X - door.Item.WorldPosition.X)) + { + return false; + } + } + return true; + })) + { + return null; + } + } + } + return penalty; + } + public static float smallRoomSize = 500; public void Wander(float deltaTime, float wallAvoidDistance = 150, bool stayStillInTightSpace = true) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs index 594ba9045..f3a2a9c39 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/NPCConversation.cs @@ -14,13 +14,11 @@ namespace Barotrauma public readonly LanguageIdentifier Language; public readonly List Conversations; - public readonly Dictionary PersonalityTraits; public NPCConversationCollection(NPCConversationsFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", "")) { Language = element.GetAttributeIdentifier("language", "English").ToLanguageIdentifier(); Conversations = new List(); - PersonalityTraits = new Dictionary(); foreach (var subElement in element.Elements()) { Identifier elemName = new Identifier(subElement.Name.LocalName); @@ -28,11 +26,6 @@ namespace Barotrauma { Conversations.Add(new NPCConversation(subElement)); } - else if (elemName == "PersonalityTrait") - { - var personalityTrait = new NPCPersonalityTrait(subElement); - PersonalityTraits.Add(personalityTrait.Name, personalityTrait); - } } } @@ -361,10 +354,13 @@ namespace Barotrauma private static float GetConversationProbability(NPCConversation conversation) { - int index = previousConversations.IndexOf(conversation); - if (index < 0) return 10.0f; + //prefer choosing conversations with more flags (= for more specific situations) when possible + float baseProbability = MathF.Pow(conversation.Flags.Count + 1, 2); - return 1.0f - 1.0f / (index + 1); + int index = previousConversations.IndexOf(conversation); + if (index < 0) { return baseProbability * 10.0f; } + + return baseProbability + 1.0f - 1.0f / (index + 1); } #if DEBUG diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index c3a21854d..34fed7024 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -512,6 +512,23 @@ namespace Barotrauma foreach (var weapon in weaponList) { float priority = weapon.CombatPriority; + if (weapon is RepairTool repairTool) + { + switch (repairTool.UsableIn) + { + case RepairTool.UseEnvironment.Air: + if (character.InWater) { continue; } + break; + case RepairTool.UseEnvironment.Water: + if (!character.InWater) { continue; } + break; + case RepairTool.UseEnvironment.None: + continue; + case RepairTool.UseEnvironment.Both: + default: + break; + } + } if (prioritizeMelee) { if (weapon is MeleeWeapon) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index 4bf65a912..640973cda 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -196,10 +196,7 @@ namespace Barotrauma character.AIController.SteeringManager.Reset(); return; } - if (!character.IsClimbing) - { - character.SelectedConstruction = null; - } + character.SelectedItem = null; if (Target is Entity e) { if (e.Removed) @@ -647,7 +644,7 @@ namespace Barotrauma { if (character.IsClimbing) { - if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder) + if (SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.CurrentPath.Finished && PathSteering.IsCurrentNodeLadder && !PathSteering.CurrentPath.IsAtEndNode) { if (Target.WorldPosition.Y > character.WorldPosition.Y) { @@ -694,7 +691,7 @@ namespace Barotrauma { if (Target is Item item) { - if (!character.IsClimbing && character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; } + if (character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; } } else if (Target is Character targetCharacter) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index 5b5665bf9..4d5fbb243 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -161,10 +161,7 @@ namespace Barotrauma character.DeselectCharacter(); } - if (!character.IsClimbing) - { - character.SelectedConstruction = null; - } + character.SelectedItem = null; CleanupItems(deltaTime); @@ -310,7 +307,7 @@ namespace Barotrauma if (character.AnimController.GetHeightFromFloor() < 0.1f) { character.AnimController.Anim = AnimController.Animation.None; - character.SelectedConstruction = null; + character.SelectedSecondaryItem = null; } return; } @@ -375,7 +372,7 @@ namespace Barotrauma } chairCheckTimer -= deltaTime; - if (chairCheckTimer <= 0.0f && character.SelectedConstruction == null) + if (chairCheckTimer <= 0.0f && character.SelectedSecondaryItem == null) { foreach (Item item in Item.ItemList) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index f1bf0e45f..56a453007 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -205,7 +205,7 @@ namespace Barotrauma if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false)) { HumanAIController.FaceTarget(target.Item); - if (character.SelectedConstruction != target.Item) + if (character.SelectedItem != target.Item) { target.Item.TryInteract(character, forceSelectKey: true); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs index ee2c985fb..78fc1a261 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -26,7 +26,7 @@ namespace Barotrauma private bool IsRepairing() => IsRepairing(character, Item); private readonly bool isPriority; - public static bool IsRepairing(Character character, Item item) => character.SelectedConstruction == item && item.Repairables.Any(r => r.CurrentFixer == character); + public static bool IsRepairing(Character character, Item item) => character.SelectedItem == item && item.Repairables.Any(r => r.CurrentFixer == character); public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1, bool isPriority = false) : base(character, objectiveManager, priorityModifier) @@ -165,7 +165,7 @@ namespace Barotrauma return; } } - if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false)) + if (character.CanInteractWith(Item, out _, checkLinked: false)) { waitTimer += deltaTime; if (waitTimer < WaitTimeBeforeRepair) { return; } @@ -184,12 +184,12 @@ namespace Barotrauma } if (!Abandon) { - if (character.SelectedConstruction != Item) + if (character.SelectedItem != Item) { if (Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) || Item.TryInteract(character, ignoreRequiredItems: true, forceUseKey: true)) { - character.SelectedConstruction = Item; + character.SelectedItem = Item; } else { @@ -232,8 +232,6 @@ namespace Barotrauma previousCondition = -1; var objective = new AIObjectiveGoTo(Item, character, objectiveManager) { - // Don't stop in ladders, because we can't interact with other items while holding the ladders. - endNodeFilter = node => node.Waypoint.Ladders == null, TargetName = Item.Name }; if (repairTool != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs index 90578f6b9..c2539d899 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -67,7 +67,7 @@ namespace Barotrauma if (!ViableForRepair(item, character, HumanAIController)) { return false; }; if (!Objectives.ContainsKey(item)) { - if (item != character.SelectedConstruction) + if (item != character.SelectedItem) { if (NearlyFullCondition(item)) { return false; } } @@ -96,7 +96,7 @@ namespace Barotrauma protected override float TargetEvaluation() { - var selectedItem = character.SelectedConstruction; + var selectedItem = character.SelectedItem; if (selectedItem != null && AIObjectiveRepairItem.IsRepairing(character, selectedItem) && selectedItem.ConditionPercentage < 100) { // Don't stop fixing until completely done diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs index 3a8186c25..9e9398673 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/PathFinder.cs @@ -109,6 +109,8 @@ namespace Barotrauma { public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode); public GetNodePenaltyHandler GetNodePenalty; + public delegate float? GetSingleNodePenaltyHandler(PathNode node); + public GetSingleNodePenaltyHandler GetSingleNodePenalty; private readonly List nodes; private readonly bool isCharacter; @@ -282,8 +284,6 @@ namespace Barotrauma } //avoid stopping at a doorway if (node.Waypoint.ConnectedDoor != null) { node.TempDistance *= 10.0f; } - //avoid stopping at a ladder - if (node.Waypoint.Ladders != null) { node.TempDistance *= 10.0f; } } //optimization: node extremely far (> 100m / 800 m) from the end position, don't try to use it as an end node if (node.TempDistance > (InsideSubmarine ? 100.0f * 100.0f : 800.0f * 800.0f)) @@ -325,15 +325,24 @@ namespace Barotrauma #endif return new SteeringPath(true); } - var path = FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize); - return path; + return FindPath(startNode, endNode, nodeFilter, errorMsgStr, minGapSize); - bool IsWaypointVisible(PathNode node, Vector2 rayStart, bool checkVisibility = true) + bool IsValidStartNode(PathNode node) => IsValidNode(node, (isCharacter, start), startNodeFilter); + + bool IsValidEndNode(PathNode node) => IsValidNode(node, (isCharacter && checkVisibility, end), endNodeFilter); + + bool IsValidNode(PathNode node, (bool check, Vector2 start) visibilityCheck, Func extraFilter) { - //if searching for a path inside the sub, make sure the waypoint is visible - if (checkVisibility && isCharacter) + if (nodeFilter != null && !nodeFilter(node)) { return false; } + if (extraFilter != null && !extraFilter(node)) { return false; } + if (GetSingleNodePenalty != null && GetSingleNodePenalty(node) == null) { return false; } + if (node.Waypoint.ConnectedGap != null) { - var body = Submarine.PickBody(rayStart, node.TempPosition, + if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; } + } + if (visibilityCheck.check) + { + var body = Submarine.PickBody(visibilityCheck.start, node.TempPosition, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs); if (body != null) { @@ -344,36 +353,6 @@ namespace Barotrauma } return true; } - - bool IsValidStartNode(PathNode node) - { - if (nodeFilter != null && !nodeFilter(node)) { return false; } - if (startNodeFilter != null && !startNodeFilter(node)) { return false; } - if (node.Waypoint.isObstructed) { return false; } - // Always check the visibility for the start node - if (!IsWaypointVisible(node, start)) { return false; } - if (node.IsBlocked()) { return false; } - if (node.Waypoint.ConnectedGap != null) - { - if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; } - } - return true; - } - - bool IsValidEndNode(PathNode node) - { - if (nodeFilter != null && !nodeFilter(node)) { return false; } - if (endNodeFilter != null && !endNodeFilter(node)) { return false; } - if (node.Waypoint.isObstructed) { return false; } - // Only check the visibility for the end node when allowed (fix leaks) - if (!IsWaypointVisible(node, end, checkVisibility: checkVisibility)) { return false; } - if (node.IsBlocked()) { return false; } - if (node.Waypoint.ConnectedGap != null) - { - if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { return false; } - } - return true; - } } private SteeringPath FindPath(PathNode start, PathNode end, Func filter = null, string errorMsgStr = "", float minGapSize = 0) @@ -402,15 +381,13 @@ namespace Barotrauma foreach (PathNode node in nodes) { if (node.state != 1 || node.F > dist) { continue; } - if (isCharacter && node.Waypoint.isObstructed) { continue; } if (filter != null && !filter(node)) { continue; } - if (node.IsBlocked()) { continue; } if (node.Waypoint.ConnectedGap != null) { if (!CanFitThroughGap(node.Waypoint.ConnectedGap, minGapSize)) { continue; } - } + } dist = node.F; - currNode = node; + currNode = node; } if (currNode == null || currNode == end) { break; } @@ -515,7 +492,4 @@ namespace Barotrauma private bool CanFitThroughGap(Gap gap, float minWidth) => gap.IsHorizontal ? gap.RectHeight > minWidth : gap.RectWidth > minWidth; } -} - - - +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs index 24a3feb2b..eb7b56db4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/SteeringPath.cs @@ -115,6 +115,8 @@ namespace Barotrauma } } + public bool IsAtEndNode => currentIndex >= nodes.Count - 1; + public List Nodes { get { return nodes; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs index 258eb4e33..928dd8e74 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/AnimController.cs @@ -101,7 +101,7 @@ namespace Barotrauma { if (InWater || !CanWalk) { - return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed); + return TargetMovement.LengthSquared() > MathUtils.Pow2(SwimSlowParams.MovementSpeed + 0.0001f); } else { @@ -134,9 +134,12 @@ namespace Barotrauma } } - public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR }; + public enum Animation { None, Climbing, UsingItem, Struggle, CPR, UsingItemWhileClimbing }; public Animation Anim; + public bool IsUsingItem => Anim == Animation.UsingItem || Anim == Animation.UsingItemWhileClimbing; + public bool IsClimbing => Anim == Animation.Climbing || Anim == Animation.UsingItemWhileClimbing; + public Vector2 AimSourceWorldPos { get @@ -280,7 +283,7 @@ namespace Barotrauma public void UpdateUseItem(bool allowMovement, Vector2 handWorldPos) { useItemTimer = 0.5f; - Anim = Animation.UsingConstruction; + StartUsingItem(); if (!allowMovement) { @@ -359,8 +362,13 @@ namespace Barotrauma Vector2 itemPos = aim ? aimPos : holdPos; - var controller = character.SelectedConstruction?.GetComponent(); + var controller = character.SelectedItem?.GetComponent(); bool usingController = controller != null && !controller.AllowAiming; + if (!usingController) + { + controller = character.SelectedSecondaryItem?.GetComponent(); + usingController = controller != null && !controller.AllowAiming; + } bool isClimbing = character.IsClimbing && Math.Abs(character.AnimController.TargetMovement.Y) > 0.01f; float itemAngle; Holdable holdable = item.GetComponent(); @@ -722,5 +730,45 @@ namespace Barotrauma CalculateArmLengths(); } } + + private void StartAnimation(Animation animation) + { + if (animation == Animation.UsingItem) + { + Anim = IsClimbing ? Animation.UsingItemWhileClimbing : Animation.UsingItem; + } + else if (animation == Animation.Climbing) + { + Anim = IsUsingItem ? Animation.UsingItemWhileClimbing : Animation.Climbing; + } + else + { + Anim = animation; + } + } + + private void StopAnimation(Animation animation) + { + if (animation == Animation.UsingItem) + { + Anim = IsClimbing ? Animation.Climbing : Animation.None; + } + else if (animation == Animation.Climbing) + { + Anim = IsUsingItem ? Animation.UsingItem : Animation.None; + } + else + { + Anim = Animation.None; + } + } + + public void StartUsingItem() => StartAnimation(Animation.UsingItem); + + public void StartClimbing() => StartAnimation(Animation.Climbing); + + public void StopUsingItem() => StopAnimation(Animation.UsingItem); + + public void StopClimbing() => StopAnimation(Animation.Climbing); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 74f7ad622..e8c70509d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -237,13 +237,14 @@ namespace Barotrauma public override void UpdateAnim(float deltaTime) { - if (Frozen) return; + if (Frozen) { return; } if (MainLimb == null) { return; } levitatingCollider = !IsHanging; ColliderIndex = Crouching && !swimming ? 1 : 0; - if (character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false || - character.SelectedConstruction?.GetComponent() != null || + if ((character.SelectedItem?.GetComponent()?.ControlCharacterPose ?? false) || + (character.SelectedSecondaryItem?.GetComponent()?.ControlCharacterPose ?? false) || + character.SelectedSecondaryItem?.GetComponent() != null || (ForceSelectAnimationType != AnimationType.Crouch && ForceSelectAnimationType != AnimationType.NotDefined)) { Crouching = false; @@ -354,12 +355,16 @@ namespace Barotrauma { ApplyTestPose(); } - else + else if (Anim != Animation.UsingItem) { - if (Anim != Animation.UsingConstruction) + if (Anim != Animation.UsingItemWhileClimbing) { ResetPullJoints(); } + else + { + ResetPullJoints(l => l.IsLowerBody); + } } if (SimplePhysicsEnabled) @@ -377,49 +382,53 @@ namespace Barotrauma switch (Anim) { case Animation.Climbing: + case Animation.UsingItemWhileClimbing: levitatingCollider = false; UpdateClimbing(); + UpdateUseItemTimer(); break; case Animation.CPR: UpdateCPR(deltaTime); break; - case Animation.UsingConstruction: + case Animation.UsingItem: default: - if (Anim == Animation.UsingConstruction) - { - useItemTimer -= deltaTime; - if (useItemTimer <= 0.0f) Anim = Animation.None; - } - + UpdateUseItemTimer(); swimmingStateLockTimer -= deltaTime; - if (forceStanding || character.AnimController.AnimationTestPose) { swimming = false; } - else + else if (swimming != inWater && swimmingStateLockTimer <= 0.0f) { //0.5 second delay for switching between swimming and walking //prevents rapid switches between swimming/walking if the water level is fluctuating around the minimum swimming depth - if (swimming != inWater && swimmingStateLockTimer <= 0.0f) - { - swimming = inWater; - swimmingStateLockTimer = 0.5f; - } + swimming = inWater; + swimmingStateLockTimer = 0.5f; } - if (swimming) { UpdateSwimming(); } - else + else if (character.SelectedItem == null || !(character.SelectedSecondaryItem?.GetComponent() is { } controller) || + !controller.ControlCharacterPose || !controller.UserInCorrectPosition) { UpdateStanding(); } - break; } + void UpdateUseItemTimer() + { + if (IsUsingItem) + { + useItemTimer -= deltaTime; + if (useItemTimer <= 0.0f) + { + StopUsingItem(); + } + } + } + if (Timing.TotalTime > LockFlippingUntil && TargetDir != dir && !IsStuck) { Flip(); @@ -841,7 +850,9 @@ namespace Barotrauma float targetSpeed = TargetMovement.Length(); if (targetSpeed > 0.1f && !character.IsRemotelyControlled && !Aiming) { - if (Anim != Animation.UsingConstruction && !(character.SelectedConstruction?.GetComponent()?.ControlCharacterPose ?? false)) + if (!IsUsingItem && + !(character.SelectedItem?.GetComponent()?.ControlCharacterPose ?? false) && + !(character.SelectedSecondaryItem?.GetComponent()?.ControlCharacterPose ?? false)) { if (rotation > 20 && rotation < 170) { @@ -1041,12 +1052,17 @@ namespace Barotrauma void UpdateClimbing() { - var ladder = character.SelectedConstruction?.GetComponent(); - if (ladder == null || character.IsIncapacitated) + var ladder = character.SelectedSecondaryItem?.GetComponent(); + if (character.IsIncapacitated) { Anim = Animation.None; return; } + else if (ladder == null) + { + StopClimbing(); + return; + } onGround = false; IgnorePlatforms = true; @@ -1209,15 +1225,21 @@ namespace Barotrauma { RotateHead(head); } + else if (Anim == Animation.UsingItemWhileClimbing && character.SelectedItem is { } selectedItem) + { + Vector2 diff = (selectedItem.WorldPosition - head.WorldPosition) * Dir; + float targetRotation = MathHelper.WrapAngle(MathUtils.VectorToAngle(diff) - MathHelper.PiOver4 * Dir); + head.body.SmoothRotate(targetRotation, force: WalkParams.HeadTorque); + } else { float movementMultiplier = targetMovement.Y < 0 ? 0 : 1; - head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, WalkParams.HeadTorque); + head.body.SmoothRotate(MathHelper.PiOver4 * movementMultiplier * Dir, force: WalkParams.HeadTorque); } - if (!ladder.Item.Prefab.Triggers.Any()) + if (ladder.Item.Prefab.Triggers.None()) { - character.SelectedConstruction = null; + character.SelectedSecondaryItem = null; return; } @@ -1247,8 +1269,7 @@ namespace Barotrauma if (!isClimbing) { - Anim = Animation.None; - character.SelectedConstruction = null; + character.StopClimbing(); IgnorePlatforms = false; } @@ -1487,7 +1508,7 @@ namespace Barotrauma target.AnimController.ResetPullJoints(); } - if (Anim == Animation.Climbing) + if (IsClimbing) { //cannot drag up ladders if the character is conscious if (target.AllowInput && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index bc2005a63..a0149777f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -922,12 +922,13 @@ namespace Barotrauma { limb.MoveToPos(pos, amount, pullFromCenter); } - - public void ResetPullJoints() + + public void ResetPullJoints(Func condition = null) { for (int i = 0; i < Limbs.Length; i++) { if (Limbs[i] == null) { continue; } + if (condition != null && !condition(Limbs[i])) { continue; } Limbs[i].PullJointEnabled = false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 9a57f98a9..d6b95ac15 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -813,43 +813,65 @@ namespace Barotrauma get { return AnimController?.Collider?.LinearVelocity.Length() ?? 0.0f; } } - private Item _selectedConstruction; - public Item SelectedConstruction + private Item _selectedItem; + /// + /// The primary selected item. It can be any device that character interacts with. This excludes items like ladders and chairs which are assigned to . + /// + public Item SelectedItem { - get => _selectedConstruction; + get => _selectedItem; set { - var prevSelectedConstruction = _selectedConstruction; - _selectedConstruction = value; + var prevSelectedItem = _selectedItem; + _selectedItem = value; #if CLIENT - HintManager.OnSetSelectedConstruction(this, prevSelectedConstruction, _selectedConstruction); + HintManager.OnSetSelectedItem(this, prevSelectedItem, _selectedItem); if (Controlled == this) { - if (_selectedConstruction == null) + if (_selectedItem == null) { GameMain.GameSession?.CrewManager?.ResetCrewList(); } - else if (_selectedConstruction.GetComponent() == null) + else if (!_selectedItem.IsLadder) { GameMain.GameSession?.CrewManager?.AutoHideCrewList(); } } #endif - if (prevSelectedConstruction == null && _selectedConstruction != null) + if (prevSelectedItem != null && (_selectedItem == null || _selectedItem != prevSelectedItem) && itemSelectedTime > 0) + { + double selectedDuration = Timing.TotalTime - itemSelectedTime; + if (itemSelectedDurations.ContainsKey(prevSelectedItem.Prefab)) + { + itemSelectedDurations[prevSelectedItem.Prefab] += selectedDuration; + } + else + { + itemSelectedDurations.Add(prevSelectedItem.Prefab, selectedDuration); + } + itemSelectedTime = 0; + } + if (_selectedItem != null && (prevSelectedItem == null || prevSelectedItem != _selectedItem)) { itemSelectedTime = Timing.TotalTime; } - else if (prevSelectedConstruction != null && _selectedConstruction == null && itemSelectedTime > 0) - { - if (!itemSelectedDurations.ContainsKey(prevSelectedConstruction.Prefab)) - { - itemSelectedDurations.Add(prevSelectedConstruction.Prefab, 0); - } - itemSelectedDurations[prevSelectedConstruction.Prefab] += Timing.TotalTime - itemSelectedTime; - itemSelectedTime = 0; - } } } + /// + /// The secondary selected item. It's an item other than a device (see ), e.g. a ladder or a chair. + /// + public Item SelectedSecondaryItem { get; set; } + /// + /// Has the characters selected a primary or a secondary item? + /// + public bool HasSelectedAnyItem => SelectedItem != null || SelectedSecondaryItem != null; + /// + /// Is the item either the primary or the secondary selected item? + /// + /// + /// + public bool IsAnySelectedItem(Item item) => item == SelectedItem || item == SelectedSecondaryItem; + public bool HasSelectedAnotherSecondaryItem(Item item) => SelectedSecondaryItem != null && SelectedSecondaryItem != item; public Item FocusedItem { @@ -938,7 +960,7 @@ namespace Barotrauma { get { - return SelectedConstruction == null || SelectedConstruction.GetComponent() != null || (SelectedConstruction.GetComponent()?.AllowAiming ?? false); + return SelectedItem == null || (SelectedItem.GetComponent()?.AllowAiming ?? false); } } @@ -1481,8 +1503,20 @@ namespace Barotrauma public void GiveJobItems(WayPoint spawnPoint = null) { - if (info?.Job == null) { return; } - info.Job.GiveJobItems(this, spawnPoint); + if (info == null) { return; } + if (info.HumanPrefabIds != default) + { + var humanPrefab = NPCSet.Get(info.HumanPrefabIds.NpcSetIdentifier, info.HumanPrefabIds.NpcIdentifier); + if (humanPrefab == null) + { + DebugConsole.ThrowError($"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\"."); + } + else if (humanPrefab.GiveItems(this, Submarine)) + { + return; + } + } + info.Job?.GiveJobItems(this, spawnPoint); } public void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent = false) @@ -1524,10 +1558,17 @@ namespace Barotrauma if (item?.GetComponent() is Wearable wearable && !Inventory.IsInLimbSlot(item, InvSlotType.Any)) { - if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue)) + foreach (var allowedSlot in wearable.AllowedSlots) { - skillLevel += skillValue; + if (allowedSlot == InvSlotType.Any) { continue; } + if (!Inventory.IsInLimbSlot(item, allowedSlot)) { continue; } + if (wearable.SkillModifiers.TryGetValue(skillIdentifier, out float skillValue)) + { + skillLevel += skillValue; + break; + } } + } } } @@ -1541,7 +1582,7 @@ namespace Barotrauma public Vector2? OverrideMovement { get; set; } public bool ForceRun { get; set; } - public bool IsClimbing => AnimController.Anim == AnimController.Animation.Climbing; + public bool IsClimbing => AnimController.IsClimbing; public Vector2 GetTargetMovement() { @@ -1771,6 +1812,11 @@ namespace Barotrauma return speed; } + /// + /// Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping. + /// + private const float cursorFollowMargin = 40; + public void Control(float deltaTime, Camera cam) { ViewTarget = null; @@ -1803,10 +1849,10 @@ namespace Barotrauma } if (!aiControlled && - AnimController.Anim != AnimController.Animation.UsingConstruction && + !AnimController.IsUsingItem && AnimController.Anim != AnimController.Animation.CPR && (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient || Controlled == this) && - AnimController.OnGround && !AnimController.InWater) + (AnimController.OnGround || IsClimbing) && !AnimController.InWater) { if (dontFollowCursor) { @@ -1814,13 +1860,11 @@ namespace Barotrauma } else { - // Values lower than this seem to cause constantious flipping when the mouse is near the player and the player is running, because the root collider moves after flipping. - float followMargin = 40; - if (CursorPosition.X < AnimController.Collider.Position.X - followMargin) + if (CursorPosition.X < AnimController.Collider.Position.X - cursorFollowMargin) { AnimController.TargetDir = Direction.Left; } - else if (CursorPosition.X > AnimController.Collider.Position.X + followMargin) + else if (CursorPosition.X > AnimController.Collider.Position.X + cursorFollowMargin) { AnimController.TargetDir = Direction.Right; } @@ -1967,7 +2011,8 @@ namespace Barotrauma } } - if (SelectedConstruction == null || !SelectedConstruction.Prefab.DisableItemUsageWhenSelected) + bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected; + if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem)) { foreach (Item item in HeldItems) { @@ -1998,24 +2043,24 @@ namespace Barotrauma } } - if (SelectedConstruction != null) + if (SelectedItem != null) { - if (IsKeyDown(InputType.Aim) || !SelectedConstruction.RequireAimToSecondaryUse) + if (IsKeyDown(InputType.Aim) || !SelectedItem.RequireAimToSecondaryUse) { - SelectedConstruction.SecondaryUse(deltaTime, this); + SelectedItem.SecondaryUse(deltaTime, this); } - if (IsKeyDown(InputType.Use) && SelectedConstruction != null && !SelectedConstruction.IsShootable) + if (IsKeyDown(InputType.Use) && SelectedItem != null && !SelectedItem.IsShootable) { - if (!SelectedConstruction.RequireAimToUse || IsKeyDown(InputType.Aim)) + if (!SelectedItem.RequireAimToUse || IsKeyDown(InputType.Aim)) { - SelectedConstruction.Use(deltaTime, this); + SelectedItem.Use(deltaTime, this); } } - if (IsKeyDown(InputType.Shoot) && SelectedConstruction != null && SelectedConstruction.IsShootable) + if (IsKeyDown(InputType.Shoot) && SelectedItem != null && SelectedItem.IsShootable) { - if (!SelectedConstruction.RequireAimToUse || IsKeyDown(InputType.Aim)) + if (!SelectedItem.RequireAimToUse || IsKeyDown(InputType.Aim)) { - SelectedConstruction.Use(deltaTime, this); + SelectedItem.Use(deltaTime, this); } } } @@ -2351,15 +2396,15 @@ namespace Barotrauma //wires are interactable if the character has selected an item the wire is connected to, //and it's disconnected from the other end - if (wire.Connections[0]?.Item != null && SelectedConstruction == wire.Connections[0].Item) + if (wire.Connections[0]?.Item != null && SelectedItem == wire.Connections[0].Item) { return wire.Connections[1] == null; } - if (wire.Connections[1]?.Item != null && SelectedConstruction == wire.Connections[1].Item) + if (wire.Connections[1]?.Item != null && SelectedItem == wire.Connections[1].Item) { return wire.Connections[0] == null; } - if (SelectedConstruction?.GetComponent()?.DisconnectedWires.Contains(wire) ?? false) + if (SelectedItem?.GetComponent()?.DisconnectedWires.Contains(wire) ?? false) { return wire.Connections[0] == null && wire.Connections[1] == null; } @@ -2385,7 +2430,7 @@ namespace Barotrauma Pickable pickableComponent = item.GetComponent(); if (pickableComponent != null && pickableComponent.Picker != this && pickableComponent.Picker != null && !pickableComponent.Picker.IsDead) { return false; } - if (SelectedConstruction?.GetComponent()?.TargetItem == item) { return true; } + if (SelectedItem?.GetComponent()?.TargetItem == item) { return true; } //optimization: don't use HeldItems because it allocates memory and this method is executed very frequently var heldItem1 = Inventory?.GetItemInLimbSlot(InvSlotType.RightHand); if (heldItem1?.GetComponent()?.TargetItem == item) { return true; } @@ -2428,27 +2473,43 @@ namespace Barotrauma distanceToItem = Vector2.Distance(rectIntersectionPoint, playerDistanceCheckPosition); } - if (distanceToItem > item.InteractDistance && item.InteractDistance > 0.0f) { return false; } + float interactDistance = item.InteractDistance; + if ((SelectedSecondaryItem != null || item.IsSecondaryItem) && AnimController is HumanoidAnimController c) + { + // Use a distance slightly shorter than the arms length to keep the character in a comfortable pose + float armLength = 0.75f * ConvertUnits.ToDisplayUnits(c.ArmLength); + interactDistance = Math.Min(interactDistance, armLength); + } + if (distanceToItem > interactDistance && item.InteractDistance > 0.0f) { return false; } + + Vector2 itemPosition = item.SimPosition; + if (Submarine == null && item.Submarine != null) + { + //character is outside, item inside + itemPosition += item.Submarine.SimPosition; + } + else if (Submarine != null && item.Submarine == null) + { + //character is inside, item outside + itemPosition -= Submarine.SimPosition; + } + else if (Submarine != item.Submarine) + { + //character and the item are inside different subs + itemPosition += item.Submarine.SimPosition; + itemPosition -= Submarine.SimPosition; + } + + if (SelectedSecondaryItem != null && !item.IsSecondaryItem) + { + if (item.GetComponent() is { } controller && controller.Direction != 0 && controller.Direction != AnimController.Direction) { return false; } + float threshold = ConvertUnits.ToSimUnits(cursorFollowMargin); + if (AnimController.Direction == Direction.Left && SimPosition.X + threshold < itemPosition.X) { return false; } + if (AnimController.Direction == Direction.Right && SimPosition.X - threshold > itemPosition.X) { return false; } + } if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger) { - Vector2 itemPosition = item.SimPosition; - if (Submarine == null && item.Submarine != null) - { - //character is outside, item inside - itemPosition += item.Submarine.SimPosition; - } - else if (Submarine != null && item.Submarine == null) - { - //character is inside, item outside - itemPosition -= Submarine.SimPosition; - } - else if (Submarine != item.Submarine) - { - //character and the item are inside different subs - itemPosition += item.Submarine.SimPosition; - itemPosition -= Submarine.SimPosition; - } var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true); if (body != null && body.UserData as Item != item && (body.UserData as ItemComponent)?.Item != item && Submarine.LastPickedFixture?.UserData as Item != item) { @@ -2521,7 +2582,7 @@ namespace Barotrauma if (!CanInteract) { - SelectedConstruction = null; + SelectedItem = SelectedSecondaryItem = null; focusedItem = null; if (!AllowInput) { @@ -2570,8 +2631,8 @@ namespace Barotrauma AnimController.InWater : head.InWater; //climb ladders automatically when pressing up/down inside their trigger area - Ladder currentLadder = SelectedConstruction?.GetComponent(); - if ((SelectedConstruction == null || currentLadder != null) && + Ladder currentLadder = SelectedSecondaryItem?.GetComponent(); + if ((SelectedSecondaryItem == null || currentLadder != null) && !headInWater && Screen.Selected != GameMain.SubEditorScreen) { bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down); @@ -2613,7 +2674,7 @@ namespace Barotrauma { if (nearbyLadder.Select(this)) { - SelectedConstruction = nearbyLadder.Item; + SelectedSecondaryItem = nearbyLadder.Item; } } } @@ -2657,16 +2718,23 @@ namespace Barotrauma { FocusedCharacter.onCustomInteract(FocusedCharacter, this); } - else if (IsKeyHit(InputType.Deselect) && SelectedConstruction != null && SelectedConstruction.GetComponent() == null) + else if (IsKeyHit(InputType.Deselect) && SelectedItem != null) { - SelectedConstruction = null; + SelectedItem = null; #if CLIENT CharacterHealth.OpenHealthWindow = null; #endif } - else if (IsKeyHit(InputType.Health) && SelectedConstruction != null && SelectedConstruction.GetComponent() == null) + else if (IsKeyHit(InputType.Deselect) && SelectedSecondaryItem != null) { - SelectedConstruction = null; + SelectedSecondaryItem = null; +#if CLIENT + CharacterHealth.OpenHealthWindow = null; +#endif + } + else if (IsKeyHit(InputType.Health) && (SelectedItem != null || SelectedSecondaryItem != null)) + { + SelectedItem = SelectedSecondaryItem = null; } else if (focusedItem != null) { @@ -2901,7 +2969,7 @@ namespace Barotrauma { Stun = Math.Max(5.0f, Stun); AnimController.ResetPullJoints(); - SelectedConstruction = null; + SelectedItem = SelectedSecondaryItem = null; return; } @@ -2958,7 +3026,7 @@ namespace Barotrauma humanAnimController.Crouching = false; } AnimController.ResetPullJoints(); - SelectedConstruction = null; + SelectedItem = SelectedSecondaryItem = null; return; } @@ -2974,9 +3042,13 @@ namespace Barotrauma DoInteractionUpdate(deltaTime, mouseSimPos); } - if (SelectedConstruction != null && !CanInteractWith(SelectedConstruction)) + if (SelectedItem != null && !CanInteractWith(SelectedItem)) { - SelectedConstruction = null; + SelectedItem = null; + } + if (SelectedSecondaryItem != null && !CanInteractWith(SelectedSecondaryItem)) + { + SelectedSecondaryItem = null; } if (!IsDead) { LockHands = false; } @@ -3914,7 +3986,7 @@ namespace Barotrauma CharacterHealth.Stun = newStun; if (newStun > 0.0f) { - SelectedConstruction = null; + SelectedItem = SelectedSecondaryItem = null; } HealthUpdateInterval = 0.0f; } @@ -3930,77 +4002,79 @@ namespace Barotrauma CharacterHealth.ReduceAfflictionOnAllLimbs("damage".ToIdentifier(), eatingRegen * deltaTime); } } - if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; } - foreach (StatusEffect statusEffect in statusEffectList) + if (statusEffects.TryGetValue(actionType, out var statusEffectList)) { - if (statusEffect.type == ActionType.OnDamaged) + foreach (StatusEffect statusEffect in statusEffectList) { - if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; } - if (statusEffect.OnlyPlayerTriggered) + if (statusEffect.type == ActionType.OnDamaged) { - if (LastAttacker == null || !LastAttacker.IsPlayer) + if (!statusEffect.HasRequiredAfflictions(LastDamage)) { continue; } + if (statusEffect.OnlyPlayerTriggered) { - continue; + if (LastAttacker == null || !LastAttacker.IsPlayer) + { + continue; + } } } - } - if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) || - statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) - { - targets.Clear(); - targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets)); - statusEffect.Apply(actionType, deltaTime, this, targets); - } - else if (statusEffect.targetLimbs != null) - { - foreach (var limbType in statusEffect.targetLimbs) + if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) || + statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters)) { - if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs)) + targets.Clear(); + targets.AddRange(statusEffect.GetNearbyTargets(WorldPosition, targets)); + statusEffect.Apply(actionType, deltaTime, this, targets); + } + else if (statusEffect.targetLimbs != null) + { + foreach (var limbType in statusEffect.targetLimbs) { - // Target all matching limbs - foreach (var limb in AnimController.Limbs) + if (statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs)) { - if (limb.IsSevered) { continue; } - if (limb.type == limbType) + // Target all matching limbs + foreach (var limb in AnimController.Limbs) + { + if (limb.IsSevered) { continue; } + if (limb.type == limbType) + { + statusEffect.sourceBody = limb.body; + statusEffect.Apply(actionType, deltaTime, this, limb); + } + } + } + else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb)) + { + // Target just the first matching limb + Limb limb = AnimController.GetLimb(limbType); + if (limb != null) + { + statusEffect.sourceBody = limb.body; + statusEffect.Apply(actionType, deltaTime, this, limb); + } + } + else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb)) + { + // Target just the last matching limb + Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden); + if (limb != null) { statusEffect.sourceBody = limb.body; statusEffect.Apply(actionType, deltaTime, this, limb); } } } - else if (statusEffect.HasTargetType(StatusEffect.TargetType.Limb)) - { - // Target just the first matching limb - Limb limb = AnimController.GetLimb(limbType); - if (limb != null) - { - statusEffect.sourceBody = limb.body; - statusEffect.Apply(actionType, deltaTime, this, limb); - } - } - else if (statusEffect.HasTargetType(StatusEffect.TargetType.LastLimb)) - { - // Target just the last matching limb - Limb limb = AnimController.Limbs.LastOrDefault(l => l.type == limbType && !l.IsSevered && !l.Hidden); - if (limb != null) - { - statusEffect.sourceBody = limb.body; - statusEffect.Apply(actionType, deltaTime, this, limb); - } - } + } + if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character)) + { + statusEffect.Apply(actionType, deltaTime, this, this); } } - if (statusEffect.HasTargetType(StatusEffect.TargetType.This) || statusEffect.HasTargetType(StatusEffect.TargetType.Character)) + if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered) { - statusEffect.Apply(actionType, deltaTime, this, this); - } - } - if (actionType != ActionType.OnDamaged && actionType != ActionType.OnSevered) - { - // OnDamaged is called only for the limb that is hit. - foreach (Limb limb in AnimController.Limbs) - { - limb.ApplyStatusEffects(actionType, deltaTime); + // OnDamaged is called only for the limb that is hit. + foreach (Limb limb in AnimController.Limbs) + { + limb.ApplyStatusEffects(actionType, deltaTime); + } } } //OnActive effects are handled by the afflictions themselves @@ -4075,10 +4149,12 @@ namespace Barotrauma return; } +#if SERVER if (GameMain.NetworkMember is { IsServer: true }) { - GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData()); + GameMain.NetworkMember.CreateEntityEvent(this, new CharacterStatusEventData(forceAfflictionData: true)); } +#endif isDead = true; @@ -4153,7 +4229,7 @@ namespace Barotrauma } } - SelectedConstruction = null; + SelectedItem = SelectedSecondaryItem = null; SelectedCharacter = null; AnimController.ResetPullJoints(); @@ -4896,6 +4972,12 @@ namespace Barotrauma /// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs /// public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group); + + public void StopClimbing() + { + AnimController.StopClimbing(); + SelectedSecondaryItem = null; + } } class ActiveTeamChange diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs index 731461475..e8b58578b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterEventData.cs @@ -54,6 +54,15 @@ namespace Barotrauma public struct CharacterStatusEventData : IEventData { public EventType EventType => EventType.Status; + +#if SERVER + public bool ForceAfflictionData; + + public CharacterStatusEventData(bool forceAfflictionData) + { + ForceAfflictionData = forceAfflictionData; + } +#endif } public struct TreatmentEventData : IEventData diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs index 1a9df1f90..aa44f780e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterInfo.cs @@ -261,6 +261,10 @@ namespace Barotrauma public string Name; + public LocalizedString Title; + + public (Identifier NpcSetIdentifier, Identifier NpcIdentifier) HumanPrefabIds; + public string DisplayName { get @@ -649,15 +653,12 @@ namespace Barotrauma { Name = name; } - else if (!npcIdentifier.IsEmpty && TextManager.Get("npctitle." + npcIdentifier) is { Loaded: true } npcTitle) - { - Name = npcTitle.Value; - } else { Name = GetRandomName(randSync); } - + + TryLoadNameAndTitle(npcIdentifier); SetPersonalityTrait(); Salary = CalculateSalary(); @@ -727,7 +728,7 @@ namespace Barotrauma } // Used for loading the data - public CharacterInfo(XElement infoElement) + public CharacterInfo(XElement infoElement, Identifier npcIdentifier = default) { ID = idCounter; idCounter++; @@ -774,6 +775,8 @@ namespace Barotrauma Head.FacialHairColor = infoElement.GetAttributeColor("facialhaircolor", Color.White); CheckColors(); + TryLoadNameAndTitle(npcIdentifier); + if (string.IsNullOrEmpty(Name)) { var nameElement = CharacterConfigElement.GetChildElement("names"); @@ -794,9 +797,21 @@ namespace Barotrauma ragdollFileName = infoElement.GetAttributeString("ragdoll", string.Empty); if (personalityName != Identifier.Empty) { - PersonalityTrait = NPCPersonalityTrait.Get(GameSettings.CurrentConfig.Language, personalityName); + if (NPCPersonalityTrait.Traits.TryGet(personalityName, out var trait) || + NPCPersonalityTrait.Traits.TryGet(personalityName.Replace(" ".ToIdentifier(), Identifier.Empty), out trait)) + { + PersonalityTrait = trait; + } + else + { + DebugConsole.ThrowError($"Error in CharacterInfo \"{OriginalName}\": could not find a personality trait with the identifier \"{personalityName}\"."); + } } + HumanPrefabIds = ( + element.GetAttributeIdentifier("npcsetid", Identifier.Empty), + element.GetAttributeIdentifier("npcid", Identifier.Empty)); + MissionsCompletedSinceDeath = infoElement.GetAttributeInt("missionscompletedsincedeath", 0); foreach (var subElement in infoElement.Elements()) @@ -838,6 +853,19 @@ namespace Barotrauma LoadHeadAttachments(); } + private void TryLoadNameAndTitle(Identifier npcIdentifier) + { + if (!npcIdentifier.IsEmpty) + { + Title = TextManager.Get("npctitle." + npcIdentifier); + string nameTag = "charactername." + npcIdentifier; + if (TextManager.ContainsTag(nameTag)) + { + Name = TextManager.Get(nameTag).Value; + } + } + } + private List hairs; public IReadOnlyList Hairs => hairs; private List beards; @@ -1292,9 +1320,16 @@ namespace Barotrauma new XAttribute("facialhaircolor", XMLExtensions.ColorToString(Head.FacialHairColor)), new XAttribute("startitemsgiven", StartItemsGiven), new XAttribute("ragdoll", ragdollFileName), - new XAttribute("personality", PersonalityTrait?.Name.Value ?? "")); + new XAttribute("personality", PersonalityTrait?.Identifier ?? Identifier.Empty)); // TODO: animations? + if (HumanPrefabIds != default) + { + charElement.Add( + new XAttribute("npcsetid", HumanPrefabIds.NpcSetIdentifier), + new XAttribute("npcid", HumanPrefabIds.NpcIdentifier)); + } + charElement.Add(new XAttribute("missionscompletedsincedeath", MissionsCompletedSinceDeath)); if (Character != null) @@ -1323,11 +1358,8 @@ namespace Barotrauma } } - - charElement.Add(savedStatElement); - - parentElement.Add(charElement); + parentElement?.Add(charElement); return charElement; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs index 4f0068328..29250acfd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterNetworking.cs @@ -10,26 +10,27 @@ namespace Barotrauma public readonly Direction Direction; public readonly Character SelectedCharacter; - public readonly Item SelectedItem; + public readonly Item SelectedItem, SelectedSecondaryItem; public readonly AnimController.Animation Animation; - public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None) - : this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, animation) + public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None) + : this(pos, rotation, velocity, angularVelocity, 0, time, dir, selectedCharacter, selectedItem, selectedSecondaryItem, animation) { } - public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None) - : this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, animation) + public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None) + : this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, selectedCharacter, selectedItem, selectedSecondaryItem, animation) { } - protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, AnimController.Animation animation = AnimController.Animation.None) + protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Character selectedCharacter, Item selectedItem, Item selectedSecondaryItem, AnimController.Animation animation = AnimController.Animation.None) : base(pos, rotation, velocity, angularVelocity, ID, time) { Direction = dir; SelectedCharacter = selectedCharacter; SelectedItem = selectedItem; + SelectedSecondaryItem = selectedSecondaryItem; Animation = animation; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs index 2b9602b33..ae592923a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CorpsePrefab.cs @@ -46,7 +46,7 @@ namespace Barotrauma [Serialize(0, IsPropertySaveable.No)] public int MaxMoney { get; private set; } - public CorpsePrefab(ContentXElement element, CorpsesFile file) : base(element, file) { } + public CorpsePrefab(ContentXElement element, CorpsesFile file) : base(element, file, npcSetIdentifier: Identifier.Empty) { } public static CorpsePrefab Random(Rand.RandSync sync = Rand.RandSync.Unsynced) => Prefabs.GetRandom(sync); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 2381b31cc..c159dfbe5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -55,13 +55,27 @@ namespace Barotrauma if (vitalityMultipliers != null) { float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f); - vitalityMultipliers.ForEach(i => VitalityMultipliers.Add(i, multiplier)); + foreach (var vitalityMultiplier in vitalityMultipliers) + { + VitalityMultipliers.Add(vitalityMultiplier, multiplier); + if (AfflictionPrefab.Prefabs.None(p => p.Identifier == vitalityMultiplier)) + { + DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions with the identifier \"{vitalityMultiplier}\". Did you mean to define the afflictions by type instead?"); + } + } } var vitalityTypeMultipliers = subElement.GetAttributeIdentifierArray("type", null) ?? subElement.GetAttributeIdentifierArray("types", null); if (vitalityTypeMultipliers != null) { float multiplier = subElement.GetAttributeFloat("multiplier", 1.0f); - vitalityTypeMultipliers.ForEach(i => VitalityTypeMultipliers.Add(i, multiplier)); + foreach (var vitalityTypeMultiplier in vitalityTypeMultipliers) + { + VitalityTypeMultipliers.Add(vitalityTypeMultiplier, multiplier); + if (AfflictionPrefab.Prefabs.None(p => p.AfflictionType == vitalityTypeMultiplier)) + { + DebugConsole.AddWarning($"Potentially incorrectly defined vitality multiplier in \"{characterHealth.Character.Name}\". Could not find any afflictions of the type \"{vitalityTypeMultiplier}\". Did you mean to define the afflictions by identifier instead?"); + } + } } if (vitalityMultipliers == null && VitalityTypeMultipliers == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 7e8a539ff..1e041438a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -10,7 +10,7 @@ namespace Barotrauma class HumanPrefab : PrefabWithUintIdentifier { [Serialize("any", IsPropertySaveable.No)] - public string Job { get; protected set; } + public Identifier Job { get; protected set; } [Serialize(1f, IsPropertySaveable.No)] public float Commonness { get; protected set; } @@ -82,17 +82,19 @@ namespace Barotrauma public XElement Element { get; protected set; } - public readonly Dictionary ItemSets = new Dictionary(); - public readonly Dictionary CustomNPCSets = new Dictionary(); + public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>(); + public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>(); - public HumanPrefab(ContentXElement element, ContentFile file) : base(file, element.GetAttributeIdentifier("identifier", "")) + private readonly Identifier npcSetIdentifier; + + public HumanPrefab(ContentXElement element, ContentFile file, Identifier npcSetIdentifier) : base(file, element.GetAttributeIdentifier("identifier", "")) { SerializableProperty.DeserializeProperties(this, element); - Job = Job.ToLowerInvariant(); Element = element; - element.GetChildElements("itemset").ForEach(e => ItemSets.Add(e, e.GetAttributeFloat("commonness", 1))); - element.GetChildElements("character").ForEach(e => CustomNPCSets.Add(e, e.GetAttributeFloat("commonness", 1))); + element.GetChildElements("itemset").ForEach(e => ItemSets.Add((e, e.GetAttributeFloat("commonness", 1)))); + element.GetChildElements("character").ForEach(e => CustomCharacterInfos.Add((e, e.GetAttributeFloat("commonness", 1)))); PreferredOutpostModuleTypes = element.GetAttributeIdentifierArray("preferredoutpostmoduletypes", Array.Empty()); + this.npcSetIdentifier = npcSetIdentifier; } public IEnumerable GetModuleFlags() @@ -107,7 +109,7 @@ namespace Barotrauma public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func predicate = null) { - return Job != null && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate); + return !Job.IsEmpty && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate); } public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null) @@ -146,23 +148,43 @@ namespace Barotrauma } } - public void GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true) + public bool GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true) { - if (ItemSets == null || !ItemSets.Any()) { return; } - var spawnItems = ToolBox.SelectWeightedRandom(ItemSets.Keys.ToList(), ItemSets.Values.ToList(), randSync); + if (ItemSets == null || !ItemSets.Any()) { return false; } + var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element; if (spawnItems != null) { foreach (XElement itemElement in spawnItems.GetChildElements("item")) { - InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents); + int amount = itemElement.GetAttributeInt("amount", 1); + for (int i = 0; i < amount; i++) + { + InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents); + } } } + return true; } - public CharacterInfo GetCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced) + /// + /// Creates a character info from the human prefab. If there are custom character infos defined, those are used, otherwise a randomized info is generated. + /// + /// + /// + public CharacterInfo CreateCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced) { - var characterElement = ToolBox.SelectWeightedRandom(CustomNPCSets.Keys.ToList(), CustomNPCSets.Values.ToList(), randSync); - return characterElement != null ? new CharacterInfo(characterElement) : null; + var characterElement = ToolBox.SelectWeightedRandom(CustomCharacterInfos, info => info.commonness, randSync).element; + CharacterInfo characterInfo; + if (characterElement == null) + { + characterInfo= new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: GetJobPrefab(randSync), npcIdentifier: Identifier); + } + else + { + characterInfo = new CharacterInfo(characterElement, Identifier); + } + characterInfo.HumanPrefabIds = (npcSetIdentifier, Identifier); + return characterInfo; } public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, Item parentItem = null, bool createNetworkEvents = true) @@ -229,7 +251,11 @@ namespace Barotrauma parentItem?.Combine(item, user: null); foreach (XElement childItemElement in itemElement.Elements()) { - InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents); + int amount = childItemElement.GetAttributeInt("amount", 1); + for (int i = 0; i < amount; i++) + { + InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs index 615a33350..e1748b990 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/Job.cs @@ -19,7 +19,7 @@ namespace Barotrauma public int Variant; - public Skill PrimarySkill { get; } + public Skill PrimarySkill { get; private set; } public Job(JobPrefab jobPrefab) : this(jobPrefab, randSync: Rand.RandSync.Unsynced, variant: 0) { } @@ -102,9 +102,14 @@ namespace Barotrauma public void OverrideSkills(Dictionary newSkills) { skills.Clear(); - foreach (var newSkill in newSkills) + foreach (var newSkillInfo in newSkills) { - skills.Add(newSkill.Key, new Skill(newSkill.Key, newSkill.Value)); + var newSkill = new Skill(newSkillInfo.Key, newSkillInfo.Value); + if (PrimarySkill != null && newSkill.Identifier == PrimarySkill.Identifier) + { + PrimarySkill = newSkill; + } + skills.Add(newSkillInfo.Key, newSkill); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs index 8153bca07..b0df4f80d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Jobs/JobPrefab.cs @@ -79,7 +79,7 @@ namespace Barotrauma /// public static IReadOnlyDictionary ItemRepairPriorities => _itemRepairPriorities; - public static JobPrefab Get(string identifier) + public static JobPrefab Get(Identifier identifier) { if (Prefabs.ContainsKey(identifier)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index e958f391f..847ef13d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -303,6 +303,17 @@ namespace Barotrauma public Vector2 DebugTargetPos; public Vector2 DebugRefPos; + public bool IsLowerBody => + type == LimbType.LeftLeg || + type == LimbType.RightLeg || + type == LimbType.LeftFoot || + type == LimbType.RightFoot || + type == LimbType.Tail || + type == LimbType.Legs || + type == LimbType.RightThigh || + type == LimbType.LeftThigh || + type == LimbType.Waist; + public bool IsSevered { get { return isSevered; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs index 292b58fc6..cc14d0fc6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/NPCPersonalityTrait.cs @@ -5,9 +5,11 @@ using System.Xml.Linq; namespace Barotrauma { - class NPCPersonalityTrait + class NPCPersonalityTrait : PrefabWithUintIdentifier { - public readonly Identifier Name; + public readonly static PrefabCollection Traits = new PrefabCollection(); + + public readonly LocalizedString DisplayName; public readonly List AllowedDialogTags; @@ -17,43 +19,29 @@ namespace Barotrauma get { return commonness; } } - public static IEnumerable GetAll(LanguageIdentifier language) + public NPCPersonalityTrait(XElement element, NPCPersonalityTraitsFile file) + : base(file, element.GetAttributeIdentifier("identifier", element.GetAttributeIdentifier("name", Identifier.Empty))) { - if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language)) + string name = element.GetAttributeString("name", null); + if (name == null) { - DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead.."); - language = TextManager.DefaultLanguage; + DisplayName = TextManager.Get("personalitytrait." + Identifier) + .Fallback(Identifier.ToString()); } - return NPCConversationCollection.Collections[language] - .SelectMany(cc => cc.PersonalityTraits.Values); - } - - public static NPCPersonalityTrait Get(LanguageIdentifier language, Identifier traitName) - { - if (language != TextManager.DefaultLanguage && !NPCConversationCollection.Collections.ContainsKey(language)) + else { - DebugConsole.AddWarning($"Could not find NPC personality traits for the language \"{language}\". Using \"{TextManager.DefaultLanguage}\" instead.."); - language = TextManager.DefaultLanguage; + DisplayName = name; } - return NPCConversationCollection.Collections[language] - .FirstOrDefault(cc => cc.PersonalityTraits.ContainsKey(traitName)) - .PersonalityTraits[traitName]; - } - - public NPCPersonalityTrait(XElement element) - { - Name = element.GetAttributeIdentifier("name", ""); AllowedDialogTags = new List(element.GetAttributeStringArray("alloweddialogtags", Array.Empty())); commonness = element.GetAttributeFloat("commonness", 1.0f); } public static NPCPersonalityTrait GetRandom(string seed) { - #warning TODO: implement NPCPersonality content type and revise this for determinism var rand = new MTRandom(ToolBox.StringToInt(seed)); - var list = GetAll(GameSettings.CurrentConfig.Language); - return ToolBox.SelectWeightedRandom(list, t => t.commonness, rand); + return ToolBox.SelectWeightedRandom(Traits.OrderBy(t => t.UintIdentifier), t => t.commonness, rand); } + public override void Dispose() { } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs index 5bcac5e83..a534df7f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityTandemFire.cs @@ -17,7 +17,7 @@ namespace Barotrauma.Abilities protected override void ApplyEffect() { - if (Character.SelectedConstruction == null || !Character.SelectedConstruction.HasTag(tag)) { return; } + if (!SelectedItemHasTag(Character)) { return; } Character closestCharacter = null; float closestDistance = squaredMaxDistance; @@ -31,13 +31,17 @@ namespace Barotrauma.Abilities } } - if (closestCharacter?.SelectedConstruction == null || !closestCharacter.SelectedConstruction.HasTag(tag)) { return; } + if (!SelectedItemHasTag(closestCharacter)) { return; } if (closestDistance < squaredMaxDistance) { ApplyEffectSpecific(Character); ApplyEffectSpecific(closestCharacter); } + + bool SelectedItemHasTag(Character character) => + (character.SelectedItem != null && character.SelectedItem.HasTag(tag)) || + (character.SelectedSecondaryItem != null && character.SelectedSecondaryItem.HasTag(tag)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCPersonalityTraitsFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCPersonalityTraitsFile.cs new file mode 100644 index 000000000..98ea05e9f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/NPCPersonalityTraitsFile.cs @@ -0,0 +1,12 @@ +namespace Barotrauma +{ + sealed class NPCPersonalityTraitsFile : GenericPrefabFile + { + public NPCPersonalityTraitsFile(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) { } + + protected override bool MatchesSingular(Identifier identifier) => identifier == "personalitytrait"; + protected override bool MatchesPlural(Identifier identifier) => identifier == "personalitytraits"; + protected override PrefabCollection Prefabs => NPCPersonalityTrait.Traits; + protected override NPCPersonalityTrait CreatePrefab(ContentXElement element) => new NPCPersonalityTrait(element, this); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs b/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs index bdde120e8..8b3842223 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/CoroutineManager.cs @@ -84,7 +84,9 @@ namespace Barotrauma { static readonly List Coroutines = new List(); - public static float UnscaledDeltaTime, DeltaTime; + public static float DeltaTime { get; private set; } + + public static bool Paused { get; private set; } public static CoroutineHandle StartCoroutine(IEnumerable func, string name = "", bool useSeparateThread = false) { @@ -191,7 +193,7 @@ namespace Barotrauma if (current != null) { if (current.EndsCoroutine(handle) || handle.AbortRequested) { return true; } - if (!current.CheckFinished(UnscaledDeltaTime)) { return false; } + if (!current.CheckFinished(DeltaTime)) { return false; } } if (!handle.Coroutine.MoveNext()) { return true; } return false; @@ -204,7 +206,7 @@ namespace Barotrauma while (!handle.AbortRequested) { if (PerformCoroutineStep(handle)) { return; } - Thread.Sleep((int)(UnscaledDeltaTime * 1000)); + Thread.Sleep((int)(DeltaTime * 1000)); } } catch (ThreadAbortException) @@ -232,7 +234,7 @@ namespace Barotrauma { if (handle.Thread.ThreadState.HasFlag(ThreadState.Stopped)) { - if (handle.Exception!=null || handle.Coroutine.Current == CoroutineStatus.Failure) + if (handle.Exception != null || handle.Coroutine.Current == CoroutineStatus.Failure) { DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed"); } @@ -254,9 +256,9 @@ namespace Barotrauma #endif } // Updating just means stepping through all the coroutines - public static void Update(float unscaledDeltaTime, float deltaTime) + public static void Update(bool paused, float deltaTime) { - UnscaledDeltaTime = unscaledDeltaTime; + Paused = paused; DeltaTime = deltaTime; List coroutineList; @@ -282,8 +284,8 @@ namespace Barotrauma { public readonly float TotalTime; - float timer; - bool ignorePause; + private float timer; + private readonly bool ignorePause; public WaitForSeconds(float time, bool ignorePause = true) { @@ -295,7 +297,7 @@ namespace Barotrauma public override bool CheckFinished(float deltaTime) { #if !SERVER - if (ignorePause || !GUI.PauseMenuOpen) + if (ignorePause || !CoroutineManager.Paused) { timer -= deltaTime; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 0b4ac4c1d..a1954e4a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -798,7 +798,55 @@ namespace Barotrauma eventPrefabs.Select(prefab => prefab.Identifier).Distinct().Select(id => id.Value).ToArray() }; })); - + + commands.Add(new Command("unlockmission", "unlockmission [identifier/tag]: Unlocks a mission in a random adjacent level.", (string[] args) => + { + if (!(GameMain.GameSession?.GameMode is CampaignMode campaign)) + { + ThrowError("The unlockmission command is only usable in the campaign mode."); + return; + } + if (args.Length == 0) + { + ThrowError("Please enter the identifier or a tag of the mission you want to unlock."); + return; + } + var currentLocation = campaign.Map.CurrentLocation; + if (MissionPrefab.Prefabs.Any(p => p.Identifier == args[0])) + { + currentLocation.UnlockMissionByIdentifier(args[0].ToIdentifier()); + } + else + { + currentLocation.UnlockMissionByTag(args[0].ToIdentifier()); + } + if (campaign is MultiPlayerCampaign mpCampaign) + { + mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions); + } + }, isCheat: true, getValidArgs: () => + { + return new[] + { + MissionPrefab.Prefabs.Select(p => p.Identifier.ToString()).ToArray() + }; + })); + + commands.Add(new Command("setcampaignmetadata", "setcampaignmetadata [identifier] [value]: Sets the specified campaign metadata value.", (string[] args) => + { + if (!(GameMain.GameSession?.GameMode is CampaignMode campaign)) + { + ThrowError("The setcampaignmetadata command is only usable in the campaign mode."); + return; + } + if (args.Length < 2) + { + ThrowError("Please specify an identifier and a value."); + return; + } + SetDataAction.PerformOperation(campaign.CampaignMetadata, args[0].ToIdentifier(), args[1], SetDataAction.OperationType.Set); + }, isCheat: true)); + commands.Add(new Command("setskill", "setskill [all/identifier] [max/level] [character]: Set your skill level.", (string[] args) => { if (args.Length < 2) @@ -2532,9 +2580,12 @@ namespace Barotrauma #if CLIENT GameMain.DebugDraw = false; GameMain.LightManager.LightingEnabled = true; + Character.DebugDrawInteract = false; #endif Hull.EditWater = false; Hull.EditFire = false; + EnemyAIController.DisableEnemyAI = false; + HumanAIController.DisableCrewAI = false; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs index 27748f54d..a438db9a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs @@ -59,7 +59,11 @@ namespace Barotrauma [Serialize(false, IsPropertySaveable.Yes)] public bool ContinueConversation { get; set; } - private Character speaker; + public Character speaker + { + get; + private set; + } private AIObjective prevIdleObjective, prevGotoObjective; @@ -120,7 +124,7 @@ namespace Barotrauma #else foreach (Client c in GameMain.Server.ConnectedClients) { - if (c.InGame && c.Character != null) { ServerWrite(speaker, c); } + if (c.InGame && c.Character != null) { ServerWrite(speaker, c, interrupt); } } #endif ResetSpeaker(); @@ -331,9 +335,11 @@ namespace Barotrauma if (!TargetTag.IsEmpty) { targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e)); - if (!targets.Any() || IsBlockedByAnotherConversation(targets)) { return; } + if (!targets.Any() || IsBlockedByAnotherConversation(targets, BlockOtherConversationsDuration)) { return; } } + if (targetCharacter != null && IsBlockedByAnotherConversation(targetCharacter.ToEnumerable(), 0.1f)) { return; } + if (speaker?.AIController is HumanAIController humanAI) { prevIdleObjective = humanAI.ObjectiveManager.GetObjective(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 70f6a7b61..5250521eb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -118,7 +118,7 @@ namespace Barotrauma ISpatialEntity spawnPos = GetSpawnPos(); if (spawnPos != null) { - Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.GetCharacterInfo(), onSpawn: newCharacter => + Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, 100.0f), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter => { if (newCharacter == null) { return; } newCharacter.HumanPrefab = humanPrefab; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs index 09a689776..385dc13ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs @@ -157,8 +157,9 @@ namespace Barotrauma npcsOrItems.Add(item); } item.CampaignInteractionType = CampaignMode.InteractionType.Examine; - if (player.SelectedConstruction == item || - player.Inventory != null && player.Inventory.Contains(item) || + if (player.SelectedItem == item || + player.SelectedSecondaryItem == item || + (player.Inventory != null && player.Inventory.Contains(item)) || (player.FocusedItem == item && player.IsKeyHit(InputType.Use))) { Trigger(e1, e2); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 63dd365ef..3fe4f0725 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -71,6 +71,10 @@ namespace Barotrauma private readonly List activeEvents = new List(); + private readonly HashSet finishedEvents = new HashSet(); + private readonly HashSet nonRepeatableEvents = new HashSet(); + + #if DEBUG && SERVER private DateTime nextIntensityLogTime; #endif @@ -169,54 +173,49 @@ namespace Barotrauma CreateEvents(additiveSet); } - if (level?.LevelData?.Type == LevelData.LevelType.Outpost) + if (level?.LevelData != null) { - //if the outpost is connected to a locked connection, create an event to unlock it - if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false) + if (level.LevelData.Type == LevelData.LevelType.Outpost) { - var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent); - var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e => - e.BiomeIdentifier.IsEmpty || - e.BiomeIdentifier == level.LevelData.Biome.Identifier); + //if the outpost is connected to a locked connection, create an event to unlock it + if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false) + { + var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent); + var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e => + e.BiomeIdentifier.IsEmpty || + e.BiomeIdentifier == level.LevelData.Biome.Identifier); - var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ? - ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) : - ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand); - if (unlockPathEventPrefab != null) - { - var newEvent = unlockPathEventPrefab.CreateInstance(); - newEvent.Init(); - ActiveEvents.Add(newEvent); - } - else - { - //if no event that unlocks the path can be found, unlock it automatically - level.StartLocation.Connections.ForEach(c => c.Locked = false); - } - } - - level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e))); - if (level.LevelData.EventHistory.Count > MaxEventHistory) - { - level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory); - } - AddChildEvents(initialEventSet); - void AddChildEvents(EventSet eventSet) - { - if (eventSet == null) { return; } - if (eventSet.OncePerOutpost) - { - foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs)) + var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ? + ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) : + ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand); + if (unlockPathEventPrefab != null) { - if (!level.LevelData.NonRepeatableEvents.Contains(ep)) - { - level.LevelData.NonRepeatableEvents.Add(ep); - } + var newEvent = unlockPathEventPrefab.CreateInstance(); + newEvent.Init(); + ActiveEvents.Add(newEvent); + } + else + { + //if no event that unlocks the path can be found, unlock it automatically + level.StartLocation.Connections.ForEach(c => c.Locked = false); } } - foreach (EventSet childSet in eventSet.ChildSets) + + AddChildEvents(initialEventSet); + void AddChildEvents(EventSet eventSet) { - AddChildEvents(childSet); + if (eventSet == null) { return; } + if (eventSet.OncePerOutpost) + { + foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs)) + { + nonRepeatableEvents.Add(ep); + } + } + foreach (EventSet childSet in eventSet.ChildSets) + { + AddChildEvents(childSet); + } } } } @@ -350,13 +349,33 @@ namespace Barotrauma selectedEvents.Clear(); activeEvents.Clear(); QueuedEvents.Clear(); + finishedEvents.Clear(); + nonRepeatableEvents.Clear(); preloadedSprites.ForEach(s => s.Remove()); preloadedSprites.Clear(); + pathFinder = null; } + /// + /// Registers the exhaustible events in the level as exhausted, and adds the current events to the event history + /// + public void RegisterEventHistory() + { + level.LevelData.EventsExhausted = true; + if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost) + { + level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e))); + if (level.LevelData.EventHistory.Count > MaxEventHistory) + { + level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory); + } + level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e))); + } + } + public void SkipEventCooldown() { eventCoolDown = 0.0f; @@ -375,6 +394,8 @@ namespace Barotrauma selectedEvents.Remove(eventSet); if (level == null) { return; } if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; } + if (eventSet.Exhaustible && level.LevelData.EventsExhausted) { return; } + DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true); int applyCount = 1; @@ -427,7 +448,8 @@ namespace Barotrauma if (suitablePrefabSubsets.Any()) { var unusedEvents = suitablePrefabSubsets.ToList(); - for (int j = 0; j < eventSet.EventCount; j++) + int eventCount = eventSet.GetEventCount(level); + for (int j = 0; j < eventCount; j++) { if (unusedEvents.All(e => e.EventPrefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; } EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, e => e.EventPrefabs.Max(p => CalculateCommonness(p, e.Commonness)), rand); @@ -706,7 +728,18 @@ namespace Barotrauma foreach (Event ev in activeEvents) { - if (!ev.IsFinished) { ev.Update(deltaTime); } + if (!ev.IsFinished) + { + ev.Update(deltaTime); + } + else if (!finishedEvents.Contains(ev)) + { + if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost) + { + if (!level.LevelData.EventHistory.Contains(ev.Prefab)) { level.LevelData.EventHistory.Add(ev.Prefab); } + } + finishedEvents.Add(ev); + } } if (QueuedEvents.Count > 0) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs index 3c2be4201..a8015d651 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventSet.cs @@ -92,7 +92,13 @@ namespace Barotrauma public readonly bool ChooseRandom; - public readonly int EventCount = 1; + private readonly int eventCount = 1; + private readonly Dictionary overrideEventCount = new Dictionary(); + + /// + /// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed. + /// + public readonly bool Exhaustible; public readonly float MinDistanceTraveled; public readonly float MinMissionTime; @@ -250,7 +256,8 @@ namespace Barotrauma MaxIntensity = Math.Max(element.GetAttributeFloat("maxintensity", 100.0f), MinIntensity); ChooseRandom = element.GetAttributeBool("chooserandom", false); - EventCount = element.GetAttributeInt("eventcount", 1); + eventCount = element.GetAttributeInt("eventcount", 1); + Exhaustible = element.GetAttributeBool("exhaustible", false); MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f); MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f); @@ -288,6 +295,13 @@ namespace Barotrauma case "eventset": childSets.Add(new EventSet(subElement, file, this)); break; + case "overrideeventcount": + Identifier locationType = subElement.GetAttributeIdentifier("locationtype", ""); + if (!overrideEventCount.ContainsKey(locationType)) + { + overrideEventCount.Add(locationType, subElement.GetAttributeInt("eventcount", eventCount)); + } + break; default: //an element with just an identifier = reference to an event prefab if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase)) @@ -332,6 +346,12 @@ namespace Barotrauma return OverrideCommonness.ContainsKey(key) ? OverrideCommonness[key] : DefaultCommonness; } + public int GetEventCount(Level level) + { + if (level?.StartLocation == null || !overrideEventCount.TryGetValue(level.StartLocation.Type.Identifier, out int count)) { return eventCount; } + return count; + } + public static List GetDebugStatistics(int simulatedRoundCount = 100, Func filter = null, bool fullLog = false) { List debugLines = new List(); @@ -358,7 +378,7 @@ namespace Barotrauma var unusedEvents = thisSet.EventPrefabs.ToList(); if (unusedEvents.Any()) { - for (int i = 0; i < thisSet.EventCount; i++) + for (int i = 0; i < thisSet.eventCount; i++) { var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced); if (eventPrefab.EventPrefabs.Any(p => p != null)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index a1434ba45..e94c4432c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -95,37 +95,42 @@ namespace Barotrauma randSync = Rand.RandSync.Unsynced; } - //if any of the escortees have a job defined, try to use a spawnpoint designated for that job + List humanPrefabsToSpawn = new List(); foreach (XElement element in characterConfig.Elements()) { + int count = CalculateScalingEscortedCharacterCount(inMission: true); var humanPrefab = GetHumanPrefabFromElement(element); - if (humanPrefab == null || string.IsNullOrEmpty(humanPrefab.Job) || humanPrefab.Job.Equals("any", StringComparison.OrdinalIgnoreCase)) { continue; } + for (int i = 0; i < count; i++) + { + humanPrefabsToSpawn.Add(humanPrefab); + } + } - var jobPrefab = humanPrefab.GetJobPrefab(); + //if any of the escortees have a job defined, try to use a spawnpoint designated for that job + foreach (var humanPrefab in humanPrefabsToSpawn) + { + if (humanPrefab == null || humanPrefab.Job.IsEmpty || humanPrefab.Job == "any") { continue; } + var jobPrefab = humanPrefab.GetJobPrefab(randSync); if (jobPrefab != null) { var jobSpecificSpawnPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, Submarine.MainSub); - if (jobSpecificSpawnPos != null) + if (jobSpecificSpawnPos != null) { explicitStayInHullPos = jobSpecificSpawnPos; break; } } } - - foreach (XElement element in characterConfig.Elements()) + foreach (var humanPrefab in humanPrefabsToSpawn) { - int count = CalculateScalingEscortedCharacterCount(inMission: true); - for (int i = 0; i < count; i++) + Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync); + if (spawnedCharacter.AIController is HumanAIController humanAI) { - Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(element), characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync); - if (spawnedCharacter.AIController is HumanAIController humanAI) - { - humanAI.InitMentalStateManager(); - } + humanAI.InitMentalStateManager(); } } + if (terroristChance > 0f) { int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index 4975202c3..e26735cc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -511,7 +511,7 @@ namespace Barotrauma protected Character CreateHuman(HumanPrefab humanPrefab, List characters, Dictionary> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, Rand.RandSync humanPrefabRandSync = Rand.RandSync.ServerAndClient, bool giveTags = true) { - var characterInfo = humanPrefab.GetCharacterInfo(Rand.RandSync.ServerAndClient) ?? new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobOrJobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync); + var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient); characterInfo.TeamID = teamType; if (positionToStayIn == null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index f861c2d10..eb296dad9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -2,21 +2,19 @@ using System.Collections.Generic; using System.Linq; using System; +using Barotrauma.Extensions; namespace Barotrauma { partial class MonsterMission : Mission { - //string = filename, point = min,max private readonly HashSet<(CharacterPrefab character, Point amountRange)> monsterPrefabs = new HashSet<(CharacterPrefab character, Point amountRange)>(); private readonly List monsters = new List(); private readonly List sonarPositions = new List(); - private readonly List tempSonarPositions = new List(); - private readonly float maxSonarMarkerDistance = 10000.0f; - private readonly Level.PositionType spawnPosType; + private Vector2? spawnPos = null; public override IEnumerable SonarPositions { @@ -114,7 +112,16 @@ namespace Barotrauma if (!IsClient) { - Level.Loaded.TryGetInterestingPosition(true, spawnPosType, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos); + float minDistBetweenMonsterMissions = 10000; + float mindDistFromSub = Level.Loaded.Size.X * 0.3f; + var monsterMissions = GameMain.GameSession.Missions.Select(e => e as MonsterMission).Where(m => m != null && m != this && m.spawnPos.HasValue); + if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Vector2 spawnPos, + filter: p => monsterMissions.None(m => Vector2.DistanceSquared(p.Position.ToVector2(), m.spawnPos.Value) < minDistBetweenMonsterMissions * minDistBetweenMonsterMissions), + suppressWarning: true)) + { + Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out spawnPos); + } + this.spawnPos = spawnPos; foreach (var (character, amountRange) in monsterPrefabs) { int amount = Rand.Range(amountRange.X, amountRange.Y + 1); @@ -123,9 +130,8 @@ namespace Barotrauma monsters.Add(Character.Create(character.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false)); } } - InitializeMonsters(monsters); - } + } } private void InitializeMonsters(IEnumerable monsters) @@ -181,7 +187,7 @@ namespace Barotrauma } if (monsters[i].Removed || monsters[i].IsDead) { continue; } - Vector2 diff = tempSonarPositions[i] - monsters[i].Position; + Vector2 diff = tempSonarPositions[i] - monsters[i].WorldPosition; float maxDist = maxSonarMarkerDistance; Submarine refSub = Character.Controlled?.Submarine ?? Submarine.MainSub; @@ -191,12 +197,12 @@ namespace Barotrauma float subDist = Vector2.Distance(refPos, tempSonarPositions[i]) / maxDist; maxDist = Math.Min(subDist * subDist * maxDist, maxDist); - maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].Position), maxDist); + maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].WorldPosition), maxDist); } if (diff.LengthSquared() > maxDist * maxDist) { - tempSonarPositions[i] = monsters[i].Position + Vector2.Normalize(diff) * maxDist; + tempSonarPositions[i] = monsters[i].WorldPosition + Vector2.Normalize(diff) * maxDist; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs index c26b3f54b..8dae48c00 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/MonsterEvent.cs @@ -125,7 +125,8 @@ namespace Barotrauma var file = CharacterPrefab.FindBySpeciesName(SpeciesName)?.ContentFile; if (file == null) { - DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\""); + DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\"."); + disallowed = true; yield break; } else @@ -535,10 +536,10 @@ namespace Barotrauma Character createdCharacter = Character.Create(SpeciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true, throwErrorIfNotFound: false); if (createdCharacter == null) { + DebugConsole.AddWarning($"Error in MonsterEvent: failed to spawn the character \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\"."); disallowed = true; return; } - var eventManager = GameMain.GameSession.EventManager; if (eventManager != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs index ac9e379a4..9a0eddeed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsManager.cs @@ -342,6 +342,21 @@ namespace Barotrauma } private static Implementation? loadedImplementation; + private static void ValidateEventID(string eventID) + { +#if DEBUG + string[] parts = eventID.Split(':'); + if (parts.Length > 5) + { + DebugConsole.ThrowError($"Invalid GameAnalytics event id \"{eventID}\". Only 5 id parts allowed separated by ':'"); + } + if (parts.Any(p => p.Length > 32)) + { + DebugConsole.ThrowError($"Invalid GameAnalytics event id \"{eventID}\". Each id part separated by ':' must be 32 characters or less."); + } +#endif + } + public static void AddErrorEvent(ErrorSeverity errorSeverity, string message) { if (!SendUserStatistics) { return; } @@ -368,12 +383,14 @@ namespace Barotrauma public static void AddDesignEvent(string eventID) { if (!SendUserStatistics) { return; } + ValidateEventID(eventID); loadedImplementation?.AddDesignEvent(eventID); } public static void AddDesignEvent(string eventID, double value) { if (!SendUserStatistics) { return; } + ValidateEventID(eventID); loadedImplementation?.AddDesignEvent(eventID, value); } @@ -450,11 +467,11 @@ namespace Barotrauma SetConsent(Consent.Error); return; } + loadedImplementation?.SetEnabledInfoLog(true); + loadedImplementation?.SetEnabledVerboseLog(true); #if DEBUG try { - loadedImplementation?.SetEnabledInfoLog(true); - loadedImplementation?.SetEnabledVerboseLog(true); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs index c4d5be01a..cab2c43d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs @@ -302,6 +302,10 @@ namespace Barotrauma var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true); foreach (PurchasedItem item in itemsToPurchase) { + // Exchange money + int itemValue = item.Quantity * buyValues[item.ItemPrefab]; + if (!campaign.TryPurchase(client, itemValue)) { continue; } + // Add to the purchased items var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab); if (purchasedItem != null) @@ -313,9 +317,6 @@ namespace Barotrauma purchasedItem = new PurchasedItem(item.ItemPrefab, item.Quantity, client); itemsPurchasedFromStore.Add(purchasedItem); } - // Exchange money - int itemValue = item.Quantity * buyValues[item.ItemPrefab]; - campaign.TryPurchase(client, itemValue); if (GameMain.IsSingleplayer) { GameAnalyticsManager.AddMoneySpentEvent(itemValue, GameAnalyticsManager.MoneySink.Store, item.ItemPrefab.Identifier.Value); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs index 1d88c39a6..06dc62593 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/CampaignMetadata.cs @@ -96,20 +96,17 @@ namespace Barotrauma private object GetTypeOrDefault(Identifier identifier, Type type, object defaultValue) { object? value = GetValue(identifier); - - if (value == null) + if (value != null) { - SetValue(identifier, defaultValue); + if (value.GetType() == type) + { + return value; + } + else + { + DebugConsole.ThrowError($"Attempted to get value \"{identifier}\" as a {type} but the value is {value.GetType()}."); + } } - else if (value.GetType() == type) - { - return value; - } - else - { - DebugConsole.ThrowError($"Attempted to get value \"{identifier}\" as a {type} but the value is {value.GetType()}."); - } - return defaultValue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index 7bf83c372..7e6bf9e6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -34,7 +34,7 @@ namespace Barotrauma public double TotalPlayTime; public int TotalPassedLevels; - public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Repair, Upgrade, PurchaseSub, MedicalClinic, Cargo } + public enum InteractionType { None, Talk, Examine, Map, Crew, Store, Upgrade, PurchaseSub, MedicalClinic, Cargo } public static bool BlocksInteraction(InteractionType interactionType) { @@ -85,7 +85,9 @@ namespace Barotrauma public bool CheatsEnabled; - public const int HullRepairCost = 500, ItemRepairCost = 500, ShuttleReplaceCost = 1000; + public const float HullRepairCostPerDamage = 0.5f, ItemRepairCostPerRepairDuration = 1.0f; + public const int ShuttleReplaceCost = 1000; + public const int MaxHullRepairCost = 2000, MaxItemRepairCost = 2000; protected bool wasDocked; @@ -152,8 +154,9 @@ namespace Barotrauma { if (!(e.ChangedData.BalanceChanged is Some { Value: var changed })) { return; } - bool isGain = changed > 0; + if (changed != 0) { return; } + bool isGain = changed > 0; Color clr = isGain ? GUIStyle.Yellow : GUIStyle.Red; switch (e.Owner) @@ -260,6 +263,39 @@ namespace Barotrauma wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost); } + public int GetHullRepairCost() + { + float totalDamage = 0; + foreach (Structure wall in Structure.WallList) + { + if (wall.Submarine == null || wall.Submarine.Info.Type != SubmarineType.Player) { continue; } + if (wall.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(wall.Submarine)) + { + for (int i = 0; i < wall.SectionCount; i++) + { + totalDamage += wall.SectionDamage(i); + } + } + } + return (int)Math.Min(totalDamage * HullRepairCostPerDamage, MaxHullRepairCost); + } + + public int GetItemRepairCost() + { + float totalRepairDuration = 0.0f; + foreach (Item item in Item.ItemList) + { + if (item.Submarine == null || item.Submarine.Info.Type != SubmarineType.Player) { continue; } + if (item.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(item.Submarine)) + { + var repairable = item.GetComponent(); + if (repairable == null) { continue; } + totalRepairDuration += repairable.FixDurationHighSkill * (1.0f - item.Condition / item.MaxCondition); + } + } + return (int)Math.Min(totalRepairDuration * ItemRepairCostPerRepairDuration, MaxItemRepairCost); + } + public void InitCampaignData() { Factions = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 68bfe6969..bb0dfda53 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -864,7 +864,7 @@ namespace Barotrauma double roundDuration = Timing.TotalTime - RoundStartTime; GameAnalyticsManager.AddProgressionEvent( success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail, - GameMode?.Name?.Value ?? "none", + GameMode?.Preset.Identifier.Value ?? "none", roundDuration); string eventId = "EndRound:" + (GameMode?.Preset?.Identifier.Value ?? "none") + ":"; LogEndRoundStats(eventId); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs index b133e0228..59e15246d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/UpgradeManager.cs @@ -60,16 +60,6 @@ namespace Barotrauma /// public const bool UpgradeAlsoConnectedSubs = true; - /// - /// Prevents the player from upgrading the submarine when we are switching to a new one. - /// - /// - /// In singleplayer we check if CampaignMode.PendingSubmarineSwitch is not null indicating we are switching submarines - /// but in multiplayer that value is not synced so we use this variable instead by setting it to false in - /// and then set it back to true when the round ends in - /// - public bool CanUpgrade = true; - /// /// This is used by the client in multiplayer, acts like a secondary PendingUpgrades list /// but is not affected by server messages. @@ -713,9 +703,9 @@ namespace Barotrauma public bool CanUpgradeSub() { - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return CanUpgrade; } - - return Campaign.PendingSubmarineSwitch == null; + return + Campaign.PendingSubmarineSwitch == null || + Campaign.PendingSubmarineSwitch.Name == Submarine.MainSub.Info.Name; } public void Save(XElement? parent) diff --git a/Barotrauma/BarotraumaShared/SharedSource/InputType.cs b/Barotrauma/BarotraumaShared/SharedSource/InputType.cs index 2b496a231..04c8c9239 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/InputType.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/InputType.cs @@ -12,7 +12,7 @@ namespace Barotrauma Ragdoll, Health, Grab, SelectNextCharacter, SelectPreviousCharacter, - Voice, + Voice, RadioVoice, LocalVoice, Deselect, Shoot, Command, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index 0fbda966d..8e49398a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -691,12 +691,30 @@ namespace Barotrauma.Items.Components { item.Drop(character); item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false); + //the light source won't get properly updated if lighting is disabled (even though the light sprite is still drawn when lighting is disabled) + //so let's ensure the light source is up-to-date + RefreshLightSources(item); } AttachToWall(); } return true; + + static void RefreshLightSources(Item item) + { + item.body?.UpdateDrawPosition(); + foreach (var light in item.GetComponents()) + { + light.SetLightSourceTransform(); + } + item.GetComponent()?.SetContainedItemPositions(); + foreach (var containedItem in item.ContainedItems) + { + RefreshLightSources(containedItem); + } + } } + public override bool SecondaryUse(float deltaTime, Character character = null) { return true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index a321e27a2..74ef0f776 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -1,12 +1,10 @@ -using Barotrauma.Networking; -using FarseerPhysics; +using FarseerPhysics; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; namespace Barotrauma.Items.Components { @@ -292,7 +290,6 @@ namespace Barotrauma.Items.Components item.body.PhysEnabled = false; } - private bool OnCollision(Fixture f1, Fixture f2, Contact contact) { if (User == null || User.Removed) @@ -419,7 +416,18 @@ namespace Barotrauma.Items.Components else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0) { if (targetItem.Removed) { return; } - Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); + var attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f); +#if CLIENT + if (attackResult.Damage > 0.0f) + { + Character.Controlled?.UpdateHUDProgressBar(targetItem, + targetItem.WorldPosition, + targetItem.Condition / targetItem.MaxCondition, + emptyColor: GUIStyle.HealthBarColorLow, + fullColor: GUIStyle.HealthBarColorHigh, + textTag: targetItem.Name); + } +#endif } else if (target.UserData is Holdable holdable && holdable.CanPush) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs index 5086b6899..61ab48fe9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Pickable.cs @@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components //attempting to pick does not select the item, so if it is selected at this point, another ItemComponent //must have been selected and we should not keep deattaching (happens when for example interacting with //an electrical component while holding both a screwdriver and a wrench). - if (picker.SelectedConstruction == item || + if (picker.IsAnySelectedItem(item)|| picker.IsKeyDown(InputType.Aim) || !picker.CanInteractWith(item) || item.Removed || item.ParentInventory != null) @@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components !string.IsNullOrWhiteSpace(PickingMsg) ? PickingMsg : this is Door ? "progressbar.opening" : "progressbar.deattaching"); #endif - picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f)); + picker.AnimController.UpdateUseItem(!picker.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f)); pickTimer += CoroutineManager.DeltaTime; yield return CoroutineStatus.Running; @@ -208,7 +208,7 @@ namespace Barotrauma.Items.Components { if (picker != null) { - picker.AnimController.Anim = AnimController.Animation.None; + picker.AnimController.StopUsingItem(); picker.PickingItem = null; } if (pickingCoroutine != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs index f7b360dad..9986d7130 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RepairTool.cs @@ -18,6 +18,7 @@ namespace Barotrauma.Items.Components }; private readonly HashSet fixableEntities; + private readonly HashSet nonFixableEntities; private Vector2 pickedPosition; private float activeTimer; @@ -135,6 +136,7 @@ namespace Barotrauma.Items.Components } fixableEntities = new HashSet(); + nonFixableEntities = new HashSet(); foreach (var subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) @@ -147,7 +149,16 @@ namespace Barotrauma.Items.Components } else { - fixableEntities.Add(subElement.GetAttributeIdentifier("identifier", "")); + foreach (Identifier id in subElement.GetAttributeIdentifierArray("identifier", Array.Empty())) + { + fixableEntities.Add(id); + } + } + break; + case "nonfixable": + foreach (Identifier id in subElement.GetAttributeIdentifierArray("identifier", Array.Empty())) + { + nonFixableEntities.Add(id); } break; } @@ -523,6 +534,7 @@ namespace Barotrauma.Items.Components if (sectionIndex < 0) { return false; } if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; } + if (nonFixableEntities.Contains(targetStructure.Prefab.Identifier) || nonFixableEntities.Any(t => targetStructure.Tags.Contains(t))) { return false; } ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, structure: targetStructure); FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 0ea9d4cad..26b9713d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -49,6 +49,8 @@ namespace Barotrauma.Items.Components } } + public readonly NamedEvent OnContainedItemsChanged = new NamedEvent(); + private bool alwaysContainedItemsSpawned; public ItemInventory Inventory; @@ -347,6 +349,7 @@ namespace Barotrauma.Items.Components //no need to Update() if this item has no statuseffects and no physics body IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null); + OnContainedItemsChanged.Invoke(this); } public override void Move(Vector2 amount, bool ignoreContacts = false) @@ -360,6 +363,7 @@ namespace Barotrauma.Items.Components //deactivate if the inventory is empty IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null); + OnContainedItemsChanged.Invoke(this); } public bool CanBeContained(Item item) @@ -496,7 +500,7 @@ namespace Barotrauma.Items.Components return false; } } - if (AutoInteractWithContained && character.SelectedConstruction == null) + if (AutoInteractWithContained && character.SelectedItem == null) { foreach (Item contained in Inventory.AllItems) { @@ -510,7 +514,15 @@ namespace Barotrauma.Items.Components var abilityItem = new AbilityItemContainer(item); character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem); - return base.Select(character); + if (item.ParentInventory?.Owner == character) + { + //can't select ItemContainers in the character's inventory (the inventory is drawn by hovering the cursor over the inventory slot, not as a GUIFrame) + return false; + } + else + { + return base.Select(character); + } } public override bool Pick(Character picker) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs index c1cb4be89..4d0f92aed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Ladder.cs @@ -19,8 +19,7 @@ namespace Barotrauma.Items.Components public override bool Select(Character character) { if (character == null || character.LockHands || character.Removed || !(character.AnimController is HumanoidAnimController)) return false; - - character.AnimController.Anim = AnimController.Animation.Climbing; + character.AnimController.StartClimbing(); return true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 84ad8903e..e11973068 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -36,6 +36,7 @@ namespace Barotrauma.Items.Components private readonly List limbPositions = new List(); private Direction dir; + public Direction Direction => dir; //the position where the user walks to when using the controller //(relative to the position of the item) @@ -128,6 +129,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, IsPropertySaveable.No, description: "If true, other items can be used simultaneously.")] + public bool IsSecondaryItem + { + get; + private set; + } + public Controller(Item item, ContentXElement element) : base(item, element) { @@ -150,7 +158,7 @@ namespace Barotrauma.Items.Components if (user == null || user.Removed - || user.SelectedConstruction != item + || !user.IsAnySelectedItem(item) || item.ParentInventory != null || !user.CanInteractWith(item) || (UsableIn == UseEnvironment.Water && !user.AnimController.InWater) @@ -165,7 +173,7 @@ namespace Barotrauma.Items.Components return; } - user.AnimController.Anim = AnimController.Animation.UsingConstruction; + user.AnimController.StartUsingItem(); if (userPos != Vector2.Zero) { @@ -186,32 +194,34 @@ namespace Barotrauma.Items.Components } else { - diff.Y = 0.0f; - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled) + // Secondary items (like ladders or chairs) will control the character position over primary items + // Only control the character position if the character doesn't have another secondary item already controlling it + if (!user.HasSelectedAnotherSecondaryItem(Item)) { - if (Math.Abs(diff.X) > 20.0f) + diff.Y = 0.0f; + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled) { - //wait for the character to walk to the correct position - return; + if (Math.Abs(diff.X) > 20.0f) + { + //wait for the character to walk to the correct position + return; + } + else if (Math.Abs(diff.X) > 0.1f) + { + //aim to keep the collider at the correct position once close enough + user.AnimController.Collider.LinearVelocity = new Vector2( + diff.X * 0.1f, + user.AnimController.Collider.LinearVelocity.Y); + } } - else if (Math.Abs(diff.X) > 0.1f) - { - //aim to keep the collider at the correct position once close enough - user.AnimController.Collider.LinearVelocity = new Vector2( - diff.X * 0.1f, - user.AnimController.Collider.LinearVelocity.Y); - } - } - else - { - if (Math.Abs(diff.X) > 10.0f) + else if (Math.Abs(diff.X) > 10.0f) { user.AnimController.TargetMovement = Vector2.Normalize(diff); user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; return; } + user.AnimController.TargetMovement = Vector2.Zero; } - user.AnimController.TargetMovement = Vector2.Zero; UserInCorrectPosition = true; } } @@ -220,9 +230,16 @@ namespace Barotrauma.Items.Components if (limbPositions.Count == 0) { return; } - user.AnimController.Anim = AnimController.Animation.UsingConstruction; + user.AnimController.StartUsingItem(); - user.AnimController.ResetPullJoints(); + if (user.SelectedItem != null) + { + user.AnimController.ResetPullJoints(l => l.IsLowerBody); + } + else + { + user.AnimController.ResetPullJoints(); + } if (dir != 0) { user.AnimController.TargetDir = dir; } @@ -230,7 +247,10 @@ namespace Barotrauma.Items.Components { Limb limb = user.AnimController.GetLimb(lb.LimbType); if (limb == null || !limb.body.Enabled) { continue; } - + // Don't move lower body limbs if there's another selected secondary item that should control them + if (limb.IsLowerBody && user.HasSelectedAnotherSecondaryItem(Item)) { continue; } + // Don't move hands if there's a selected primary item that should control them + if (!limb.IsLowerBody && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; } if (lb.AllowUsingLimb) { switch (lb.LimbType) @@ -247,12 +267,9 @@ namespace Barotrauma.Items.Components break; } } - limb.Disabled = true; - Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.Position * item.Scale; Vector2 diff = worldPosition - limb.WorldPosition; - limb.PullJointEnabled = true; limb.PullJointWorldAnchorB = limb.SimPosition + ConvertUnits.ToSimUnits(diff); } @@ -266,9 +283,7 @@ namespace Barotrauma.Items.Components { return false; } - - if (user == null || user.Removed || - user.SelectedConstruction != item || !user.CanInteractWith(item)) + if (user == null || user.Removed || !user.IsAnySelectedItem(item) || !user.CanInteractWith(item)) { user = null; return false; @@ -290,46 +305,44 @@ namespace Barotrauma.Items.Components } lastUsed = Timing.TotalTime; - - ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); - + ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); return true; } public override bool SecondaryUse(float deltaTime, Character character = null) { - if (this.user != character) + if (user != character) { return false; } - - if (this.user == null || character.Removed || - this.user.SelectedConstruction != item || !character.CanInteractWith(item)) + if (user == null || character.Removed || !user.IsAnySelectedItem(item) || !character.CanInteractWith(item)) + { + user = null; + return false; + } + if (character == null) { - this.user = null; return false; } - if (character == null) return false; focusTarget = GetFocusTarget(); + if (focusTarget == null) { Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y); - Vector2 offset = character.CursorWorldPosition - centerPos; offset.Y = -offset.Y; - targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset)); return false; } character.ViewTarget = focusTarget; + #if CLIENT if (character == Character.Controlled && cam != null) { Lights.LightManager.ViewTarget = focusTarget; cam.TargetPos = focusTarget.WorldPosition; - cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected * focusTarget.OffsetOnSelectedMultiplier, deltaTime * 10.0f); HideHUDs(true); } @@ -338,16 +351,12 @@ namespace Barotrauma.Items.Components if (!character.IsRemotePlayer || character.ViewTarget == focusTarget) { Vector2 centerPos = new Vector2(focusTarget.WorldRect.Center.X, focusTarget.WorldRect.Center.Y); - - Turret turret = focusTarget.GetComponent(); - if (turret != null) + if (focusTarget.GetComponent() is { } turret) { centerPos = new Vector2(focusTarget.WorldRect.X + turret.TransformedBarrelPos.X, focusTarget.WorldRect.Y - turret.TransformedBarrelPos.Y); } - Vector2 offset = character.CursorWorldPosition - centerPos; offset.Y = -offset.Y; - targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset)); } return true; @@ -425,9 +434,10 @@ namespace Barotrauma.Items.Components humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f; } - if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; } + if (character.SelectedItem == item) { character.SelectedItem = null; } + if (character.SelectedSecondaryItem == item) { character.SelectedSecondaryItem = null; } - character.AnimController.Anim = AnimController.Animation.None; + character.AnimController.StopUsingItem(); if (character == Character.Controlled) { HideHUDs(false); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index c4f04b034..c119ceec5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -3,6 +3,7 @@ using Barotrauma.Extensions; using Barotrauma.Networking; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; @@ -62,11 +63,18 @@ namespace Barotrauma.Items.Components inputContainer = containers[0]; outputContainer = containers[1]; +#if CLIENT + Identifier eventIdentifier = new Identifier(nameof(Deconstructor)); + inputContainer.OnContainedItemsChanged.RegisterOverwriteExisting(eventIdentifier, OnItemSlotsChanged); +#endif + OnItemLoadedProjSpecific(); } partial void OnItemLoadedProjSpecific(); + partial void OnItemSlotsChanged(ItemContainer container); + public override void Update(float deltaTime, Camera cam) { MoveInputQueue(); @@ -281,6 +289,7 @@ namespace Barotrauma.Items.Components { Entity.Spawner.AddItemToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) => { + spawnedItem.SpawnedInCurrentOutpost = item.SpawnedInCurrentOutpost; spawnedItem.StolenDuringRound = targetItem.StolenDuringRound; spawnedItem.AllowStealing = targetItem.AllowStealing; for (int i = 0; i < outputContainer.Capacity; i++) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs index 1d48a1378..6c15619ce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Fabricator.cs @@ -556,8 +556,20 @@ namespace Barotrauma.Items.Components const int MaxCraftingSkill = 100; + //having a higher-than-100 skill (e.g. due to talents) gives +1 quality quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0; - quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0; + foreach (var skill in fabricatedItem.RequiredSkills) + { + //+1 quality if the character's skill level is >20% from the min requirement towards max skill + //e.g. if the skill requirement is 10 -> 28 + //40 -> 52 + //90 -> 92 + float skillRequirement = MathHelper.Lerp(skill.Level, MaxCraftingSkill, 0.2f); + if (user.GetSkillLevel(skill.Identifier) > skillRequirement) + { + quality += 1; + } + } return quality; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs index 4d9561116..f8191d796 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Reactor.cs @@ -226,7 +226,7 @@ namespace Barotrauma.Items.Components // (= bots turn autotemp back on when leaving the reactor) if (LastAIUser != null) { - if (LastAIUser.SelectedConstruction != item && LastAIUser.CanInteractWith(item)) + if (LastAIUser.SelectedItem != item && LastAIUser.CanInteractWith(item)) { AutoTemp = true; if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; } @@ -505,7 +505,7 @@ namespace Barotrauma.Items.Components { fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition); #if SERVER - if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item) + if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedItem == item) { GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime); } @@ -705,7 +705,7 @@ namespace Barotrauma.Items.Components { if (lastUser != null && lastUser != character && lastUser != LastAIUser) { - if (lastUser.SelectedConstruction == item && character.IsOnPlayerTeam) + if (lastUser.SelectedItem == item && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogReactorTaken").Value, null, 0.0f, "reactortaken".ToIdentifier(), 10.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs index a04b1eb25..fc32c5c30 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Steering.cs @@ -301,7 +301,7 @@ namespace Barotrauma.Items.Components float userSkill = 0.0f; if (user != null && controlledSub != null && - (user.SelectedConstruction == item || item.linkedTo.Contains(user.SelectedConstruction))) + (user.SelectedItem == item || item.linkedTo.Contains(user.SelectedItem))) { userSkill = user.GetSkillLevel("helm") / 100.0f; } @@ -333,7 +333,7 @@ namespace Barotrauma.Items.Components { showIceSpireWarning = false; if (user != null && user.Info != null && - user.SelectedConstruction == item && + user.SelectedItem == item && controlledSub != null && controlledSub.Velocity.LengthSquared() > 0.01f) { IncreaseSkillLevel(user, deltaTime); @@ -389,7 +389,7 @@ namespace Barotrauma.Items.Components } // if our tactical AI pilot has left, revert back to maintaining position - if (navigateTactically && (user == null || user.SelectedConstruction != item)) + if (navigateTactically && (user == null || user.SelectedItem != item)) { navigateTactically = false; AIRamTimer = 0f; @@ -722,7 +722,7 @@ namespace Barotrauma.Items.Components character.AIController.SteeringManager.Reset(); if (objective.Override) { - if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam) + if (user != character && user != null && user.SelectedItem == item && character.IsOnPlayerTeam) { character.Speak(TextManager.Get("DialogSteeringTaken").Value, null, 0.0f, "steeringtaken".ToIdentifier(), 10.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs index 46eac6dca..e23357700 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Power/PowerContainer.cs @@ -117,9 +117,6 @@ namespace Barotrauma.Items.Components [Serialize(false, IsPropertySaveable.Yes, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")] public bool ExponentialRechargeSpeed { get; set; } - [Editable(minValue: 0.0f, maxValue: 10.0f, decimals: 2), Serialize(0.5f, IsPropertySaveable.Yes)] - public float RechargeAdjustSpeed { get; set; } - private float efficiency; [Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, IsPropertySaveable.Yes, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")] public float Efficiency diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 9c281f00d..a6fee0611 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -851,7 +851,7 @@ namespace Barotrauma.Items.Components } else if (target.Body.UserData is Limb limb) { - if (!FriendlyFire && User != null && limb.character.IsFriendly(User)) + if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User)) { return false; } @@ -872,7 +872,18 @@ namespace Barotrauma.Items.Components if (targetItem.Removed) { return false; } if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0) { - attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f); + attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f); +#if CLIENT + if (attackResult.Damage > 0.0f) + { + Character.Controlled?.UpdateHUDProgressBar(targetItem, + targetItem.WorldPosition, + targetItem.Condition / targetItem.MaxCondition, + emptyColor: GUIStyle.HealthBarColorLow, + fullColor: GUIStyle.HealthBarColorHigh, + textTag: targetItem.Name); + } +#endif } } else if (target.Body.UserData is IDamageable damageable) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index 0d3dd454c..bd2becdac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -47,7 +47,7 @@ namespace Barotrauma.Items.Components private int qualityLevel; - [Editable, Serialize(0, IsPropertySaveable.Yes)] + [Editable(MinValueInt = 0, MaxValueInt = MaxQuality), Serialize(0, IsPropertySaveable.Yes)] public int QualityLevel { get { return qualityLevel; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 7f8fc6789..b8a0436e3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -343,7 +343,7 @@ namespace Barotrauma.Items.Components { CurrentFixer.CheckTalents(AbilityEffectType.OnStopTinkering); } - CurrentFixer.AnimController.Anim = AnimController.Animation.None; + CurrentFixer.AnimController.StopUsingItem(); CurrentFixer = null; currentRepairItem = null; currentFixerAction = FixActions.None; @@ -430,7 +430,7 @@ namespace Barotrauma.Items.Components if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } - if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead)) + if (CurrentFixer != null && (CurrentFixer.SelectedItem != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead)) { StopRepairing(CurrentFixer); return; @@ -502,7 +502,7 @@ namespace Barotrauma.Items.Components SteamAchievementManager.OnItemRepaired(item, CurrentFixer); CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete); } - if (CurrentFixer?.SelectedConstruction == item) { CurrentFixer.SelectedConstruction = null; } + if (CurrentFixer?.SelectedItem == item) { CurrentFixer.SelectedItem = null; } deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay); wasBroken = false; StopRepairing(CurrentFixer); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs index 41a02b481..c5d847e8e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs @@ -179,7 +179,7 @@ namespace Barotrauma.Items.Components { UpdateProjSpecific(deltaTime); - if (user == null || user.SelectedConstruction != item) + if (user == null || user.SelectedItem != item) { #if SERVER if (user != null) { item.CreateServerEvent(this); } @@ -196,7 +196,7 @@ namespace Barotrauma.Items.Components return; } - user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f)); + user.AnimController.UpdateUseItem(!user.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f)); } public override void UpdateBroken(float deltaTime, Camera cam) @@ -206,7 +206,7 @@ namespace Barotrauma.Items.Components partial void UpdateProjSpecific(float deltaTime); - public override bool Select(Character picker) + public bool CanRewire() { //attaching wires to items with a body is not allowed //(signal items remove their bodies when attached to a wall) @@ -214,6 +214,15 @@ namespace Barotrauma.Items.Components { return false; } + return true; + } + + public override bool Select(Character picker) + { + if (!CanRewire()) + { + return false; + } user = picker; #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs index 13409a48e..1d6188ebd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Terminal.cs @@ -106,11 +106,11 @@ namespace Barotrauma.Items.Components { case "set_text": case "signal_in": + if (string.IsNullOrEmpty(signal.value)) { return; } if (signal.value.Length > MaxMessageLength) { signal.value = signal.value.Substring(0, MaxMessageLength); } - string inputSignal = signal.value.Replace("\\n", "\n"); ShowOnDisplay(inputSignal, addToHistory: true, TextColor); break; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs index ec17bd33b..863210c6b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Wire.cs @@ -309,7 +309,7 @@ namespace Barotrauma.Items.Components if (nodes.Count == 0) { return; } Character user = item.ParentInventory?.Owner as Character; - editNodeDelay = (user?.SelectedConstruction == null) ? editNodeDelay - deltaTime : 0.5f; + editNodeDelay = (user?.SelectedItem == null) ? editNodeDelay - deltaTime : 0.5f; Submarine sub = item.Submarine; if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; } @@ -369,7 +369,7 @@ namespace Barotrauma.Items.Components user.AnimController.Collider.ApplyForce(forceDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f); if (diff.LengthSquared() > 50.0f * 50.0f) { - user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length())); + user.AnimController.UpdateUseItem(!user.IsClimbing, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length())); } if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) @@ -428,7 +428,7 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character character = null) { if (character == null || character != Character.Controlled) { return false; } - if (character.SelectedConstruction != null) { return false; } + if (character.HasSelectedAnyItem) { return false; } #if CLIENT if (Screen.Selected == GameMain.SubEditorScreen && !PlayerInput.PrimaryMouseButtonClicked()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index cf6c5cc42..a0b7b75be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -525,7 +525,7 @@ namespace Barotrauma.Items.Components UpdateLightComponents(); } - private void UpdateLightComponents() + public void UpdateLightComponents() { if (lightComponents != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 02095120c..6137938e3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -303,6 +303,10 @@ namespace Barotrauma { light.SetLightSourceTransform(); } + foreach (var turret in GetComponents()) + { + turret.UpdateLightComponents(); + } } #endif } @@ -458,7 +462,7 @@ namespace Barotrauma [Serialize(0.0f, IsPropertySaveable.No)] /// - /// Can be used by status effects or conditionals to modify the sound range + /// Can be used by status effects or conditionals to modify the sight range /// public new float SightRange { @@ -806,6 +810,10 @@ namespace Barotrauma } } + public bool IsLadder { get; } + + public bool IsSecondaryItem { get; } + public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID, bool callOnItemLoaded = true) : this(new Rectangle( (int)(position.X - itemPrefab.Sprite.size.X / 2 * itemPrefab.Scale), @@ -1013,6 +1021,9 @@ namespace Barotrauma qualityComponent = GetComponent(); + IsLadder = GetComponent() != null; + IsSecondaryItem = IsLadder || GetComponent() is { IsSecondaryItem: true }; + InitProjSpecific(); if (callOnItemLoaded) @@ -2491,16 +2502,30 @@ namespace Barotrauma if (user != null) { - if (user.SelectedConstruction == this) + if (user.SelectedItem == this) { if (user.IsKeyHit(InputType.Select) || forceSelectKey) { - user.SelectedConstruction = null; + user.SelectedItem = null; + } + } + else if (user.SelectedSecondaryItem == this) + { + if (user.IsKeyHit(InputType.Select) || forceSelectKey) + { + user.SelectedSecondaryItem = null; } } else if (selected) { - user.SelectedConstruction = this; + if (IsSecondaryItem) + { + user.SelectedSecondaryItem = this; + } + else + { + user.SelectedItem = this; + } } } @@ -3245,7 +3270,7 @@ namespace Barotrauma relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.RotationRad); Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin; - item.rect.Location -= (origin - oldOrigin).ToPoint(); + item.rect.Location -= ((origin - oldOrigin) * scaleRelativeToPrefab).ToPoint(); } if (item.PurchasedNewSwap && !string.IsNullOrEmpty(appliedSwap.SwappableItem?.SpawnWithId)) @@ -3433,7 +3458,8 @@ namespace Barotrauma foreach (Character character in Character.CharacterList) { - if (character.SelectedConstruction == this) { character.SelectedConstruction = null; } + if (character.SelectedItem == this) { character.SelectedItem = null; } + if (character.SelectedSecondaryItem == this) { character.SelectedSecondaryItem = null; } } Door door = GetComponent(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index 981ea9027..a6f3e2038 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -3128,14 +3128,14 @@ namespace Barotrauma return success; } - public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func filter = null) + public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Vector2 position, Func filter = null, bool suppressWarning = false) { - bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter); + bool success = TryGetInterestingPosition(useSyncedRand, positionType, minDistFromSubs, out Point pos, Vector2.Zero, minDistFromPoint: 0, filter, suppressWarning); position = pos.ToVector2(); return success; } - public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func filter = null) + public bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out Point position, Vector2 awayPoint, float minDistFromPoint = 0f, Func filter = null, bool suppressWarning = false) { if (!PositionsOfInterest.Any()) { @@ -3155,11 +3155,14 @@ namespace Barotrauma } if (!suitablePositions.Any()) { - string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace(); - GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); + if (!suppressWarning) + { + string errorMsg = "Could not find a suitable position of interest. (PositionType: " + positionType + ", minDistFromSubs: " + minDistFromSubs + ")\n" + Environment.StackTrace.CleanupStackTrace(); + GameAnalyticsManager.AddErrorEventOnce("Level.TryGetInterestingPosition:PositionTypeNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); #if DEBUG - DebugConsole.ThrowError(errorMsg); + DebugConsole.ThrowError(errorMsg); #endif + } position = PositionsOfInterest[Rand.Int(PositionsOfInterest.Count, (useSyncedRand ? Rand.RandSync.ServerAndClient : Rand.RandSync.Unsynced))].Position; return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs index a6de99b67..340238aa7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -57,6 +57,8 @@ namespace Barotrauma public readonly List EventHistory = new List(); public readonly List NonRepeatableEvents = new List(); + public bool EventsExhausted { get; set; } + public float CrushDepth { get @@ -130,6 +132,8 @@ namespace Barotrauma string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { }); NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n))); + + EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false); } @@ -238,7 +242,8 @@ namespace Barotrauma new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)), new XAttribute("size", XMLExtensions.PointToString(Size)), new XAttribute("generationparams", GenerationParams.Identifier), - new XAttribute("initialdepth", InitialDepth)); + new XAttribute("initialdepth", InitialDepth), + new XAttribute(nameof(EventsExhausted).ToLower(), EventsExhausted)); if (HasBeaconStation) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs index b09b2400a..a1086d71c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs @@ -944,15 +944,20 @@ namespace Barotrauma { foreach (Location location in Locations) { + location.LevelData.EventsExhausted = false; if (location.Discovered) { - if (furthestDiscoveredLocation == null || + if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X) { furthestDiscoveredLocation = location; } } } + foreach (LocationConnection connection in Connections) + { + connection.LevelData.EventsExhausted = false; + } foreach (Location location in Locations) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs index 48cce8bfc..965965805 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/NPCSet.cs @@ -19,7 +19,7 @@ namespace Barotrauma public NPCSet(ContentXElement element, NPCSetsFile file) : base(file, element.GetAttributeIdentifier("identifier", "")) { - Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file)).ToImmutableArray(); + Humans = element.Elements().Select(npcElement => new HumanPrefab(npcElement, file, Identifier)).ToImmutableArray(); } public static HumanPrefab? Get(Identifier setIdentifier, Identifier npcidentifier) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs index 4c65efaa3..6e9b95be9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerationParams.cs @@ -203,7 +203,7 @@ namespace Barotrauma } else { - newCollection.Add(new HumanPrefab(npcElement, file)); + newCollection.Add(new HumanPrefab(npcElement, file, npcSetIdentifier: from)); } } humanPrefabCollections.Add(newCollection); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs index a0e5ceed6..30217e2a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs @@ -1426,7 +1426,7 @@ namespace Barotrauma static bool ShouldRemoveLinkedEntity(MapEntity e, bool doorInUse, PlacedModule module) { - if (e is Item it && it.GetComponent() != null) + if (e is Item it && it.IsLadder) { if (module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Top) || module.UsedGapPositions.HasFlag(OutpostModuleInfo.GapPosition.Bottom)) { @@ -1568,7 +1568,7 @@ namespace Barotrauma foreach (HumanPrefab humanPrefab in humanPrefabs) { if (humanPrefab is null) { continue; } - var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient); + var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient); if (location != null && location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier())) { killedCharacters.Add(humanPrefab); @@ -1582,7 +1582,7 @@ namespace Barotrauma { for (int tries = 0; tries < 100; tries++) { - var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: killedCharacter.GetJobPrefab(Rand.RandSync.ServerAndClient), randSync: Rand.RandSync.ServerAndClient); + var characterInfo = killedCharacter.CreateCharacterInfo(Rand.RandSync.ServerAndClient); if (!location.KilledCharacterIdentifiers.Contains(characterInfo.GetIdentifier())) { selectedCharacters.Add((killedCharacter, characterInfo)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/RoundEndCinematic.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/RoundEndCinematic.cs deleted file mode 100644 index 1fb8afbae..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/RoundEndCinematic.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - class RoundEndCinematic - { - public bool Running - { - get; - private set; - } - - public Camera AssignedCamera; - - private float duration; - - private CoroutineHandle updateCoroutine; - - public RoundEndCinematic(Submarine submarine, Camera cam, float duration = 10.0f) - : this(new List() { submarine }, cam, duration) - { - - } - - public RoundEndCinematic(List submarines, Camera cam, float duration) - { - if (!submarines.Any(s => s != null)) return; - - this.duration = duration; - AssignedCamera = cam; - - Running = true; - updateCoroutine = CoroutineManager.StartCoroutine(Update(submarines, cam)); - } - - public void Stop() - { - CoroutineManager.StopCoroutines(updateCoroutine); - Running = false; -#if CLIENT - GUI.ScreenOverlayColor = Color.TransparentBlack; -#endif - } - - private IEnumerable Update(List subs, Camera cam) - { - if (!subs.Any()) yield return CoroutineStatus.Success; - -#if CLIENT - Character.Controlled = null; - GameMain.LightManager.LosEnabled = false; -#endif - cam.TargetPos = Vector2.Zero; - - Level.Loaded.TopBarrier.Enabled = false; - - foreach (Character character in Character.CharacterList) - { - character.AnimController.Frozen = true; - foreach (Limb limb in character.AnimController.Limbs) - { - limb.body.PhysEnabled = false; - } - } - - cam.TargetPos = Vector2.Zero; - float timer = 0.0f; - float initialZoom = cam.Zoom; - Vector2 initialCameraPos = cam.Position; - - while (timer < duration) - { - if (Screen.Selected != GameMain.GameScreen) - { - yield return new WaitForSeconds(0.1f); - -#if CLIENT - GUI.ScreenOverlayColor = Color.TransparentBlack; -#endif - - Running = false; - yield return CoroutineStatus.Success; - } - - Vector2 minPos = new Vector2( - subs.Min(s => s.WorldPosition.X - s.Borders.Width / 2), - subs.Min(s => s.WorldPosition.Y - s.Borders.Height / 2)); - Vector2 maxPos = new Vector2( - subs.Min(s => s.WorldPosition.X + s.Borders.Width / 2), - subs.Min(s => s.WorldPosition.Y + s.Borders.Height / 2)); - Vector2 cameraPos = new Vector2( - MathHelper.SmoothStep(minPos.X, maxPos.X, timer / duration), - (minPos.Y + maxPos.Y) / 2.0f); - cam.Translate(cameraPos - cam.Position); - - foreach (Submarine sub in subs) - { - sub.PhysicsBody?.ResetDynamics(); - } - -#if CLIENT - cam.Zoom = MathHelper.SmoothStep(initialZoom, 0.5f, timer / duration); - if (timer / duration > 0.9f) - { - GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((timer / duration) - 0.9f) * 10.0f); - } -#endif - timer += CoroutineManager.UnscaledDeltaTime; - - yield return CoroutineStatus.Running; - } - - Running = false; - - yield return new WaitForSeconds(0.1f); - -#if CLIENT - GUI.ScreenOverlayColor = Color.TransparentBlack; -#endif - - yield return CoroutineStatus.Success; - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs index 4e6e22f7f..b5f2d85d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs @@ -986,14 +986,6 @@ namespace Barotrauma subBody.Body.ResetDynamics(); subBody.Body.Enabled = false; - foreach (MapEntity e in MapEntity.mapEntityList) - { - if (e.Submarine == this) - { - Spawner.AddEntityToRemoveQueue(e); - } - } - foreach (Character c in Character.CharacterList) { if (c.Submarine == this) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs index 673a6218b..ab8ea3922 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs @@ -1,8 +1,6 @@ using Barotrauma.Extensions; using Barotrauma.Items.Components; -using Barotrauma.Networking; using FarseerPhysics; -using FarseerPhysics.Collision; using FarseerPhysics.Common; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; @@ -898,13 +896,15 @@ namespace Barotrauma } bool holdingOntoSomething = false; - if (c.SelectedConstruction != null) + if (c.SelectedSecondaryItem != null) { - holdingOntoSomething = - c.SelectedConstruction.GetComponent() != null || - (c.SelectedConstruction.GetComponent()?.LimbPositions.Any() ?? false); + holdingOntoSomething = c.SelectedSecondaryItem.IsLadder || + (c.SelectedSecondaryItem.GetComponent()?.LimbPositions.Any() ?? false); + } + if (!holdingOntoSomething && c.SelectedItem != null) + { + holdingOntoSomething = c.SelectedItem.GetComponent()?.LimbPositions.Any() ?? false; } - if (!holdingOntoSomething) { c.AnimController.Collider.ApplyLinearImpulse(c.AnimController.Collider.Mass * impulse, 10.0f); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 6972ac705..1a99f675a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -19,7 +19,7 @@ namespace Barotrauma public static bool ShowWayPoints = true, ShowSpawnPoints = true; - public const float LadderWaypointInterval = 70.0f; + public const float LadderWaypointInterval = 55.0f; protected SpawnType spawnType; private string[] idCardTags; @@ -560,21 +560,22 @@ namespace Barotrauma stairPoints.ForEach(wp => wp.FindStairs()); } + // Ladders foreach (Item item in Item.ItemList) { var ladders = item.GetComponent(); if (ladders == null) { continue; } Vector2 bottomPoint = new Vector2(item.Rect.Center.X, item.Rect.Top - item.Rect.Height + 10); - List ladderPoints = new List + List<(WayPoint wp, bool connectHullPoints)> ladderPoints = new List<(WayPoint, bool)> { - new WayPoint(bottomPoint, SpawnType.Path, submarine), + (new WayPoint(bottomPoint, SpawnType.Path, submarine), true) }; List ignoredBodies = new List(); // Lowest point is only meaningful for hanging ladders inside the sub, but it shouldn't matter in other cases either. // Start point is where the bots normally grasp the ladder when they stand on ground. - WayPoint lowestPoint = ladderPoints[0]; + WayPoint lowestPoint = ladderPoints[0].wp; WayPoint prevPoint = lowestPoint; Vector2 prevPos = prevPoint.SimPosition; Body ground = Submarine.PickBody(lowestPoint.SimPosition, lowestPoint.SimPosition - Vector2.UnitY, ignoredBodies, @@ -589,7 +590,7 @@ namespace Barotrauma if (lowestPoint == null || Math.Abs(startPoint.Position.Y - startHeight) > 40 && Hull.FindHull(nextPos) != null) { startPoint = new WayPoint(nextPos, SpawnType.Path, submarine); - ladderPoints.Add(startPoint); + ladderPoints.Add((startPoint, true)); if (lowestPoint != null) { startPoint.ConnectTo(lowestPoint); @@ -613,18 +614,13 @@ namespace Barotrauma } else { - //no door, check for walls + //no door, check for platforms/walls pickedBody = Submarine.PickBody( ConvertUnits.ToSimUnits(new Vector2(startPoint.Position.X, y)), prevPos, ignoredBodies, null, false, (Fixture f) => f.Body.UserData is Structure); } - if (pickedBody == null) - { - prevPos = Submarine.LastPickedPosition; - continue; - } - else + if (pickedBody != null) { ignoredBodies.Add(pickedBody); } @@ -632,19 +628,29 @@ namespace Barotrauma if (pickedDoor != null) { WayPoint newPoint = new WayPoint(pickedDoor.Item.Position, SpawnType.Path, submarine); - ladderPoints.Add(newPoint); + ladderPoints.Add((newPoint, true)); newPoint.ConnectedGap = pickedDoor.LinkedGap; + // TODO: Prevent the waypoint below being too close to the door newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = new Vector2(prevPos.X, ConvertUnits.ToSimUnits(pickedDoor.Item.Position.Y - pickedDoor.Item.Rect.Height)); + // Adjust y to prevent waypoints clamping up together + y = Math.Max(pickedDoor.Item.Position.Y, y); } else { - WayPoint newPoint = new WayPoint(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor, SpawnType.Path, submarine); - ladderPoints.Add(newPoint); + Vector2 pos = pickedBody == null ? new Vector2(startPoint.Position.X, y) : + ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * heightFromFloor; + WayPoint newPoint = new WayPoint(pos, SpawnType.Path, submarine); + ladderPoints.Add((newPoint, pickedBody != null)); newPoint.ConnectTo(prevPoint); prevPoint = newPoint; prevPos = ConvertUnits.ToSimUnits(newPoint.Position); + if (pickedBody != null) + { + // Adjust y to prevent waypoints clamping up together + y = Math.Max(newPoint.Position.Y, y); + } } } @@ -652,33 +658,30 @@ namespace Barotrauma if (prevPoint.rect.Y < item.Rect.Y - 40) { WayPoint wayPoint = new WayPoint(new Vector2(item.Rect.Center.X, item.Rect.Y - 1.0f), SpawnType.Path, submarine); - ladderPoints.Add(wayPoint); + ladderPoints.Add((wayPoint, true)); wayPoint.ConnectTo(prevPoint); } // Connect ladder waypoints to hull points at the right and left side - foreach (WayPoint ladderPoint in ladderPoints) + var ladderWaypoints = ladderPoints.Select(lp => lp.wp); + foreach (var ladderPoint in ladderPoints) { - ladderPoint.Ladders = ladders; - bool isHatch = ladderPoint.ConnectedGap != null && !ladderPoint.ConnectedGap.IsRoomToRoom; + var wp = ladderPoint.wp; + wp.Ladders = ladders; + if (!ladderPoint.connectHullPoints) { continue; } + bool isHatch = wp.ConnectedGap != null && !wp.ConnectedGap.IsRoomToRoom; for (int dir = -1; dir <= 1; dir += 2) { - WayPoint closest = null; - if (isHatch) - { - closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderPoints); - } - else - { - closest = ladderPoint.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), ladderPoint.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderPoints); - } + WayPoint closest = isHatch ? + wp.FindClosest(dir, horizontalSearch: true, new Vector2(500, 1000), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, filter: wp => wp.CurrentHull == null, ignored: ladderWaypoints) : + wp.FindClosest(dir, horizontalSearch: true, new Vector2(150, 100), wp.ConnectedGap?.ConnectedDoor?.Body.FarseerBody, ignored: ladderWaypoints); if (closest == null) { continue; } - ladderPoint.ConnectTo(closest); + wp.ConnectTo(closest); } } } - // Another pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders) + // Another ladder pass: connect cap and bottom points with other ladders when they are vertically adjacent to another (double ladders) foreach (Item item in Item.ItemList) { var ladders = item.GetComponent(); @@ -1035,12 +1038,10 @@ namespace Barotrauma w.tags = element.GetAttributeIdentifierArray("tags", Array.Empty()).ToHashSet(); - string jobIdentifier = element.GetAttributeString("job", "").ToLowerInvariant(); - if (!string.IsNullOrWhiteSpace(jobIdentifier)) + Identifier jobIdentifier = element.GetAttributeIdentifier("job", Identifier.Empty); + if (!jobIdentifier.IsEmpty) { - w.AssignedJob = - JobPrefab.Get(jobIdentifier) ?? - JobPrefab.Prefabs.Find(jp => jp.Name.Equals(jobIdentifier, StringComparison.OrdinalIgnoreCase)); + w.AssignedJob = JobPrefab.Get(jobIdentifier); } w.linkedToID = new List(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index bdafe7ea8..ec76db8e0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -810,6 +810,13 @@ namespace Barotrauma.Networking private set; } + [Serialize(120.0f, IsPropertySaveable.Yes)] + public float DisallowKickVoteTime + { + get; + private set; + } + [Serialize(300.0f, IsPropertySaveable.Yes)] public float KillDisconnectedTime { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs index 36743762a..7b97635be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/GameScreen.cs @@ -87,10 +87,12 @@ namespace Barotrauma GameSettings.SaveCurrentConfig(); GameMain.SoundManager.SetCategoryMuffle("default", false); GUI.ClearMessages(); +#if !DEBUG if (GameMain.GameSession?.GameMode is TestGameMode) { DebugConsole.DeactivateCheats(); } +#endif #endif } @@ -168,9 +170,9 @@ namespace Barotrauma if (Character.Controlled != null) { - if (Character.Controlled.SelectedConstruction != null && Character.Controlled.CanInteractWith(Character.Controlled.SelectedConstruction)) + if (Character.Controlled.SelectedItem != null && Character.Controlled.CanInteractWith(Character.Controlled.SelectedItem)) { - Character.Controlled.SelectedConstruction.UpdateHUD(cam, Character.Controlled, (float)deltaTime); + Character.Controlled.SelectedItem.UpdateHUD(cam, Character.Controlled, (float)deltaTime); } if (Character.Controlled.Inventory != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs index 869d4ccff..86cce0a43 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs @@ -19,6 +19,7 @@ { Selected.Deselect(); #if CLIENT + GameMain.ParticleManager.ClearParticles(); GUIContextMenu.CurrentContextMenu = null; GUI.ClearCursorWait(); //make sure any textbox in the previously selected screen doesn't stay selected diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index d13c4975e..4fc3665d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -290,6 +290,8 @@ namespace Barotrauma { InputType.CrewOrders, Keys.C }, { InputType.Voice, Keys.V }, + { InputType.RadioVoice, Keys.None }, + { InputType.LocalVoice, Keys.None }, { InputType.ToggleChatMode, Keys.R }, { InputType.Command, MouseButton.MiddleMouse }, { InputType.PreviousFireMode, MouseButton.MouseWheelDown }, @@ -333,16 +335,15 @@ namespace Barotrauma } bool playerConfigContainsNewChatBinds = false; + bool playerConfigContainsRestoredVoipBinds = false; foreach (XElement element in elements) { foreach (XAttribute attribute in element.Attributes()) { if (Enum.TryParse(attribute.Name.LocalName, out InputType result)) { - if (!playerConfigContainsNewChatBinds) - { - playerConfigContainsNewChatBinds = result == InputType.ActiveChat; - } + playerConfigContainsNewChatBinds |= result == InputType.ActiveChat; + playerConfigContainsRestoredVoipBinds |= result == InputType.RadioVoice; bindings[result] = element.GetAttributeKeyOrMouse(attribute.Name.LocalName, bindings[result]); } } @@ -351,14 +352,15 @@ namespace Barotrauma // Clear the old chat binds for configs saved before the introduction of the new chat binds if (!playerConfigContainsNewChatBinds) { - if (bindings.ContainsKey(InputType.Chat)) - { - bindings[InputType.Chat] = Keys.None; - } - if (bindings.ContainsKey(InputType.RadioChat)) - { - bindings[InputType.RadioChat] = Keys.None; - } + bindings[InputType.Chat] = Keys.None; + bindings[InputType.RadioChat] = Keys.None; + } + + // Clear old VOIP binds to make sure we have no overlapping binds + if (!playerConfigContainsRestoredVoipBinds) + { + bindings[InputType.LocalVoice] = Keys.None; + bindings[InputType.RadioVoice] = Keys.None; } Bindings = bindings.ToImmutableDictionary(); @@ -511,6 +513,7 @@ namespace Barotrauma if (hudScaleChanged) { HUDLayoutSettings.CreateAreas(); + GameMain.GameSession?.HUDScaleChanged(); } GameMain.SoundManager?.ApplySettings(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index 3fae997c0..71b24e12a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -264,12 +264,36 @@ namespace Barotrauma public string Name => $"Character Spawn Info ({SpeciesName})"; public Dictionary SerializableProperties { get; set; } + [Serialize(false, IsPropertySaveable.No)] + public bool TransferBuffs { get; private set; } + + [Serialize(false, IsPropertySaveable.No)] + public bool TransferAfflictions { get; private set; } + + [Serialize(false, IsPropertySaveable.No)] + public bool TransferInventory { get; private set; } + [Serialize("", IsPropertySaveable.No)] public Identifier SpeciesName { get; private set; } [Serialize(1, IsPropertySaveable.No)] public int Count { get; private set; } + [Serialize(0, IsPropertySaveable.No)] + public int Stun { get; private set; } + + [Serialize("", IsPropertySaveable.No)] + public Identifier AfflictionOnSpawn { get; private set; } + + [Serialize(1, IsPropertySaveable.No)] + public int AfflictionStrength { get; private set; } + + [Serialize(false, IsPropertySaveable.No)] + public bool TransferControl { get; private set; } + + [Serialize(false, IsPropertySaveable.No)] + public bool RemovePreviousCharacter { get; private set; } + [Serialize(0f, IsPropertySaveable.No)] public float Spread { get; private set; } @@ -1596,6 +1620,64 @@ namespace Barotrauma { SwarmBehavior.CreateSwarm(characters.Cast()); } + if (!characterSpawnInfo.AfflictionOnSpawn.IsEmpty) + { + if (!AfflictionPrefab.Prefabs.TryGet(characterSpawnInfo.AfflictionOnSpawn, out AfflictionPrefab afflictionPrefab)) + { + DebugConsole.NewMessage($"Could not apply an affliction to the spawned character(s). No affliction with the identifier \"{characterSpawnInfo.AfflictionOnSpawn}\" found.", Color.Red); + return; + } + newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, afflictionPrefab.Instantiate(characterSpawnInfo.AfflictionStrength)); + } + if (characterSpawnInfo.Stun > 0) + { + newCharacter.SetStun(characterSpawnInfo.Stun); + } + foreach (var target in targets) + { + if (!(target is Character character)) { continue; } + if (characterSpawnInfo.TransferInventory && character.Inventory != null && newCharacter.Inventory != null) + { + if (character.Inventory.Capacity != newCharacter.Inventory.Capacity) { return; } + for (int i = 0; i < character.Inventory.Capacity && i < newCharacter.Inventory.Capacity; i++) + { + character.Inventory.GetItemsAt(i).ForEachMod(item => newCharacter.Inventory.TryPutItem(item, i, allowSwapping: true, allowCombine: false, user: null)); + } + } + if (characterSpawnInfo.TransferBuffs || characterSpawnInfo.TransferAfflictions) + { + foreach (Affliction affliction in character.CharacterHealth.GetAllAfflictions()) + { + if (!characterSpawnInfo.TransferAfflictions && characterSpawnInfo.TransferBuffs && affliction.Prefab.IsBuff) + { + newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, affliction.Prefab.Instantiate(affliction.Strength)); + } + if (characterSpawnInfo.TransferAfflictions) + { + newCharacter.CharacterHealth.ApplyAffliction(newCharacter.AnimController.MainLimb, affliction.Prefab.Instantiate(affliction.Strength)); + } + } + } + if (i == characterSpawnInfo.Count) // Only perform the below actions if this is the last character being spawned. + { + if (characterSpawnInfo.TransferControl) + { +#if CLIENT + if (Character.Controlled == target) + { + Character.Controlled = newCharacter; + } +#elif SERVER + foreach (Client c in GameMain.Server.ConnectedClients) + { + if (c.Character != target) { continue; } + GameMain.Server.SetClientCharacter(c, newCharacter); + } +#endif + } + if (characterSpawnInfo.RemovePreviousCharacter) { Entity.Spawner?.AddEntityToRemoveQueue(character); } + } + } }); } } @@ -1897,7 +1979,7 @@ namespace Barotrauma { continue; } - element.Parent.ApplyToProperty(target, property, n, CoroutineManager.UnscaledDeltaTime); + element.Parent.ApplyToProperty(target, property, n, CoroutineManager.DeltaTime); } foreach (Affliction affliction in element.Parent.Afflictions) diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 635a98b76..d3ba6592d 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,3 +1,108 @@ +--------------------------------------------------------------------------------------------------------- +v0.19.0.0 +--------------------------------------------------------------------------------------------------------- + +Changes: +- Device/item UIs can be moved around by dragging. +- Allow using devices while on a ladder or sitting on a chair. +- Readded the option to have separate push-to-talk binds for local and radio voice chat (by default not bind to anything). +- The deconstructor UI shows what the input items deconstruct to (particularly important now with the lossy deconstruction recipes - it can be risky to deconstruct something just to see what materials it gives out if that results in material loss). +- Wall and device repair costs in outposts are calculated based on the amount of damage in your sub, instead of always having a fixed price. +- Inflamed lung doesn't affect characters that don't need oxygen. +- Added swarm behavior for crawler husks. +- Added some more oomph to nuclear explosions. +- Adjust the alpha of the outpost service icons according to distance to make it easier to estimate where the NPC is at, show the title of the NPC when hovering the cursor over the icon. +- Added "unlockmission" console command. +- Added "setcampaignmetadata" console command (may be useful for modders creating custom scripted events for the campaign?). +- Changed how NPC "titles" work. Previously we defined "titles" for the pirates (e.g. "Pirate Lord" and such), and the title replaced the name of the NPC (which made their dialog a little awkward). Now we display both the name and the title over the character, and special outpost NPCs also have titles. +- Gave diving masks to most NPCs. +- Changed the burn overlay formula: now also the non-affected limbs get half of the effect, because it looks weird if there's a sharp contrast between the limbs. +- Restored the 3 shell railgun rack as a legacy option. +- Reworded the "respawn with penalty" prompt to make it less confusing: you always get a penalty to your skills when you die now, and Reaper's Tax is an "extra penalty" you get on top of that if you opt to respawn mid-round. The intention behind this is to incur a cost to respawning: it shouldn't be possible to get unlimited free reinforcements and supplies mid-round. +- Made SIGTERM close the linux server gracefully. +- Made respawn items (suits, scooters) spawn in the respawn shuttle's cabinets when possible. +- Show a healthbar on items (e.g. eggs and thalamus organs) when damaging them with handheld weapons (melee or ranged). + +Balance: +- "Mission cheesing" by repeatedly undocking and redocking to an outpost to reroll the mission events no longer works: new mission events don't reappear until one "world step" has passed (~10 minutes or traversing through one level). +- Balance pass on handheld weapons: adjusted reload times, damages, stun durations, recoil and ammo stack sizes. +- Reduced tools' structure damages (dual-wielded storage containers no longer chew through submarine walls in seconds). +- Increased heavy ruin wall health to make it less easy to cheese your way in to the artifact room in ruins. +- Made boomstick fire in bursts of 2 (similar to deadeye carbide) to prevent ridiculous fire rates with quick-reloading. +- Added EMP effect to nuclear depth charges for consistency. +- Changed how skill levels affect the quality of fabricated items. Previously having a skill level equal to or higher than the item's skill requirement would result in a good quality item, meaning that practically everyone could e.g. fabricate good quality oxygen tanks. Now your skill needs to be >20% from the minimum skill requirement towards 100, (e.g. if the item requires 20 skill to fabricate, 36 results in a higher quality item). +- Reduced PUCS's radiation resistance from 100% to 90%. Complete invulnerability to radiation has way too much potential for exploits and overpowered strategies. +- Adjusted supplies in pirate submarines. +- Turn some weapons' burn damage into explosion damage (wip). +- Terminal ignores empty signals. + +Multiplayer: +- Clients who've recently joined (by default 2 minutes) are not allowed to vote to kick others, and vote kicking someone always requires at least 2 votes. +- Fixed "missing entity" errors in a specific situation in multiplayer. Occurred when a respawn shuttle was enabled and loaded on the server (= i.e. in a non-outpost level), and a client disconnected and immediately reconnected. This would cause the client to deselect the respawn shuttle, and make them start the round without loading one, leading to the "missing entity" issues due to the shuttle only existing server-side. +- Fixes damage visuals not showing on characters who've died off-screen. +- Servers doent allow selecting hidden jobs (jobs only used by NPCs) as job preferences. +- Fixed ability to upgrade the sub when there's a switch pending in multiplayer. +- Fixed friendly fire and karma always showing up as disabled on dedicated servers in the server list. +- Fixed spineling spikes fired by a human with spineling genes not damaging any human characters (enemies in PvP, pirates in pirate missions) when friendly fire is disabled. +- Fixed "invalid ExecuteAttack message: limb index out of bounds" errors when you join a server where a character has fired spineling spikes with spineling genes mid-round. +- Fixed "entity not found" errors if a shuttle or submarine ends up absurdly deep in multiplayer (> 100 km). I don't even know how someone managed to pull this off. +- Fixed rapidly clicking on the mission giver with the Use input set to LMB sometimes not giving all the available missions. Happened because the conversation logic didn't check if there's another conversation active, causing the server to show a new conversation when clicking the NPC, without interrupting/continuing the previous conversation. +- Made shockjock event only show for the player triggering the event (making it visible for everyone works kind of weirdly, when the event involves talking to an NPC next to the character who triggered the event). +- Fixed outpost events getting stuck at the last ConversationAction if another client has finished the action. + +Submarines: +- Changed default reactor output from 10,000 kW to 5000 kW. +- Decreased Winterhalter reactor output, increased fuel consumption rate. +- Fixed some gap issues in Winterhalter. +- Fixed medics not having access to the toxin cabinet in Barsuk. +- Fixed medic, engineer and mechanic spawnpoints having no tags in Typhon. + +Fixes: +- Fixed fire, breach and intruder report icons not being shown to anyone. +- Fixed missing/unwired lighting in ResearchModule_02_Colony. +- Remove particles when switching screens (otherwise e.g. particles from the previous round are still in the level if you happen to be looking at the right spot). +- Thalamus or ice walls can't be welded. +- Quick-reloading tries to reload the item whose contained items have the lowest condition. In other words, if you've equipped 2 weapons, quick-reloading reloads the one with the least ammo instead of the one that's the first in your inventory. +- Fixed messed up dementonite and depleted fuel tool recipes. +- Fixed swapping a scaled turret/hardpoint causing the new one to be misplaced. +- Fixed inability to upgrade the sub or do maintenance if you buy and opt to switch to a new sub, and then go to the submarine switch terminal to cancel the switching. +- Fixed stolen items becoming non-stolen when deconstructed. +- Fixed ItemContainer UI popping up (with no visible inventory slot) when you pick one up. For example when you pick up a detonator from the floor. +- Fixed "[E] Rewire" hover text being shown on attachable items that haven't been attached to a wall (even though they can't be rewired until attached). +- Fixed trying to bind multiple console commands to the same key with the "binkey" command crashing the game. +- Fixed high-quality revolvers having no difference to normal-quality ones. They should get a 10% damage boost per quality level, but didn't due to incorrectly configured quality stats. +- Fixed multiple monster missions sometimes spawning the monsters close to each other, causing them to attack each other. +- Fix monsters sometimes using wrong animation parameters while idling (or moving slowly). +- Fixed nuclear depth decoy using the same sprite as the normal depth decoy. +- Fixed fractal guardian VitalityMultipliers being configured incorrectly (using the "type" attribute but with affliction identifiers instead of types). +- Fixed incorrectly sized thalamus wall colliders, added background sprites to the walls. +- Fixed "tried to overwrite a submarine that's not in a local package" error when loading and trying to save a submarine autosave file. +- Fixed location portraits sometimes not showing up in the mission tab. Happened when we initialized the mission tab before the portrait had been loaded. +- Fixed coilguns and chainguns not always playing the firing sound when fired. Happened because their audio clips were so long (albeit mostly silence) that firing the them continously lead to a ton of clips playing simultaneously, exhausting the available audio channels. +- Fixed monster missions' sonar marker being placed incorrectly if a monster ends up inside the sub, making it look as if the monster was far outside the level. This often made it look like the monster was moving away from the sub when trying to "approach it". +- Fixed security officer tutorial getting stuck if you equip the weapons and gear before the objective to do so appears. +- Fixed bandolier (and other items that give bonuses when worn) giving the bonuses when the item is held. +- Fixed mod texts being briefly misaligned when scrolling down the list of unpublished mods. +- Fixed light sprite rotation not getting refreshed when placing an attachable item on a wall when lighting has been disabled with console commands. +- Fixed supercapacitors showing 1% as the initial recharge rate because the recharge rate defaulted to 10. +- Fixed some ending options of the "good samaritan" outpost event not ending the event. +- Fixed random (non-mission) events disappearing from outposts when you save and quit. + +Submarine editor: +- Fixed crashing when trying to multi-edit a string value in the sub editor. +- Fixed dragged object becoming invisible if you bring the cursor over an UI element in the sub editor. +- Fixed screwdrivers and wires in your "inventory" being included in the total item count in the sub editor's wiring mode. +- Fixed entities that were below the cursor when starting to resize a structure staying highlighted during resizing. +- Fixed sub editor treating the autosave interval as minutes instead of seconds (saving every 300 minutes instead of 300 seconds). + +Modding: +- Made NPC personality traits a separate content type instead of defining them in the localization files. +- Fixed OnDeath status effects defined in afflictions not working. Did not affect any vanilla content. +- Fixed crash when controlling a character with more than 10 "Any" inventory slots. Did not affect any vanilla content. +- Fixed custom husk appendages' textures failing to load. +- Added new properties to StatusEffect's SpawnCharacter feature: Stun, AfflictionOnSpawn, AfflictionStrength, TransferControl, RemovePreviousCharacter, TransferBuffs, TransferAfflictions, TransferInventory. +- Fixed bots always choosing their "personality trait" from the first 6 even if more are modded in. + --------------------------------------------------------------------------------------------------------- v0.18.15.0 --------------------------------------------------------------------------------------------------------- @@ -41,12 +146,10 @@ Fixes: - Fixed non-player-team interactable items getting transferred on sub switch. - Fixed ballast flora root emitting particles when damaged client-side, even if it's already been destroyed. - Fixed recycle recipes for Piercing Ammunition Box and Pulse Tri-Laser Fuel Box. -- Fixed friendly fire and karma always showing up as disabled on dedicated servers in the server list. - Fixed some lights (e.g. vending machines, neon lights, holographics displays) looking different in the sub editor than they do in-game. - Fixed undocked shuttles remaining undocked if you save and start a new game with the same submarine during the same session. Restarting the game fixed the issue though. - Fixed sonar markers going crazy if the start and end locations have the same name + added some more variety to location names to prevent duplicate location names. - Fixed multiediting an ItemComponent modifying all the components of that type in all the selected items (e.g. when editing the 1st light component of a switch, all lights in all switches would be edited). -- Fixed spineling spikes fired by a human with spineling genes not damaging any human characters (enemies in PvP, pirates in pirate missions) when friendly fire is disabled. - Fixed melee weapons not damaging structures from outside. Modding: diff --git a/Barotrauma/BarotraumaShared/serversettings.xml b/Barotrauma/BarotraumaShared/serversettings.xml index 50ed25dac..482899fc2 100644 --- a/Barotrauma/BarotraumaShared/serversettings.xml +++ b/Barotrauma/BarotraumaShared/serversettings.xml @@ -11,7 +11,7 @@ autorestart="false" LevelDifficulty="20" AllowedRandomMissionTypes="Random,Salvage,Monster,Cargo,Combat" - AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1279,19968-21327,21329-40959,13312-19903,131072-173791,173824-178207,178208-183983,63744-64255,194560-195103" + AllowedClientNameChars="32-33,38-46,48-57,65-90,91-91,93-93,95-122,192-255,384-591,1024-1159,1162-1279,19968-21327,21329-40959,13312-19903,131072-173791,173824-178207,178208-183983,63744-64255,194560-195103" ServerMessage="" tickrate="20" randomizeseed="True"