diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs index b20e0e7c8..a44a18f66 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs @@ -91,7 +91,7 @@ namespace Barotrauma GUI.SmallFont.DrawString(spriteBatch, currentNode.ID.ToString(), - new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), + new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30), Color.Red); } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs index 28686501e..4204f8957 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs @@ -1,5 +1,6 @@ using Microsoft.Xna.Framework; using FarseerPhysics; +using System.Linq; namespace Barotrauma { @@ -37,16 +38,20 @@ namespace Barotrauma var currentOrder = ObjectiveManager.CurrentOrder; if (currentOrder != null) { - GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); + GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black); + } + else if (ObjectiveManager.WaitTimer > 0) + { + GUI.DrawString(spriteBatch, pos + textOffset, $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black); } var currentObjective = ObjectiveManager.CurrentObjective; if (currentObjective != null) { - GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); - var subObjective = currentObjective.CurrentSubObjective; + GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black); + var subObjective = currentObjective.SubObjectives.FirstOrDefault(); if (subObjective != null) { - GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); + GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black); } } } @@ -75,8 +80,8 @@ namespace Barotrauma GUI.SmallFont.DrawString(spriteBatch, currentNode.ID.ToString(), - new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), - Color.SkyBlue); + new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30), + Color.Blue); } } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index fa30d8a82..8f9276859 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -121,90 +121,7 @@ namespace Barotrauma MainLimb.PullJointWorldAnchorB = Collider.SimPosition; MainLimb.PullJointEnabled = true; } - character.SelectedConstruction = character.MemState[0].SelectedItem; } - - if (character.MemState[0].Animation == AnimController.Animation.CPR) - { - character.AnimController.Anim = AnimController.Animation.CPR; - } - else if (character.AnimController.Anim == AnimController.Animation.CPR) - { - character.AnimController.Anim = AnimController.Animation.None; - } - - Vector2 newVelocity = Collider.LinearVelocity; - Vector2 newPosition = Collider.SimPosition; - float newRotation = Collider.Rotation; - float newAngularVelocity = Collider.AngularVelocity; - Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity); - - newVelocity = newVelocity.ClampLength(100.0f); - if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; } - overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero; - - Collider.LinearVelocity = newVelocity; - Collider.AngularVelocity = newAngularVelocity; - - float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition); - float errorTolerance = character.AllowInput ? 0.01f : 0.2f; - if (distSqrd > errorTolerance) - { - if (distSqrd > 10.0f || !character.AllowInput) - { - Collider.TargetRotation = newRotation; - SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false); - } - else - { - Collider.TargetRotation = newRotation; - Collider.TargetPosition = newPosition; - Collider.MoveToTargetPosition(true); - } - } - - //unconscious/dead characters can't correct their position using AnimController movement - // -> we need to correct it manually - if (!character.AllowInput) - { - float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition); - float mainLimbErrorTolerance = 0.1f; - //if the main limb is roughly at the correct position and the collider isn't moving (much at least), - //don't attempt to correct the position. - if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f) - { - MainLimb.PullJointWorldAnchorB = Collider.SimPosition; - MainLimb.PullJointEnabled = true; - } - } - } - character.MemLocalState.Clear(); - } - else - { - //remove states with a timestamp (there may still timestamp-based states - //in the list if the controlled character switches from timestamp-based interpolation to ID-based) - character.MemState.RemoveAll(m => m.Timestamp > 0.0f); - - for (int i = 0; i < character.MemLocalState.Count; i++) - { - if (character.Submarine == null) - { - //transform in-sub coordinates to outside coordinates - if (character.MemLocalState[i].Position.Y > lowestSubPos) - { - character.MemLocalState[i].TransformInToOutside(); - } - } - else if (currentHull?.Submarine != null) - { - //transform outside coordinates to in-sub coordinates - if (character.MemLocalState[i].Position.Y < lowestSubPos) - { - character.MemLocalState[i].TransformOutToInside(currentHull.Submarine); - } - } - } character.MemLocalState.Clear(); } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs index 34cc2ebeb..a82be27e8 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs @@ -86,7 +86,7 @@ namespace Barotrauma if (character.Inventory != null) { if (!character.LockHands && character.Stun < 0.1f && - (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent() == null)) + (character.SelectedConstruction == null || character.SelectedConstruction?.GetComponent()?.User != character)) { character.Inventory.Update(deltaTime, cam); } @@ -321,7 +321,7 @@ namespace Barotrauma } if (character.Inventory != null && !character.LockHands) { - character.Inventory.Locked = (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent() != null); + character.Inventory.Locked = (character.SelectedConstruction?.GetComponent()?.User == character); character.Inventory.DrawOwn(spriteBatch); character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ? CharacterInventory.Layout.Default : diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs index 34ebcb336..8442fe1ba 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Health/CharacterHealth.cs @@ -463,7 +463,7 @@ namespace Barotrauma return; } } - + bool forceAfflictionContainerUpdate = false; if (updateDisplayedAfflictionsTimer > 0.0f) { @@ -505,7 +505,7 @@ namespace Barotrauma Math.Min(healthShadowSize + deltaTime, healthBar.BarSize) : Math.Max(healthShadowSize - deltaTime, healthBar.BarSize); } - + dropItemArea.Visible = !Character.IsDead; float blurStrength = 0.0f; @@ -733,7 +733,9 @@ namespace Barotrauma Rectangle.Union(HUDLayoutSettings.AfflictionAreaLeft, HUDLayoutSettings.HealthBarAreaLeft) : Rectangle.Union(HUDLayoutSettings.AfflictionAreaRight, HUDLayoutSettings.HealthBarAreaRight); - if (Character.AllowInput && UseHealthWindow && hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null) + if (Character.AllowInput && UseHealthWindow && + Character.SelectedConstruction?.GetComponent()?.User != Character && + hoverArea.Contains(PlayerInput.MousePosition) && Inventory.SelectedSlot == null) { healthBar.State = GUIComponent.ComponentState.Hover; if (PlayerInput.LeftButtonClicked()) diff --git a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs index 9dd8d9763..2f446ffab 100644 --- a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs +++ b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs @@ -56,11 +56,11 @@ namespace Barotrauma } public ScalableFont(XElement element, GraphicsDevice gd = null) - : this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd) + : this (element.GetAttributeString("file", ""), (uint)element.GetAttributeInt("size", 14), gd, element.GetAttributeBool("dynamicloading", false)) { } - public ScalableFont(string filename, uint size, GraphicsDevice gd = null) + public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false) { if (Lib == null) Lib = new Library(); this.filename = filename; @@ -310,6 +310,11 @@ namespace Barotrauma } uint charIndex = text[i]; + if (DynamicLoading && !texCoords.ContainsKey(charIndex)) + { + DynamicRenderAtlas(graphicsDevice, charIndex); + } + if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square { if (gd.texIndex >= 0) @@ -344,7 +349,13 @@ namespace Barotrauma currentPos.Y += baseHeight * 1.8f; continue; } - uint charIndex = text[i]; + + uint charIndex = text[i]; + if (DynamicLoading && !texCoords.ContainsKey(charIndex)) + { + DynamicRenderAtlas(graphicsDevice, charIndex); + } + if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square { if (gd.texIndex >= 0) @@ -376,6 +387,10 @@ namespace Barotrauma continue; } uint charIndex = text[i]; + if (DynamicLoading && !texCoords.ContainsKey(charIndex)) + { + DynamicRenderAtlas(graphicsDevice, charIndex); + } if (texCoords.TryGetValue(charIndex, out GlyphData gd)) { currentLineX += gd.advance; @@ -389,6 +404,10 @@ namespace Barotrauma { Vector2 retVal = Vector2.Zero; retVal.Y = baseHeight * 1.8f; + if (DynamicLoading && !texCoords.ContainsKey(c)) + { + DynamicRenderAtlas(graphicsDevice, c); + } if (texCoords.TryGetValue(c, out GlyphData gd)) { retVal.X = gd.advance; diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs index c032f705a..4aa7f60c8 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIMessageBox.cs @@ -9,9 +9,11 @@ namespace Barotrauma public class GUIMessageBox : GUIFrame { public static List MessageBoxes = new List(); + private static int DefaultWidth + { + get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); } + } - public const int DefaultWidth = 400, DefaultHeight = 250; - public List Buttons { get; private set; } = new List(); //public GUIFrame BackgroundFrame { get; private set; } public GUILayoutGroup Content { get; private set; } @@ -21,24 +23,31 @@ namespace Barotrauma public string Tag { get; private set; } public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); - - public GUIMessageBox(string headerText, string text) - : this(headerText, text, new string[] {"OK"}, DefaultWidth, 0) + + public GUIMessageBox(string headerText, string text, Vector2? relativeSize = null, Point? minSize = null) + : this(headerText, text, new string[] { "OK" }, relativeSize, minSize) { this.Buttons[0].OnClicked = Close; } - public GUIMessageBox(string headerText, string text, int width, int height) - : this(headerText, text, new string[] { "OK" }, width, height) - { - this.Buttons[0].OnClicked = Close; - } - - // TODO: allow to use a relative size. - public GUIMessageBox(string headerText, string text, string[] buttons, int width = DefaultWidth, int height = 0, Alignment textAlignment = Alignment.TopLeft, string tag = "") + public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, string tag = "") : base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "") { - if (width == DefaultWidth) width = (int)(width * GUI.Scale); + //int width = (int)(DefaultWidth * GUI.Scale), height = 0; + int width = DefaultWidth, height = 0; + if (relativeSize.HasValue) + { + width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X); + height = (int)(GameMain.GraphicsHeight * relativeSize.Value.Y); + } + if (minSize.HasValue) + { + width = Math.Max(width, minSize.Value.X); + if (height > 0) + { + height = Math.Max(height, minSize.Value.Y); + } + } InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, Anchor.Center) { IsFixedSize = false }, style: null); GUI.Style.Apply(InnerFrame, "", this); @@ -51,7 +60,7 @@ namespace Barotrauma GUI.Style.Apply(Header, "", this); Header.RectTransform.MinSize = new Point(0, Header.Rect.Height); - if (!string.IsNullOrWhiteSpace(text)) + if (height == 0) { Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true); @@ -76,7 +85,11 @@ namespace Barotrauma height += Header.Rect.Height + Content.AbsoluteSpacing; height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing; height += buttonContainer.Rect.Height; - + if (minSize.HasValue) + { + height = Math.Max(height, minSize.Value.Y); + } + InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + 50)); Content.RectTransform.NonScaledSize = diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs index cc32e819a..2eddac84f 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs @@ -137,9 +137,10 @@ namespace Barotrauma private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice) { - string file = GetFontFilePath(element); - uint size = GetFontSize(element); - return new ScalableFont(file, size, graphicsDevice); + string file = GetFontFilePath(element); + uint size = GetFontSize(element); + bool dynamicLoading = GetFontDynamicLoading(element); + return new ScalableFont(file, size, graphicsDevice, dynamicLoading); } private uint GetFontSize(XElement element) @@ -170,6 +171,20 @@ namespace Barotrauma return element.GetAttributeString("file", ""); } + private bool GetFontDynamicLoading(XElement element) + { + foreach (XElement subElement in element.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } + string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); + if (GameMain.Config.Language.ToLowerInvariant() == language) + { + return subElement.GetAttributeBool("dynamicloading", false); + } + } + return element.GetAttributeBool("dynamicloading", false); + } + public GUIComponentStyle GetComponentStyle(string name) { componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style); diff --git a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs index 6b0fc2a5a..eac4ffa23 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs @@ -15,21 +15,41 @@ namespace Barotrauma private RenderTarget2D renderTarget; - private Video splashScreen; - public Video SplashScreen + private Sprite languageSelectionCursor; + private ScalableFont languageSelectionFont; + + private Video currSplashScreen; + private DateTime videoStartTime; + + private Queue> pendingSplashScreens = new Queue>(); + /// + /// Pair.first = filepath, Pair.second = resolution + /// + public Queue> PendingSplashScreens { get { lock (loadMutex) { - return splashScreen; + return pendingSplashScreens; } } set { lock (loadMutex) { - splashScreen = value; + pendingSplashScreens = value; + } + } + } + + public bool PlayingSplashScreen + { + get + { + lock (loadMutex) + { + return currSplashScreen != null; } } } @@ -71,11 +91,17 @@ namespace Barotrauma } public bool WaitForLanguageSelection + { + get; + set; + } + + public LoadingScreen(GraphicsDevice graphics) { backgroundTexture = TextureLoader.FromFile("Content/UI/titleBackground.png"); renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); - GameMain.Instance.OnResolutionChanged += () => + GameMain.Instance.OnResolutionChanged += () => { renderTarget?.Dispose(); renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight); @@ -85,15 +111,14 @@ namespace Barotrauma selectedTip = TextManager.Get("LoadingScreenTip", true); } - public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime) { if (GameMain.Config.EnableSplashScreen) { try { - DrawSplashScreen(spriteBatch); - if (SplashScreen != null && SplashScreen.IsPlaying) return; + DrawSplashScreen(spriteBatch, graphics); + if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; } } catch (Exception e) { @@ -101,10 +126,10 @@ namespace Barotrauma GameMain.Config.EnableSplashScreen = false; } } - + var titleStyle = GUI.Style?.GetComponentStyle("TitleText"); Sprite titleSprite = null; - if (titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None)) + if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None)) { titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite; } @@ -147,8 +172,6 @@ namespace Barotrauma titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); - titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); - if (WaitForLanguageSelection) { DrawLanguageSelectionPrompt(spriteBatch, graphics); @@ -191,28 +214,81 @@ namespace Barotrauma spriteBatch.End(); } - private void DrawSplashScreen(SpriteBatch spriteBatch) + private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice) { - if (SplashScreen != null) + if (languageSelectionFont == null) { - if (SplashScreen.IsPlaying) - { - spriteBatch.Begin(); - spriteBatch.Draw(SplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White); - spriteBatch.End(); + languageSelectionFont = new ScalableFont("Content/Fonts/BebasNeue-Regular.otf", (uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice); + } + if (languageSelectionCursor == null) + { + languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero); + } - if (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.LeftButtonDown()) - { - SplashScreen.Dispose(); SplashScreen = null; - } - } - else + Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f); + Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count()); + foreach (string language in TextManager.AvailableLanguages) + { + Vector2 textSize = languageSelectionFont.MeasureString(language); + bool hover = + Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 && + Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2; + + //TODO: display the name of the language in the target language? + languageSelectionFont.DrawString(spriteBatch, language, textPos - textSize / 2, + hover ? Color.White : Color.White * 0.6f); + if (hover && PlayerInput.LeftButtonClicked()) { - SplashScreen.Dispose(); SplashScreen = null; + GameMain.Config.Language = language; + WaitForLanguageSelection = false; } - } + + textPos += textSpacing; + } + + languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition); } - + + private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics) + { + if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; } + + if (currSplashScreen == null) + { + var newSplashScreen = PendingSplashScreens.Dequeue(); + string fileName = newSplashScreen.First; + Point resolution = newSplashScreen.Second; + try + { + currSplashScreen = new Video(graphics, GameMain.SoundManager, fileName, (uint)resolution.X, (uint)resolution.Y); + videoStartTime = DateTime.Now; + } + catch (Exception e) + { + GameMain.Config.EnableSplashScreen = false; + DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e); + PendingSplashScreens.Clear(); + currSplashScreen = null; + } + } + + if (currSplashScreen.IsPlaying) + { + spriteBatch.Begin(); + spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White); + spriteBatch.End(); + + if (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.LeftButtonDown()) + { + currSplashScreen.Dispose(); currSplashScreen = null; + } + } + else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500)) + { + currSplashScreen.Dispose(); currSplashScreen = null; + } + } + bool drawn; public IEnumerable DoLoading(IEnumerable loader) { diff --git a/Barotrauma/BarotraumaClient/Source/GameMain.cs b/Barotrauma/BarotraumaClient/Source/GameMain.cs index f9c122c48..5e7e3fc80 100644 --- a/Barotrauma/BarotraumaClient/Source/GameMain.cs +++ b/Barotrauma/BarotraumaClient/Source/GameMain.cs @@ -178,7 +178,6 @@ namespace Barotrauma GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window); - PerformanceCounter = new PerformanceCounter(); IsFixedTimeStep = false; @@ -271,15 +270,17 @@ namespace Barotrauma WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content); loadingScreenOpen = true; - TitleScreen = new LoadingScreen(GraphicsDevice); - TitleScreen.WaitForLanguageSelection = Config.ShowLanguageSelectionPrompt; + TitleScreen = new LoadingScreen(GraphicsDevice) + { + WaitForLanguageSelection = Config.ShowLanguageSelectionPrompt + }; bool canLoadInSeparateThread = false; #if WINDOWS canLoadInSeparateThread = true; #endif - loadingCoroutine = CoroutineManager.StartCoroutine(Load(), "", canLoadInSeparateThread); + loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "", canLoadInSeparateThread); } private void InitUserStats() @@ -323,6 +324,11 @@ namespace Barotrauma DebugConsole.NewMessage("LOADING COROUTINE", Color.Lime); } + while (TitleScreen.WaitForLanguageSelection) + { + yield return CoroutineStatus.Running; + } + SoundManager = new Sounds.SoundManager(); SoundManager.SetCategoryGainMultiplier("default", Config.SoundVolume); SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume); @@ -331,16 +337,21 @@ namespace Barotrauma SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume); if (Config.EnableSplashScreen) { - try + var pendingSplashScreens = TitleScreen.PendingSplashScreens; + pendingSplashScreens?.Enqueue(new Pair("Content/Splash_UTG.mp4", new Point(1280, 720))); + pendingSplashScreens?.Enqueue(new Pair("Content/Splash_FF.mp4", new Point(1280, 720))); + pendingSplashScreens?.Enqueue(new Pair("Content/Splash_Daedalic.mp4", new Point(1920, 1080))); + } + + //if not loading in a separate thread, wait for the splash screens to finish before continuing the loading + //otherwise the videos will look extremely choppy + if (!isSeparateThread) + { + while (TitleScreen.PlayingSplashScreen || TitleScreen.PendingSplashScreens.Count > 0) { - (TitleScreen as LoadingScreen).SplashScreen = new Video(base.GraphicsDevice, SoundManager, "Content/splashscreen.mp4", 1280, 720); + yield return CoroutineStatus.Running; } - catch (Exception e) - { - Config.EnableSplashScreen = false; - DebugConsole.ThrowError("Playing the splash screen failed.", e); - } - } + } GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice); DebugConsole.Init(); @@ -371,11 +382,9 @@ namespace Barotrauma InitUserStats(); yield return CoroutineStatus.Running; - - + LightManager = new Lights.LightManager(base.GraphicsDevice, Content); - WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content); TitleScreen.LoadState = 1.0f; yield return CoroutineStatus.Running; @@ -526,7 +535,7 @@ namespace Barotrauma protected override void UnloadContent() { Video.Close(); - SoundManager.Dispose(); + SoundManager?.Dispose(); } /// diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 8ec9ebd84..e9bd2ba83 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -74,17 +74,12 @@ namespace Barotrauma public CrewManager(XElement element, bool isSinglePlayer) : this(isSinglePlayer) { - if (GameMain.Client != null) + if (!isSinglePlayer) { - //let the server create random conversations in MP + DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace); return; } - List availableSpeakers = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - !c.IsDead && - c.SpeechImpediment <= 100.0f); - pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); - } + if (string.IsNullOrEmpty(text)) { return; } var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); @@ -95,6 +90,7 @@ namespace Barotrauma break; } } + ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); } partial void InitProjectSpecific() @@ -199,6 +195,7 @@ namespace Barotrauma if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; SetCharacterOrder(null, order, null, Character.Controlled); HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull); + HumanAIController.RefreshTargets(Character.Controlled, order, Character.Controlled.CurrentHull); return true; }, UserData = order, @@ -242,24 +239,27 @@ namespace Barotrauma public IEnumerable GetCharacters() { - if (characterInfos.Contains(characterInfo)) - { - DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); - return; - } + if (character?.Inventory == null) return null; - characterInfos.Add(characterInfo); + var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent() != null); + if (radioItem == null) return null; + if (requireEquipped && !character.HasEquippedItem(radioItem)) return null; + + return radioItem.GetComponent(); } public IEnumerable GetCharacterInfos() { - if (character == null) + if (GameMain.Client != null) { - DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace); + //let the server create random conversations in MP return; } - characters.Remove(character); - if (removeInfo) characterInfos.Remove(character.Info); + List availableSpeakers = Character.CharacterList.FindAll(c => + c.AIController is HumanAIController && + !c.IsDead && + c.SpeechImpediment <= 100.0f); + pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); } public void AddCharacter(Character character) @@ -633,183 +633,9 @@ namespace Barotrauma { characterListBox.BarScroll = roundedPos; } - var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox") - { - UserData = character, - Color = frame.Color, - SelectedColor = frame.SelectedColor, - HoverColor = frame.HoverColor, - ToolTip = characterToolTip - }; - - var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - "GUISoundIcon") - { - UserData = "soundicon", - CanBeFocused = false, - Visible = true - }; - soundIcon.Color = new Color(soundIcon.Color, 0.0f); - new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - "GUISoundIconDisabled") - { - UserData = "soundicondisabled", - CanBeFocused = true, - Visible = false - }; - - if (isSinglePlayer) - { - characterArea.OnClicked = CharacterClicked; - } - else - { - characterArea.CanBeFocused = false; - characterArea.CanBeSelected = false; - } - - var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft), - onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())) - { - CanBeFocused = false, - HoverColor = Color.White, - SelectedColor = Color.White, - ToolTip = characterToolTip - }; - - var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height), - characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) }, - character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true) - { - Color = frame.Color, - HoverColor = Color.Transparent, - SelectedColor = Color.Transparent, - CanBeFocused = false, - ToolTip = characterToolTip, - AutoScale = true - }; - - //---------------- order buttons ---------------- - - var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform) - { AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) }, - isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - AbsoluteSpacing = (int)(10 * GUI.Scale), - UserData = "orderbuttons", - CanBeFocused = false - }; - - //listbox for holding the orders inappropriate for this character - //(so we can easily toggle their visibility) - var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null) - { - ScrollBarEnabled = false, - ScrollBarVisible = false, - Enabled = false, - Spacing = spacing, - ClampMouseRectToParent = false - }; - wrongOrderList.Content.ClampMouseRectToParent = false; - - for (int i = 0; i < orders.Count; i++) - { - var order = orders[i]; - if (order.TargetAllCharacters) continue; - - RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ? - wrongOrderList.Content.RectTransform : - orderButtonFrame.RectTransform; - - var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft), - style: null) - { - UserData = order - }; - - new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow") - { - Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f, - HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f, - PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f, - UserData = "selected", - CanBeFocused = false, - Visible = false - }; - - var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite); - img.Scale = iconSize / (float)img.SourceRect.Width; - img.Color = Color.Lerp(order.Color, frame.Color, 0.5f); - img.ToolTip = order.Name; - img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f); - - btn.OnClicked += (GUIButton button, object userData) => - { - if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; - - if (btn.GetChildByUserData("selected").Visible) - { - SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled); - } - else - { - if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1) - { - CreateOrderTargetFrame(button, character, order); - } - else - { - SetCharacterOrder(character, order, null, Character.Controlled); - } - } - return true; - }; - btn.UserData = order; - btn.ToolTip = order.Name; - - //divider between different groups of orders - if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1) - { - //TODO: divider sprite - new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton"); - } - } - - var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), - "", style: "UIToggleButton") - { - UserData = "togglewrongorder", - CanBeFocused = false - }; - - wrongOrderList.RectTransform.NonScaledSize = new Point( - wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing), - wrongOrderList.RectTransform.NonScaledSize.Y); - wrongOrderList.RectTransform.SetAsLastChild(); - - new GUIFrame(new RectTransform(new Point( - wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2, - wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), - style: null) - { - CanBeFocused = false - }; - - //scale to fit the content - orderButtonFrame.RectTransform.NonScaledSize = new Point( - orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing), - orderButtonFrame.RectTransform.NonScaledSize.Y); - - frame.RectTransform.NonScaledSize = new Point( - characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width), - frame.RectTransform.NonScaledSize.Y); - - characterListBox.RectTransform.NonScaledSize = new Point( - characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width, - characterListBox.RectTransform.NonScaledSize.Y); - characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize; - characterListBox.UpdateScrollBarSize(); - return frame; + soundIcon.Visible = !muted && !mutedLocally; + soundIconDisabled.Visible = muted || mutedLocally; + soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally"); } private IEnumerable KillCharacterAnim(GUIComponent component) @@ -953,12 +779,6 @@ namespace Barotrauma } return; } - List availableSpeakers = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - !c.IsDead && - c.SpeechImpediment <= 100.0f); - pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); - } character.SetOrder(order, option, orderGiver, speak: orderGiver != character); if (IsSinglePlayer) @@ -1016,23 +836,19 @@ namespace Barotrauma } } } - - character.SetOrder(order, option, orderGiver, speak: orderGiver != character); - if (IsSinglePlayer) + //only one target (or an order with no particular targets), just show options + else { - orderGiver?.Speak( - order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); - } - else if (orderGiver != null) - { - OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver); - if (GameMain.Client != null) + orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas) + { AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) }, + isHorizontal: true, childAnchor: Anchor.BottomLeft) { - GameMain.Client.SendChatMessage(msg); - } - } - DisplayCharacterOrder(character, order); - } + UserData = character, + Stretch = true + }; + //line connecting the order button to the option buttons + //TODO: sprite + new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null); /// /// Create the UI panel that's used to select the target and options for a given order diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs index 00ae9403c..18512989d 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/CaptainTutorial.cs @@ -201,7 +201,7 @@ namespace Barotrauma.Tutorials SetHighlight(captain_statusMonitor, true); do { - captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); + //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); yield return new WaitForSeconds(1.0f); } while (Submarine.MainSub.DockedTo.Count > 0); RemoveCompletedObjective(segments[4]); @@ -225,7 +225,7 @@ namespace Barotrauma.Tutorials TriggerTutorialSegment(6); // Docking do { - captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); + //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); yield return new WaitForSeconds(1.0f); } while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0); RemoveCompletedObjective(segments[6]); diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 8658a7ba1..79addfd49 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -132,7 +132,8 @@ namespace Barotrauma var languageDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.045f), generalLayoutGroup.RectTransform)); foreach (string language in TextManager.AvailableLanguages) { - languageDD.AddItem(TextManager.Get("Language." + language, returnNull: true) ?? language, language); + //TODO: display the name of the language in the target language? + languageDD.AddItem(language, language); } languageDD.SelectItem(TextManager.Language); languageDD.OnSelected = (guiComponent, obj) => diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs index 5ef0e010c..8dc301955 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs @@ -78,8 +78,8 @@ namespace Barotrauma.Items.Components CanBeFocused = false }; - var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.25f, 0.0f) }, - "", textAlignment: Alignment.BottomLeft); + var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) }, + "", textAlignment: Alignment.BottomLeft, wrap: true); string pumpSpeedStr = TextManager.Get("PumpSpeed"); pumpSpeedText.TextGetter = () => { return pumpSpeedStr + ": " + (int)flowPercentage + " %"; }; @@ -90,7 +90,7 @@ namespace Barotrauma.Items.Components }; new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform), - TextManager.Get("PumpOut"), textAlignment: Alignment.Center); + TextManager.Get("PumpOut"), textAlignment: Alignment.Center, wrap: true, font: GUI.SmallFont); pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.8f, 1.0f), sliderArea.RectTransform), barSize: 0.25f, style: "GUISlider") { Step = 0.05f, @@ -111,7 +111,7 @@ namespace Barotrauma.Items.Components }; new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sliderArea.RectTransform), - TextManager.Get("PumpIn"), textAlignment: Alignment.Center); + TextManager.Get("PumpIn"), textAlignment: Alignment.Center, wrap: true, font: GUI.SmallFont); } public override void OnItemLoaded() diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs index 1fc3955ca..4dbe476c3 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Reactor.cs @@ -104,13 +104,13 @@ namespace Barotrauma.Items.Components var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), GuiFrame.RectTransform, Anchor.Center), isHorizontal: true) { - RelativeSpacing = 0.015f, + RelativeSpacing = 0.012f, Stretch = true }; - GUIFrame columnLeft = new GUIFrame(new RectTransform(new Vector2(0.2f, 1.0f), paddedFrame.RectTransform), style: null); + GUIFrame columnLeft = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), paddedFrame.RectTransform), style: null); leftHUDColumn = columnLeft; - GUIFrame columnMid = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), style: null); + GUIFrame columnMid = new GUIFrame(new RectTransform(new Vector2(0.45f, 1.0f), paddedFrame.RectTransform), style: null); GUIFrame columnRight = new GUIFrame(new RectTransform(new Vector2(0.3f, 1.0f), paddedFrame.RectTransform), style: null); //---------------------------------------------------------- @@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components }; var btnText = warningBtn.GetChild(); - btnText.Font = GUI.SmallFont; + btnText.Font = GUI.Font; btnText.Wrap = true; btnText.SetTextPos(); warningButtons.Add(warningTexts[i], warningBtn); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index f757c009d..3c6eabe3c 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -776,6 +776,37 @@ namespace Barotrauma ic.DrawHUD(spriteBatch, character); } } + } + + List texts = new List(); + public List GetHUDTexts(Character character) + { + texts.Clear(); + foreach (ItemComponent ic in components) + { + if (string.IsNullOrEmpty(ic.DisplayMsg)) continue; + if (!ic.CanBePicked && !ic.CanBeSelected) continue; + if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue; + + Color color = Color.Gray; + bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false); + if (hasRequiredSkillsAndItems) + { + if (ic is Repairable repairable) + { + if (Condition < repairable.ShowRepairUIThreshold) + { + color = Color.Cyan; + } + } + else + { + color = Color.Cyan; + } + } + + texts.Add(new ColoredText(ic.DisplayMsg, color, false)); + } return texts; } diff --git a/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs b/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs index 6ef97a119..7483ce030 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/ItemInventory.cs @@ -92,6 +92,7 @@ namespace Barotrauma if (!string.IsNullOrEmpty(uiLabel) && !subInventory) { + uiLabel = ToolBox.WrapText(uiLabel, BackgroundFrame.Width, GUI.Font, 1); GUI.DrawString(spriteBatch, new Vector2((int)(BackgroundFrame.Center.X - GUI.Font.MeasureString(uiLabel).X / 2), (int)BackgroundFrame.Y + 5), uiLabel, Color.White * 0.9f); diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs index 4f94c4362..aea1e2ea7 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/Level.cs @@ -25,17 +25,21 @@ namespace Barotrauma var allLevelObjects = levelObjectManager.GetAllObjects(); foreach (var levelObj in allLevelObjects) { - if (levelObj.Prefab.Sprite != null && - !uniqueTextures.Contains(levelObj.Prefab.Sprite.Texture)) + foreach (Sprite sprite in levelObj.Prefab.Sprites) { - uniqueTextures.Add(levelObj.Prefab.Sprite.Texture); - uniqueSprites.Add(levelObj.Prefab.Sprite); + if (!uniqueTextures.Contains(sprite.Texture)) + { + uniqueTextures.Add(sprite.Texture); + uniqueSprites.Add(sprite); + } } - if (levelObj.Prefab.SpecularSprite != null && - !uniqueTextures.Contains(levelObj.Prefab.SpecularSprite.Texture)) + foreach (Sprite specularSprite in levelObj.Prefab.SpecularSprites) { - uniqueTextures.Add(levelObj.Prefab.SpecularSprite.Texture); - uniqueSprites.Add(levelObj.Prefab.SpecularSprite); + if (!uniqueTextures.Contains(specularSprite.Texture)) + { + uniqueTextures.Add(specularSprite.Texture); + uniqueSprites.Add(specularSprite); + } } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObject.cs index 181e555c8..7637d0ecb 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObject.cs @@ -76,8 +76,8 @@ namespace Barotrauma partial void InitProjSpecific() { - Prefab.Sprite?.EnsureLazyLoaded(); - Prefab.SpecularSprite?.EnsureLazyLoaded(); + Sprite?.EnsureLazyLoaded(); + SpecularSprite?.EnsureLazyLoaded(); Prefab.DeformableSprite?.EnsureLazyLoaded(); CurrentSwingAmount = Prefab.SwingAmountRad; diff --git a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObjectManager.cs index 772178ac7..4bf6eee6a 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -109,7 +109,7 @@ namespace Barotrauma Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter; camDiff.Y = -camDiff.Y; - Sprite activeSprite = specular ? obj.ActivePrefab.SpecularSprite : obj.ActivePrefab.Sprite; + Sprite activeSprite = specular ? obj.SpecularSprite : obj.Sprite; activeSprite?.Draw( spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f, diff --git a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index ecce66ee1..7e9d2051b 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs @@ -491,30 +491,6 @@ namespace Barotrauma } } - foreach (MapEntity e in MapEntity.mapEntityList) - { - if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000) - { - //move disabled items (wires, items inside containers) inside the sub - if (e is Item item && item.body != null && !item.body.Enabled) - { - item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f); - } - } - } - - foreach (MapEntity e in MapEntity.mapEntityList) - { - if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000) - { - //move disabled items (wires, items inside containers) inside the sub - if (e is Item item && item.body != null && !item.body.Enabled) - { - item.SetTransform(ConvertUnits.ToSimUnits(HiddenSubPosition), 0.0f); - } - } - } - foreach (MapEntity e in MapEntity.mapEntityList) { if (Vector2.Distance(e.Position, HiddenSubPosition) > 20000) diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs index 3cbe63212..5807af85a 100644 --- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs @@ -1012,7 +1012,7 @@ namespace Barotrauma.Networking } } - GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("PermissionsChanged"), msg, GUIMessageBox.DefaultWidth, 0) + GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("PermissionsChanged"), msg) { UserData = "permissions" }; @@ -1707,7 +1707,7 @@ namespace Barotrauma.Networking infoButton.UserData = newSub; infoButton.OnClicked = (component, userdata) => { - ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 400)); + ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", new Vector2(0.25f, 0.25f), new Point(500, 400))); return true; }; } @@ -2401,7 +2401,7 @@ namespace Barotrauma.Networking { var banReasonPrompt = new GUIMessageBox( TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"), - "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, 400, 300); + "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, new Vector2(0.25f, 0.2f), new Point(400, 200)); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center)); var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index 531a2a396..707aed3c6 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -334,6 +334,8 @@ namespace Barotrauma private GUILayoutGroup subPreviewContainer; + private GUILayoutGroup subPreviewContainer; + private GUIButton loadGameButton; public Action StartNewGame; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs index 93c47bb39..32d12fc66 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs @@ -2043,12 +2043,13 @@ namespace Barotrauma GUI.AddMessage(GetCharacterEditorTranslation("RagdollReset"), Color.WhiteSmoke, font: GUI.Font); return true; }; + Vector2 messageBoxRelSize = new Vector2(0.5f, 0.5f); int messageBoxWidth = GameMain.GraphicsWidth / 2; int messageBoxHeight = GameMain.GraphicsHeight / 2; var saveRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveRagdoll")); saveRagdollButton.OnClicked += (button, userData) => { - var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveRagdoll"), $"{GetCharacterEditorTranslation("ProvideFileName")}: ", new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxWidth, messageBoxHeight); + var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveRagdoll"), $"{GetCharacterEditorTranslation("ProvideFileName")}: ", new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxRelSize); var inputField = new GUITextBox(new RectTransform(new Point(box.Content.Rect.Width, 30), box.Content.RectTransform, Anchor.Center), RagdollParams.Name); box.Buttons[0].OnClicked += (b, d) => { @@ -2076,7 +2077,7 @@ namespace Barotrauma var loadRagdollButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadRagdoll")); loadRagdollButton.OnClicked += (button, userData) => { - var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxWidth, messageBoxHeight); + var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadRagdoll"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize); loadBox.Buttons[0].OnClicked += loadBox.Close; var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform, Anchor.TopCenter)); var deleteButton = loadBox.Buttons[2]; @@ -2122,7 +2123,7 @@ namespace Barotrauma var msgBox = new GUIMessageBox( TextManager.Get("DeleteDialogLabel"), TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile), - new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }, messageBoxWidth - 100, messageBoxHeight - 100); + new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); msgBox.Buttons[0].OnClicked += (b, d) => { try @@ -2163,7 +2164,7 @@ namespace Barotrauma var saveAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("SaveAnimation")); saveAnimationButton.OnClicked += (button, userData) => { - var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveAnimation"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxWidth, messageBoxHeight); + var box = new GUIMessageBox(GetCharacterEditorTranslation("SaveAnimation"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Save") }, messageBoxRelSize); var textArea = new GUIFrame(new RectTransform(new Vector2(1, 0.1f), box.Content.RectTransform) { MinSize = new Point(350, 30) }, style: null); var inputLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), textArea.RectTransform) { MinSize = new Point(250, 30) }, $"{GetCharacterEditorTranslation("ProvideFileName")}: "); var inputField = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), textArea.RectTransform, Anchor.TopRight) { MinSize = new Point(100, 30) }, CurrentAnimation.Name); @@ -2211,7 +2212,7 @@ namespace Barotrauma var loadAnimationButton = new GUIButton(new RectTransform(buttonSize, layoutGroup.RectTransform), GetCharacterEditorTranslation("LoadAnimation")); loadAnimationButton.OnClicked += (button, userData) => { - var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxWidth, messageBoxHeight); + var loadBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadAnimation"), "", new string[] { TextManager.Get("Cancel"), TextManager.Get("Load"), TextManager.Get("Delete") }, messageBoxRelSize); loadBox.Buttons[0].OnClicked += loadBox.Close; var listBox = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.6f), loadBox.Content.RectTransform)); var deleteButton = loadBox.Buttons[2]; @@ -2273,7 +2274,7 @@ namespace Barotrauma var msgBox = new GUIMessageBox( TextManager.Get("DeleteDialogLabel"), TextManager.Get("DeleteDialogQuestion").Replace("[file]", selectedFile), - new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }, messageBoxWidth - 100, messageBoxHeight - 100); + new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); msgBox.Buttons[0].OnClicked += (b, d) => { try @@ -4425,7 +4426,7 @@ namespace Barotrauma protected override GUIMessageBox Create() { - var box = new GUIMessageBox(GetCharacterEditorTranslation("CreateNewCharacter"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Next") }, GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight); + var box = new GUIMessageBox(GetCharacterEditorTranslation("CreateNewCharacter"), string.Empty, new string[] { TextManager.Get("Cancel"), TextManager.Get("Next") }, new Vector2(0.5f, 1.0f)); box.Content.ChildAnchor = Anchor.TopCenter; box.Content.AbsoluteSpacing = 20; int elementSize = 30; @@ -4541,7 +4542,7 @@ namespace Barotrauma protected override GUIMessageBox Create() { - var box = new GUIMessageBox(GetCharacterEditorTranslation("DefineRagdoll"), string.Empty, new string[] { TextManager.Get("Previous"), TextManager.Get("Create") }, GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight); + var box = new GUIMessageBox(GetCharacterEditorTranslation("DefineRagdoll"), string.Empty, new string[] { TextManager.Get("Previous"), TextManager.Get("Create") }, new Vector2(0.5f, 1.0f)); box.Content.ChildAnchor = Anchor.TopCenter; box.Content.AbsoluteSpacing = 20; int elementSize = 30; @@ -4621,7 +4622,7 @@ namespace Barotrauma { if (htmlBox == null) { - htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new string[] { TextManager.Get("Close"), TextManager.Get("Load") }, GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight); + htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new string[] { TextManager.Get("Close"), TextManager.Get("Load") }, new Vector2(0.5f, 1.0f)); var element = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.05f), htmlBox.Content.RectTransform), style: null, color: Color.Gray * 0.25f); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1), element.RectTransform), GetCharacterEditorTranslation("HTMLPath")); var htmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.5f, 1), element.RectTransform, Anchor.TopRight), $"Content/Characters/{Name}/{Name}.html"); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CreditsPlayer.cs b/Barotrauma/BarotraumaClient/Source/Screens/CreditsPlayer.cs index 663a85eb9..2406d8970 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CreditsPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CreditsPlayer.cs @@ -1,8 +1,5 @@ using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; using System; -using System.Collections.Generic; -using System.Text; using System.Xml.Linq; namespace Barotrauma @@ -11,7 +8,7 @@ namespace Barotrauma { private GUIListBox listBox; - private float scrollSpeed; + private readonly float scrollSpeed; public CreditsPlayer(RectTransform rectT, string configFile) : base(null, rectT) { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/LevelEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/LevelEditorScreen.cs index ba8a40c79..14dd89537 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/LevelEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/LevelEditorScreen.cs @@ -174,7 +174,10 @@ namespace Barotrauma foreach (LevelObjectPrefab levelObjPrefab in LevelObjectPrefab.List) { - levelObjPrefab.Sprite?.EnsureLazyLoaded(); + foreach (Sprite sprite in levelObjPrefab.Sprites) + { + sprite?.EnsureLazyLoaded(); + } } pointerLightSource = new LightSource(Vector2.Zero, 1000.0f, Color.White, submarine: null); @@ -251,7 +254,7 @@ namespace Barotrauma CanBeFocused = false, }; - Sprite sprite = levelObjPrefab.Sprite ?? levelObjPrefab.DeformableSprite?.Sprite; + Sprite sprite = levelObjPrefab.Sprites.FirstOrDefault() ?? levelObjPrefab.DeformableSprite?.Sprite; GUIImage img = new GUIImage(new RectTransform(new Point(paddedFrame.Rect.Height, paddedFrame.Rect.Height - textBlock.Rect.Height), paddedFrame.RectTransform, Anchor.TopCenter), sprite, scaleToFit: true) { @@ -289,7 +292,7 @@ namespace Barotrauma editor.AddCustomContent(commonnessContainer, 1); } - Sprite sprite = levelObjectPrefab.Sprite ?? levelObjectPrefab.DeformableSprite?.Sprite; + Sprite sprite = levelObjectPrefab.Sprites.FirstOrDefault() ?? levelObjectPrefab.DeformableSprite?.Sprite; if (sprite != null) { editor.AddCustomContent(new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, 20)), @@ -297,7 +300,6 @@ namespace Barotrauma { OnClicked = (btn, userdata) => { - GameMain.SpriteEditorScreen.RefreshLists(); editingSprite = sprite; GameMain.SpriteEditorScreen.SelectSprite(editingSprite); return true; @@ -602,7 +604,7 @@ namespace Barotrauma public GUIMessageBox Create() { var box = new GUIMessageBox(TextManager.Get("LevelEditorCreateLevelObj"), string.Empty, - new string[] { TextManager.Get("Cancel"), TextManager.Get("Done") }, GameMain.GraphicsWidth / 2, (int)(GameMain.GraphicsHeight * 0.8f)); + new string[] { TextManager.Get("Cancel"), TextManager.Get("Done") }, new Vector2(0.5f, 0.8f)); box.Content.ChildAnchor = Anchor.TopCenter; box.Content.AbsoluteSpacing = 20; @@ -618,8 +620,8 @@ namespace Barotrauma var texturePathBox = new GUITextBox(new RectTransform(new Point(listBox.Content.Rect.Width, elementSize), listBox.Content.RectTransform)); foreach (LevelObjectPrefab prefab in LevelObjectPrefab.List) { - if (prefab.Sprite == null) continue; - texturePathBox.Text = Path.GetDirectoryName(prefab.Sprite.FilePath); + if (prefab.Sprites.FirstOrDefault() == null) continue; + texturePathBox.Text = Path.GetDirectoryName(prefab.Sprites.FirstOrDefault().FilePath); break; } @@ -676,6 +678,8 @@ namespace Barotrauma doc.WriteTo(writer); writer.Flush(); } + // Recreate the prefab so that the sprite loads correctly: TODO: consider a better way to do this + newPrefab = new LevelObjectPrefab(newElement); break; } diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 09d48fdbc..287ac0265 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -44,15 +44,15 @@ namespace Barotrauma { backgroundVignette = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero); - new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) - { RelativeOffset = new Vector2(0.05f, 0.1f), AbsoluteOffset = new Point(-8, -8) }, + new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight) + { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) }, style: "TitleText") { Color = Color.Black * 0.5f, CanBeFocused = false }; - titleText = new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) - { RelativeOffset = new Vector2(0.05f, 0.1f) }, + titleText = new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight) + { RelativeOffset = new Vector2(0.08f, 0.05f) }, style: "TitleText"); buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft) @@ -354,6 +354,12 @@ namespace Barotrauma }; var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f); creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml"); + + new GUIButton(new RectTransform(new Vector2(0.1f, 0.05f), menuTabs[(int)Tab.Credits].RectTransform, Anchor.BottomLeft) { RelativeOffset = new Vector2(0.25f, 0.02f) }, + TextManager.Get("Back"), style: "GUIButtonLarge") + { + OnClicked = SelectTab + }; } #endregion @@ -383,10 +389,9 @@ namespace Barotrauma private bool SelectTab(GUIButton button, object obj) { + titleText.Visible = true; if (obj is Tab) { - titleText.Visible = true; - if (GameMain.Config.UnsavedSettings) { var applyBox = new GUIMessageBox( diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index fd5dfbfd0..76acba2cd 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -1212,7 +1212,7 @@ namespace Barotrauma }; infoButton.OnClicked += (component, userdata) => { - ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 600)); + ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", new Vector2(0.25f, 0.25f), new Point(500, 400))); return true; }; } @@ -2005,7 +2005,8 @@ namespace Barotrauma return false; } - var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg, new string[] { TextManager.Get("Yes"), TextManager.Get("No") }, 400, 300) + var requestFileBox = new GUIMessageBox(TextManager.Get("DownloadSubLabel"), errorMsg, + new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) { UserData = "request" + subName }; diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs index f924e04a8..38946e9ec 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SpriteEditorScreen.cs @@ -193,50 +193,7 @@ namespace Barotrauma { Sprite sprite = userData as Sprite; if (sprite == null) return false; - if (selectedSprites.Any(s => s.Texture != selectedTexture)) - { - ResetWidgets(); - } - if (Widget.EnableMultiSelect) - { - if (selectedSprites.Contains(sprite)) - { - selectedSprites.Remove(sprite); - } - else - { - selectedSprites.Add(sprite); - dirtySprites.Add(sprite); - lastSelected = sprite; - } - } - else - { - selectedSprites.Clear(); - selectedSprites.Add(sprite); - dirtySprites.Add(sprite); - lastSelected = sprite; - } - if (selectedTexture != sprite.Texture) - { - textureList.Select(sprite.Texture, autoScroll: false); - UpdateScrollBar(textureList); - } - xmlPathText.Text = string.Empty; - foreach (var s in selectedSprites) - { - texturePathText.Text = s.FilePath; - var element = s.SourceElement; - if (element != null) - { - string xmlPath = element.ParseContentPathFromUri(); - if (!xmlPathText.Text.Contains(xmlPath)) - { - xmlPathText.Text += "\n" + xmlPath; - } - } - } - xmlPathText.TextColor = Color.LightGray; + SelectSprite(sprite); return true; } }; @@ -610,11 +567,56 @@ namespace Barotrauma public void SelectSprite(Sprite sprite) { - ResetWidgets(); - textureList.Select(sprite.Texture); - ResetZoom(); - selectedSprites.Clear(); - selectedSprites.Add(sprite); + if (!loadedSprites.Contains(sprite)) + { + loadedSprites.Add(sprite); + RefreshLists(); + } + + if (selectedSprites.Any(s => s.Texture != selectedTexture)) + { + ResetWidgets(); + } + if (Widget.EnableMultiSelect) + { + if (selectedSprites.Contains(sprite)) + { + selectedSprites.Remove(sprite); + } + else + { + selectedSprites.Add(sprite); + dirtySprites.Add(sprite); + lastSelected = sprite; + } + } + else + { + selectedSprites.Clear(); + selectedSprites.Add(sprite); + dirtySprites.Add(sprite); + lastSelected = sprite; + } + if (selectedTexture != sprite.Texture) + { + textureList.Select(sprite.Texture, autoScroll: false); + UpdateScrollBar(textureList); + } + xmlPathText.Text = string.Empty; + foreach (var s in selectedSprites) + { + texturePathText.Text = s.FilePath; + var element = s.SourceElement; + if (element != null) + { + string xmlPath = element.ParseContentPathFromUri(); + if (!xmlPathText.Text.Contains(xmlPath)) + { + xmlPathText.Text += "\n" + xmlPath; + } + } + } + xmlPathText.TextColor = Color.LightGray; } public void RefreshLists() @@ -659,6 +661,7 @@ namespace Barotrauma public void ResetZoom() { + if (selectedTexture == null) { return; } var viewArea = GetViewArea; float width = viewArea.Width / (float)selectedTexture.Width; float height = viewArea.Height / (float)selectedTexture.Height; diff --git a/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs index fb6a86f1e..d7570deb8 100644 --- a/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs @@ -906,7 +906,7 @@ namespace Barotrauma public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox) { - var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, width: 300, height: 400); + var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400)); msgBox.Buttons[0].OnClicked = msgBox.Close; var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter)) diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems index 8a4a85dc3..3ce8598e9 100644 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ b/Barotrauma/BarotraumaShared/SharedCode.projitems @@ -36,6 +36,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems index f9667893d..6313bfa09 100644 --- a/Barotrauma/BarotraumaShared/SharedContent.projitems +++ b/Barotrauma/BarotraumaShared/SharedContent.projitems @@ -454,24 +454,12 @@ PreserveNewest - - PreserveNewest - PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest - - PreserveNewest - PreserveNewest @@ -505,6 +493,66 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -527,6 +575,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -557,6 +614,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1631,9 +1691,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -3146,9 +3203,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 73bc75374..6f4bf76fb 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -3,6 +3,8 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; namespace Barotrauma { @@ -12,7 +14,28 @@ namespace Barotrauma private AIObjectiveManager objectiveManager; - private float updateObjectiveTimer; + private float sortTimer; + private float crouchRaycastTimer; + private float reportTimer; + private bool shouldCrouch; + + const float crouchRaycastInterval = 1; + const float reportInterval = 1; + const float sortObjectiveInterval = 1; + + public static float HULL_SAFETY_THRESHOLD = 50; + + public HashSet UnsafeHulls { get; private set; } = new HashSet(); + + private SteeringManager outsideSteering, insideSteering; + + public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager; + public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController; + + public override AIObjectiveManager ObjectiveManager + { + get { return objectiveManager; } + } private bool shouldCrouch; private float crouchRaycastTimer; @@ -48,7 +71,6 @@ namespace Barotrauma { insideSteering = new IndoorsSteeringManager(this, true, false); outsideSteering = new SteeringManager(this); - objectiveManager = new AIObjectiveManager(c); reportTimer = Rand.Range(0f, reportInterval); sortTimer = Rand.Range(0f, sortObjectiveInterval); @@ -76,35 +98,37 @@ namespace Barotrauma Character.ClearInputs(); objectiveManager.UpdateObjectives(deltaTime); - if (updateObjectiveTimer > 0.0f) + if (sortTimer > 0.0f) + { + sortTimer -= deltaTime; + } + else + { + objectiveManager.SortObjectives(); + sortTimer = sortObjectiveInterval; + } + + if (reportTimer > 0.0f) { reportTimer -= deltaTime; } else { - objectiveManager.SortObjectives(); - updateObjectiveTimer = UpdateObjectiveInterval; + if (Character.SpeechImpediment < 100.0f) + { + ReportProblems(); + UpdateSpeaking(); + } + reportTimer = reportInterval; } - if (Character.SpeechImpediment < 100.0f) - { - ReportProblems(); - UpdateSpeaking(); - } + if (objectiveManager.CurrentObjective == null) { return; } objectiveManager.DoCurrentObjective(deltaTime); - - bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.OrderPriority; + bool run = objectiveManager.CurrentObjective.ForceRun || objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority; if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null) { - if (Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3) - { - run = true; - } - } - if (!run) - { - run = objectiveManager.CurrentObjective.ForceRun; + run = Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3; } if (run) { @@ -161,7 +185,7 @@ namespace Barotrauma { bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold; bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0; - bool shouldKeepTheGearOn = objectiveManager.CurrentObjective.KeepDivingGearOn; + bool shouldKeepTheGearOn = !ObjectiveManager.IsCurrentObjective(); bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs); if (removeDivingSuit) @@ -187,7 +211,7 @@ namespace Barotrauma } } } - if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire)) + if (!ObjectiveManager.IsCurrentObjective() && !ObjectiveManager.IsCurrentObjective()) { var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher"); if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem)) @@ -196,6 +220,21 @@ namespace Barotrauma extinguisherItem.Drop(Character); } } + foreach (var item in Character.Inventory.Items) + { + if (item == null) { continue; } + if (ObjectiveManager.CurrentObjective is AIObjectiveIdle) + { + if (item.AllowedSlots.Contains(InvSlotType.RightHand | InvSlotType.LeftHand) && Character.HasEquippedItem(item)) + { + // Try to put the weapon in an Any slot, and drop it if that fails + if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List() { InvSlotType.Any })) + { + item.Drop(Character); + } + } + } + } if (Character.IsKeyDown(InputType.Aim)) { @@ -228,41 +267,70 @@ namespace Barotrauma Order newOrder = null; if (Character.CurrentHull != null) { - if (Character.CurrentHull.FireSources.Count > 0) + if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull, Character)) { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); + if (AddTargets(Character, Character.CurrentHull)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } } - if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f)) + foreach (var gap in Character.CurrentHull.ConnectedGaps) { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); + if (AIObjectiveFixLeaks.IsValidTarget(gap, Character)) + { + if (AddTargets(Character, gap)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } + } + } + + foreach (Item item in Item.ItemList) + { + if (item.CurrentHull != Character.CurrentHull) { continue; } + if (AIObjectiveRepairItems.IsValidTarget(item, Character)) + { + if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; } + if (AddTargets(Character, item)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices"); + newOrder = new Order(orderPrefab, Character.CurrentHull, item.Repairables?.FirstOrDefault()); + } + } } foreach (Character c in Character.CharacterList) { - if (c.CurrentHull == Character.CurrentHull && !c.IsDead && - (c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC))) + if (c.CurrentHull != Character.CurrentHull) { continue; } + if (AIObjectiveFightIntruders.IsValidTarget(c, Character)) { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); + if (AddTargets(Character, c)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); + } + } + } + + if (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f) + { + + if (AddTargets(Character, Character)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid"); newOrder = new Order(orderPrefab, Character.CurrentHull, null); } } } - if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - } - if (newOrder != null) { if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime)) { - Character.Speak( - newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); + Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order); } } } @@ -289,6 +357,7 @@ namespace Barotrauma { float damage = attackResult.Damage; if (damage <= 0) { return; } + if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; } if (attacker == null || attacker.IsDead || attacker.Removed) { // Ignore damage from falling etc that we shouldn't react to. @@ -336,18 +405,18 @@ namespace Barotrauma { // Replace the old objective with the new. ObjectiveManager.Objectives.Remove(combatObjective); - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode)); + objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager)); } } else { if (delay > 0) { - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode), delay); + objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager), delay); } else { - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode)); + objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager)); } } } @@ -358,8 +427,10 @@ namespace Barotrauma CurrentOrderOption = option; CurrentOrder = order; objectiveManager.SetOrder(order, option, orderGiver); - if (speak && Character.SpeechImpediment < 100.0f) Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f); - + if (speak && Character.SpeechImpediment < 100.0f) + { + Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f); + } SetOrderProjSpecific(order); } @@ -375,7 +446,7 @@ namespace Barotrauma crouchRaycastTimer -= deltaTime; if (crouchRaycastTimer > 0.0f) return; - crouchRaycastTimer = CrouchRaycastInterval; + crouchRaycastTimer = crouchRaycastInterval; //start the raycast in front of the character in the direction it's heading to Vector2 startPos = Character.SimPosition; @@ -396,7 +467,7 @@ namespace Barotrauma /// /// Check whether the character has a diving mask in usable condition plus some oxygen. /// - public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource"); + public static bool HasDivingMask(Character character) => HasItem(character, "diving", "oxygensource"); public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0) { @@ -416,12 +487,9 @@ namespace Barotrauma { foreach (var c in Character.CharacterList) { - if (c.TeamID == character.TeamID) + if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character)) { - if (c.AIController is HumanAIController humanAi) - { - humanAi.RefreshHullSafety(hull); - } + humanAi.RefreshHullSafety(hull); } } } @@ -438,13 +506,86 @@ namespace Barotrauma } } + public static void RefreshTargets(Character character, Order order, Hull hull) + { + switch (order.AITag) + { + case "reportfire": + AddTargets(character, hull); + break; + case "reportbreach": + foreach (var gap in hull.ConnectedGaps) + { + if (AIObjectiveFixLeaks.IsValidTarget(gap, character)) + { + AddTargets(character, gap); + } + } + break; + case "reportbrokendevices": + foreach (var item in Item.ItemList) + { + if (item.CurrentHull != hull) { continue; } + if (AIObjectiveRepairItems.IsValidTarget(item, character)) + { + if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { continue; } + AddTargets(character, item); + } + } + break; + case "reportintruders": + foreach (var enemy in Character.CharacterList) + { + if (enemy.CurrentHull != hull) { continue; } + if (AIObjectiveFightIntruders.IsValidTarget(enemy, character)) + { + AddTargets(character, enemy); + } + } + break; + case "requestfirstaid": + foreach (var c in Character.CharacterList) + { + if (c.CurrentHull != hull) { continue; } + if (AIObjectiveRescueAll.IsValidTarget(c, character)) + { + AddTargets(character, c); + } + } + break; + default: +#if DEBUG + DebugConsole.ThrowError(order.AITag + " not implemented!"); +#endif + break; + } + } + + private static bool AddTargets(Character caller, T2 target) where T1 : AIObjectiveLoop + { + bool targetAdded = false; + foreach (var c in Character.CharacterList) + { + if (IsFriendly(caller, c) && c.AIController is HumanAIController humanAI) + { + var objective = humanAI.ObjectiveManager.GetObjective(); + if (objective == null) { continue; } + if (!targetAdded && objective.AddTarget(target)) + { + targetAdded = true; + } + } + } + return targetAdded; + } + public float GetHullSafety(Hull hull) { if (hull == null) { return 0; } - bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires; + bool ignoreFire = ObjectiveManager.IsCurrentObjective() || ObjectiveManager.IsCurrentObjective(); bool ignoreWater = HasDivingSuit(Character); - bool ignoreOxygen = ignoreWater || HasDivingGear(Character); - bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat; + bool ignoreOxygen = ignoreWater || HasDivingMask(Character); + bool ignoreEnemies = ObjectiveManager.IsCurrentObjective(); return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); } @@ -462,7 +603,7 @@ namespace Barotrauma // Even the smallest fire reduces the safety by 50% float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X; float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); - int enemyCount = Character.CharacterList.Count(e => + int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC))); // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) @@ -471,7 +612,8 @@ namespace Barotrauma return MathHelper.Clamp(safety * 100, 0, 100); } + public bool IsFriendly(Character other) => IsFriendly(Character, other); // TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species. - public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName; + public static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index e38496d90..4c3b02b4c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -287,7 +287,8 @@ namespace Barotrauma character.AnimController.Anim = AnimController.Animation.None; character.SelectedConstruction = null; } - if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2)) + float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1)); + if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 2 * multiplier, 2)) { currentPath.SkipToNextNode(); } @@ -296,13 +297,13 @@ namespace Barotrauma { Vector2 colliderBottom = character.AnimController.GetColliderBottom(); Vector2 colliderSize = collider.GetSize(); + Vector2 velocity = collider.LinearVelocity; // Cannot use the head position, because not all characters have head or it can be below the total height of the character float characterHeight = colliderSize.Y + character.AnimController.ColliderHeightFromFloor; float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y; bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight; - - if (horizontalDistance < collider.radius * 3 && isAboveFeet && isNotTooHigh) + if (InStairs) { float multiplierX = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1)); float multiplierY = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.Y) / 10, 0, 1)); @@ -350,6 +351,8 @@ namespace Barotrauma { for (int i = 0; i < 2; i++) { + WayPoint currentWaypoint = null; + WayPoint nextWaypoint = null; Door door = null; bool shouldBeOpen = false; @@ -360,21 +363,19 @@ namespace Barotrauma } else { - WayPoint node = null; - WayPoint nextNode = null; if (i == 0) { - node = currentPath.CurrentNode; - nextNode = currentPath.NextNode; + currentWaypoint = currentPath.CurrentNode; + nextWaypoint = currentPath.NextNode; } else { - node = currentPath.PrevNode; - nextNode = currentPath.CurrentNode; + currentWaypoint = currentPath.PrevNode; + nextWaypoint = currentPath.CurrentNode; } - if (node?.ConnectedDoor == null) continue; + if (currentWaypoint?.ConnectedDoor == null) { continue; } - if (nextNode == null) + if (nextWaypoint == null) { //the node we're heading towards is the last one in the path, and at a door //the door needs to be open for the character to reach the node @@ -382,21 +383,21 @@ namespace Barotrauma } else { - door = node.ConnectedGap.ConnectedDoor; + door = currentWaypoint.ConnectedGap.ConnectedDoor; if (door.LinkedGap.IsHorizontal) { - int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X); + int currentDir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f; } else { - int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y); + int currentDir = Math.Sign(nextWaypoint.WorldPosition.Y - door.Item.WorldPosition.Y); shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f; } } } - if (door == null) return; + if (door == null) { return; } //toggle the door if it's the previous node and open, or if it's current node and closed if (door.IsOpen != shouldBeOpen) @@ -422,22 +423,29 @@ namespace Barotrauma buttonPressCooldown = ButtonPressInterval; break; } - - closestButton.Item.TryInteract(character, false, true, false); - buttonPressCooldown = ButtonPressInterval; - break; - } - else - { - if (!door.HasRequiredItems(character, false) && shouldBeOpen) + else if (closestButton != null) { - currentPath.Unreachable = true; - return; + if (Vector2.DistanceSquared(closestButton.Item.WorldPosition, character.WorldPosition) < MathUtils.Pow(closestButton.Item.InteractDistance * 2, 2)) + { + closestButton.Item.TryInteract(character, false, true, false); + buttonPressCooldown = ButtonPressInterval; + break; + } + else + { + // Can't reach the button closest to the door. + // It's possible that we could reach another buttons. + // If this becomes an issue, we could go through them here and check if any of them are reachable + // (would have to cache a collection of buttons instead of a single reference in the CanAccess filter method above) + currentPath.Unreachable = true; + return; + } } - - door.Item.TryInteract(character, false, true, true); - buttonPressCooldown = ButtonPressInterval; - break; + } + else if (shouldBeOpen) + { + currentPath.Unreachable = true; + return; } } } @@ -445,32 +453,38 @@ namespace Barotrauma private float? GetNodePenalty(PathNode node, PathNode nextNode) { - if (character == null) { return 0.0f; } - + if (character == null) { return 0.0f; } float penalty = 0.0f; if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f) { - if (nextNode.Waypoint.ConnectedDoor == null) + var door = nextNode.Waypoint.ConnectedDoor; + if (door == null) { penalty = 100.0f; } - else if (!canBreakDoors) + else { - //door closed and the character can't open doors -> node can't be traversed - if (!canOpenDoors || character.LockHands) { return null; } - - var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents(); - if (!doorButtons.Any()) + 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; + })) { - if (!nextNode.Waypoint.ConnectedDoor.HasRequiredItems(character, false)) { return null; } - } - - foreach (Controller button in doorButtons) - { - if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) != - Math.Sign(node.Position.X - nextNode.Position.X)) { continue; } - - if (!button.HasRequiredItems(character, false)) { return null; } + return null; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index 20cb75348..b991f524f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -12,21 +12,6 @@ namespace Barotrauma public abstract string DebugTag { get; } public virtual bool ForceRun => false; - public virtual bool KeepDivingGearOn => false; - - protected readonly List subObjectives = new List(); - public float Priority { get; set; } - protected readonly Character character; - protected string option; - protected bool abandon; - - public virtual bool CanBeCompleted => !abandon && subObjectives.All(so => so.CanBeCompleted); - public IEnumerable SubObjectives => subObjectives; - public AIObjective CurrentSubObjective { get; private set; } - - protected HumanAIController HumanAIController => character.AIController as HumanAIController; - protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering; - protected SteeringManager SteeringManager => HumanAIController.SteeringManager; /// /// Run the main objective with all subobjectives concurrently? @@ -62,6 +47,7 @@ namespace Barotrauma public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null) { + this.objectiveManager = objectiveManager; this.character = character; Option = option ?? string.Empty; @@ -94,18 +80,6 @@ namespace Barotrauma #endif subObjectives.Remove(subObjective); } - else if (subObjective.ShouldInterruptSubObjective(subObjective)) - { -#if DEBUG - DebugConsole.NewMessage($"Removing subobjective {subObjective.DebugTag} of {DebugTag}, because it is interrupted."); -#endif - subObjectives.Remove(subObjective); - } - } - - if (!subObjectives.Contains(CurrentSubObjective)) - { - CurrentSubObjective = null; } foreach (AIObjective objective in subObjectives) @@ -132,29 +106,40 @@ namespace Barotrauma subObjectives.Add(objective); } - public void SortSubObjectives(AIObjectiveManager objectiveManager) + public void SortSubObjectives() { if (subObjectives.None()) { return; } - subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager))); - CurrentSubObjective = SubObjectives.First(); - CurrentSubObjective.SortSubObjectives(objectiveManager); + subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority())); + if (ConcurrentObjectives) + { + subObjectives.ForEach(so => so.SortSubObjectives()); + } + else + { + subObjectives.First().SortSubObjectives(); + } } - public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority; + public virtual float GetPriority() => Priority * PriorityModifier; - public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime) + public virtual void Update(float deltaTime) { - var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective; if (objectiveManager.CurrentOrder == this) { Priority = AIObjectiveManager.OrderPriority; } - else if (objectiveManager.CurrentObjective == this || subObjective == this) + else if (objectiveManager.WaitTimer <= 0) { - Priority += Devotion * deltaTime; + if (objectiveManager.CurrentObjective != null) + { + if (objectiveManager.CurrentObjective == this || objectiveManager.CurrentObjective.subObjectives.Any(so => so == this)) + { + Priority += Devotion * PriorityModifier * deltaTime; + } + } + Priority = MathHelper.Clamp(Priority, 0, 100); + subObjectives.ForEach(so => so.Update(deltaTime)); } - Priority = MathHelper.Clamp(Priority, 0, 100); - subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime)); } /// @@ -174,13 +159,54 @@ namespace Barotrauma } } - protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false; - public virtual void OnSelected() { } + /// + /// Checks if the objective already is created and added in subobjectives. If not, creates it. + /// Handles objectives that cannot be completed. If the objective has been removed form the subobjectives, a null value is assigned to the reference. + /// Returns true if the objective was created. + /// + protected bool TryAddSubObjective(ref T objective, Func constructor, Action onAbandon = null) where T : AIObjective + { + if (objective != null) + { + // Sub objective already found, no need to do anything if it remains in the subobjectives + // If the sub objective is removed -> it's either completed or impossible to complete. + if (!subObjectives.Contains(objective)) + { + if (!objective.CanBeCompleted) + { + abandon = true; + onAbandon?.Invoke(); + } + objective = null; + } + return false; + } + else + { + objective = constructor(); + if (!subObjectives.Contains(objective)) + { + AddSubObjective(objective); + } + return true; + } + } + + public virtual void OnSelected() + { + // Should we reset steering here? + //if (!ConcurrentObjectives) + //{ + // SteeringManager.Reset(); + //} + } + public virtual void Reset() { } protected abstract void Act(float deltaTime); public abstract bool IsCompleted(); + public abstract bool IsDuplicate(AIObjective otherObjective); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs index b0224241d..92522e7b2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs @@ -1,19 +1,19 @@ using Barotrauma.Items.Components; using Barotrauma.Extensions; +using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Xna.Framework; namespace Barotrauma { class AIObjectiveChargeBatteries : AIObjectiveLoop { public override string DebugTag => "charge batteries"; - private readonly IEnumerable batteryList; + private IEnumerable batteryList; - public AIObjectiveChargeBatteries(Character character, string option) : base(character, option) - { - batteryList = Item.ItemList.Select(i => i.GetComponent()).Where(b => b != null); - } + public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier) + : base(character, objectiveManager, priorityModifier, option) { } public override bool IsDuplicate(AIObjective otherObjective) { @@ -22,37 +22,57 @@ namespace Barotrauma protected override void FindTargets() { - foreach (Item item in Item.ItemList) - { - if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) { continue; } - if (item.Submarine == null) { continue; } - if (item.Submarine.TeamID != character.TeamID) { continue; } - if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } - var battery = item.GetComponent(); - if (battery != null) - { - if (!ignoreList.Contains(battery)) - { - if (!targets.Contains(battery)) - { - targets.Add(battery); - } - } - } - } - if (targets.None()) + base.FindTargets(); + if (targets.None() && objectiveManager.CurrentOrder == this) { character.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f); } + } + + protected override bool Filter(PowerContainer battery) + { + if (battery == null) { return false; } + var item = battery.Item; + if (item.Submarine == null) { return false; } + if (item.CurrentHull == null) { return false; } + if (item.Submarine.TeamID != character.TeamID) { return false; } + if (item.ConditionPercentage <= 0) { return false; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; } + if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; } + if (Option == "charge") + { + if (battery.RechargeRatio >= PowerContainer.aiRechargeTargetRatio - 0.01f) { return false; } + } else { - targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage)); + if (battery.RechargeRatio <= 0) { return false; } + } + return true; + } + + protected override float TargetEvaluation() + { + if (Option == "charge") + { + return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(PowerContainer.aiRechargeTargetRatio - t.RechargeRatio))); + } + else + { + + return targets.Max(t => MathHelper.Lerp(0, 100, t.RechargeRatio)); } } - protected override bool Filter(PowerContainer battery) => true; - protected override float Average(PowerContainer battery) => 100 - battery.ChargePercentage; - protected override IEnumerable GetList() => batteryList; - protected override AIObjective ObjectiveConstructor(PowerContainer battery) => new AIObjectiveOperateItem(battery, character, Option, false); + protected override IEnumerable GetList() + { + if (batteryList == null) + { + batteryList = character.Submarine.GetItems(true).Select(i => i.GetComponent()).Where(b => b != null); + } + return batteryList; + } + + protected override AIObjective ObjectiveConstructor(PowerContainer battery) + => new AIObjectiveOperateItem(battery, character, objectiveManager, Option, false, priorityModifier: PriorityModifier) { IsLoop = false }; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index d20db60c5..ce7122b63 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -1,15 +1,17 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; +using FarseerPhysics; namespace Barotrauma { class AIObjectiveCombat : AIObjective { public override string DebugTag => "combat"; - public override bool KeepDivingGearOn => true; + public bool useCoolDown = true; const float CoolDown = 10.0f; @@ -23,7 +25,14 @@ namespace Barotrauma { _weapon = value; _weaponComponent = null; - reloadWeaponObjective = null; + if (reloadWeaponObjective != null) + { + if (subObjectives.Contains(reloadWeaponObjective)) + { + subObjectives.Remove(reloadWeaponObjective); + } + reloadWeaponObjective = null; + } } } private ItemComponent _weaponComponent; @@ -41,29 +50,41 @@ namespace Barotrauma return _weaponComponent; } } - private AIObjectiveContainItem reloadWeaponObjective; - private Hull retreatTarget; - private AIObjectiveGoTo retreatObjective; - private AIObjectiveFindSafety findSafety; + public override bool ConcurrentObjectives => true; + + private readonly AIObjectiveFindSafety findSafety; + private readonly HashSet rangedWeapons = new HashSet(); + private readonly HashSet meleeWeapons = new HashSet(); + private readonly HashSet adHocWeapons = new HashSet(); + + private AIObjectiveContainItem reloadWeaponObjective; + private AIObjectiveGoTo retreatObjective; + private AIObjectiveGoTo followTargetObjective; + + private Hull retreatTarget; private float coolDownTimer; public enum CombatMode { Defensive, - Offensive, // Not implemented + Offensive, Retreat } public CombatMode Mode { get; private set; } - public AIObjectiveCombat(Character character, Character enemy, CombatMode mode) : base(character, "") + public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { Enemy = enemy; coolDownTimer = CoolDown; - findSafety = HumanAIController.ObjectiveManager.GetObjective(); - findSafety.Priority = 0; - findSafety.unreachable.Clear(); + findSafety = objectiveManager.GetObjective(); + if (findSafety != null) + { + findSafety.Priority = 0; + findSafety.unreachable.Clear(); + } Mode = mode; if (Enemy == null) { @@ -71,14 +92,51 @@ namespace Barotrauma } } + public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100); + + public override bool IsDuplicate(AIObjective otherObjective) + { + if (!(otherObjective is AIObjectiveCombat objective)) return false; + return objective.Enemy == Enemy; + } + + public override void OnSelected() => Weapon = null; + + public override bool IsCompleted() + { + bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (useCoolDown && coolDownTimer <= 0); + if (completed) + { + if (objectiveManager.CurrentOrder == this && Enemy != null && Enemy.IsDead) + { + character.Speak(TextManager.Get("DialogTargetDown"), null, 3.0f, "targetdown", 30.0f); + } + if (Weapon != null) + { + Unequip(); + } + } + return completed; + } + protected override void Act(float deltaTime) { - coolDownTimer -= deltaTime; + if (useCoolDown) + { + coolDownTimer -= deltaTime; + } if (abandon) { return; } + Arm(deltaTime); + Move(deltaTime); + } + + private void Arm(float deltaTime) + { switch (Mode) { + case CombatMode.Offensive: case CombatMode.Defensive: - if (Weapon != null && character.Inventory.Items.Contains(_weapon)) + if (Weapon != null && !character.Inventory.Items.Contains(_weapon) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false)) { Weapon = null; } @@ -90,40 +148,65 @@ namespace Barotrauma { Mode = CombatMode.Retreat; } - else if (Equip(deltaTime)) + if (Equip()) { if (Reload(deltaTime)) { Attack(deltaTime); } } - // When defensive, try to retreat to safety. TODO: in offsensive mode, engage the target - Retreat(deltaTime); break; + case CombatMode.Retreat: + break; + default: + throw new NotImplementedException(); + } + } + + private void Move(float deltaTime) + { + switch (Mode) + { + case CombatMode.Offensive: + Engage(deltaTime); + break; + case CombatMode.Defensive: case CombatMode.Retreat: Retreat(deltaTime); break; - case CombatMode.Offensive: default: - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } private Item GetWeapon() { + rangedWeapons.Clear(); + meleeWeapons.Clear(); + adHocWeapons.Clear(); + Item weapon = null; _weaponComponent = null; - var weapon = character.Inventory.FindItemByTag("weapon"); - if (weapon == null) + foreach (var item in character.Inventory.Items) { - foreach (var item in character.Inventory.Items) + if (item == null) { continue; } + foreach (var component in item.Components) { - if (item == null) { continue; } - foreach (var component in item.Components) + if (component is RangedWeapon rw) { - if (component is MeleeWeapon || component is RangedWeapon) + if (rw.HasRequiredContainedItems(false)) { - return item; + rangedWeapons.Add(rw); } + } + else if (component is MeleeWeapon mw) + { + if (mw.HasRequiredContainedItems(false)) + { + meleeWeapons.Add(mw); + } + } + else + { var effects = component.statusEffectLists; if (effects != null) { @@ -133,7 +216,10 @@ namespace Barotrauma { if (statusEffect.Afflictions.Any()) { - return item; + if (component.HasRequiredContainedItems(false)) + { + adHocWeapons.Add(item); + } } } } @@ -141,6 +227,20 @@ namespace Barotrauma } } } + var rangedWeapon = rangedWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault(); + var meleeWeapon = meleeWeapons.OrderByDescending(w => w.CombatPriority).FirstOrDefault(); + if (rangedWeapon != null) + { + weapon = rangedWeapon.Item; + } + else if (meleeWeapon != null) + { + weapon = meleeWeapon.Item; + } + if (weapon == null) + { + weapon = adHocWeapons.GetRandom(Rand.RandSync.Server); + } return weapon; } @@ -153,10 +253,9 @@ namespace Barotrauma Weapon.Drop(character); } } - return true; } - private bool Equip(float deltaTime) + private bool Equip() { if (!character.SelectedItems.Contains(Weapon)) { @@ -167,8 +266,7 @@ namespace Barotrauma } else { - //couldn't equip the item, escape - //Abandon(deltaTime); + Mode = CombatMode.Retreat; return false; } } @@ -177,18 +275,52 @@ namespace Barotrauma private void Retreat(float deltaTime) { + if (followTargetObjective != null) + { + if (subObjectives.Contains(followTargetObjective)) + { + subObjectives.Remove(followTargetObjective); + } + followTargetObjective = null; + } + if (retreatObjective != null && retreatObjective.Target != retreatTarget) + { + retreatObjective = null; + } if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { retreatTarget = findSafety.FindBestHull(new List() { character.CurrentHull }); } - if (retreatTarget != null) + TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true)); + } + + private void Engage(float deltaTime) + { + retreatTarget = null; + if (retreatObjective != null) { - if (retreatObjective == null || retreatObjective.Target != retreatTarget) + if (subObjectives.Contains(retreatObjective)) { - retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true); + subObjectives.Remove(retreatObjective); } - retreatObjective.TryComplete(deltaTime); + retreatObjective = null; } + TryAddSubObjective(ref followTargetObjective, + constructor: () => new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true) + { + AllowGoingOutside = true, + IgnoreIfTargetDead = true, + CheckVisibility = true, + CloseEnough = + WeaponComponent is RangedWeapon ? 3 : + WeaponComponent is MeleeWeapon mw ? ConvertUnits.ToSimUnits(mw.Range) : + WeaponComponent is RepairTool rt ? ConvertUnits.ToSimUnits(rt.Range) : 0.5f + }, + onAbandon: () => + { + SteeringManager.Reset(); + Mode = CombatMode.Retreat; + }); } private bool Reload(float deltaTime) @@ -201,43 +333,47 @@ namespace Barotrauma Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it)); if (containedItem == null) { - if (reloadWeaponObjective == null) - { - reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent()); - } + TryAddSubObjective(ref reloadWeaponObjective, + constructor: () => new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent(), objectiveManager), + onAbandon: () => + { + SteeringManager.Reset(); + Mode = CombatMode.Retreat; + }); } } } - if (reloadWeaponObjective != null) - { - if (reloadWeaponObjective.IsCompleted()) - { - reloadWeaponObjective = null; - } - else if (!reloadWeaponObjective.CanBeCompleted) - { - Mode = CombatMode.Retreat; - } - else - { - reloadWeaponObjective.TryComplete(deltaTime); - } - return false; - } - return true; + return reloadWeaponObjective == null || reloadWeaponObjective.IsCompleted(); } private IEnumerable myBodies; private void Attack(float deltaTime) { + float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position); character.CursorPosition = Enemy.Position; + float engageDistance = 500; + if (squaredDistance > engageDistance * engageDistance) { return; } + bool canSeeTarget = character.CanSeeCharacter(Enemy); + if (!canSeeTarget && character.CurrentHull != Enemy.CurrentHull) { return; } if (Weapon.RequireAimToUse) { - character.SetInput(InputType.Aim, false, true); + bool isOperatingButtons = false; + if (SteeringManager == PathSteering) + { + var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor; + if (door != null && !door.IsOpen) + { + isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents(true).Any(); + } + } + if (!isOperatingButtons && character.SelectedConstruction == null) + { + character.SetInput(InputType.Aim, false, true); + } } if (WeaponComponent is MeleeWeapon meleeWeapon) { - if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range) + if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range) { character.SetInput(InputType.Shoot, false, true); Weapon.Use(deltaTime, character); @@ -247,7 +383,7 @@ namespace Barotrauma { if (WeaponComponent is RepairTool repairTool) { - if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; } + if (squaredDistance > repairTool.Range * repairTool.Range) { return; } } if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4) { @@ -255,7 +391,8 @@ namespace Barotrauma { myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody); } - var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies); + var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall; + var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories); if (pickedBody != null) { Character target = null; @@ -277,34 +414,6 @@ namespace Barotrauma } } - private void Abandon(float deltaTime) - { - abandon = true; - SteeringManager.Reset(); - } - - public override bool IsCompleted() - { - bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || coolDownTimer <= 0; - if (completed) - { - if (Weapon != null) - { - Unequip(); - } - } - return completed; - } - - public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted); - public override float GetPriority(AIObjectiveManager objectiveManager) => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : 100; - - public override bool IsDuplicate(AIObjective otherObjective) - { - if (!(otherObjective is AIObjectiveCombat objective)) return false; - return objective.Enemy == Enemy; - } - //private float CalculateEnemyStrength() //{ // float enemyStrength = 0; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index 8ecebb0f3..903c1cbb7 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -9,15 +9,9 @@ namespace Barotrauma { public override string DebugTag => "contain item"; - public int MinContainedAmount = 1; - - //can either be a tag or an identifier - private string[] itemIdentifiers; - - private ItemContainer container; - public Func GetItemPriority; + public int MinContainedAmount = 1; public string[] ignoredContainerIdentifiers; //can either be a tag or an identifier @@ -27,14 +21,12 @@ namespace Barotrauma private bool isCompleted; private AIObjectiveGetItem getItemObjective; private AIObjectiveGoTo goToObjective; - - public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container) - : this(character, new string[] { itemIdentifier }, container) - { - } - public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container) - : base (character, "") + public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { } + + public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base (character, objectiveManager, priorityModifier) { this.itemIdentifiers = itemIdentifiers; for (int i = 0; i < itemIdentifiers.Length; i++) @@ -51,22 +43,10 @@ namespace Barotrauma int containedItemCount = 0; foreach (Item item in container.Inventory.Items) { - if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++; - } - - return containedItemCount >= MinContainedAmount; - } - - public override bool CanBeCompleted - { - get - { - if (goToObjective != null) + if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { containedItemCount++; } - - return getItemObjective == null || getItemObjective.CanBeCompleted; } return containedItemCount >= MinContainedAmount; } @@ -88,17 +68,16 @@ namespace Barotrauma foreach (string identifier in itemIdentifiers) { itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier); - if (itemToContain != null && itemToContain.Condition > 0.0f) break; - } - + if (itemToContain != null && itemToContain.Condition > 0.0f) { break; } + } if (itemToContain == null) { - getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers) - { - GetItemPriority = GetItemPriority, - ignoredContainerIdentifiers = ignoredContainerIdentifiers - }; - AddSubObjective(getItemObjective); + TryAddSubObjective(ref getItemObjective, () => + new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager) + { + GetItemPriority = GetItemPriority, + ignoredContainerIdentifiers = ignoredContainerIdentifiers + }); return; } if (container.Item.ParentInventory == character.Inventory) @@ -115,7 +94,8 @@ namespace Barotrauma } else { - if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition))) + if (container.Item.CurrentHull != character.CurrentHull || + (Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))) { TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager)); return; @@ -127,14 +107,15 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem; - if (objective == null) return false; - if (objective.container != container) return false; - if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false; - + if (!(otherObjective is AIObjectiveContainItem objective)) { return false; } + if (objective.container != container) { return false; } + if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } for (int i = 0; i < itemIdentifiers.Length; i++) { - if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false; + if (objective.itemIdentifiers[i] != itemIdentifiers[i]) + { + return false; + } } return true; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs index a73d7ec57..43a357106 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs @@ -8,78 +8,53 @@ namespace Barotrauma { public override string DebugTag => "decontain item"; - //can either be a tag or an identifier - private string[] itemIdentifiers; - - private ItemContainer container; - - private bool isCompleted; - public Func GetItemPriority; - private AIObjectiveGetItem getItemObjective; - private AIObjectiveGoTo goToObjective; - private Item targetItem; + //can either be a tag or an identifier + private readonly string[] itemIdentifiers; + private readonly ItemContainer container; + private readonly Item targetItem; - public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container) - : base(character, "") + private AIObjectiveGoTo goToObjective; + private bool isCompleted; + + public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { this.targetItem = targetItem; this.container = container; } - public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container) - : this(character, new string[] { itemIdentifier }, container) - { - } + public AIObjectiveDecontainItem(Character character, string itemIdentifier, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : this(character, new string[] { itemIdentifier }, container, objectiveManager, priorityModifier) { } - public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container) - : base(character, "") + public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { this.itemIdentifiers = itemIdentifiers; for (int i = 0; i < itemIdentifiers.Length; i++) { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); } - this.container = container; } - public override bool IsCompleted() - { - return isCompleted; - } + public override bool IsCompleted() => isCompleted; - public override bool CanBeCompleted - { - get - { - if (goToObjective != null) - { - return goToObjective.CanBeCompleted; - } - - return getItemObjective == null || getItemObjective.CanBeCompleted; - } - } - - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { if (objectiveManager.CurrentOrder == this) { return AIObjectiveManager.OrderPriority; } - return 1.0f; } protected override void Act(float deltaTime) { - if (isCompleted) return; - + if (isCompleted) { return; } Item itemToDecontain = null; - //get the item that should be de-contained if (targetItem == null) { @@ -88,7 +63,7 @@ namespace Barotrauma foreach (string identifier in itemIdentifiers) { itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier); - if (itemToDecontain != null) break; + if (itemToDecontain != null) { break; } } } } @@ -96,38 +71,32 @@ namespace Barotrauma { itemToDecontain = targetItem; } - if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete { isCompleted = true; return; } - if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory) { - if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance - && !container.Item.IsInsideTrigger(character.WorldPosition)) + if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)) { - goToObjective = new AIObjectiveGoTo(container.Item, character); - AddSubObjective(goToObjective); + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager)); return; } } - itemToDecontain.Drop(character); isCompleted = true; } public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem; - if (decontainItem == null) return false; + if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; } if (decontainItem.itemIdentifiers != null && itemIdentifiers != null) { - if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) return false; + if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++) { - if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) return false; + if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; } } return true; } @@ -135,7 +104,6 @@ namespace Barotrauma { return decontainItem.targetItem == targetItem; } - return false; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index a3e12d61b..62d4653f3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -3,6 +3,7 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -10,125 +11,117 @@ namespace Barotrauma { public override string DebugTag => "extinguish fire"; public override bool ForceRun => true; - public override bool KeepDivingGearOn => true; + public override bool ConcurrentObjectives => true; - private Hull targetHull; + private readonly Hull targetHull; private AIObjectiveGetItem getExtinguisherObjective; - private AIObjectiveGoTo gotoObjective; - private float useExtinquisherTimer; - public AIObjectiveExtinguishFire(Character character, Hull targetHull) : base(character, "") + public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { this.targetHull = targetHull; } - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { - if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } + if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { return 0; } // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist)); - float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1)); - return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100); + float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull); + float severityFactor = MathHelper.Lerp(0, 1, severity / 100); + float devotion = Math.Min(Priority, 10) / 100; + return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1)); } - public override bool IsCompleted() - { - return targetHull.FireSources.Count == 0; - } + public override bool IsCompleted() => targetHull.FireSources.None(); - public override bool IsDuplicate(AIObjective otherObjective) - { - var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire; - return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull; - } - - public override bool CanBeCompleted - { - get { return getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted; } - } + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull; protected override void Act(float deltaTime) { var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher"); if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem)) { - if (getExtinguisherObjective == null) + TryAddSubObjective(ref getExtinguisherObjective, () => { character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f); - getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true); - } - else - { - getExtinguisherObjective.TryComplete(deltaTime); - } - - return; + return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true); + }); } - - var extinguisher = extinguisherItem.GetComponent(); - if (extinguisher == null) + else { - DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); - return; - } - - foreach (FireSource fs in targetHull.FireSources) - { - bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); - bool move = !inRange; - if (inRange || useExtinquisherTimer > 0.0f) + var extinguisher = extinguisherItem.GetComponent(); + if (extinguisher == null) { - useExtinquisherTimer += deltaTime; - if (useExtinquisherTimer > 2.0f) +#if DEBUG + DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); +#endif + abandon = true; + return; + } + foreach (FireSource fs in targetHull.FireSources) + { + bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); + bool move = !inRange; + if (inRange || useExtinquisherTimer > 0.0f) { - useExtinquisherTimer = 0.0f; - } - character.AIController.SteeringManager.Reset(); - character.CursorPosition = fs.Position; - if (extinguisher.Item.RequireAimToUse) - { - character.SetInput(InputType.Aim, false, true); - } - Limb sightLimb = null; - if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand)) - { - sightLimb = character.AnimController.GetLimb(LimbType.RightHand); - } - else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand)) - { - sightLimb = character.AnimController.GetLimb(LimbType.LeftHand); - } - if (!character.CanSeeTarget(fs, sightLimb)) - { - move = true; - } - else - { - move = false; - extinguisher.Use(deltaTime, character); - if (!targetHull.FireSources.Contains(fs)) + useExtinquisherTimer += deltaTime; + if (useExtinquisherTimer > 2.0f) { - character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f); + useExtinquisherTimer = 0.0f; + } + character.AIController.SteeringManager.Reset(); + character.CursorPosition = fs.Position; + if (extinguisher.Item.RequireAimToUse) + { + bool isOperatingButtons = false; + if (SteeringManager == PathSteering) + { + var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor; + if (door != null && !door.IsOpen) + { + isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents(true).Any(); + } + } + if (!isOperatingButtons) + { + character.SetInput(InputType.Aim, false, true); + } + } + Limb sightLimb = null; + if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand)) + { + sightLimb = character.AnimController.GetLimb(LimbType.RightHand); + } + else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand)) + { + sightLimb = character.AnimController.GetLimb(LimbType.LeftHand); + } + if (!character.CanSeeTarget(fs, sightLimb)) + { + move = true; + } + else + { + move = false; + extinguisher.Use(deltaTime, character); + if (!targetHull.FireSources.Contains(fs)) + { + character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f); + } } } - } - if (move) - { - //go to the first firesource - if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted()) + if (move) { - gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character); - } - else - { - gotoObjective.TryComplete(deltaTime); + //go to the first firesource + TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager)); } + break; } - break; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index 4da5093e8..521da313c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -1,62 +1,44 @@ using System.Linq; using System.Collections.Generic; using Barotrauma.Extensions; -using Microsoft.Xna.Framework; namespace Barotrauma { - class AIObjectiveExtinguishFires : AIObjective + class AIObjectiveExtinguishFires : AIObjectiveLoop { public override string DebugTag => "extinguish fires"; public override bool ForceRun => true; - public override bool KeepDivingGearOn => true; - private Dictionary extinguishObjectives = new Dictionary(); + public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } - public AIObjectiveExtinguishFires(Character character) : base(character, "") { } - - public override float GetPriority(AIObjectiveManager objectiveManager) + protected override void FindTargets() { - if (character.Submarine == null) { return 0; } - int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count); - if (objectiveManager.CurrentOrder == this && fireCount > 0) + base.FindTargets(); + if (targets.None() && objectiveManager.CurrentOrder == this) { - return AIObjectiveManager.OrderPriority; + character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f); } - - return MathHelper.Clamp(fireCount * 20, 0, 100); } - public override bool IsCompleted() => false; - public override bool CanBeCompleted => true; + protected override bool Filter(Hull hull) => IsValidTarget(hull, character); - public override bool IsDuplicate(AIObjective otherObjective) - { - return otherObjective is AIObjectiveExtinguishFires; - } + protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t)); - protected override void Act(float deltaTime) + public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X); + + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFires; + protected override IEnumerable GetList() => Hull.hullList; + + protected override AIObjective ObjectiveConstructor(Hull target) => new AIObjectiveExtinguishFire(character, target, objectiveManager, PriorityModifier); + + public static bool IsValidTarget(Hull hull, Character character) { - SyncRemovedObjectives(extinguishObjectives, Hull.hullList); - if (character.Submarine == null) { return; } - foreach (Hull hull in Hull.hullList) - { - if (hull.FireSources.None()) { continue; } - if (hull.Submarine == null) { continue; } - if (hull.Submarine.TeamID != character.TeamID) { continue; } - // If the character is inside, only take connected hulls into account. - if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { continue; } - if (!extinguishObjectives.TryGetValue(hull, out AIObjectiveExtinguishFire objective)) - { - objective = new AIObjectiveExtinguishFire(character, hull); - extinguishObjectives.Add(hull, objective); - AddSubObjective(objective); - } - } - if (extinguishObjectives.None()) - { - character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f); - } + if (hull == null) { return false; } + if (hull.FireSources.None()) { return false; } + if (hull.Submarine == null) { return false; } + if (hull.Submarine.TeamID != character.TeamID) { return false; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(hull, true)) { return false; } + return true; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs index 9039a5011..91ebb94f6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFightIntruders.cs @@ -29,7 +29,7 @@ namespace Barotrauma protected override IEnumerable GetList() => Character.CharacterList; - protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier); + protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveCombat(character, target, AIObjectiveCombat.CombatMode.Offensive, objectiveManager, PriorityModifier) { useCoolDown = false }; protected override float TargetEvaluation() { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index 6721f779e..ec88cd1f3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -1,6 +1,7 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -9,8 +10,10 @@ namespace Barotrauma public override string DebugTag => "find diving gear"; public override bool ForceRun => true; - private AIObjective subObjective; - private string gearTag; + private readonly string gearTag; + + private AIObjectiveGetItem getDivingGear; + private AIObjectiveContainItem getOxygen; public override bool IsCompleted() { @@ -21,15 +24,16 @@ namespace Barotrauma { var containedItems = character.Inventory.Items[i].ContainedItems; if (containedItems == null) { continue; } - - var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); - if (oxygenTank != null) { return true; } + return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); } } return false; } - public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "") + public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100); + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear; + + public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { gearTag = needDivingSuit ? "divingsuit" : "diving"; } @@ -42,16 +46,23 @@ namespace Barotrauma TryAddSubObjective(ref getDivingGear, () => { character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); - subObjective = new AIObjectiveGetItem(character, gearTag, true); - } + return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true); + }); } if (getDivingGear != null) { return; } var containedItems = item.ContainedItems; if (containedItems == null) { var containedItems = item.ContainedItems; - if (containedItems == null) { return; } - //check if there's an oxygen tank in the mask/suit + if (containedItems == null) + { +#if DEBUG + DebugConsole.ThrowError("AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory"); +#endif + abandon = true; + return; + } + // Drop empty tanks foreach (Item containedItem in containedItems) { if (containedItem == null) { continue; } @@ -59,26 +70,16 @@ namespace Barotrauma { containedItem.Drop(character); } - else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource")) - { - //we've got an oxygen source inside the mask/suit, all good - return; - } - } - if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted()) + } + if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f)) { - character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); - subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent()); + TryAddSubObjective(ref getOxygen, () => + { + character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); + return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent(), objectiveManager); + }); } } - if (subObjective != null) - { - subObjective.TryComplete(deltaTime); - } } - - public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted; - public override float GetPriority(AIObjectiveManager objectiveManager) => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100); - public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs index efea2390e..f43eadbe6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -10,7 +10,6 @@ namespace Barotrauma { public override string DebugTag => "find safety"; public override bool ForceRun => true; - public override bool KeepDivingGearOn => true; // TODO: expose? const float priorityIncrease = 25; @@ -18,7 +17,7 @@ namespace Barotrauma const float SearchHullInterval = 3.0f; const float clearUnreachableInterval = 30; - public readonly List unreachable = new List(); + public readonly HashSet unreachable = new HashSet(); private float currenthullSafety; private float unreachableClearTimer; @@ -27,49 +26,15 @@ namespace Barotrauma private AIObjectiveGoTo goToObjective; private AIObjectiveFindDivingGear divingGearObjective; - public AIObjectiveFindSafety(Character character) : base(character, "") { } + public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } public override bool IsCompleted() => false; public override bool CanBeCompleted => true; + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindSafety; + public override void Update(float deltaTime) { - var currentHull = character.AnimController.CurrentHull; - if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null) - { - bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90; - bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character); - if (!hasEquipment) - { - divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit); - } - } - if (divingGearObjective != null) - { - divingGearObjective.TryComplete(deltaTime); - if (divingGearObjective.IsCompleted()) - { - divingGearObjective = null; - Priority = 0; - } - else if (divingGearObjective.CanBeCompleted) - { - // If diving gear objective is active and can be completed, wait for it to complete. - return; - } - else - { - divingGearObjective = null; - // Reduce the timer so that we get a safe hull target faster. - searchHullTimer = Math.Min(1, searchHullTimer); - } - } - - if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) - { - searchHullTimer = Math.Min(1, searchHullTimer); - } - if (unreachableClearTimer > 0) { unreachableClearTimer -= deltaTime; @@ -79,56 +44,86 @@ namespace Barotrauma unreachableClearTimer = clearUnreachableInterval; unreachable.Clear(); } - - if (searchHullTimer > 0.0f) - { - unreachableClearTimer = clearUnreachableInterval; - unreachable.Clear(); - } if (character.CurrentHull == null) { - var bestHull = FindBestHull(); - if (bestHull != null && bestHull != currentHull) - { - if (goToObjective != null) - { - if (goToObjective.Target != bestHull) - { - // If we need diving gear, we should already have it, if possible. - goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false) - { - AllowGoingOutside = HumanAIController.HasDivingSuit(character) - }; - } - } - else - { - goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false) - { - AllowGoingOutside = HumanAIController.HasDivingSuit(character) - }; - } - } - searchHullTimer = SearchHullInterval; + currenthullSafety = 0; + Priority = 100; + return; } if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; } currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull); if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD) { - goToObjective.TryComplete(deltaTime); - if (!goToObjective.CanBeCompleted) - { - if (!unreachable.Contains(goToObjective.Target)) - { - unreachable.Add(goToObjective.Target as Hull); - } - goToObjective = null; - HumanAIController.ObjectiveManager.GetObjective().Wander(deltaTime); - //SteeringManager.SteeringWander(); - } + Priority -= priorityDecrease * deltaTime; } - else if (currentHull != null) + else { + float dangerFactor = (100 - currenthullSafety) / 100; + Priority += dangerFactor * priorityIncrease * deltaTime; + } + Priority = MathHelper.Clamp(Priority, 0, 100); + if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted) + { + Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100)); + } + } + + protected override void Act(float deltaTime) + { + var currentHull = character.AnimController.CurrentHull; + bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull); + bool needsDivingSuit = needsDivingGear && (currentHull == null || currentHull.WaterPercentage > 90); + bool needsEquipment = false; + if (needsDivingSuit) + { + needsEquipment = !HumanAIController.HasDivingSuit(character); + } + else if (needsDivingGear) + { + needsEquipment = !HumanAIController.HasDivingMask(character); + } + if (needsEquipment) + { + TryAddSubObjective(ref divingGearObjective, + () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager), + onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer)); + } + else + { + if (divingGearObjective != null && divingGearObjective.IsCompleted()) + { + // Reset the devotion. + Priority = 0; + divingGearObjective = null; + } + if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) + { + searchHullTimer = Math.Min(1, searchHullTimer); + } + if (searchHullTimer > 0.0f) + { + searchHullTimer -= deltaTime; + } + else + { + searchHullTimer = SearchHullInterval; + var bestHull = FindBestHull(); + if (bestHull != null && bestHull != currentHull) + { + if (goToObjective?.Target != bestHull) + { + goToObjective = null; + } + TryAddSubObjective(ref goToObjective, () => + new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false) + { + // If we need diving gear, we should already have it, if possible. + AllowGoingOutside = HumanAIController.HasDivingSuit(character) + }, onAbandon: () => unreachable.Add(goToObjective.Target as Hull)); + } + } + if (goToObjective != null) { return; } + if (currentHull == null) { return; } //goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found) // -> attempt to manually steer away from hazards Vector2 escapeVel = Vector2.Zero; @@ -136,17 +131,15 @@ namespace Barotrauma { Vector2 dir = character.Position - fireSource.Position; float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); - escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); + escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } - foreach (Character enemy in Character.CharacterList) { //don't run from friendly NPCs if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; } //friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters) if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; } - - if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && + if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) { Vector2 dir = character.Position - enemy.Position; @@ -154,7 +147,6 @@ namespace Barotrauma escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier); } } - if (escapeVel != Vector2.Zero) { //only move if we haven't reached the edge of the room @@ -251,36 +243,5 @@ namespace Barotrauma } return bestHull; } - - public override bool IsDuplicate(AIObjective otherObjective) - { - return (otherObjective is AIObjectiveFindSafety); - } - - public override void Update(AIObjectiveManager objectiveManager, float deltaTime) - { - if (character.CurrentHull == null) - { - currenthullSafety = 0; - Priority = 5; - return; - } - if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; } - currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull); - if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD) - { - Priority -= priorityDecrease * deltaTime; - } - else - { - float dangerFactor = (100 - currenthullSafety) / 100; - Priority += dangerFactor * priorityIncrease * deltaTime; - } - Priority = MathHelper.Clamp(Priority, 0, 100); - if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted) - { - Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10); - } - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index a156d8f56..c7a8b3bc1 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -3,28 +3,24 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { class AIObjectiveFixLeak : AIObjective { public override string DebugTag => "fix leak"; - - public override bool KeepDivingGearOn => true; public override bool ForceRun => true; - private readonly Gap leak; + public Gap Leak { get; private set; } private AIObjectiveFindDivingGear findDivingGear; + private AIObjectiveGetItem getWeldingTool; + private AIObjectiveContainItem refuelObjective; private AIObjectiveGoTo gotoObjective; private AIObjectiveOperateItem operateObjective; - - public Gap Leak - { - get { return leak; } - } - public AIObjectiveFixLeak(Gap leak, Character character) : base (character, "") + public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier) { Leak = leak; } @@ -34,17 +30,16 @@ namespace Barotrauma return Leak.Open <= 0.0f || Leak.Removed; } - public override bool CanBeCompleted => !abandon && base.CanBeCompleted; - - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { - if (leak.Open == 0.0f) { return 0.0f; } - - float leakSize = (leak.IsHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f); - - float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition); - dist = Math.Max(dist / 100.0f, 1.0f); - return Math.Min(leakSize / dist, 40.0f); + if (Leak.Open == 0.0f) { return 0.0f; } + // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) + float dist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y) * 2.0f; + float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 10000, dist)); + float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak); + float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90); + float devotion = Math.Min(Priority, 10) / 100; + return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1)); } public override bool IsDuplicate(AIObjective otherObjective) @@ -55,59 +50,44 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (!leak.IsRoomToRoom) + if (!Leak.IsRoomToRoom) { - if (findDivingGear == null) + if (!HumanAIController.HasDivingSuit(character)) { - findDivingGear = new AIObjectiveFindDivingGear(character, true); - AddSubObjective(findDivingGear); - } - else if (!findDivingGear.CanBeCompleted) - { - abandon = true; + TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager)); return; } } - var weldingTool = character.Inventory.FindItemByTag("weldingtool"); - if (weldingTool == null) { - AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true)); + TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true)); return; } else { var containedItems = weldingTool.ContainedItems; - if (containedItems == null) return; - - var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f); - if (fuelTank == null) + if (containedItems == null) { - AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent())); +#if DEBUG + DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory"); +#endif + abandon = true; return; } - } - - var repairTool = weldingTool.GetComponent(); - if (repairTool == null) { return; } - - Vector2 gapDiff = leak.WorldPosition - character.WorldPosition; - - // TODO: use the collider size/reach? - if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) - { - HumanAIController.AnimController.Crouching = true; - } - - float reach = ConvertUnits.ToSimUnits(repairTool.Range); - bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach; - if (canReach) - { - Limb sightLimb = null; - if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.RightHand)) + // Drop empty tanks + foreach (Item containedItem in containedItems) { - sightLimb = character.AnimController.GetLimb(LimbType.RightHand); + if (containedItem == null) { continue; } + if (containedItem.Condition <= 0.0f) + { + containedItem.Drop(character); + } + } + if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f)) + { + TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent(), objectiveManager)); + return; } else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand)) { @@ -115,45 +95,31 @@ namespace Barotrauma } canReach = character.CanSeeTarget(leak, sightLimb); } + var repairTool = weldingTool.GetComponent(); + if (repairTool == null) + { +#if DEBUG + DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool"); +#endif + abandon = true; + return; + } + Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition; + // TODO: use the collider size/reach? + if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) + { + HumanAIController.AnimController.Crouching = true; + } + float reach = ConvertUnits.ToSimUnits(repairTool.Range); + bool canOperate = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach; + if (canOperate) + { + TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak)); + } else { - if (gotoObjective != null) - { - // Check if the objective is already removed -> completed/impossible - if (!subObjectives.Contains(gotoObjective)) - { - if (!gotoObjective.CanBeCompleted) - { - abandon = true; - } - gotoObjective = null; - return; - } - } - else - { - gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character) - { - CloseEnough = reach - }; - if (!subObjectives.Contains(gotoObjective)) - { - AddSubObjective(gotoObjective); - } - } + TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); } - if (gotoObjective == null || gotoObjective.IsCompleted()) - { - if (operateObjective == null) - { - operateObjective = new AIObjectiveOperateItem(repairTool, character, "", true, leak); - AddSubObjective(operateObjective); - } - else if (!subObjectives.Contains(operateObjective)) - { - operateObjective = null; - } - } } private Vector2 GetStandPosition() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index 3a6348b4e..9255e072d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -9,38 +9,28 @@ namespace Barotrauma class AIObjectiveFixLeaks : AIObjectiveLoop { public override string DebugTag => "fix leaks"; - public override bool KeepDivingGearOn => true; public override bool ForceRun => true; - public AIObjectiveFixLeaks(Character character) : base (character, "") { } - - protected override void FindTargets() - { - if (character.Submarine == null) { return 0; } - if (targets.None()) { return 0; } - if (objectiveManager.CurrentOrder == this) - { - return AIObjectiveManager.OrderPriority; - } - return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, targets.Average(t => Average(t))); - } + public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } protected override void FindTargets() { base.FindTargets(); - targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x))); + if (targets.None() && objectiveManager.CurrentOrder == this) + { + character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f); + } } - protected override bool Filter(Gap gap) + protected override bool Filter(Gap gap) => IsValidTarget(gap, character); + + public static float GetLeakSeverity(Gap leak) { - bool ignore = ignoreList.Contains(gap) || gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null); - if (!ignore) - { - if (gap.Submarine == null) { ignore = true; } - else if (gap.Submarine.TeamID != character.TeamID) { ignore = true; } - else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { ignore = true; } - } - return ignore; + if (leak == null) { return 0; } + float sizeFactor = MathHelper.Lerp(1, 10, MathUtils.InverseLerp(0, 200, (leak.IsHorizontal ? leak.Rect.Width : leak.Rect.Height))); + float severity = sizeFactor * leak.Open; + if (!leak.IsRoomToRoom) { severity *= 50; } + return MathHelper.Min(severity, 100); } public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks; @@ -48,9 +38,14 @@ namespace Barotrauma protected override IEnumerable GetList() => Gap.GapList; protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier); - public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks; - protected override float Average(Gap gap) => gap.Open; - protected override IEnumerable GetList() => Gap.GapList; - protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character); + public static bool IsValidTarget(Gap gap, Character character) + { + if (gap == null) { return false; } + if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; } + if (gap.Submarine == null) { return false; } + if (gap.Submarine.TeamID != character.TeamID) { return false; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { return false; } + return true; + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 6fc26a7c6..9e6b648e2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -11,6 +11,9 @@ namespace Barotrauma { public override string DebugTag => "get item"; + private readonly bool equip; + private readonly HashSet ignoredItems = new HashSet(); + public Func GetItemPriority; //can be either tags or identifiers @@ -20,14 +23,8 @@ namespace Barotrauma public string[] ignoredContainerIdentifiers; private AIObjectiveGoTo goToObjective; private float currItemPriority; - private bool equip; - private HashSet ignoredItems = new HashSet(); - - private bool canBeCompleted = true; - public override bool CanBeCompleted => canBeCompleted; - - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { if (objectiveManager.CurrentOrder == this) { @@ -36,16 +33,19 @@ namespace Barotrauma return 1.0f; } - public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "") + public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { currSearchIndex = -1; this.equip = equip; this.targetItem = targetItem; } - public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { } + public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1) + : this(character, new string[] { itemIdentifier }, objectiveManager, equip, priorityModifier) { } - public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "") + public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { currSearchIndex = -1; this.equip = equip; @@ -54,20 +54,15 @@ namespace Barotrauma { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); } - CheckInventory(); } private void CheckInventory() { - if (itemIdentifiers == null) - { - return; - } - + if (itemIdentifiers == null) { return; } for (int i = 0; i < character.Inventory.Items.Length; i++) { - if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue; + if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) { continue; } if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id))) { targetItem = character.Inventory.Items[i]; @@ -81,7 +76,7 @@ namespace Barotrauma { foreach (Item containedItem in containedItems) { - if (containedItem == null || containedItem.Condition <= 0.0f) continue; + if (containedItem == null || containedItem.Condition <= 0.0f) { continue; } if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id))) { targetItem = containedItem; @@ -99,13 +94,11 @@ namespace Barotrauma FindTargetItem(); if (targetItem == null || moveToTarget == null) { - HumanAIController.ObjectiveManager.GetObjective().Wander(deltaTime); - //SteeringManager.SteeringWander(); + objectiveManager.GetObjective()?.Wander(deltaTime); return; } - if (moveToTarget.CurrentHull == character.CurrentHull && - Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2)) + Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2)) { int targetSlot = -1; if (equip) @@ -123,8 +116,7 @@ namespace Barotrauma for (int i = 0; i < character.Inventory.Items.Length; i++) { //slot not needed by the item, continue - if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue; - + if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; } targetSlot = i; //slot free, continue if (character.Inventory.Items[i] == null) { continue; } @@ -136,7 +128,6 @@ namespace Barotrauma } } targetItem.TryInteract(character, false, true); - if (targetSlot > -1 && !character.HasEquippedItem(targetItem)) { character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character); @@ -144,23 +135,20 @@ namespace Barotrauma } else { - if (goToObjective == null || moveToTarget != goToObjective.Target) - { - //check if we're already looking for a diving gear - bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) || - (itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit"))); - - //don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear - goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear); - } - - goToObjective.TryComplete(deltaTime); - if (!goToObjective.CanBeCompleted) - { - targetItem = null; - moveToTarget = null; - ignoredItems.Add(targetItem); - } + TryAddSubObjective(ref goToObjective, + constructor: () => + { + //check if we're already looking for a diving gear + bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) || + (itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit"))); + return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear); + }, + onAbandon: () => + { + targetItem = null; + moveToTarget = null; + ignoredItems.Add(targetItem); + }); } } @@ -176,11 +164,10 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined."); #endif - canBeCompleted = false; + abandon = true; } return; } - for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++) { currSearchIndex++; @@ -189,15 +176,18 @@ namespace Barotrauma if (item.Submarine == null) { continue; } else if (item.Submarine.TeamID != character.TeamID) { continue; } else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } - if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; } if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; } - if (ignoredContainerIdentifiers != null && item.Container != null) { if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; } } + // TODO: need to exclude items that already are in the inventory? + //// Don't allow to get items in rooms that have a fire (except fire extinguishers) or an enemy inside (except weapons) + //if (!itemIdentifiers.Contains("extinguisher") && item.CurrentHull.FireSources.Count > 0 || + // item.GetComponent() == null && item.GetComponent() == null && Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { continue; } + //if the item is inside a character's inventory, don't steal it unless the character is dead if (item.ParentInventory is CharacterInventory) { @@ -219,11 +209,9 @@ namespace Barotrauma itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f; //ignore if the item has a lower priority than the currently selected one if (moveToTarget != null && itemPriority < currItemPriority) { continue; } - currItemPriority = itemPriority; targetItem = item; moveToTarget = rootContainer ?? item; - } //if searched through all the items and a target wasn't found, can't be completed if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null) @@ -231,21 +219,21 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}"); #endif - canBeCompleted = false; + abandon = true; } } public override bool IsDuplicate(AIObjective otherObjective) { AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem; - if (getItem == null) return false; - if (getItem.equip != equip) return false; + if (getItem == null) { return false; } + if (getItem.equip != equip) { return false; } if (getItem.itemIdentifiers != null && itemIdentifiers != null) { - if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false; + if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } for (int i = 0; i < getItem.itemIdentifiers.Length; i++) { - if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false; + if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; } } return true; } @@ -263,7 +251,10 @@ namespace Barotrauma foreach (string itemName in itemIdentifiers) { var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName); - if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true; + if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) + { + return true; + } } return false; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index aa1fbd519..10e18887e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -9,29 +9,23 @@ namespace Barotrauma { public override string DebugTag => "go to"; - private AIObjectiveFindDivingGear findDivingGear; - private AIObjectiveFindDivingGear findDivingGear; private Vector2 targetPos; private bool repeat; - private bool cannotReach; - //how long until the path to the target is declared unreachable private float waitUntilPathUnreachable; private bool getDivingGearIfNeeded; - public float CloseEnough = 0.5f; + public float CloseEnough { get; set; } = 0.5f; + public bool IgnoreIfTargetDead { get; set; } + public bool AllowGoingOutside { get; set; } + public bool CheckVisibility { get; set; } - public bool IgnoreIfTargetDead; - - public bool AllowGoingOutside = false; - - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; } if (Target != null && Target.Removed) { return 0.0f; } - if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; } - + if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; } if (objectiveManager.CurrentOrder == this) { return AIObjectiveManager.OrderPriority; @@ -39,44 +33,6 @@ namespace Barotrauma return 1.0f; } - public override bool CanBeCompleted - { - get - { - bool canComplete = !cannotReach && !abandon; - if (canComplete) - { - if (FollowControlledCharacter && Character.Controlled == null) - { - canComplete = false; - } - else if (Target != null && Target.Removed) - { - canComplete = false; - } - else if (!repeat && waitUntilPathUnreachable < 0) - { - if (SteeringManager == PathSteering && PathSteering.CurrentPath != null) - { - canComplete = !PathSteering.CurrentPath.Unreachable; - } - } - } - if (!canComplete) - { -#if DEBUG - DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow); -#endif - if (HumanAIController.ObjectiveManager.CurrentOrder != null) - { - character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f); - } - character.AIController.SteeringManager.Reset(); - } - return canComplete; - } - } - public Entity Target { get; private set; } public Vector2 TargetPos => Target != null ? Target.SimPosition : targetPos; @@ -89,7 +45,7 @@ namespace Barotrauma this.Target = target; this.repeat = repeat; - waitUntilPathUnreachable = 1.0f; + waitUntilPathUnreachable = 2.0f; this.getDivingGearIfNeeded = getDivingGearIfNeeded; CalculateCloseEnough(); } @@ -110,27 +66,36 @@ namespace Barotrauma { if (FollowControlledCharacter) { - if (Character.Controlled == null) { return; } + if (Character.Controlled == null) + { + abandon = true; + return; + } Target = Character.Controlled; } - if (Target == character) { character.AIController.SteeringManager.Reset(); + abandon = true; return; - } - + } waitUntilPathUnreachable -= deltaTime; - if (!character.IsClimbing) { character.SelectedConstruction = null; } - - if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); } - + if (Target != null) + { + if (Target.Removed) + { + abandon = true; + } + else + { + character.AIController.SelectTarget(Target.AiTarget); + } + } Vector2 currTargetPos = Vector2.Zero; - if (Target == null) { currTargetPos = targetPos; @@ -145,8 +110,12 @@ namespace Barotrauma currTargetPos -= character.Submarine.SimPosition; } } - - if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough) + bool sightCheck = true; + if (CheckVisibility && Target != null) + { + sightCheck = Target is Character ch ? character.CanSeeCharacter(ch) : character.CanSeeTarget(Target); + } + if (sightCheck && Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough) { character.AIController.SteeringManager.Reset(); character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; @@ -154,31 +123,50 @@ namespace Barotrauma else { bool isInside = character.CurrentHull != null; - bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null; + bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); if (isInside && targetIsOutside && !AllowGoingOutside) { - cannotReach = true; + abandon = true; + } + else if (!repeat && waitUntilPathUnreachable < 0) + { + if (SteeringManager == PathSteering && PathSteering.CurrentPath != null) + { + abandon = PathSteering.CurrentPath.Unreachable; + } + } + if (abandon) + { +#if DEBUG + DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow); +#endif + if (objectiveManager.CurrentOrder != null) + { + character.Speak(TextManager.Get("DialogCannotReach"), identifier: "cannotreach", minDurationBetweenSimilar: 10.0f); + } + character.AIController.SteeringManager.Reset(); } else { character.AIController.SteeringManager.SteeringSeek(currTargetPos); if (getDivingGearIfNeeded) { - if (targetIsOutside || - Target is Hull h && HumanAIController.NeedsDivingGear(h) || - Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || - Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) + var targetHull = Target is Hull h ? h : Target is Item i ? i.CurrentHull : Target is Character c ? c.CurrentHull : character.CurrentHull; + bool needsDivingGear = HumanAIController.NeedsDivingGear(targetHull); + bool needsDivingSuit = needsDivingGear && (targetIsOutside || targetHull.WaterPercentage > 90); + bool needsEquipment = false; + if (needsDivingSuit) { - if (findDivingGear == null) - { - findDivingGear = new AIObjectiveFindDivingGear(character, true); - AddSubObjective(findDivingGear); - } - else if (!findDivingGear.CanBeCompleted) - { - abandon = true; - } + needsEquipment = !HumanAIController.HasDivingSuit(character); + } + else if (needsDivingGear) + { + needsEquipment = !HumanAIController.HasDivingMask(character); + } + if (needsEquipment) + { + TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager)); } } } @@ -189,40 +177,30 @@ namespace Barotrauma { if (repeat) { return false; } bool completed = false; - if (Target is Item item) { - if (item.IsInsideTrigger(character.WorldPosition)) completed = true; + if (item.IsInsideTrigger(character.WorldPosition)) { completed = true; } } else if (Target is Character targetCharacter) { - if (character.CanInteractWith(targetCharacter)) completed = true; + if (character.CanInteractWith(targetCharacter)) { completed = true; } } - completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough; - - if (completed) character.AIController.SteeringManager.Reset(); - + if (completed) { character.AIController.SteeringManager.Reset(); } return completed; } public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo; - if (objective == null) return false; - - if (objective.Target == Target) return true; + if (!(otherObjective is AIObjectiveGoTo objective)) { return false; } + if (objective.Target == Target) { return true; } + return objective.targetPos == targetPos; + } private void CalculateCloseEnough() { float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0; CloseEnough = Math.Max(interactionDistance, CloseEnough); } - - private void CalculateCloseEnough() - { - float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance) : 0; - CloseEnough = Math.Max(interactionDistance, CloseEnough); - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 675960c62..eeef8131c 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -38,6 +38,18 @@ namespace Barotrauma public override bool IsCompleted() => false; public override bool CanBeCompleted => true; + public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); } + + private float randomTimer; + private float randomUpdateInterval = 5; + public float Random { get; private set; } + + public void SetRandom() + { + Random = Rand.Range(0.5f, 1.5f); + randomTimer = randomUpdateInterval; + } + public override float GetPriority() { float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100); @@ -71,7 +83,7 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (PathSteering == null) return; + if (PathSteering == null) { return; } //don't keep dragging others when idling if (character.SelectedCharacter != null) @@ -83,24 +95,10 @@ namespace Barotrauma character.SelectedConstruction = null; } - bool currentHullForbidden = IsForbidden(character.CurrentHull); - if (!currentHullForbidden && !character.AnimController.InWater && !character.IsClimbing && HumanAIController.ObjectiveManager.WaitTimer > 0) - { - SteeringManager.Reset(); - return; - } - if (!character.IsClimbing) - { - character.SelectedConstruction = null; - } - bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); - bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || - (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); - - if (currentTargetIsInvalid || (currentTarget == null && currentHullForbidden)) + if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull)) { newTargetTimer = 0; standStillTimer = 0; @@ -137,7 +135,6 @@ namespace Barotrauma { //choose a random available hull var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); - bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); if (isCurrentHullOK) { @@ -145,7 +142,8 @@ namespace Barotrauma // Only do this when the current hull is ok, because otherwise would block all paths from the current hull to the target hull. var path = PathSteering.PathFinder.FindPath(character.SimPosition, randomHull.SimPosition); if (path.Unreachable || - path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) + path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || + IsForbidden(n.CurrentHull))) { //can't go to this room, remove it from the list and try another room next frame int index = targetHulls.IndexOf(randomHull); @@ -268,7 +266,6 @@ namespace Barotrauma private void FindTargetHulls() { - bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); targetHulls.Clear(); hullWeights.Clear(); foreach (var hull in Hull.hullList) @@ -300,7 +297,7 @@ namespace Barotrauma } } - private bool IsForbidden(Hull hull) + public static bool IsForbidden(Hull hull) { if (hull == null) { return true; } string hullName = hull.RoomName?.ToLowerInvariant(); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs index 00c02f3bd..60e619d78 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -7,7 +7,7 @@ namespace Barotrauma { abstract class AIObjectiveLoop : AIObjective { - protected List targets = new List(); + protected HashSet targets = new HashSet(); protected Dictionary objectives = new Dictionary(); protected HashSet ignoreList = new HashSet(); private float ignoreListTimer; @@ -15,9 +15,25 @@ namespace Barotrauma // By default, doesn't clear the list automatically protected virtual float IgnoreListClearInterval => 0; - protected virtual float TargetUpdateInterval => 2; - public AIObjectiveLoop(Character character, string option) : base(character, option) + public HashSet ReportedTargets { get; private set; } = new HashSet(); + + public bool AddTarget(T target) + { + if (ReportedTargets.Contains(target)) + { + return false; + } + if (Filter(target)) + { + ReportedTargets.Add(target); + return true; + } + return false; + } + + public AIObjectiveLoop(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null) + : base(character, objectiveManager, priorityModifier, option) { Reset(); } @@ -26,9 +42,11 @@ namespace Barotrauma public override bool IsCompleted() => false; public override bool CanBeCompleted => true; - public override void Update(AIObjectiveManager objectiveManager, float deltaTime) + public override bool IsLoop { get => true; set => throw new System.Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); } + + public override void Update(float deltaTime) { - base.Update(objectiveManager, deltaTime); + base.Update(deltaTime); if (IgnoreListClearInterval > 0) { if (ignoreListTimer > IgnoreListClearInterval) @@ -40,14 +58,13 @@ namespace Barotrauma ignoreListTimer += deltaTime; } } - if (targetUpdateTimer >= TargetUpdateInterval) + if (targetUpdateTimer < 0) { - targetUpdateTimer = 0; UpdateTargets(); } else { - targetUpdateTimer += deltaTime; + targetUpdateTimer -= deltaTime; } // Sync objectives, subobjectives and targets foreach (var objective in objectives) @@ -56,7 +73,7 @@ namespace Barotrauma if (!objective.Value.CanBeCompleted) { ignoreList.Add(target); - targetUpdateTimer = TargetUpdateInterval; + targetUpdateTimer = 0; } if (!targets.Contains(target)) { @@ -70,6 +87,9 @@ namespace Barotrauma } } + // the timer is set between 1 and 10 seconds, depending on the priority + private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1); + public override void Reset() { ignoreList.Clear(); @@ -79,19 +99,34 @@ namespace Barotrauma public override void OnSelected() { - Reset(); + base.OnSelected(); + if (HumanAIController.ObjectiveManager.CurrentOrder == this) + { + Reset(); + } } - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { if (character.Submarine == null) { return 0; } if (targets.None()) { return 0; } - float avg = targets.Average(t => Average(t)); - return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100); + // Allow the target value to be more than 100. + float targetValue = TargetEvaluation(); + // If the target value is less than 1% of the max value, let's just treat it as zero. + if (targetValue < 1) { return 0; } + if (objectiveManager.CurrentOrder == this) + { + return AIObjectiveManager.OrderPriority; + } + float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90); + float devotion = MathHelper.Min(10, Priority); + float value = MathHelper.Clamp((devotion + targetValue * PriorityModifier) / 100, 0, 1); + return MathHelper.Lerp(0, max, value); } protected void UpdateTargets() { + SetTargetUpdateTimer(); targets.Clear(); FindTargets(); CreateObjectives(); @@ -99,12 +134,19 @@ namespace Barotrauma protected virtual void FindTargets() { - foreach (T item in GetList()) + foreach (T target in GetList()) { - if (Filter(item)) { continue; } - if (!targets.Contains(item)) + // The bots always find targets when the objective is an order. + if (objectiveManager.CurrentOrder != this) { - targets.Add(item); + // Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention. + bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater; + if (!ignore && !ReportedTargets.Contains(target)) { continue; } + } + if (!Filter(target)) { continue; } + if (!ignoreList.Contains(target)) + { + targets.Add(target); } } } @@ -116,6 +158,7 @@ namespace Barotrauma if (!objectives.TryGetValue(target, out AIObjective objective)) { objective = ObjectiveConstructor(target); + objective.Completed += () => ReportedTargets.Remove(target); objectives.Add(target, objective); AddSubObjective(objective); } @@ -126,7 +169,9 @@ namespace Barotrauma /// List of all possible items of the specified type. Used for filtering the removed objectives. /// protected abstract IEnumerable GetList(); - protected abstract float Average(T target); + + protected abstract float TargetEvaluation(); + protected abstract AIObjective ObjectiveConstructor(T target); protected abstract bool Filter(T target); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs index e9b96ae0e..2514e490e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -9,27 +9,29 @@ namespace Barotrauma class AIObjectiveManager { // TODO: expose - public const float OrderPriority = 50.0f; + public const float OrderPriority = 70; + public const float RunPriority = 50; // Constantly increases the priority of the selected objective, unless overridden public const float baseDevotion = 2; - public List Objectives { get; private set; } + public List Objectives { get; private set; } = new List(); private Character character; /// - /// When set above zero, the character will stand still doing nothing until the timer runs out. Only affects idling. + /// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders. /// public float WaitTimer; public AIObjective CurrentOrder { get; private set; } public AIObjective CurrentObjective { get; private set; } + public bool IsCurrentObjective() where T : AIObjective => CurrentObjective is T; + public AIObjectiveManager(Character character) { this.character = character; - - Objectives = new List(); + CreateAutonomousObjectives(); } public void AddObjective(AIObjective objective) @@ -46,6 +48,30 @@ namespace Barotrauma } public Dictionary DelayedObjectives { get; private set; } = new Dictionary(); + + public void CreateAutonomousObjectives() + { + Objectives.Clear(); + AddObjective(new AIObjectiveFindSafety(character, this), delay: Rand.Value() / 2); + AddObjective(new AIObjectiveIdle(character, this), delay: Rand.Value() / 2); + int objectiveCount = Objectives.Count; + foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == automaticOrder.aiTag); + if (orderPrefab == null) { throw new Exception("Could not find a matching prefab by ai tag: " + automaticOrder.aiTag); } + // TODO: Similar code is used in CrewManager:815-> DRY + var matchingItems = orderPrefab.ItemIdentifiers.Any() ? + Item.ItemList.FindAll(it => orderPrefab.ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(orderPrefab.ItemIdentifiers)) : + Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == orderPrefab.ItemComponentType)); + matchingItems.RemoveAll(it => it.Submarine != character.Submarine); + var item = matchingItems.GetRandom(); + var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType)); + AddObjective(CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier), delay: Rand.Value() / 2); + objectiveCount++; + } + WaitTimer = Math.Max(WaitTimer, Rand.Range(0.5f, 1f) * objectiveCount); + } + public void AddObjective(AIObjective objective, float delay, Action callback = null) { if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine)) @@ -75,7 +101,7 @@ namespace Barotrauma { var previousObjective = CurrentObjective; var firstObjective = Objectives.FirstOrDefault(); - if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this)) + if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority()) { CurrentObjective = CurrentOrder; } @@ -86,18 +112,24 @@ namespace Barotrauma if (previousObjective != CurrentObjective) { CurrentObjective?.OnSelected(); + GetObjective()?.SetRandom(); } return CurrentObjective; } public float GetCurrentPriority() { - return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(this); + return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(); } public void UpdateObjectives(float deltaTime) { - CurrentOrder?.Update(this, deltaTime); + CurrentOrder?.Update(deltaTime); + if (WaitTimer > 0) + { + WaitTimer -= deltaTime; + return; + } for (int i = 0; i < Objectives.Count; i++) { var objective = Objectives[i]; @@ -115,9 +147,9 @@ namespace Barotrauma #endif Objectives.Remove(objective); } - else + else if (objective != CurrentOrder) { - objective.Update(this, deltaTime); + objective.Update(deltaTime); } } GetCurrentObjective(); @@ -127,15 +159,41 @@ namespace Barotrauma { if (Objectives.Any()) { - Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); + Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority())); } - CurrentObjective?.SortSubObjectives(this); + CurrentObjective?.SortSubObjectives(); } public void DoCurrentObjective(float deltaTime) { - if (WaitTimer > 0.0f) { WaitTimer -= deltaTime; } - CurrentObjective?.TryComplete(deltaTime); + if (WaitTimer <= 0) + { + CurrentObjective?.TryComplete(deltaTime); + } + else + { + if (CurrentOrder != null) + { + CurrentOrder.TryComplete(deltaTime); + } + else + { + if (character.AIController is HumanAIController humanAI && humanAI.SteeringManager != null) + { + if (!character.AnimController.InWater && + !character.IsClimbing && + !humanAI.UnsafeHulls.Contains(character.CurrentHull) && + !AIObjectiveIdle.IsForbidden(character.CurrentHull)) + { + humanAI.SteeringManager.Reset(); + } + else + { + CurrentObjective?.TryComplete(deltaTime); + } + } + } + } } public void SetOrder(AIObjective objective) @@ -145,13 +203,22 @@ namespace Barotrauma public void SetOrder(Order order, string option, Character orderGiver) { - CurrentOrder = null; - if (order == null) return; + CurrentOrder = CreateObjective(order, option, orderGiver); + if (CurrentOrder == null) + { + // Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding) + CreateAutonomousObjectives(); + } + } + public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1) + { + if (order == null) { return null; } + AIObjective newObjective; switch (order.AITag.ToLowerInvariant()) { case "follow": - CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true) + newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier) { CloseEnough = 1.5f, AllowGoingOutside = true, @@ -160,38 +227,41 @@ namespace Barotrauma }; break; case "wait": - CurrentOrder = new AIObjectiveGoTo(character, character, true) + newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier) { AllowGoingOutside = true }; break; case "fixleaks": - CurrentOrder = new AIObjectiveFixLeaks(character); + newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier); break; case "chargebatteries": - CurrentOrder = new AIObjectiveChargeBatteries(character, option); + newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier); break; case "rescue": - CurrentOrder = new AIObjectiveRescueAll(character); + newObjective = new AIObjectiveRescueAll(character, this, priorityModifier); break; case "repairsystems": - CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" }; + newObjective = new AIObjectiveRepairItems(character, this, priorityModifier) { RequireAdequateSkills = option != "all" }; break; case "pumpwater": - CurrentOrder = new AIObjectivePumpWater(character, option); + newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier); break; case "extinguishfires": - CurrentOrder = new AIObjectiveExtinguishFires(character); + newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier); + break; + case "fightintruders": + newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier); break; case "steer": var steering = (order?.TargetEntity as Item)?.GetComponent(); if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; - if (order.TargetItemComponent == null) return; - CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); + if (order.TargetItemComponent == null) { return null; } + newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true }; break; default: - if (order.TargetItemComponent == null) return; - CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); + if (order.TargetItemComponent == null) { return null; } + newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true }; break; } return newObjective; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 2c68996e2..28e57da45 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -10,8 +10,6 @@ namespace Barotrauma { public override string DebugTag => "operate item"; - private ItemComponent component, controller; - private ItemComponent component, controller; private Entity operateTarget; private bool isCompleted; @@ -20,35 +18,25 @@ namespace Barotrauma private AIObjectiveGoTo goToObjective; private AIObjectiveGetItem getItemObjective; - private bool useController; - - private AIObjectiveGoTo gotoObjective; - - public override bool CanBeCompleted - { - get - { - if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false; - - if (useController && controller == null) return false; - - return canBeCompleted; - } - } + public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null); public Entity OperateTarget => operateTarget; public ItemComponent Component => component; - public ItemComponent Component => component; - - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { - if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } + if (component.Item.ConditionPercentage <= 0) { return 0; } if (objectiveManager.CurrentOrder == this) { return AIObjectiveManager.OrderPriority; } - return base.GetPriority(objectiveManager); + if (component.Item.CurrentHull == null) { return 0; } + if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; } + if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return 0; } + float devotion = MathHelper.Min(10, Priority); + float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier; + float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90); + return MathHelper.Clamp(value, 0, max); } public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1) @@ -58,34 +46,31 @@ namespace Barotrauma this.requireEquip = requireEquip; this.operateTarget = operateTarget; this.useController = useController; - if (useController) { - var controllers = component.Item.GetConnectedComponents(); - if (controllers.Any()) controller = controllers[0]; + //try finding the controller with the simpler non-recursive method first + controller = + component.Item.GetConnectedComponents().FirstOrDefault() ?? + component.Item.GetConnectedComponents(recursive: true).FirstOrDefault(); } - - canBeCompleted = true; } protected override void Act(float deltaTime) { ItemComponent target = useController ? controller : component; - if (useController && controller == null) { character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f); + abandon = true; return; } - - if (target.CanBeSelected) { bool inSameRoom = character.CurrentHull == target.Item.CurrentHull; bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2); if (inSameRoom && withinReach) { - if (character.CurrentHull == target.Item.CurrentHull) + if (character.SelectedConstruction != target.Item) { if (character.SelectedConstruction != target.Item && target.CanBeSelected) { @@ -97,19 +82,19 @@ namespace Barotrauma } return; } + if (component.AIOperate(deltaTime, character, this)) + { + isCompleted = true; + } + } + else + { + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager)); } - - AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character)); } else { if (component.Item.GetComponent() == null) - { - //controller/target can't be selected and the item cannot be picked -> objective can't be completed - canBeCompleted = false; - return; - } - else if (!character.Inventory.Items.Contains(component.Item)) { //controller/target can't be selected and the item cannot be picked -> objective can't be completed abandon = true; @@ -132,11 +117,9 @@ namespace Barotrauma #endif return; } - for (int i = 0; i < character.Inventory.Capacity; i++) { - if (character.Inventory.SlotTypes[i] == InvSlotType.Any || - !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i]))) + if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i]))) { continue; } @@ -170,10 +153,8 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem; - if (operateItem == null) return false; - - return (operateItem.component == component ||otherObjective.Option == Option); + if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; } + return (operateItem.component == component || otherObjective.Option == Option); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs index 5409a82d5..74d49f667 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs @@ -1,68 +1,74 @@ using Barotrauma.Items.Components; +using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Xna.Framework; +using Barotrauma.Extensions; namespace Barotrauma { class AIObjectivePumpWater : AIObjectiveLoop { public override string DebugTag => "pump water"; - public override bool KeepDivingGearOn => true; - private readonly IEnumerable pumpList; + private IEnumerable pumpList; - public AIObjectivePumpWater(Character character, string option) : base(character, option) - { - pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent()).Where(p => p != null); - } - - public override float GetPriority(AIObjectiveManager objectiveManager) - { - if (character.Submarine == null) { return 0; } - if (objectiveManager.CurrentOrder == this && targets.Count > 0) - { - return AIObjectiveManager.OrderPriority; - } - return 0.0f; - } + public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier, option) { } public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectivePumpWater && otherObjective.Option == Option; - //availablePumps = allPumps.Where(p => !p.Item.HasTag("ballast") && p.Item.Connections.None(c => c.IsPower && p.Item.GetConnectedComponentsRecursive(c).None())).ToList(); protected override void FindTargets() { - if (option == null) { return; } - foreach (Item item in Item.ItemList) + if (Option == null) { return; } + base.FindTargets(); + if (targets.None() && objectiveManager.CurrentOrder == this) { - if (item.HasTag("ballast")) { continue; } - if (item.Submarine == null) { continue; } - if (item.Submarine.TeamID != character.TeamID) { continue; } - if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } - var pump = item.GetComponent(); - if (pump != null) - { - if (!ignoreList.Contains(pump)) - { - if (option == "stoppumping") - { - if (!pump.IsActive || pump.FlowPercentage == 0.0f) { continue; } - } - else - { - if (!pump.Item.InWater) { continue; } - if (pump.IsActive && pump.FlowPercentage <= -90.0f) { continue; } - } - if (!targets.Contains(pump)) - { - targets.Add(pump); - } - } - } + character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f); } } - protected override bool Filter(Pump pump) => true; - protected override IEnumerable GetList() => pumpList; - protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, Option, false); - protected override float Average(Pump target) => 0; + protected override bool Filter(Pump pump) + { + if (pump == null) { return false; } + if (pump.Item.HasTag("ballast")) { return false; } + if (pump.Item.Submarine == null) { return false; } + if (pump.Item.CurrentHull == null) { return false; } + if (pump.Item.Submarine.TeamID != character.TeamID) { return false; } + if (pump.Item.ConditionPercentage <= 0) { return false; } + if (pump.Item.CurrentHull.FireSources.Count > 0) { return false; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { return false; } + if (Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; } + if (Option == "stoppumping") + { + if (!pump.IsActive || MathUtils.NearlyEqual(pump.FlowPercentage, 0)) { return false; } + } + else + { + if (!pump.Item.InWater) { return false; } + if (pump.IsActive && pump.FlowPercentage <= -99.9f) { return false; } + } + return true; + } + protected override IEnumerable GetList() + { + if (pumpList == null) + { + pumpList = character.Submarine.GetItems(true).Select(i => i.GetComponent()).Where(p => p != null); + } + return pumpList; + } + + protected override AIObjective ObjectiveConstructor(Pump pump) => new AIObjectiveOperateItem(pump, character, objectiveManager, Option, false) { IsLoop = false }; + protected override float TargetEvaluation() + { + if (Option == "stoppumping") + { + return targets.Max(t => MathHelper.Lerp(0, 100, Math.Abs(t.FlowPercentage / 100))); + } + else + { + return targets.Max(t => MathHelper.Lerp(100, 0, Math.Abs(-t.FlowPercentage / 100))); + } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs index a7d634505..b64af04a5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -11,37 +11,33 @@ namespace Barotrauma { public override string DebugTag => "repair item"; - public override bool KeepDivingGearOn => true; - public Item Item { get; private set; } private AIObjectiveGoTo goToObjective; - private float previousCondition = -1; + private RepairTool repairTool; - public AIObjectiveRepairItem(Character character, Item item) : base(character, "") + public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { Item = item; } - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { // TODO: priority list? - if (Item.Repairables.None()) { return 0; } // Ignore items that are being repaired by someone else. if (Item.Repairables.Any(r => r.CurrentFixer != null && r.CurrentFixer != character)) { return 0; } // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Item.WorldPosition.Y) * 2.0f; float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist)); - float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition); + float damagePriority = MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition); float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character))); float isSelected = character.SelectedConstruction == Item ? 50 : 0; - float baseLevel = Math.Max(Priority + isSelected, 1); - return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100); + float devotion = (Math.Min(Priority, 10) + isSelected) / 100; + float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90); + return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1)); } - public override bool CanBeCompleted => !abandon; - public override bool IsCompleted() { bool isCompleted = Item.IsFullCondition; @@ -59,15 +55,6 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (goToObjective != null && !subObjectives.Contains(goToObjective)) - { - if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted) - { - abandon = true; - character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f); - } - goToObjective = null; - } foreach (Repairable repairable in Item.Repairables) { if (!repairable.HasRequiredItems(character, false)) @@ -77,12 +64,14 @@ namespace Barotrauma { foreach (RelatedItem requiredItem in kvp.Value) { - AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true)); + AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true)); } } return; } } + // Only continue when the get item sub objectives have been completed. + if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; } if (repairTool == null) { FindRepairTool(); @@ -114,31 +103,31 @@ namespace Barotrauma { // If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it. abandon = true; - character?.Speak(TextManager.Get("DialogRepairFailed").Replace("[itemname]", Item.Name), null, 0.0f, "repairfailed", 10.0f); + character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f); } } repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character; break; } } - else if (goToObjective == null || goToObjective.Target != Item) + else { - previousCondition = -1; - if (goToObjective != null) - { - subObjectives.Remove(goToObjective); - } - goToObjective = new AIObjectiveGoTo(Item, character); - if (repairTool != null) - { - //goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f; - goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range); - } - AddSubObjective(goToObjective); + // If cannot reach the item, approach it. + TryAddSubObjective(ref goToObjective, + constructor: () => + { + previousCondition = -1; + var objective = new AIObjectiveGoTo(Item, character, objectiveManager); + if (repairTool != null) + { + objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range) * 0.75f; + } + return objective; + }, + onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f)); } } - private RepairTool repairTool; private void FindRepairTool() { foreach (Repairable repairable in Item.Repairables) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs index 2b2afd3f5..fe45dbfb9 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Barotrauma.Items.Components; using Barotrauma.Extensions; @@ -7,18 +8,25 @@ namespace Barotrauma class AIObjectiveRepairItems : AIObjectiveLoop { public override string DebugTag => "repair items"; - public override bool KeepDivingGearOn => true; /// /// Should the character only attempt to fix items they have the skills to fix, or any damaged item /// public bool RequireAdequateSkills; - public AIObjectiveRepairItems(Character character) : base(character, "") { } + public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } - // TODO: This can allow two active repair items objectives, if RequireAdequateSkills is not at the same value. We don't want that. public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills; + protected override void FindTargets() + { + base.FindTargets(); + if (targets.None() && objectiveManager.CurrentOrder == this) + { + character.Speak(TextManager.Get("DialogNoRepairTargets"), null, 3.0f, "norepairtargets", 30.0f); + } + } + protected override void CreateObjectives() { foreach (var item in targets) @@ -38,37 +46,35 @@ namespace Barotrauma protected override bool Filter(Item item) { - bool ignore = ignoreList.Contains(item) || item.IsFullCondition; - if (!ignore) + if (!IsValidTarget(item, character)) { return false; } + if (item.CurrentHull.FireSources.Count > 0) { return false; } + // Don't repair items in rooms that have enemies inside. + if (Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; } + if (!objectives.ContainsKey(item)) { - if (item.Submarine == null) { ignore = true; } - else if (item.Submarine.TeamID != character.TeamID) { ignore = true; } - else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { ignore = true; } - else - { - if (item.Repairables.None()) { ignore = true; } - else - { - foreach (Repairable repairable in item.Repairables) - { - if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold) - { - ignore = true; - } - else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character)) - { - ignore = true; - } - if (ignore) { break; } - } - } - } + if (item.Repairables.All(r => item.Condition > r.ShowRepairUIThreshold)) { return false; } } - return ignore; + if (RequireAdequateSkills) + { + if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; } + } + return true; } - protected override float Average(Item item) => 100 - item.ConditionPercentage; + protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage); protected override IEnumerable GetList() => Item.ItemList; - protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item); + protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier); + + public static bool IsValidTarget(Item item, Character character) + { + if (item == null) { return false; } + if (item.IsFullCondition) { return false; } + if (item.CurrentHull == null) { return false; } + if (item.Submarine == null) { return false; } + if (item.Submarine.TeamID != character.TeamID) { return false; } + if (item.Repairables.None()) { return false; } + if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; } + return true; + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index 8ea61cfe0..336fdc373 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -11,7 +11,6 @@ namespace Barotrauma { public override string DebugTag => "rescue"; public override bool ForceRun => true; - public override bool KeepDivingGearOn => true; const float TreatmentDelay = 0.5f; @@ -21,26 +20,9 @@ namespace Barotrauma private float treatmentTimer; - public override bool CanBeCompleted + public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { - get - { - if (targetCharacter.Removed || - targetCharacter.IsDead || - targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold) - { - return false; - } - if (goToObjective != null && !goToObjective.CanBeCompleted) { return false; } - - return true; - } - } - - public AIObjectiveRescue(Character character, Character targetCharacter) - : base(character, "") - { - Debug.Assert(character != targetCharacter); this.targetCharacter = targetCharacter; } @@ -96,68 +78,28 @@ namespace Barotrauma if (subObjectives.Any()) { return; } - protected override void Act(float deltaTime) - { - //target in water -> move to a dry place first - if (targetCharacter.AnimController.InWater) - { - if (character.SelectedCharacter != targetCharacter) - { - if (!character.CanInteractWith(targetCharacter)) - { - AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character)); - } - else - { - character.SelectCharacter(targetCharacter); - } - } - else - { - AddSubObjective(new AIObjectiveFindSafety(character)); - } - return; - } - - //target not in water -> we can start applying treatment if (!character.CanInteractWith(targetCharacter)) { - AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character)); + // Go to the target and select it + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(targetCharacter, character, objectiveManager)); } else { - if (character.SelectedCharacter == null) + // We can start applying treatment + if (character.SelectedCharacter != targetCharacter) { - character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget") + character.Speak(TextManager.Get("DialogFoundWoundedTarget") .Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.DisplayName), null, 1.0f, - "foundunconscioustarget" + targetCharacter.Name, 60.0f); - } + "foundwoundedtarget" + targetCharacter.Name, 60.0f); - character.SelectCharacter(targetCharacter); + character.SelectCharacter(targetCharacter); + } GiveTreatment(deltaTime); } } - protected override bool ShouldInterruptSubObjective(AIObjective subObjective) - { - if (subObjective is AIObjectiveFindSafety) - { - if (character.SelectedCharacter != targetCharacter) return true; - if (character.AnimController.InWater || targetCharacter.AnimController.InWater) return false; - - foreach (Limb limb in targetCharacter.AnimController.Limbs) - { - if (!Submarine.RectContains(targetCharacter.CurrentHull.WorldRect, limb.WorldPosition)) return false; - } - - return !character.AnimController.InWater && !targetCharacter.AnimController.InWater && - HumanAIController.GetHullSafety(character.CurrentHull, character) > HumanAIController.HULL_SAFETY_THRESHOLD; - } - - return false; - } - + // TODO: consider optimizing a bit private void GiveTreatment(float deltaTime) { if (treatmentTimer > 0.0f) @@ -174,7 +116,6 @@ namespace Barotrauma { return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth)); }); - //check if we already have a suitable treatment for any of the afflictions foreach (Affliction affliction in allAfflictions) { @@ -189,7 +130,6 @@ namespace Barotrauma } } } - //didn't have any suitable treatments available, try to find some medical items HashSet suitableItemIdentifiers = new HashSet(); foreach (Affliction affliction in allAfflictions) @@ -212,9 +152,8 @@ namespace Barotrauma itemNameList.Add(itemPrefab.Name); } //only list the first 4 items - if (itemNameList.Count >= 4) break; + if (itemNameList.Count >= 4) { break; } } - if (itemNameList.Count > 0) { string itemListStr = ""; @@ -226,35 +165,34 @@ namespace Barotrauma { itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last()); } - character?.Speak(TextManager.Get("DialogListRequiredTreatments") + character.Speak(TextManager.Get("DialogListRequiredTreatments") .Replace("[targetname]", targetCharacter.Name) .Replace("[treatmentlist]", itemListStr), null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f); } - character.DeselectCharacter(); - AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true)); + AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true)); } - character.AnimController.Anim = AnimController.Animation.CPR; } private void ApplyTreatment(Affliction affliction, Item item) { var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction); - bool remove = false; foreach (ItemComponent ic in item.Components) { - if (!ic.HasRequiredContainedItems(addMessage: false)) continue; + if (!ic.HasRequiredContainedItems(addMessage: false)) { continue; } #if CLIENT ic.PlaySound(ActionType.OnUse, character.WorldPosition, character); #endif ic.WasUsed = true; ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb); - if (ic.DeleteOnUse) remove = true; + if (ic.DeleteOnUse) + { + remove = true; + } } - if (remove) { Entity.Spawner?.AddToRemoveQueue(item); @@ -263,28 +201,35 @@ namespace Barotrauma public override bool IsCompleted() { - bool isCompleted = - targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold; - + bool isCompleted = targetCharacter.Vitality / targetCharacter.MaxVitality > AIObjectiveRescueAll.VitalityThreshold; if (isCompleted) { - character?.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name), + character.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name), null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f); } - - return isCompleted || targetCharacter.IsDead; + return isCompleted || targetCharacter.Removed || targetCharacter.IsDead; } - public override float GetPriority(AIObjectiveManager objectiveManager) + public override float GetPriority() { - if (targetCharacter.AnimController.CurrentHull == null || targetCharacter.IsDead) { return 0.0f; } - - Vector2 diff = targetCharacter.WorldPosition - character.WorldPosition; - float distance = Math.Abs(diff.X) + Math.Abs(diff.Y); - - float vitalityFactor = (targetCharacter.MaxVitality - targetCharacter.Vitality) / targetCharacter.MaxVitality; - - return 1000.0f * vitalityFactor / distance; + if (targetCharacter == null) { return 0; } + if (targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead) + { + abandon = true; + return 0; + } + // Don't go into rooms that have enemies + if (Character.CharacterList.Any(c => c.CurrentHull == targetCharacter.CurrentHull && !HumanAIController.IsFriendly(c))) + { + abandon = true; + return 0; + } + // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) + float dist = Math.Abs(character.WorldPosition.X - targetCharacter.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetCharacter.WorldPosition.Y) * 2.0f; + float distanceFactor = MathHelper.Lerp(1, 0.5f, MathUtils.InverseLerp(0, 10000, dist)); + float vitalityFactor = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter); + float devotion = Math.Min(Priority, 10) / 100; + return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + vitalityFactor * distanceFactor, 0, 1)); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index da0b2afc4..0eff6361b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -4,21 +4,18 @@ using Barotrauma.Extensions; namespace Barotrauma { - class AIObjectiveRescueAll : AIObjective + class AIObjectiveRescueAll : AIObjectiveLoop { public override string DebugTag => "rescue all"; + public override bool ForceRun => true; - public override bool KeepDivingGearOn => true; - - //only treat characters whose vitality is below this (0.8 = 80% of max vitality) - public const float VitalityThreshold = 0.8f; - - private List rescueTargets; + //only treat characters whose vitality is below this (0.9 = 90% of max vitality) + public const float VitalityThreshold = 0.9f; - public AIObjectiveRescueAll(Character character) : base (character, "") - { - rescueTargets = new List(); - } + public AIObjectiveRescueAll(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) + : base(character, objectiveManager, priorityModifier) { } + + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveRescueAll; protected override void FindTargets() { @@ -29,31 +26,13 @@ namespace Barotrauma } } - public override float GetPriority(AIObjectiveManager objectiveManager) - { - if (character.Submarine == null) { return 0; } - GetRescueTargets(); - if (!rescueTargets.Any()) { return 0.0f; } - - if (objectiveManager.CurrentOrder == this) - { - return AIObjectiveManager.OrderPriority; - } + protected override bool Filter(Character target) => IsValidTarget(target, character); - //if there are targets to rescue, the priority is slightly less - //than the priority of explicit orders given to the character - return AIObjectiveManager.OrderPriority - 5.0f; - } + protected override IEnumerable GetList() => Character.CharacterList; - private void GetRescueTargets() - { - rescueTargets = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - c.TeamID == character.TeamID && - c != character && - !c.IsDead && - c.Vitality / c.MaxVitality < VitalityThreshold); - } + protected override AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier); + + protected override float TargetEvaluation() => targets.Max(t => GetVitalityFactor(t)) * 100; public static float GetVitalityFactor(Character character) => (character.MaxVitality - character.Vitality) / character.MaxVitality; @@ -69,8 +48,5 @@ namespace Barotrauma if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; } return true; } - - public override bool IsCompleted() => false; - public override bool CanBeCompleted => true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Order.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Order.cs index dda8d24cb..b7986dcff 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Order.cs @@ -64,8 +64,7 @@ namespace Barotrauma private Order(XElement orderElement) { AITag = orderElement.GetAttributeString("aitag", ""); - Name = TextManager.Get("OrderName." + AITag, true) ?? orderElement.GetAttributeString("name", "Name not found"); - DoingText = TextManager.Get("OrderNameDoing." + AITag, true) ?? orderElement.GetAttributeString("doingtext", ""); + Name = TextManager.Get("OrderName." + AITag, true) ?? "Name not found"; string targetItemType = orderElement.GetAttributeString("targetitemtype", ""); if (!string.IsNullOrWhiteSpace(targetItemType)) @@ -127,7 +126,6 @@ namespace Barotrauma Name = prefab.Name; AITag = prefab.AITag; - DoingText = prefab.DoingText; ItemComponentType = prefab.ItemComponentType; Options = prefab.Options; SymbolSprite = prefab.SymbolSprite; @@ -142,8 +140,10 @@ namespace Barotrauma { if (UseController) { - var controllers = targetItem.Item.GetConnectedComponents(); - if (controllers.Count > 0) ConnectedController = controllers[0]; + //try finding the controller with the simpler non-recursive method first + ConnectedController = + targetItem.Item.GetConnectedComponents().FirstOrDefault() ?? + targetItem.Item.GetConnectedComponents(recursive: true).FirstOrDefault(); } TargetEntity = targetItem.Item; TargetItemComponent = targetItem; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs index 4c1046dcb..a014a6137 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs @@ -235,7 +235,7 @@ namespace Barotrauma //} var body = Submarine.PickBody(end, node.Waypoint.SimPosition, null, - Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform); + Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs ); if (body != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs index d7329dc9d..7261ab890 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs @@ -24,7 +24,10 @@ namespace Barotrauma { public static List List; - public List Skills; + public readonly XElement Items; + public readonly List ItemNames = new List(); + public readonly List Skills = new List(); + public readonly List AutomaticOrders = new List(); [Serialize("1,1,1,1", false)] public Color UIColor @@ -118,14 +121,6 @@ namespace Barotrauma public XElement ClothingElement { get; private set; } - public JobPrefab(XElement element) - { - SerializableProperty.DeserializeProperties(this, element); - Name = TextManager.Get("JobName." + Identifier); - Description = TextManager.Get("JobDescription." + Identifier); - - public XElement ClothingElement { get; private set; } - public JobPrefab(XElement element) { SerializableProperty.DeserializeProperties(this, element); diff --git a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs index cdc0b10cb..f43235f81 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSession/GameModes/CampaignMode.cs @@ -173,8 +173,8 @@ namespace Barotrauma var spawnedCharacter = Character.Create(characterInfo, watchmanSpawnpoint.WorldPosition, Level.Loaded.Seed + (outpost == Level.Loaded.StartOutpost ? "start" : "end")); InitializeWatchman(spawnedCharacter); - (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder( - new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false)); + var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager; + objectiveManager?.SetOrder(new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false)); if (watchmanJob != null) { spawnedCharacter.GiveJobItems(); diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs index c501ba206..469a636b6 100644 --- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs @@ -279,6 +279,8 @@ namespace Barotrauma } public static bool ShowUserStatisticsPrompt { get; set; } + public bool ShowLanguageSelectionPrompt { get; set; } + public GameSettings() { SelectedContentPackages = new HashSet(); @@ -764,6 +766,7 @@ namespace Barotrauma CheckBindings(!fileFound); if (!fileFound) { + ShowLanguageSelectionPrompt = true; SaveNewPlayerConfig(); } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 73b362727..6f3068515 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -26,6 +26,8 @@ namespace Barotrauma.Items.Components private bool autoOrientGap; private bool isStuck; + public bool IsStuck => isStuck; + private float resetPredictionTimer; private Rectangle doorRect; @@ -227,8 +229,7 @@ namespace Barotrauma.Items.Components { msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText); } - if (isBroken) { return true; } - return base.HasRequiredItems(character, addMessage, msg); + return isBroken || base.HasRequiredItems(character, addMessage, msg); } public override bool Pick(Character picker) diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs index 188d307c5..b24f8bf79 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs @@ -281,7 +281,7 @@ namespace Barotrauma.Items.Components float dist = fromItemToLeak.Length(); //too far away -> consider this done and hope the AI is smart enough to move closer - if (dist > Range * 5.0f) return true; + if (dist > Range * 3.0f) { return true; } // TODO: use the collider size? if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController && @@ -344,7 +344,19 @@ namespace Barotrauma.Items.Components character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist); if (item.RequireAimToUse) { - character.SetInput(InputType.Aim, false, true); + bool isOperatingButtons = false; + if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering) + { + var door = indoorSteering.CurrentPath?.CurrentNode?.ConnectedDoor; + if (door != null && !door.IsOpen) + { + isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents(true).Any(); + } + } + if (!isOperatingButtons) + { + character.SetInput(InputType.Aim, false, true); + } } // Press the trigger only when the tool is approximately facing the target. var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak); @@ -400,7 +412,8 @@ namespace Barotrauma.Items.Components object value = property.GetValue(target); if (value.GetType() == typeof(float)) { - user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White); + var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White); + if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); } } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs index 2509f5d2f..ba73f2cf5 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Controller.cs @@ -43,6 +43,11 @@ namespace Barotrauma.Items.Components set { userPos = value; } } + public Character User + { + get { return user; } + } + public Controller(Item item, XElement element) : base(item, element) { @@ -163,7 +168,7 @@ namespace Barotrauma.Items.Components return false; } - item.SendSignal(0, "1", "trigger_out", character); + item.SendSignal(0, "1", "trigger_out", user); ApplyStatusEffects(ActionType.OnUse, 1.0f, activator); @@ -233,7 +238,7 @@ namespace Barotrauma.Items.Components private Item GetFocusTarget() { - item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", character); + item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", user); for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index c6646effc..c4ea8f2e2 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -962,6 +962,25 @@ namespace Barotrauma.Items.Components } } + if (targetItem.Prefab.DeconstructItems.Any()) + { + inputContainer.Inventory.RemoveItem(targetItem); + Entity.Spawner.AddToRemoveQueue(targetItem); + MoveInputQueue(); + PutItemsToLinkedContainer(); + } + else + { + if (outputContainer.Inventory.Items.All(i => i != null)) + { + targetItem.Drop(dropper: null); + } + else + { + outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true); + } + } + if (targetItem.Prefab.DeconstructItems.Any()) { inputContainer.Inventory.RemoveItem(targetItem); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs index ee38171f6..427f81c40 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs @@ -499,7 +499,7 @@ namespace Barotrauma.Items.Components //load more fuel if the current maximum output is only 50% of the current load if (NeedMoreFuel(minimumOutputRatio: 0.5f)) { - var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent()) + var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent(), objective.objectiveManager) { MinContainedAmount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1, GetItemPriority = (Item fuelItem) => diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs index 2aafee648..a251165ce 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -212,6 +212,33 @@ namespace Barotrauma.Items.Components } } + public Vector2? PosToMaintain + { + get { return posToMaintain; } + set { posToMaintain = value; } + } + + struct ObstacleDebugInfo + { + public Vector2 Point1; + public Vector2 Point2; + + public Vector2? Intersection; + + public float Dot; + + public Vector2 AvoidStrength; + + public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength) + { + Point1 = edge.Point1; + Point2 = edge.Point2; + Intersection = intersection; + Dot = dot; + AvoidStrength = avoidStrength; + } + } + //edge point 1, edge point 2, avoid strength private List debugDrawObstacles = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs index 9e116b9b6..f411cf341 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Power/PowerContainer.cs @@ -235,12 +235,12 @@ namespace Barotrauma.Items.Components if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge") { - if (Math.Abs(rechargeSpeed - maxRechargeSpeed * 0.5f) > 0.05f) + if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f) { #if SERVER item.CreateServerEvent(this); #endif - RechargeSpeed = maxRechargeSpeed * 0.5f; + RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio; #if CLIENT rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); #endif diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs index 09d4e740a..9015e1159 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Signal/LightComponent.cs @@ -152,7 +152,8 @@ namespace Barotrauma.Items.Components ParentSub = item.CurrentHull?.Submarine, Position = item.Position, CastShadows = castShadows, - IsBackground = drawBehindSubs + IsBackground = drawBehindSubs, + SpriteScale = Vector2.One * item.Scale }; #endif diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index a91f7103f..9019d488a 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs @@ -415,7 +415,7 @@ namespace Barotrauma.Items.Components if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f) { - objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false)); + objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, objective.objectiveManager, option: "", requireEquip: false)); return false; } } @@ -454,11 +454,11 @@ namespace Barotrauma.Items.Components if (container.Inventory.Items[0] != null && container.Inventory.Items[0].Condition <= 0.0f) { - var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container); + var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container, objective.objectiveManager); objective.AddSubObjective(removeShellObjective); } - var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container); + var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container, objective.objectiveManager); character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f); containShellObjective.MinContainedAmount = usableProjectileCount + 1; containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier }; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index cc62e0d8a..8310a0e66 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1828,6 +1828,10 @@ namespace Barotrauma { ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); } + if (!broken) + { + ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); + } ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; } @@ -1964,6 +1968,9 @@ namespace Barotrauma } } + /// + /// Note: This function generates garbage and might be a bit too heavy to be used once per frame. + /// public List GetConnectedComponents(bool recursive = false) where T : ItemComponent { List connectedComponents = new List(); @@ -1972,7 +1979,6 @@ namespace Barotrauma { HashSet alreadySearched = new HashSet(); GetConnectedComponentsRecursive(alreadySearched, connectedComponents); - return connectedComponents; } @@ -2001,27 +2007,13 @@ namespace Barotrauma { if (alreadySearched.Contains(c)) { continue; } alreadySearched.Add(c); - - var recipients = c.Recipients; - foreach (Connection recipient in recipients) - { - if (alreadySearched.Contains(recipient)) { continue; } - var component = recipient.Item.GetComponent(); - if (component != null) - { - var receiverConnections = wifiReceiver.Item.Connections; - if (receiverConnections == null) { continue; } - foreach (Connection wifiOutput in receiverConnections) - { - if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; } - GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents); - } - } - recipient.Item.GetConnectedComponentsRecursive(alreadySearched, connectedComponents); - } + GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents); } } + /// + /// Note: This function generates garbage and might be a bit too heavy to be used once per frame. + /// public List GetConnectedComponentsRecursive(Connection c) where T : ItemComponent { List connectedComponents = new List(); @@ -2051,13 +2043,28 @@ namespace Barotrauma foreach (Connection recipient in recipients) { if (alreadySearched.Contains(recipient)) { continue; } - var component = recipient.Item.GetComponent(); if (component != null) { connectedComponents.Add(component); } + //connected to a wifi component -> see which other wifi components it can communicate with + var wifiComponent = recipient.Item.GetComponent(); + if (wifiComponent != null && wifiComponent.CanTransmit()) + { + foreach (var wifiReceiver in wifiComponent.GetReceiversInRange()) + { + var receiverConnections = wifiReceiver.Item.Connections; + if (receiverConnections == null) { continue; } + foreach (Connection wifiOutput in receiverConnections) + { + if ((wifiOutput.IsOutput == recipient.IsOutput) || alreadySearched.Contains(wifiOutput)) { continue; } + GetConnectedComponentsRecursive(wifiOutput, alreadySearched, connectedComponents); + } + } + } + recipient.Item.GetConnectedComponentsRecursive(recipient, alreadySearched, connectedComponents); } @@ -2360,29 +2367,6 @@ namespace Barotrauma if (remove) { Spawner?.AddToRemoveQueue(this); } } - List texts = new List(); - public List GetHUDTexts(Character character) - { - texts.Clear(); - foreach (ItemComponent ic in components) - { - if (string.IsNullOrEmpty(ic.DisplayMsg)) continue; - if (!ic.CanBePicked && !ic.CanBeSelected) continue; - if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue; - - Color color = Color.Gray; - bool hasRequiredSkillsAndItems = ic.HasRequiredSkills(character) && ic.HasRequiredItems(character, false); - if (hasRequiredSkillsAndItems) - { - color = Color.Cyan; - } - - texts.Add(new ColoredText(ic.DisplayMsg, color, false)); - } - - if (remove) { Spawner?.AddToRemoveQueue(this); } - } - public bool Combine(Item item) { bool isCombined = false; diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index 5e2c1f95f..8b0e7b269 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -493,6 +493,25 @@ namespace Barotrauma } } + public string DisplayName + { + get; + private set; + } + + private string roomName; + [Editable, Serialize("", true, translationTextTag: "RoomName.")] + public string RoomName + { + get { return roomName; } + set + { + if (roomName == value) { return; } + roomName = value; + DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName; + } + } + public override Rectangle Rect { get diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs index 3874415b6..723dcdd31 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObject.cs @@ -20,6 +20,8 @@ namespace Barotrauma public float Rotation; + private int spriteIndex; + public LevelObjectPrefab ActivePrefab; public PhysicsBody PhysicsBody @@ -40,6 +42,15 @@ namespace Barotrauma set { Triggers.ForEach(t => t.NeedsNetworkSyncing = false); } } + public Sprite Sprite + { + get { return spriteIndex < 0 || Prefab.Sprites.Count == 0 ? null : Prefab.Sprites[spriteIndex % Prefab.Sprites.Count]; } + } + public Sprite SpecularSprite + { + get { return spriteIndex < 0 || Prefab.SpecularSprites.Count == 0 ? null : Prefab.SpecularSprites[spriteIndex % Prefab.SpecularSprites.Count]; } + } + public LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation = 0.0f) { Triggers = new List(); @@ -49,6 +60,8 @@ namespace Barotrauma Scale = scale; Rotation = rotation; + spriteIndex = ActivePrefab.Sprites.Any() ? Rand.Int(ActivePrefab.Sprites.Count, Rand.RandSync.Server) : -1; + if (prefab.PhysicsBodyElement != null) { PhysicsBody = new PhysicsBody(prefab.PhysicsBodyElement, ConvertUnits.ToSimUnits(new Vector2(position.X, position.Y)), Scale); diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs index b75965a40..19f6b09e5 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectManager.cs @@ -166,7 +166,7 @@ namespace Barotrauma Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero }; - Sprite sprite = newObject.Prefab.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite; + Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite; //calculate the positions of the corners of the rotated sprite if (sprite != null) diff --git a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectPrefab.cs b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectPrefab.cs index 34d0f2991..04eda0a0a 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Levels/LevelObjects/LevelObjectPrefab.cs @@ -43,18 +43,18 @@ namespace Barotrauma SeaFloor = 4, MainPath = 8 } - - public Sprite Sprite - { - get; - private set; - } - public Sprite SpecularSprite + public List Sprites { get; private set; - } + } = new List(); + + public List SpecularSprites + { + get; + private set; + } = new List(); public DeformableSprite DeformableSprite { @@ -319,7 +319,7 @@ namespace Barotrauma //use the maximum width of the sprite as the minimum surface width if no value is given if (element != null && !element.Attributes("minsurfacewidth").Any()) { - if (Sprite != null) MinSurfaceWidth = Sprite.size.X * MaxSize; + if (Sprites.Any()) MinSurfaceWidth = Sprites[0].size.X * MaxSize; if (DeformableSprite != null) MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize); } } @@ -331,10 +331,10 @@ namespace Barotrauma switch (subElement.Name.ToString().ToLowerInvariant()) { case "sprite": - Sprite = new Sprite(subElement, lazyLoad: true); + Sprites.Add( new Sprite(subElement, lazyLoad: true)); break; case "specularsprite": - SpecularSprite = new Sprite(subElement, lazyLoad: true); + SpecularSprites.Add(new Sprite(subElement, lazyLoad: true)); break; case "deformablesprite": DeformableSprite = new DeformableSprite(subElement, lazyLoad: true); @@ -358,9 +358,9 @@ namespace Barotrauma case "overrideproperties": var propertyOverride = new LevelObjectPrefab(subElement); OverrideProperties[OverrideProperties.Count - 1] = propertyOverride; - if (propertyOverride.Sprite == null && propertyOverride.DeformableSprite == null) + if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null) { - propertyOverride.Sprite = Sprite; + propertyOverride.Sprites = Sprites; propertyOverride.DeformableSprite = DeformableSprite; } break; diff --git a/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs b/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs index 25400d5f1..f27815971 100644 --- a/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs +++ b/Barotrauma/BarotraumaShared/Source/Utils/SaveUtil.cs @@ -393,7 +393,9 @@ namespace Barotrauma { using (FileStream inFile = new FileStream(sCompressedFile, FileMode.Open, FileAccess.Read, FileShare.None)) using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true)) - while (DecompressFile(sDir, zipStream, progress)) ; + while (DecompressFile(sDir, zipStream, progress)) { }; + + break; } catch (IOException e) { diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index 1faf47a55..f3c656388 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub index 751bb85a9..77b32bf89 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index b11988da2..8dd2e2953 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ