diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems index e2df0a544..991ce2d39 100644 --- a/Barotrauma/BarotraumaClient/ClientCode.projitems +++ b/Barotrauma/BarotraumaClient/ClientCode.projitems @@ -188,6 +188,7 @@ + diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/EnemyAIController.cs index a44a18f66..b20e0e7c8 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 - 10, -currentNode.DrawPosition.Y - 30), + new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), Color.Red); } } diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs index 4204f8957..28686501e 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs @@ -1,6 +1,5 @@ using Microsoft.Xna.Framework; using FarseerPhysics; -using System.Linq; namespace Barotrauma { @@ -38,20 +37,16 @@ namespace Barotrauma var currentOrder = ObjectiveManager.CurrentOrder; if (currentOrder != null) { - 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); + GUI.DrawString(spriteBatch, pos + textOffset, $"ORDER: {currentOrder.DebugTag} ({currentOrder.GetPriority(ObjectiveManager).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().FormatZeroDecimal()})", Color.White, Color.Black); - var subObjective = currentObjective.SubObjectives.FirstOrDefault(); + GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); + var subObjective = currentObjective.CurrentSubObjective; if (subObjective != null) { - GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority().FormatZeroDecimal()})", Color.White, Color.Black); + GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.GetPriority(ObjectiveManager).FormatZeroDecimal()})", Color.White, Color.Black); } } } @@ -80,8 +75,8 @@ namespace Barotrauma GUI.SmallFont.DrawString(spriteBatch, currentNode.ID.ToString(), - new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30), - Color.Blue); + new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), + Color.SkyBlue); } } } 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/CharacterInfo.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs index e01a3dd2b..473dd6774 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterInfo.cs @@ -50,7 +50,7 @@ namespace Barotrauma Job.Name, textColor: Job.Prefab.UIColor, font: font); } - if (personalityTrait != null) + if (personalityTrait != null && TextManager.Language == "English") { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), TextManager.Get("PersonalityTrait") + ": " + personalityTrait.Name, font: font); diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaClient/Source/Characters/Jobs/JobPrefab.cs index 1ef7b8cc5..4fc63b3cb 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Jobs/JobPrefab.cs @@ -20,7 +20,7 @@ namespace Barotrauma var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) }); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform), - TextManager.Get("Skills") + ": ", font: GUI.LargeFont); + TextManager.Get("Skills"), font: GUI.LargeFont); foreach (SkillPrefab skill in Skills) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform), @@ -30,7 +30,7 @@ namespace Barotrauma var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) }); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform), - TextManager.Get("Items") + ": ", font: GUI.LargeFont); + TextManager.Get("Items", fallBackTag: "mapentitycategory.equipment"), font: GUI.LargeFont); foreach (string itemName in ItemNames) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform), diff --git a/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs b/Barotrauma/BarotraumaClient/Source/Fonts/ScalableFont.cs index faea7dee8..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; @@ -249,15 +249,7 @@ namespace Barotrauma { throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions"); } - - //no more room in current texture atlas, create a new one - if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1) - { - currentDynamicAtlasCoords.X = 0; - currentDynamicAtlasCoords.Y = 0; - textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); - } - + currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2); if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1) { @@ -270,6 +262,7 @@ namespace Barotrauma { currentDynamicAtlasCoords.X = 0; currentDynamicAtlasCoords.Y = 0; + currentDynamicAtlasNextY = 0; textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); } @@ -317,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) @@ -351,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) @@ -383,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; @@ -396,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/GUIComponent.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs index e49bb3810..41b769390 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIComponent.cs @@ -166,9 +166,7 @@ namespace Barotrauma get { return enabled; } set { enabled = value; } } - - public bool TileSprites; - + private static GUITextBlock toolTipBlock; public Vector2 Center diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs index f54b8d28f..2eddac84f 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUIStyle.cs @@ -12,6 +12,10 @@ namespace Barotrauma private XElement configElement; + private GraphicsDevice graphicsDevice; + + private ScalableFont defaultFont; + public ScalableFont Font { get; private set; } public ScalableFont SmallFont { get; private set; } public ScalableFont LargeFont { get; private set; } @@ -83,6 +87,26 @@ namespace Barotrauma } } + /// + /// Returns the default font of the currently selected language + /// + public ScalableFont LoadCurrentDefaultFont() + { + defaultFont?.Dispose(); + defaultFont = null; + foreach (XElement subElement in configElement.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "font": + defaultFont = LoadFont(subElement, graphicsDevice); + break; + } + } + return defaultFont; + } + + private void RescaleFonts() { foreach (XElement subElement in configElement.Elements()) @@ -113,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) @@ -146,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 260e45a1d..e8e7f7d0a 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/LoadingScreen.cs @@ -5,12 +5,13 @@ using System; using System.Collections.Generic; using System.Xml.Linq; using Barotrauma.Media; +using System.Linq; namespace Barotrauma { class LoadingScreen { - private Texture2D backgroundTexture, monsterTexture, titleTexture; + private Texture2D backgroundTexture; private RenderTarget2D renderTarget; @@ -43,18 +44,7 @@ namespace Barotrauma private object loadMutex = new object(); private float? loadState; - - public Vector2 TitleSize - { - get { return new Vector2(titleTexture.Width, titleTexture.Height); } - } - - public float Scale - { - get; - private set; - } - + public float? LoadState { get @@ -103,7 +93,7 @@ namespace Barotrauma try { DrawSplashScreen(spriteBatch); - if (SplashScreen!=null && SplashScreen.IsPlaying) return; + if (SplashScreen != null && SplashScreen.IsPlaying) return; } catch (Exception e) { @@ -111,20 +101,27 @@ namespace Barotrauma GameMain.Config.EnableSplashScreen = false; } } - + + var titleStyle = GUI.Style?.GetComponentStyle("TitleText"); + Sprite titleSprite = null; + if (titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None)) + { + titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite; + } + drawn = true; graphics.SetRenderTarget(renderTarget); - Scale = GameMain.GraphicsHeight / 1500.0f; + float backgroundScale = GameMain.GraphicsHeight / 1500.0f; + float titleScale = MathHelper.SmoothStep(0.8f, 1.0f, state / 10.0f) * GameMain.GraphicsHeight / 1000.0f; state += deltaTime; if (DrawLoadingText) { - CenterPosition = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight / 2.0f); - TitlePosition = CenterPosition + new Vector2(-0.0f + (float)Math.Sqrt(state) * 220.0f, 0.0f) * Scale; - TitlePosition.X = Math.Min(TitlePosition.X, (float)GameMain.GraphicsWidth / 2.0f); + BackgroundPosition = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight * 0.45f); + TitlePosition = new Vector2(GameMain.GraphicsWidth * 0.5f, GameMain.GraphicsHeight * 0.45f); } spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); @@ -132,16 +129,10 @@ namespace Barotrauma spriteBatch.Draw(backgroundTexture, BackgroundPosition, null, Color.White * Math.Min(state / 5.0f, 1.0f), 0.0f, new Vector2(backgroundTexture.Width / 2.0f, backgroundTexture.Height / 2.0f), - Scale * 1.5f, SpriteEffects.None, 0.2f); - - spriteBatch.Draw(monsterTexture, - CenterPosition + new Vector2((state % 40) * 100.0f - 1800.0f, (state % 40) * 30.0f - 200.0f) * Scale, null, - Color.White, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0.1f); - - spriteBatch.Draw(titleTexture, - TitlePosition, null, - Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f); - + backgroundScale * 1.5f, SpriteEffects.None, 0.2f); + + titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); + spriteBatch.End(); graphics.SetRenderTarget(null); @@ -154,9 +145,7 @@ namespace Barotrauma spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); - spriteBatch.Draw(titleTexture, - TitlePosition, null, - Color.White * Math.Min((state - 3.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f); + titleSprite?.Draw(spriteBatch, TitlePosition, Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), scale: titleScale); if (DrawLoadingText) { diff --git a/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs b/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs index ea9adc6f3..be069e5da 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/VideoPlayer.cs @@ -141,7 +141,7 @@ namespace Barotrauma } currentVideo = CreateVideo(scaledVideoResolution); - title.Text = TextManager.Get(contentId); + title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty; textContent.Text = textSettings != null ? textSettings.Text : string.Empty; objectiveText.Text = objective; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index e9bd2ba83..8ec9ebd84 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -74,12 +74,17 @@ namespace Barotrauma public CrewManager(XElement element, bool isSinglePlayer) : this(isSinglePlayer) { - if (!isSinglePlayer) + if (GameMain.Client != null) { - DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace); + //let the server create random conversations in MP return; } - if (string.IsNullOrEmpty(text)) { return; } + List availableSpeakers = Character.CharacterList.FindAll(c => + c.AIController is HumanAIController && + !c.IsDead && + c.SpeechImpediment <= 100.0f); + pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); + } var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); @@ -90,7 +95,6 @@ namespace Barotrauma break; } } - ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); } partial void InitProjectSpecific() @@ -195,7 +199,6 @@ 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, @@ -239,27 +242,24 @@ namespace Barotrauma public IEnumerable GetCharacters() { - if (character?.Inventory == null) return null; + if (characterInfos.Contains(characterInfo)) + { + DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); + return; + } - 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(); + characterInfos.Add(characterInfo); } public IEnumerable GetCharacterInfos() { - if (GameMain.Client != null) + if (character == null) { - //let the server create random conversations in MP + DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\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)); + characters.Remove(character); + if (removeInfo) characterInfos.Remove(character.Info); } public void AddCharacter(Character character) @@ -633,9 +633,183 @@ namespace Barotrauma { characterListBox.BarScroll = roundedPos; } - soundIcon.Visible = !muted && !mutedLocally; - soundIconDisabled.Visible = muted || mutedLocally; - soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally"); + 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; } private IEnumerable KillCharacterAnim(GUIComponent component) @@ -779,6 +953,12 @@ 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) @@ -836,19 +1016,23 @@ namespace Barotrauma } } } - //only one target (or an order with no particular targets), just show options - else + + character.SetOrder(order, option, orderGiver, speak: orderGiver != character); + if (IsSinglePlayer) { - 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) + 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) { - 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); + GameMain.Client.SendChatMessage(msg); + } + } + DisplayCharacterOrder(character, order); + } /// /// 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/Tutorial.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs index 5106b9aba..442b34a43 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameModes/Tutorials/Tutorial.cs @@ -305,7 +305,7 @@ namespace Barotrauma.Tutorials infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, activeContentSegment.TextContent.GetAttributeInt("width", 300), activeContentSegment.TextContent.GetAttributeInt("height", 80), - activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment, false)); + activeContentSegment.TextContent.GetAttributeString("anchor", "Center"), true, StopCurrentContentSegment, () => LoadVideo(activeContentSegment)); break; case TutorialContentTypes.TextOnly: infoBox = CreateInfoFrame(TextManager.Get(activeContentSegment.Id), tutorialText, @@ -404,7 +404,8 @@ namespace Barotrauma.Tutorials { if (ContentRunning) return; ContentRunning = true; - videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); + LoadVideo(segment); + //videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, callback: () => ContentRunning = false); } private void ShowSegmentText(TutorialSegment segment) @@ -419,7 +420,7 @@ namespace Barotrauma.Tutorials if (segment.ContentType != TutorialContentTypes.TextOnly) { - videoAction = () => LoadVideo(segment, false); + videoAction = () => LoadVideo(segment); } infoBox = CreateInfoFrame(TextManager.Get(segment.Id), tutorialText, @@ -566,16 +567,16 @@ namespace Barotrauma.Tutorials #endregion #region Video - protected void LoadVideo(TutorialSegment segment, bool showText = true) + protected void LoadVideo(TutorialSegment segment) { if (videoPlayer == null) videoPlayer = new VideoPlayer(); - if (showText) + if (segment.ContentType != TutorialContentTypes.ManualVideo) { videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), new VideoPlayer.TextSettings(segment.VideoContent), segment.Id, true, segment.Objective, StopCurrentContentSegment); } else { - videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, segment.Objective, null); + videoPlayer.LoadContent(playableContentPath, new VideoPlayer.VideoSettings(segment.VideoContent), null, segment.Id, true, string.Empty, null); } } #endregion diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs index 017a3e7e9..8658a7ba1 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs @@ -139,12 +139,16 @@ namespace Barotrauma { string newLanguage = obj as string; if (newLanguage == Language) return true; - - UnsavedSettings = true; + Language = newLanguage; ApplySettings(); - new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage")); + var msgBox = new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage")); + //change fonts to the default font of the new language to make sure + //they can be displayed when for example changing from English to Chinese + var defaultFont = GUI.Style.LoadCurrentDefaultFont(); + msgBox.Header.Font = defaultFont; + msgBox.Text.Font = defaultFont; return true; }; diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Pump.cs index 5ef0e010c..8b885cab9 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); 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); } public override void OnItemLoaded() diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs index 124992e2c..e3480c50a 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Signal/CustomInterface.cs @@ -117,11 +117,11 @@ namespace Barotrauma.Items.Components { if (uiElements[i] is GUIButton button) { - button.Text = labels[i]; + button.Text = customInterfaceElementList[i].Label; } else if (uiElements[i] is GUITickBox tickBox) { - tickBox.Text = labels[i]; + tickBox.Text = customInterfaceElementList[i].Label; } } } diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs index 3c6eabe3c..f757c009d 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs @@ -776,37 +776,6 @@ 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/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs index 45a0a836b..ad7f5a622 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Map/Map.cs @@ -638,7 +638,9 @@ namespace Barotrauma if (mouseOn && PlayerInput.LeftButtonClicked() && !messageBoxOpen) { + //TODO: translate or replace var messageBox = new GUIMessageBox("Mysteries lie ahead...", "This area is unreachable in this version of Barotrauma. Please wait for future updates!"); + messageBoxOpen = true; CoroutineManager.StartCoroutine(WaitForMessageBoxClosed(messageBox)); } } diff --git a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs index bcea54eb8..66c925d4c 100644 --- a/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs +++ b/Barotrauma/BarotraumaClient/Source/Map/Submarine.cs @@ -315,7 +315,7 @@ namespace Barotrauma var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), dimensionsText.RectTransform, Anchor.TopRight), + new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), dimensionsText.RectTransform, Anchor.TopRight), dimensionsStr, textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height); @@ -326,7 +326,7 @@ namespace Barotrauma var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), crewSizeText.RectTransform, Anchor.TopRight), + new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), crewSizeText.RectTransform, Anchor.TopRight), RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height); @@ -337,7 +337,7 @@ namespace Barotrauma var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight), + new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight), TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height); @@ -348,18 +348,21 @@ namespace Barotrauma var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: GUI.Font) { CanBeFocused = false }; - new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight), + new GUITextBlock(new RectTransform(new Vector2(0.45f, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight), string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: GUI.Font, wrap: true) { CanBeFocused = false }; contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height); } + GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast()); + //space new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null); if (Description.Length != 0) { - new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("SaveSubDialogDescription") + ":", font: GUI.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = true }; + new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), + TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = true }; } new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), Description, font: GUI.Font, wrap: true) diff --git a/Barotrauma/BarotraumaClient/Source/Program.cs b/Barotrauma/BarotraumaClient/Source/Program.cs index 1fc149762..37646cf3f 100644 --- a/Barotrauma/BarotraumaClient/Source/Program.cs +++ b/Barotrauma/BarotraumaClient/Source/Program.cs @@ -214,6 +214,10 @@ namespace Barotrauma sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed)); sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash + ")")); sb.AppendLine("Selected screen: " + (Screen.Selected == null ? "None" : Screen.Selected.ToString())); + if (SteamManager.IsInitialized) + { + sb.AppendLine("SteamManager initialized"); + } if (GameMain.Client != null) { diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index ccce1733e..c04d2a0f9 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -196,6 +196,8 @@ namespace Barotrauma private GUILayoutGroup subPreviewContainer; + private GUILayoutGroup subPreviewContainer; + private GUIButton loadGameButton; public Action StartNewGame; @@ -465,7 +467,7 @@ namespace Barotrauma { IsFixedSize = false }, - TextManager.Get("Shuttle"), textAlignment: Alignment.Right, font: GUI.SmallFont) + TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.Right, font: GUI.SmallFont) { TextColor = textBlock.TextColor * 0.8f, ToolTip = textBlock.ToolTip diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs index 3d0b38907..09d48fdbc 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs @@ -15,7 +15,7 @@ namespace Barotrauma { class MainMenuScreen : Screen { - public enum Tab { NewGame = 1, LoadGame = 2, HostServer = 3, Settings = 4, Tutorials = 5, JoinServer = 6, CharacterEditor = 7, SubmarineEditor = 8, QuickStartDev = 9, SteamWorkshop = 10 } + public enum Tab { NewGame = 1, LoadGame = 2, HostServer = 3, Settings = 4, Tutorials = 5, JoinServer = 6, CharacterEditor = 7, SubmarineEditor = 8, QuickStartDev = 9, SteamWorkshop = 10, Credits = 11 } private GUIComponent buttonsParent; @@ -35,22 +35,27 @@ namespace Barotrauma private Sprite backgroundSprite; private Sprite backgroundVignette; + private GUIComponent titleText; + + private CreditsPlayer creditsPlayer; + #region Creation public MainMenuScreen(GameMain game) { 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.05f), AbsoluteOffset = new Point(-5, -5) }, + { RelativeOffset = new Vector2(0.05f, 0.1f), AbsoluteOffset = new Point(-8, -8) }, style: "TitleText") { Color = Color.Black * 0.5f, CanBeFocused = false }; - new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.05f, 0.05f) }, + titleText = new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) + { RelativeOffset = new Vector2(0.05f, 0.1f) }, style: "TitleText"); - buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft, pivot: Pivot.BottomLeft) + buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft) { AbsoluteOffset = new Point(50, 0) }) @@ -223,7 +228,7 @@ namespace Barotrauma }; // === OPTION - var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.5f), parent: buttonsParent.RectTransform), isHorizontal: true); + var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), parent: buttonsParent.RectTransform), isHorizontal: true); new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon") { @@ -233,9 +238,9 @@ namespace Barotrauma //spacing new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null); - var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.05f) }); + var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) }); - var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.3f), parent: optionButtons.RectTransform)) + var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.25f), parent: optionButtons.RectTransform)) { Stretch = false, RelativeSpacing = 0.035f @@ -247,6 +252,13 @@ namespace Barotrauma UserData = Tab.Settings, OnClicked = SelectTab }; + //TODO: translate + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") + { + ForceUpperCase = true, + UserData = Tab.Credits, + OnClicked = SelectTab + }; new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("QuitButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") { ForceUpperCase = true, @@ -255,9 +267,9 @@ namespace Barotrauma //debug button for quickly starting a new round #if DEBUG - new GUIButton(new RectTransform(new Vector2(0.8f, 0.1f), buttonsParent.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(0, -40) }, + new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 40) }, "Quickstart (dev)", style: "GUIButtonLarge", color: Color.Red) - { + { IgnoreLayoutGroups = true, UserData = Tab.QuickStartDev, OnClicked = (tb, userdata) => @@ -373,6 +385,8 @@ namespace Barotrauma { if (obj is Tab) { + titleText.Visible = true; + if (GameMain.Config.UnsavedSettings) { var applyBox = new GUIMessageBox( @@ -449,6 +463,9 @@ namespace Barotrauma if (!Steam.SteamManager.IsInitialized) return false; GameMain.SteamWorkshopScreen.Select(); break; + case Tab.Credits: + titleText.Visible = false; + break; } } else @@ -702,11 +719,6 @@ namespace Barotrauma public override void Update(double deltaTime) { - GameMain.TitleScreen.TitlePosition = - Vector2.Lerp(GameMain.TitleScreen.TitlePosition, new Vector2( - GameMain.TitleScreen.TitleSize.X / 2.0f * GameMain.TitleScreen.Scale + 30.0f, - GameMain.TitleScreen.TitleSize.Y / 2.0f * GameMain.TitleScreen.Scale + 30.0f), - 0.1f); #if !DEBUG #if !OSX if (Steam.SteamManager.USE_STEAM) @@ -780,27 +792,30 @@ namespace Barotrauma GUI.Font.DrawString(spriteBatch, "Barotrauma v" + GameMain.Version, new Vector2(10, GameMain.GraphicsHeight - 20), Color.White * 0.7f); #endif - Vector2 textPos = new Vector2(GameMain.GraphicsWidth - 10, GameMain.GraphicsHeight - 10); - for (int i = legalCrap.Length - 1; i >= 0; i--) + if (selectedTab != Tab.Credits) { - Vector2 textSize = GUI.SmallFont.MeasureString(legalCrap[i]); - bool mouseOn = i == 0 && - PlayerInput.MousePosition.X > textPos.X - textSize.X && PlayerInput.MousePosition.X < textPos.X && - PlayerInput.MousePosition.Y > textPos.Y - textSize.Y && PlayerInput.MousePosition.Y < textPos.Y; - - GUI.SmallFont.DrawString(spriteBatch, - legalCrap[i], textPos - textSize, - mouseOn ? Color.White : Color.White * 0.7f); - - if (i == 0) + Vector2 textPos = new Vector2(GameMain.GraphicsWidth - 10, GameMain.GraphicsHeight - 10); + for (int i = legalCrap.Length - 1; i >= 0; i--) { - GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f); - if (mouseOn && PlayerInput.LeftButtonClicked()) + Vector2 textSize = GUI.SmallFont.MeasureString(legalCrap[i]); + bool mouseOn = i == 0 && + PlayerInput.MousePosition.X > textPos.X - textSize.X && PlayerInput.MousePosition.X < textPos.X && + PlayerInput.MousePosition.Y > textPos.Y - textSize.Y && PlayerInput.MousePosition.Y < textPos.Y; + + GUI.SmallFont.DrawString(spriteBatch, + legalCrap[i], textPos - textSize, + mouseOn ? Color.White : Color.White * 0.7f); + + if (i == 0) { - Process.Start("http://privacypolicy.daedalic.com"); + GUI.DrawLine(spriteBatch, textPos, textPos - Vector2.UnitX * textSize.X, mouseOn ? Color.White : Color.White * 0.7f); + if (mouseOn && PlayerInput.LeftButtonClicked()) + { + Process.Start("http://privacypolicy.daedalic.com"); + } } + textPos.Y -= textSize.Y; } - textPos.Y -= textSize.Y; } spriteBatch.End(); diff --git a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs index f136ff3a2..fd5dfbfd0 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/NetLobbyScreen.cs @@ -437,9 +437,13 @@ namespace Barotrauma GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), modeList.Content.RectTransform), mode.Name, style: "ListBoxElement", textAlignment: Alignment.CenterLeft) { - ToolTip = mode.Description, UserData = mode }; + //TODO: translate mission descriptions + if (TextManager.Language == "English") + { + textBlock.ToolTip = mode.Description; + } } //mission type ------------------------------------------------------------------ @@ -1216,7 +1220,7 @@ namespace Barotrauma if (sub.HasTag(SubmarineTag.Shuttle)) { new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.1f, 0.0f) }, - TextManager.Get("Shuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) + TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) { TextColor = subTextBlock.TextColor * 0.8f, ToolTip = subTextBlock.ToolTip, diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs index 97d2e92a6..f0ae24fe4 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs @@ -1184,7 +1184,6 @@ namespace Barotrauma { OnClicked = SaveSub }; -#endif } @@ -1357,7 +1356,7 @@ namespace Barotrauma if (sub.HasTag(SubmarineTag.Shuttle)) { var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.2f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), - TextManager.Get("Shuttle"), font: GUI.SmallFont) + TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), font: GUI.SmallFont) { TextColor = textBlock.TextColor * 0.8f, ToolTip = textBlock.ToolTip diff --git a/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs index 6d244e815..fb6a86f1e 100644 --- a/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/Source/Serialization/SerializableEntityEditor.cs @@ -927,6 +927,7 @@ namespace Barotrauma textTag = textTag.ToLowerInvariant(); var tagTextPairs = TextManager.GetAllTagTextPairs(); + tagTextPairs.Sort((t1, t2) => { return t1.Value.CompareTo(t2.Value); }); foreach (KeyValuePair tagTextPair in tagTextPairs) { if (!tagTextPair.Key.StartsWith(textTag)) { continue; } diff --git a/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs b/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs index a9ad8e446..7e76d6ad2 100644 --- a/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs +++ b/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs @@ -11,7 +11,7 @@ namespace Barotrauma { private static Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"])*\"|[^,]*)", RegexOptions.Compiled); // Handling commas inside data fields surrounded by "" private static List conversationClosingIndent = new List(); - private static char[] separator = new char[1] { ',' }; + private static char[] separator = new char[1] { '|' }; private const string conversationsPath = "Content/NPCConversations"; private const string infoTextPath = "Content/Texts"; @@ -48,7 +48,7 @@ namespace Barotrauma DebugConsole.ThrowError("NPCConversation Localization .csv to .xml conversion failed for: " + conversationFiles[i]); continue; } - string xmlFileFullPath = $"{conversationsPath}/NPCConversations_{language}_NEW.xml"; + string xmlFileFullPath = $"{conversationsPath}/NpcConversations_{language}_NEW.xml"; File.WriteAllLines(xmlFileFullPath, xmlContent); DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath); } @@ -141,7 +141,8 @@ namespace Barotrauma for (int i = 0; i < NPCPersonalityTrait.List.Count; i++) // Traits { - string[] split = SplitCSV(csvContent[traitStart + i].Trim(separator)); + //string[] split = SplitCSV(csvContent[traitStart + i].Trim(separator)); + string[] split = csvContent[traitStart + i].Split(separator); xmlContent.Add( $" words = new List(); + string currWord = ""; + for (int i = 0; i < text.Length; i++) { - words = new string[text.Length]; - for (int i = 0; i < text.Length; i++) + if (isCJK.IsMatch(text[i].ToString())) { - words[i] = text[i].ToString(); + if (currWord.Length > 0) + { + words.Add(currWord); + currWord = ""; + } + words.Add(text[i].ToString()); + } + else if (text[i] == ' ') + { + if (currWord.Length > 0) + { + words.Add(currWord); + currWord = ""; + } + } + else + { + currWord += text[i]; } } - else + if (currWord.Length > 0) { - words = text.Split(' '); + words.Add(currWord); + currWord = ""; } StringBuilder wrappedText = new StringBuilder(); float linePos = 0f; Vector2 spaceSize = font.MeasureString(" ") * textScale; - for (int i = 0; i < words.Length; ++i) + for (int i = 0; i < words.Count; ++i) { if (words[i].Length == 0) { diff --git a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml index b7b5d6657..3bd2357dd 100644 --- a/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml +++ b/Barotrauma/BarotraumaShared/Data/ContentPackages/Vanilla 0.9.xml @@ -73,6 +73,7 @@ + @@ -83,8 +84,12 @@ - - + + + + + + @@ -98,7 +103,13 @@ - + + + + + + + diff --git a/Barotrauma/BarotraumaShared/SharedCode.projitems b/Barotrauma/BarotraumaShared/SharedCode.projitems index 3ce8598e9..8a4a85dc3 100644 --- a/Barotrauma/BarotraumaShared/SharedCode.projitems +++ b/Barotrauma/BarotraumaShared/SharedCode.projitems @@ -36,7 +36,6 @@ - diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems index 8ac5b0f8f..f9667893d 100644 --- a/Barotrauma/BarotraumaShared/SharedContent.projitems +++ b/Barotrauma/BarotraumaShared/SharedContent.projitems @@ -361,6 +361,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -499,10 +505,47 @@ PreserveNewest - + PreserveNewest - + + PreserveNewest + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest @@ -556,6 +599,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1306,11 +1352,6 @@ PreserveNewest - - - - PreserveNewest - PreserveNewest @@ -2070,6 +2111,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 637511d00..e43ff5fb2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -3,8 +3,6 @@ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; -using Barotrauma.Extensions; -using Barotrauma.Items.Components; namespace Barotrauma { @@ -52,6 +50,7 @@ namespace Barotrauma { insideSteering = new IndoorsSteeringManager(this, true, false); outsideSteering = new SteeringManager(this); + objectiveManager = new AIObjectiveManager(c); updateObjectiveTimer = Rand.Range(0.0f, UpdateObjectiveInterval); InitProjSpecific(); @@ -94,10 +93,9 @@ namespace Barotrauma UpdateSpeaking(); } - if (objectiveManager.CurrentObjective == null) { return; } - objectiveManager.DoCurrentObjective(deltaTime); - bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority; + + bool run = objectiveManager.GetCurrentPriority() > AIObjectiveManager.OrderPriority; if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null) { if (Vector2.DistanceSquared(Character.SimPosition, goTo.Target.SimPosition) > 3 * 3) @@ -164,7 +162,7 @@ namespace Barotrauma { bool oxygenLow = Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold; bool highPressure = Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0 && Character.PressureProtection <= 0; - bool shouldKeepTheGearOn = !ObjectiveManager.IsCurrentObjective(); + bool shouldKeepTheGearOn = objectiveManager.CurrentObjective.KeepDivingGearOn; bool removeDivingSuit = (oxygenLow && !highPressure) || (!shouldKeepTheGearOn && Character.CurrentHull.WaterPercentage < 1 && !Character.IsClimbing && steeringManager == insideSteering && !PathSteering.InStairs); if (removeDivingSuit) @@ -190,7 +188,7 @@ namespace Barotrauma } } } - if (!ObjectiveManager.IsCurrentObjective() && !ObjectiveManager.IsCurrentObjective()) + if (!(ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires) && !(ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire)) { var extinguisherItem = Character.Inventory.FindItemByIdentifier("extinguisher") ?? Character.Inventory.FindItemByTag("extinguisher"); if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem)) @@ -199,21 +197,6 @@ 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)) { @@ -246,58 +229,41 @@ namespace Barotrauma Order newOrder = null; if (Character.CurrentHull != null) { - if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull)) + if (Character.CurrentHull.FireSources.Count > 0) { var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); newOrder = new Order(orderPrefab, Character.CurrentHull, null); - AddTargets(Character, Character.CurrentHull); } - foreach (var gap in Character.CurrentHull.ConnectedGaps) + if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f)) { - if (AIObjectiveFixLeaks.IsValidTarget(gap)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); - AddTargets(Character, gap); - } - } - - foreach (Item item in Item.ItemList) - { - if (item.CurrentHull != Character.CurrentHull) { continue; } - if (AIObjectiveRepairItems.IsValidTarget(item)) - { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices"); - newOrder = new Order(orderPrefab, Character.CurrentHull, item.Repairables?.FirstOrDefault()); - AddTargets(Character, item); - } + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); + newOrder = new Order(orderPrefab, Character.CurrentHull, null); } foreach (Character c in Character.CharacterList) { - if (c.CurrentHull != Character.CurrentHull) { continue; } - if (AIObjectiveFightIntruders.IsValidTarget(c)) + 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))) { var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); newOrder = new Order(orderPrefab, Character.CurrentHull, null); - AddTargets(Character, c); } } + } - if (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); - AddTargets(Character, Character); - } + 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); } } } @@ -324,7 +290,6 @@ 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. @@ -372,18 +337,18 @@ namespace Barotrauma { // Replace the old objective with the new. ObjectiveManager.Objectives.Remove(combatObjective); - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager)); + objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode)); } } else { if (delay > 0) { - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager), delay); + objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode), delay); } else { - objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode, objectiveManager)); + objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker, mode)); } } } @@ -394,10 +359,8 @@ 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); } @@ -434,7 +397,7 @@ namespace Barotrauma /// /// Check whether the character has a diving mask in usable condition plus some oxygen. /// - public static bool HasDivingMask(Character character) => HasItem(character, "diving", "oxygensource"); + public static bool HasDivingGear(Character character) => HasItem(character, "diving", "oxygensource"); public static bool HasItem(Character character, string tag, string containedTag, float conditionPercentage = 0) { @@ -454,9 +417,12 @@ namespace Barotrauma { foreach (var c in Character.CharacterList) { - if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character)) + if (c.TeamID == character.TeamID) { - humanAi.RefreshHullSafety(hull); + if (c.AIController is HumanAIController humanAi) + { + humanAi.RefreshHullSafety(hull); + } } } } @@ -473,78 +439,13 @@ 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)) - { - AddTargets(character, gap); - } - } - break; - case "reportbrokendevices": - foreach (var item in Item.ItemList) - { - if (item.CurrentHull != hull) { continue; } - if (AIObjectiveRepairItems.IsValidTarget(item)) - { - AddTargets(character, item); - } - } - break; - case "reportintruders": - foreach (var enemy in Character.CharacterList) - { - if (enemy.CurrentHull != hull) { continue; } - if (AIObjectiveFightIntruders.IsValidTarget(enemy)) - { - AddTargets(character, enemy); - } - } - break; - case "requestfirstaid": - foreach (var c in Character.CharacterList) - { - if (c.CurrentHull != hull) { continue; } - if (AIObjectiveRescueAll.IsValidTarget(c)) - { - AddTargets(character, c); - } - } - break; - default: -#if DEBUG - DebugConsole.ThrowError(order.AITag + " not implemented!"); -#endif - break; - } - } - - private static void AddTargets(Character caller, T2 target) where T1 : AIObjectiveLoop - { - foreach (var c in Character.CharacterList) - { - if (IsFriendly(caller, c) && c.AIController is HumanAIController humanAI) - { - humanAI.ObjectiveManager.GetObjective()?.AddTarget(target); - } - } - } - public float GetHullSafety(Hull hull) { if (hull == null) { return 0; } - bool ignoreFire = ObjectiveManager.IsCurrentObjective() || ObjectiveManager.IsCurrentObjective(); + bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires; bool ignoreWater = HasDivingSuit(Character); - bool ignoreOxygen = ignoreWater || HasDivingMask(Character); - bool ignoreEnemies = ObjectiveManager.IsCurrentObjective(); + bool ignoreOxygen = ignoreWater || HasDivingGear(Character); + bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat; return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); } @@ -562,7 +463,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) @@ -571,8 +472,7 @@ 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 static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName; + public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs index ba6400d3f..49a253309 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/IndoorsSteeringManager.cs @@ -287,7 +287,7 @@ namespace Barotrauma character.AnimController.Anim = AnimController.Animation.None; character.SelectedConstruction = null; } - if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 4, 2)) + if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2)) { currentPath.SkipToNextNode(); } @@ -302,7 +302,7 @@ namespace Barotrauma bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y; bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight; - if (horizontalDistance < collider.radius * 4 && isAboveFeet && isNotTooHigh) + if (horizontalDistance < collider.radius * 3 && isAboveFeet && isNotTooHigh) { currentPath.SkipToNextNode(); } @@ -325,50 +325,6 @@ namespace Barotrauma if (door.IsStuck) { return false; } if (!canOpenDoors || character.LockHands) { return false; } if (door.HasIntegratedButtons) - { - return door.HasRequiredItems(character, false); - } - else - { - bool canUseButton = false; - float closestDistance = 0; - foreach (var button in door.Item.GetConnectedComponents(true)) - { - if (!button.HasRequiredItems(character, false)) - { - continue; - } - // Ignore buttons that are on the wrong side of the door - if (door.IsHorizontal) - { - if (Math.Sign(button.Item.Position.Y - nextNode.Position.Y) != Math.Sign(currentNode.Position.Y - nextNode.Position.Y)) { continue; } - } - else - { - if (Math.Sign(button.Item.Position.X - nextNode.Position.X) != Math.Sign(currentNode.Position.X - nextNode.Position.X)) { continue; } - } - float distance = Vector2.DistanceSquared(button.Item.WorldPosition, currentNode.WorldPosition); - // Too far from the current node (can't reach) - if (distance > button.Item.InteractDistance * button.Item.InteractDistance) - { - continue; - } - else if (closestButton == null || distance < closestDistance) - { - closestButton = button; - closestDistance = distance; - } - canUseButton = true; - } - return canUseButton; - } - } - - // TODO: use the CanAccessThroughDoor method. - private void CheckDoorsInPath() - { - // TODO: if no doors was found, seek more nodes? - for (int i = 0; i < 2; i++) { Door door = null; bool shouldBeOpen = false; @@ -416,7 +372,7 @@ namespace Barotrauma } } - 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) @@ -482,9 +438,24 @@ namespace Barotrauma { penalty = 100.0f; } - if (!CanAccessThroughDoor(node.Waypoint, nextNode.Waypoint, out _)) + else if (!canBreakDoors) { - return null; + //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 (!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; } + } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index 2258682dc..f5e1bb9bd 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -12,6 +12,21 @@ 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? @@ -47,7 +62,6 @@ namespace Barotrauma public AIObjective(Character character, AIObjectiveManager objectiveManager, float priorityModifier, string option = null) { - this.objectiveManager = objectiveManager; this.character = character; Option = option ?? string.Empty; @@ -89,6 +103,11 @@ namespace Barotrauma } } + if (!subObjectives.Contains(CurrentSubObjective)) + { + CurrentSubObjective = null; + } + foreach (AIObjective objective in subObjectives) { objective.TryComplete(deltaTime); @@ -113,40 +132,29 @@ namespace Barotrauma subObjectives.Add(objective); } - public void SortSubObjectives() + public void SortSubObjectives(AIObjectiveManager objectiveManager) { if (subObjectives.None()) { return; } - subObjectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority())); - if (ConcurrentObjectives) - { - subObjectives.ForEach(so => SortSubObjectives()); - } - else - { - subObjectives.First().SortSubObjectives(); - } + subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager))); + CurrentSubObjective = SubObjectives.First(); + CurrentSubObjective.SortSubObjectives(objectiveManager); } - public virtual float GetPriority() => Priority * PriorityModifier; + public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority; - public virtual void Update(float deltaTime) + public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime) { + var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective; if (objectiveManager.CurrentOrder == this) { Priority = AIObjectiveManager.OrderPriority; } - else if (objectiveManager.WaitTimer <= 0) + else if (objectiveManager.CurrentObjective == this || subObjective == this) { - 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 += Devotion * deltaTime; } + Priority = MathHelper.Clamp(Priority, 0, 100); + subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime)); } /// @@ -166,57 +174,13 @@ namespace Barotrauma } } - /// - /// 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; - } - } - - // TODO: remove protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false; - - public virtual void OnSelected() - { - // Should we reset steering here? - //if (!ConcurrentObjectives) - //{ - // SteeringManager.Reset(); - //} - } - + public virtual void OnSelected() { } 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 77db2a809..b0224241d 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 IEnumerable batteryList; + private readonly IEnumerable batteryList; - public AIObjectiveChargeBatteries(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier) - : base(character, objectiveManager, priorityModifier, option) { } + public AIObjectiveChargeBatteries(Character character, string option) : base(character, option) + { + batteryList = Item.ItemList.Select(i => i.GetComponent()).Where(b => b != null); + } public override bool IsDuplicate(AIObjective otherObjective) { @@ -22,57 +22,37 @@ namespace Barotrauma protected override void FindTargets() { - base.FindTargets(); - if (targets.None() && objectiveManager.CurrentOrder == this) + 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()) { 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 { - 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)); + targets.Sort((x, y) => x.ChargePercentage.CompareTo(y.ChargePercentage)); } } - protected override IEnumerable GetList() - { - if (batteryList == null) - { - batteryList = Item.ItemList.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 }; + 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); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index 9c9e41d76..9acf4bc99 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -1,17 +1,15 @@ 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 bool useCoolDown = true; + public override bool KeepDivingGearOn => true; const float CoolDown = 10.0f; @@ -43,39 +41,29 @@ namespace Barotrauma return _weaponComponent; } } - - 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 AIObjectiveGoTo retreatObjective; + private AIObjectiveFindSafety findSafety; + private float coolDownTimer; public enum CombatMode { Defensive, - Offensive, + Offensive, // Not implemented Retreat } public CombatMode Mode { get; private set; } - public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public AIObjectiveCombat(Character character, Character enemy, CombatMode mode) : base(character, "") { Enemy = enemy; coolDownTimer = CoolDown; - findSafety = objectiveManager.GetObjective(); - if (findSafety != null) - { - findSafety.Priority = 0; - findSafety.unreachable.Clear(); - } + findSafety = HumanAIController.ObjectiveManager.GetObjective(); + findSafety.Priority = 0; + findSafety.unreachable.Clear(); Mode = mode; if (Enemy == null) { @@ -85,22 +73,12 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (useCoolDown) - { - coolDownTimer -= deltaTime; - } + 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) || _weaponComponent != null && !_weaponComponent.HasRequiredContainedItems(false)) + if (Weapon != null && character.Inventory.Items.Contains(_weapon)) { Weapon = null; } @@ -112,65 +90,40 @@ namespace Barotrauma { Mode = CombatMode.Retreat; } - if (Equip()) + else if (Equip(deltaTime)) { 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 NotImplementedException(); + throw new System.NotImplementedException(); } } private Item GetWeapon() { - rangedWeapons.Clear(); - meleeWeapons.Clear(); - adHocWeapons.Clear(); - Item weapon = null; _weaponComponent = null; - foreach (var item in character.Inventory.Items) + var weapon = character.Inventory.FindItemByTag("weapon"); + if (weapon == null) { - if (item == null) { continue; } - foreach (var component in item.Components) + foreach (var item in character.Inventory.Items) { - if (component is RangedWeapon rw) + if (item == null) { continue; } + foreach (var component in item.Components) { - if (rw.HasRequiredContainedItems(false)) + if (component is MeleeWeapon || component is RangedWeapon) { - rangedWeapons.Add(rw); + return item; } - } - else if (component is MeleeWeapon mw) - { - if (mw.HasRequiredContainedItems(false)) - { - meleeWeapons.Add(mw); - } - } - else - { var effects = component.statusEffectLists; if (effects != null) { @@ -180,10 +133,7 @@ namespace Barotrauma { if (statusEffect.Afflictions.Any()) { - if (component.HasRequiredContainedItems(false)) - { - adHocWeapons.Add(item); - } + return item; } } } @@ -191,20 +141,6 @@ 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; } @@ -219,7 +155,7 @@ namespace Barotrauma } } - private bool Equip() + private bool Equip(float deltaTime) { if (!character.SelectedItems.Contains(Weapon)) { @@ -230,7 +166,8 @@ namespace Barotrauma } else { - Mode = CombatMode.Retreat; + //couldn't equip the item, escape + //Abandon(deltaTime); return false; } } @@ -239,7 +176,6 @@ namespace Barotrauma private void Retreat(float deltaTime) { - followTargetObjective = null; if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted)) { retreatTarget = findSafety.FindBestHull(new List() { character.CurrentHull }); @@ -248,45 +184,12 @@ namespace Barotrauma { if (retreatObjective == null || retreatObjective.Target != retreatTarget) { - retreatObjective = new AIObjectiveGoTo(retreatTarget, character, objectiveManager, false, true, priorityModifier: PriorityModifier); + retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true); } retreatObjective.TryComplete(deltaTime); } } - private void Engage(float deltaTime) - { - retreatTarget = null; - retreatObjective = null; - if (followTargetObjective == null) - { - followTargetObjective = new AIObjectiveGoTo(Enemy, character, objectiveManager, repeat: true, getDivingGearIfNeeded: true, priorityModifier: PriorityModifier) - { - AllowGoingOutside = true, - IgnoreIfTargetDead = true, - CheckVisibility = true - }; - } - if (WeaponComponent is RangedWeapon) - { - followTargetObjective.CloseEnough = 3; - } - else if (WeaponComponent is MeleeWeapon mw) - { - followTargetObjective.CloseEnough = ConvertUnits.ToSimUnits(mw.Range); - } - else if (WeaponComponent is RepairTool rt) - { - followTargetObjective.CloseEnough = ConvertUnits.ToSimUnits(rt.Range); - } - else if (WeaponComponent is RepairTool rt) - { - SteeringManager.Reset(); - Mode = CombatMode.Retreat; - } - followTargetObjective.TryComplete(deltaTime); - } - private bool Reload(float deltaTime) { if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) @@ -299,7 +202,7 @@ namespace Barotrauma { if (reloadWeaponObjective == null) { - reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent(), objectiveManager); + reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent()); } } } @@ -312,7 +215,6 @@ namespace Barotrauma } else if (!reloadWeaponObjective.CanBeCompleted) { - SteeringManager.Reset(); Mode = CombatMode.Retreat; } else @@ -327,31 +229,14 @@ namespace Barotrauma 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) { - 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); - } + character.SetInput(InputType.Aim, false, true); } if (WeaponComponent is MeleeWeapon meleeWeapon) { - if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range) + if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range) { character.SetInput(InputType.Shoot, false, true); Weapon.Use(deltaTime, character); @@ -361,7 +246,7 @@ namespace Barotrauma { if (WeaponComponent is RepairTool repairTool) { - if (squaredDistance > repairTool.Range * repairTool.Range) { return; } + if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; } } if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4) { @@ -369,8 +254,7 @@ namespace Barotrauma { myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody); } - var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall; - var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories); + var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies); if (pickedBody != null) { Character target = null; @@ -392,15 +276,17 @@ namespace Barotrauma } } + private void Abandon(float deltaTime) + { + abandon = true; + SteeringManager.Reset(); + } + public override bool IsCompleted() { - bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (useCoolDown && coolDownTimer <= 0); + bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || 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(); @@ -409,12 +295,8 @@ namespace Barotrauma return completed; } - public override bool CanBeCompleted => !abandon && - (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && - (retreatObjective == null || retreatObjective.CanBeCompleted) && - (followTargetObjective == null || followTargetObjective.CanBeCompleted); - - public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100); + 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) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index ebb59710e..8ecebb0f3 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -9,9 +9,15 @@ 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 @@ -22,13 +28,13 @@ namespace Barotrauma private AIObjectiveGetItem getItemObjective; private AIObjectiveGoTo goToObjective; - public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container, float priorityModifier = 1) : this(character, new string[] { itemIdentifier }, container, priorityModifier) { } + public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container) + : this(character, new string[] { itemIdentifier }, container) + { + } - 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) + public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container) + : base (character, "") { this.itemIdentifiers = itemIdentifiers; for (int i = 0; i < itemIdentifiers.Length; i++) @@ -45,10 +51,22 @@ 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))) + 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) { containedItemCount++; } + + return getItemObjective == null || getItemObjective.CanBeCompleted; } return containedItemCount >= MinContainedAmount; } @@ -70,16 +88,17 @@ 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) { - TryAddSubObjective(ref getItemObjective, () => - new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager) - { - GetItemPriority = GetItemPriority, - ignoredContainerIdentifiers = ignoredContainerIdentifiers - }); + getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers) + { + GetItemPriority = GetItemPriority, + ignoredContainerIdentifiers = ignoredContainerIdentifiers + }; + AddSubObjective(getItemObjective); return; } if (container.Item.ParentInventory == character.Inventory) @@ -96,8 +115,7 @@ namespace Barotrauma } else { - if (container.Item.CurrentHull != character.CurrentHull || - (Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))) + if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition))) { TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager)); return; @@ -109,15 +127,14 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) { - if (!(otherObjective is AIObjectiveContainItem objective)) { return false; } - if (objective.container != container) { return false; } - if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } + AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem; + if (objective == null) 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 43a357106..a73d7ec57 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs @@ -8,53 +8,78 @@ namespace Barotrauma { public override string DebugTag => "decontain item"; - public Func GetItemPriority; - //can either be a tag or an identifier - private readonly string[] itemIdentifiers; - private readonly ItemContainer container; - private readonly Item targetItem; + private string[] itemIdentifiers; + + private ItemContainer container; - private AIObjectiveGoTo goToObjective; private bool isCompleted; - public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public Func GetItemPriority; + + private AIObjectiveGetItem getItemObjective; + private AIObjectiveGoTo goToObjective; + private Item targetItem; + + public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container) + : base(character, "") { this.targetItem = targetItem; this.container = 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 itemIdentifier, ItemContainer container) + : this(character, new string[] { itemIdentifier }, container) + { + } - public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public AIObjectiveDecontainItem(Character character, string[] itemIdentifiers, ItemContainer container) + : base(character, "") { this.itemIdentifiers = itemIdentifiers; for (int i = 0; i < itemIdentifiers.Length; i++) { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); } + this.container = container; } - public override bool IsCompleted() => isCompleted; + public override bool IsCompleted() + { + return isCompleted; + } - public override float GetPriority() + public override bool CanBeCompleted + { + get + { + if (goToObjective != null) + { + return goToObjective.CanBeCompleted; + } + + return getItemObjective == null || getItemObjective.CanBeCompleted; + } + } + + public override float GetPriority(AIObjectiveManager objectiveManager) { 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) { @@ -63,7 +88,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; } } } @@ -71,32 +96,38 @@ 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.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)) + if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance + && !container.Item.IsInsideTrigger(character.WorldPosition)) { - TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager)); + goToObjective = new AIObjectiveGoTo(container.Item, character); + AddSubObjective(goToObjective); return; } } + itemToDecontain.Drop(character); isCompleted = true; } public override bool IsDuplicate(AIObjective otherObjective) { - if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; } + AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem; + if (decontainItem == null) 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; } @@ -104,6 +135,7 @@ 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 884d60e7b..a3e12d61b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -3,7 +3,6 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Linq; -using Barotrauma.Extensions; namespace Barotrauma { @@ -11,117 +10,125 @@ namespace Barotrauma { public override string DebugTag => "extinguish fire"; public override bool ForceRun => true; - public override bool ConcurrentObjectives => true; + public override bool KeepDivingGearOn => true; - private readonly Hull targetHull; + private Hull targetHull; private AIObjectiveGetItem getExtinguisherObjective; + private AIObjectiveGoTo gotoObjective; + private float useExtinquisherTimer; - public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public AIObjectiveExtinguishFire(Character character, Hull targetHull) : base(character, "") { this.targetHull = targetHull; } - public override float GetPriority() + public override float GetPriority(AIObjectiveManager objectiveManager) { - if (Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c))) { return 0; } + if (gotoObjective != null && !gotoObjective.CanBeCompleted) { 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 severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull); - float severityFactor = MathHelper.Lerp(0, 1, severity / 100); - float devotion = Math.Max(Priority, 10) / 100; - return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1)); + 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); } - public override bool IsCompleted() => targetHull.FireSources.None(); + public override bool IsCompleted() + { + return targetHull.FireSources.Count == 0; + } - public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull; + 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; } + } 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)) { - TryAddSubObjective(ref getExtinguisherObjective, () => + if (getExtinguisherObjective == null) { character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f); - return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true); - }); + getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true); + } + else + { + getExtinguisherObjective.TryComplete(deltaTime); + } + + return; } - else + + var extinguisher = extinguisherItem.GetComponent(); + if (extinguisher == null) { - var extinguisher = extinguisherItem.GetComponent(); - if (extinguisher == null) + 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) { -#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 += deltaTime; + if (useExtinquisherTimer > 2.0f) { - useExtinquisherTimer += deltaTime; - if (useExtinquisherTimer > 2.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 = 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); - } + character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f); } } - if (move) - { - //go to the first firesource - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager)); - } - break; } + if (move) + { + //go to the first firesource + if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted()) + { + gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character); + } + else + { + gotoObjective.TryComplete(deltaTime); + } + } + break; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index 8bce1dcaf..4da5093e8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -1,43 +1,62 @@ using System.Linq; using System.Collections.Generic; using Barotrauma.Extensions; +using Microsoft.Xna.Framework; namespace Barotrauma { - class AIObjectiveExtinguishFires : AIObjectiveLoop + class AIObjectiveExtinguishFires : AIObjective { public override string DebugTag => "extinguish fires"; public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; - public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } + private Dictionary extinguishObjectives = new Dictionary(); - protected override void FindTargets() + public AIObjectiveExtinguishFires(Character character) : base(character, "") { } + + public override float GetPriority(AIObjectiveManager objectiveManager) { - base.FindTargets(); - if (targets.None() && objectiveManager.CurrentOrder == this) + if (character.Submarine == null) { return 0; } + int fireCount = character.Submarine.GetHulls(true).Sum(h => h.FireSources.Count); + if (objectiveManager.CurrentOrder == this && fireCount > 0) { - character.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f); + return AIObjectiveManager.OrderPriority; + } + + return MathHelper.Clamp(fireCount * 20, 0, 100); + } + + public override bool IsCompleted() => false; + public override bool CanBeCompleted => true; + + public override bool IsDuplicate(AIObjective otherObjective) + { + return otherObjective is AIObjectiveExtinguishFires; + } + + protected override void Act(float deltaTime) + { + 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); } } - - protected override bool Filter(Hull target) - { - if (target == null) { return false; } - if (target.FireSources.None()) { return false; } - if (target.Submarine == null) { return false; } - if (target.Submarine.TeamID != character.TeamID) { return false; } - if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target, true)) { return false; } - //if (Character.CharacterList.Any(c => c.CurrentHull == target && !HumanAIController.IsFriendly(c))) { return false; } - return true; - } - - protected override float TargetEvaluation() => objectiveManager.CurrentObjective == this ? 100 : targets.Sum(t => GetFireSeverity(t)); - - 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); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index ec88cd1f3..6721f779e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -1,7 +1,6 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System.Linq; -using Barotrauma.Extensions; namespace Barotrauma { @@ -10,10 +9,8 @@ namespace Barotrauma public override string DebugTag => "find diving gear"; public override bool ForceRun => true; - private readonly string gearTag; - - private AIObjectiveGetItem getDivingGear; - private AIObjectiveContainItem getOxygen; + private AIObjective subObjective; + private string gearTag; public override bool IsCompleted() { @@ -24,16 +21,15 @@ namespace Barotrauma { var containedItems = character.Inventory.Items[i].ContainedItems; if (containedItems == null) { continue; } - return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); + + var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); + if (oxygenTank != null) { return true; } } } return false; } - 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) + public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "") { gearTag = needDivingSuit ? "divingsuit" : "diving"; } @@ -46,23 +42,16 @@ namespace Barotrauma TryAddSubObjective(ref getDivingGear, () => { character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); - return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true); - }); + subObjective = new AIObjectiveGetItem(character, gearTag, true); + } } if (getDivingGear != null) { return; } var containedItems = item.ContainedItems; if (containedItems == null) { var containedItems = item.ContainedItems; - if (containedItems == null) - { -#if DEBUG - DebugConsole.ThrowError("AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory"); -#endif - abandon = true; - return; - } - // Drop empty tanks + if (containedItems == null) { return; } + //check if there's an oxygen tank in the mask/suit foreach (Item containedItem in containedItems) { if (containedItem == null) { continue; } @@ -70,16 +59,26 @@ namespace Barotrauma { containedItem.Drop(character); } - } - if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f)) - { - TryAddSubObjective(ref getOxygen, () => + else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource")) { - character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); - return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent(), objectiveManager); - }); + //we've got an oxygen source inside the mask/suit, all good + return; + } + } + if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted()) + { + character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); + subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent()); } } + 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 52891a5b9..efea2390e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -10,6 +10,7 @@ namespace Barotrauma { public override string DebugTag => "find safety"; public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; // TODO: expose? const float priorityIncrease = 25; @@ -17,7 +18,7 @@ namespace Barotrauma const float SearchHullInterval = 3.0f; const float clearUnreachableInterval = 30; - public readonly HashSet unreachable = new HashSet(); + public readonly List unreachable = new List(); private float currenthullSafety; private float unreachableClearTimer; @@ -26,15 +27,49 @@ namespace Barotrauma private AIObjectiveGoTo goToObjective; private AIObjectiveFindDivingGear divingGearObjective; - public AIObjectiveFindSafety(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } + public AIObjectiveFindSafety(Character character) : base(character, "") { } 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; @@ -44,753 +79,74 @@ namespace Barotrauma unreachableClearTimer = clearUnreachableInterval; unreachable.Clear(); } + + if (searchHullTimer > 0.0f) + { + unreachableClearTimer = clearUnreachableInterval; + unreachable.Clear(); + } if (character.CurrentHull == null) { - currenthullSafety = 0; - Priority = 100; - return; + 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; } if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; } currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull); if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD) { - Priority -= priorityDecrease * deltaTime; + 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(); + } } - else + else if (currentHull != null) { - 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)); - } - } + //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; + foreach (FireSource fireSource in currentHull.FireSources) + { + 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); + } - 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)); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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 && - (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) - { - Vector2 dir = character.Position - enemy.Position; - float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f); - 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 - if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) || - (escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50)) - { - character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel); - } - else - { - character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left; - character.AIController.SteeringManager.Reset(); - } - } - else - { - character.AIController.SteeringManager.Reset(); - } - 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; - foreach (FireSource fireSource in currentHull.FireSources) - { - 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); - } - 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; @@ -798,6 +154,7 @@ 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 @@ -894,5 +251,36 @@ 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 8225544bd..a156d8f56 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -3,24 +3,28 @@ 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; - public Gap Leak { get; private set; } + private readonly Gap leak; 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, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier) + public AIObjectiveFixLeak(Gap leak, Character character) : base (character, "") { Leak = leak; } @@ -30,16 +34,17 @@ namespace Barotrauma return Leak.Open <= 0.0f || Leak.Removed; } - public override float GetPriority() + public override bool CanBeCompleted => !abandon && base.CanBeCompleted; + + public override float GetPriority(AIObjectiveManager objectiveManager) { - 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)); + 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); } public override bool IsDuplicate(AIObjective otherObjective) @@ -50,447 +55,105 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (!Leak.IsRoomToRoom) + if (!leak.IsRoomToRoom) { - if (!HumanAIController.HasDivingSuit(character)) + if (findDivingGear == null) { - TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager)); + findDivingGear = new AIObjectiveFindDivingGear(character, true); + AddSubObjective(findDivingGear); + } + else if (!findDivingGear.CanBeCompleted) + { + abandon = true; return; } } + var weldingTool = character.Inventory.FindItemByTag("weldingtool"); + if (weldingTool == null) { - TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true)); + AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true)); return; } else { var containedItems = weldingTool.ContainedItems; - if (containedItems == null) + if (containedItems == null) return; + + var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f); + if (fuelTank == null) { -#if DEBUG - DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory"); -#endif - abandon = true; + AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent())); return; } - // Drop empty tanks - foreach (Item containedItem in containedItems) + } + + 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)) { - if (containedItem == null) { continue; } - if (containedItem.Condition <= 0.0f) + sightLimb = character.AnimController.GetLimb(LimbType.RightHand); + } + else if (character.Inventory.IsInLimbSlot(repairTool.Item, InvSlotType.LeftHand)) + { + sightLimb = character.AnimController.GetLimb(LimbType.LeftHand); + } + canReach = character.CanSeeTarget(leak, sightLimb); + } + else + { + if (gotoObjective != null) + { + // Check if the objective is already removed -> completed/impossible + if (!subObjectives.Contains(gotoObjective)) { - containedItem.Drop(character); + if (!gotoObjective.CanBeCompleted) + { + abandon = true; + } + gotoObjective = null; + return; } } - if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f)) + else { - TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent(), objectiveManager)); - return; + gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character) + { + CloseEnough = reach + }; + if (!subObjectives.Contains(gotoObjective)) + { + AddSubObjective(gotoObjective); + } } - canReach = character.CanSeeTarget(Leak, sightLimb); } - var repairTool = weldingTool.GetComponent(); - if (repairTool == null) + if (gotoObjective == null || gotoObjective.IsCompleted()) { -#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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } - 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 - { - TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach * 0.75f }); - } + 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 bc5074636..d8daaf54b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -9,41 +9,60 @@ 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, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } + public AIObjectiveFixLeaks(Character character) : base (character, "") { } + + protected override bool Filter(Gap gap) + { + 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))); + } protected override void FindTargets() { base.FindTargets(); - if (targets.None() && objectiveManager.CurrentOrder == this) - { - character.Speak(TextManager.Get("DialogNoLeaks"), null, 3.0f, "noleaks", 30.0f); - } + targets.Sort((x, y) => GetGapFixPriority(y).CompareTo(GetGapFixPriority(x))); } protected override bool Filter(Gap gap) { - 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; + 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; } - public static float GetLeakSeverity(Gap leak) + private float GetGapFixPriority(Gap gap) { - 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); + if (gap == null) return 0.0f; + + //larger gap -> higher priority + float gapPriority = (gap.IsHorizontal ? gap.Rect.Width : gap.Rect.Height) * gap.Open; + + //prioritize gaps that are close + gapPriority /= Math.Max(Vector2.Distance(character.WorldPosition, gap.WorldPosition), 1.0f); + + //gaps to outside are much higher priority + if (!gap.IsRoomToRoom) gapPriority *= 10.0f; + + return gapPriority; + } public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFixLeaks; - protected override float TargetEvaluation() => targets.Max(t => GetLeakSeverity(t)); + protected override float Average(Gap gap) => gap.Open; protected override IEnumerable GetList() => Gap.GapList; - protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character, objectiveManager, PriorityModifier); + protected override AIObjective ObjectiveConstructor(Gap gap) => new AIObjectiveFixLeak(gap, character); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 9e6b648e2..6fc26a7c6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -11,9 +11,6 @@ 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 @@ -23,8 +20,14 @@ namespace Barotrauma public string[] ignoredContainerIdentifiers; private AIObjectiveGoTo goToObjective; private float currItemPriority; + private bool equip; - public override float GetPriority() + private HashSet ignoredItems = new HashSet(); + + private bool canBeCompleted = true; + public override bool CanBeCompleted => canBeCompleted; + + public override float GetPriority(AIObjectiveManager objectiveManager) { if (objectiveManager.CurrentOrder == this) { @@ -33,19 +36,16 @@ namespace Barotrauma return 1.0f; } - public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "") { currSearchIndex = -1; this.equip = equip; this.targetItem = targetItem; } - 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 itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { } - public AIObjectiveGetItem(Character character, string[] itemIdentifiers, AIObjectiveManager objectiveManager, bool equip = false, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "") { currSearchIndex = -1; this.equip = equip; @@ -54,15 +54,20 @@ 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]; @@ -76,7 +81,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; @@ -94,11 +99,13 @@ namespace Barotrauma FindTargetItem(); if (targetItem == null || moveToTarget == null) { - objectiveManager.GetObjective()?.Wander(deltaTime); + HumanAIController.ObjectiveManager.GetObjective().Wander(deltaTime); + //SteeringManager.SteeringWander(); return; } + if (moveToTarget.CurrentHull == character.CurrentHull && - Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2)) + Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2)) { int targetSlot = -1; if (equip) @@ -116,7 +123,8 @@ 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; } @@ -128,6 +136,7 @@ namespace Barotrauma } } targetItem.TryInteract(character, false, true); + if (targetSlot > -1 && !character.HasEquippedItem(targetItem)) { character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character); @@ -135,20 +144,23 @@ namespace Barotrauma } else { - 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); - }); + 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); + } } } @@ -164,10 +176,11 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined."); #endif - abandon = true; + canBeCompleted = false; } return; } + for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++) { currSearchIndex++; @@ -176,18 +189,15 @@ 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) { @@ -209,9 +219,11 @@ 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) @@ -219,21 +231,21 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}"); #endif - abandon = true; + canBeCompleted = false; } } 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; } @@ -251,10 +263,7 @@ 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 4b4c70c92..aa1fbd519 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -9,23 +9,29 @@ 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 { get; set; } = 0.5f; - public bool IgnoreIfTargetDead { get; set; } - public bool AllowGoingOutside { get; set; } - public bool CheckVisibility { get; set; } + public float CloseEnough = 0.5f; - public override float GetPriority() + public bool IgnoreIfTargetDead; + + public bool AllowGoingOutside = false; + + public override float GetPriority(AIObjectiveManager objectiveManager) { 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; @@ -33,6 +39,44 @@ 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; @@ -66,36 +110,27 @@ namespace Barotrauma { if (FollowControlledCharacter) { - if (Character.Controlled == null) - { - abandon = true; - return; - } + if (Character.Controlled == null) { 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) - { - if (Target.Removed) - { - abandon = true; - } - else - { - character.AIController.SelectTarget(Target.AiTarget); - } - } + + if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); } + Vector2 currTargetPos = Vector2.Zero; + if (Target == null) { currTargetPos = targetPos; @@ -110,12 +145,8 @@ namespace Barotrauma currTargetPos -= character.Submarine.SimPosition; } } - 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) + + if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough) { character.AIController.SteeringManager.Reset(); character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left; @@ -123,50 +154,31 @@ namespace Barotrauma else { bool isInside = character.CurrentHull != null; - bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty; + bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null; bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); if (isInside && targetIsOutside && !AllowGoingOutside) { - 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(); + cannotReach = true; } else { character.AIController.SteeringManager.SteeringSeek(currTargetPos); if (getDivingGearIfNeeded) { - 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 (targetIsOutside || + Target is Hull h && HumanAIController.NeedsDivingGear(h) || + Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || + Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) { - needsEquipment = !HumanAIController.HasDivingSuit(character); - } - else if (needsDivingGear) - { - needsEquipment = !HumanAIController.HasDivingMask(character); - } - if (needsEquipment) - { - TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager)); + if (findDivingGear == null) + { + findDivingGear = new AIObjectiveFindDivingGear(character, true); + AddSubObjective(findDivingGear); + } + else if (!findDivingGear.CanBeCompleted) + { + abandon = true; + } } } } @@ -177,30 +189,40 @@ 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) { - if (!(otherObjective is AIObjectiveGoTo objective)) { return false; } - if (objective.Target == Target) { return true; } - return objective.targetPos == targetPos; - } + AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo; + if (objective == null) return false; + + if (objective.Target == Target) return true; 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 cf391c49b..b3ecee52d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -29,7 +29,7 @@ namespace Barotrauma private readonly List targetHulls = new List(20); private readonly List hullWeights = new List(20); - public AIObjectiveIdle(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) + public void SetRandom() { standStillTimer = Rand.Range(-10.0f, 10.0f); walkDuration = Rand.Range(0.0f, 10.0f); @@ -38,27 +38,6 @@ 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() - { - return 1; - float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100); - float initiative = character.GetSkillLevel("initiative"); - Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random)); - return Priority; - } - public override void Update(float deltaTime) { if (objectiveManager.CurrentObjective == this) @@ -99,7 +78,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) @@ -111,10 +90,17 @@ namespace Barotrauma character.SelectedConstruction = null; } + bool currentHullForbidden = IsForbidden(character.CurrentHull); + if (!currentHullForbidden && !character.AnimController.InWater && !character.IsClimbing && HumanAIController.ObjectiveManager.WaitTimer > 0) + { + SteeringManager.Reset(); + return; + } + bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); - if (currentTargetIsInvalid || currentTarget == null && IsForbidden(character.CurrentHull)) + if (currentTargetIsInvalid || (currentTarget == null && currentHullForbidden)) { newTargetTimer = 0; standStillTimer = 0; @@ -151,6 +137,7 @@ 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) { @@ -158,8 +145,7 @@ 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); @@ -282,6 +268,7 @@ 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) @@ -313,7 +300,7 @@ namespace Barotrauma } } - public static bool IsForbidden(Hull hull) + private 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 aaa36082e..00c02f3bd 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -7,34 +7,18 @@ namespace Barotrauma { abstract class AIObjectiveLoop : AIObjective { - protected HashSet targets = new HashSet(); + protected List targets = new List(); protected Dictionary objectives = new Dictionary(); protected HashSet ignoreList = new HashSet(); private float ignoreListTimer; private float targetUpdateTimer; - private static AIObjectiveLoop instance; - // By default, doesn't clear the list automatically protected virtual float IgnoreListClearInterval => 0; protected virtual float TargetUpdateInterval => 2; - public HashSet ReportedTargets { get; private set; } = new HashSet(); - - public bool AddTarget(T target) + public AIObjectiveLoop(Character character, string option) : base(character, option) { - 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) - { - instance = this; Reset(); } @@ -42,11 +26,9 @@ 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); } - - public override void Update(float deltaTime) + public override void Update(AIObjectiveManager objectiveManager, float deltaTime) { - base.Update(deltaTime); + base.Update(objectiveManager, deltaTime); if (IgnoreListClearInterval > 0) { if (ignoreListTimer > IgnoreListClearInterval) @@ -97,29 +79,15 @@ namespace Barotrauma public override void OnSelected() { - base.OnSelected(); - if (HumanAIController.ObjectiveManager.CurrentOrder == this) - { - Reset(); - } + Reset(); } - public override float GetPriority() + public override float GetPriority(AIObjectiveManager objectiveManager) { if (character.Submarine == null) { return 0; } if (targets.None()) { return 0; } - // 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); + float avg = targets.Average(t => Average(t)); + return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100); } protected void UpdateTargets() @@ -131,19 +99,12 @@ namespace Barotrauma protected virtual void FindTargets() { - foreach (T target in GetList()) + foreach (T item in GetList()) { - // The bots always find targets when the objective is an order. - if (objectiveManager.CurrentOrder != this) + if (Filter(item)) { continue; } + if (!targets.Contains(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); + targets.Add(item); } } } @@ -155,7 +116,6 @@ namespace Barotrauma if (!objectives.TryGetValue(target, out AIObjective objective)) { objective = ObjectiveConstructor(target); - objective.Completed += () => ReportedTargets.Remove(target); objectives.Add(target, objective); AddSubObjective(objective); } @@ -166,12 +126,8 @@ namespace Barotrauma /// List of all possible items of the specified type. Used for filtering the removed objectives. /// protected abstract IEnumerable GetList(); - - protected abstract float TargetEvaluation(); - + protected abstract float Average(T target); protected abstract AIObjective ObjectiveConstructor(T target); protected abstract bool Filter(T target); - - public static bool IsValidTarget(T target) => instance == null ? false : instance.Filter(target); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs index 9104e547b..e9b96ae0e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs @@ -9,29 +9,27 @@ namespace Barotrauma class AIObjectiveManager { // TODO: expose - public const float OrderPriority = 70; - public const float RunPriority = 50; + public const float OrderPriority = 50.0f; // Constantly increases the priority of the selected objective, unless overridden public const float baseDevotion = 2; - public List Objectives { get; private set; } = new List(); + public List Objectives { get; private set; } private Character character; /// - /// When set above zero, the character will stand still doing nothing until the timer runs out. Does not affect orders. + /// When set above zero, the character will stand still doing nothing until the timer runs out. Only affects idling. /// 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; - CreateAutonomousObjectives(); + + Objectives = new List(); } public void AddObjective(AIObjective objective) @@ -48,30 +46,6 @@ 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)) @@ -101,7 +75,7 @@ namespace Barotrauma { var previousObjective = CurrentObjective; var firstObjective = Objectives.FirstOrDefault(); - if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority() > firstObjective.GetPriority()) + if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this)) { CurrentObjective = CurrentOrder; } @@ -112,24 +86,18 @@ namespace Barotrauma if (previousObjective != CurrentObjective) { CurrentObjective?.OnSelected(); - GetObjective()?.SetRandom(); } return CurrentObjective; } public float GetCurrentPriority() { - return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(); + return CurrentObjective == null ? 0.0f : CurrentObjective.GetPriority(this); } public void UpdateObjectives(float deltaTime) { - CurrentOrder?.Update(deltaTime); - if (WaitTimer > 0) - { - WaitTimer -= deltaTime; - return; - } + CurrentOrder?.Update(this, deltaTime); for (int i = 0; i < Objectives.Count; i++) { var objective = Objectives[i]; @@ -149,7 +117,7 @@ namespace Barotrauma } else { - objective.Update(deltaTime); + objective.Update(this, deltaTime); } } GetCurrentObjective(); @@ -159,41 +127,15 @@ namespace Barotrauma { if (Objectives.Any()) { - Objectives.Sort((x, y) => y.GetPriority().CompareTo(x.GetPriority())); + Objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this))); } - CurrentObjective?.SortSubObjectives(); + CurrentObjective?.SortSubObjectives(this); } public void DoCurrentObjective(float 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); - } - } - } - } + if (WaitTimer > 0.0f) { WaitTimer -= deltaTime; } + CurrentObjective?.TryComplete(deltaTime); } public void SetOrder(AIObjective objective) @@ -203,22 +145,13 @@ namespace Barotrauma public void SetOrder(Order order, string option, Character orderGiver) { - 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(); - } - } + CurrentOrder = null; + if (order == null) return; - 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": - newObjective = new AIObjectiveGoTo(orderGiver, character, this, repeat: true, priorityModifier: priorityModifier) + CurrentOrder = new AIObjectiveGoTo(orderGiver, character, true) { CloseEnough = 1.5f, AllowGoingOutside = true, @@ -227,41 +160,38 @@ namespace Barotrauma }; break; case "wait": - newObjective = new AIObjectiveGoTo(character, character, this, repeat: true, priorityModifier: priorityModifier) + CurrentOrder = new AIObjectiveGoTo(character, character, true) { AllowGoingOutside = true }; break; case "fixleaks": - newObjective = new AIObjectiveFixLeaks(character, this, priorityModifier); + CurrentOrder = new AIObjectiveFixLeaks(character); break; case "chargebatteries": - newObjective = new AIObjectiveChargeBatteries(character, this, option, priorityModifier); + CurrentOrder = new AIObjectiveChargeBatteries(character, option); break; case "rescue": - newObjective = new AIObjectiveRescueAll(character, this, priorityModifier); + CurrentOrder = new AIObjectiveRescueAll(character); break; case "repairsystems": - newObjective = new AIObjectiveRepairItems(character, this, priorityModifier) { RequireAdequateSkills = option != "all" }; + CurrentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" }; break; case "pumpwater": - newObjective = new AIObjectivePumpWater(character, this, option, priorityModifier: priorityModifier); + CurrentOrder = new AIObjectivePumpWater(character, option); break; case "extinguishfires": - newObjective = new AIObjectiveExtinguishFires(character, this, priorityModifier); - break; - case "fightintruders": - newObjective = new AIObjectiveFightIntruders(character, this, priorityModifier); + CurrentOrder = new AIObjectiveExtinguishFires(character); break; case "steer": var steering = (order?.TargetEntity as Item)?.GetComponent(); if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; - if (order.TargetItemComponent == null) { return null; } - newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true }; + if (order.TargetItemComponent == null) return; + CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); break; default: - if (order.TargetItemComponent == null) { return null; } - newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier) { IsLoop = true }; + if (order.TargetItemComponent == null) return; + CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController); break; } return newObjective; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index 1598459b9..7de3f8db1 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -10,6 +10,8 @@ namespace Barotrauma { public override string DebugTag => "operate item"; + private ItemComponent component, controller; + private ItemComponent component, controller; private Entity operateTarget; private bool isCompleted; @@ -18,25 +20,35 @@ namespace Barotrauma private AIObjectiveGoTo goToObjective; private AIObjectiveGetItem getItemObjective; - public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null); + 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 Entity OperateTarget => operateTarget; public ItemComponent Component => component; - public override float GetPriority() + public ItemComponent Component => component; + + public override float GetPriority(AIObjectiveManager objectiveManager) { - if (component.Item.ConditionPercentage <= 0) { return 0; } + if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } if (objectiveManager.CurrentOrder == this) { return AIObjectiveManager.OrderPriority; } - 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); + return base.GetPriority(objectiveManager); } public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1) @@ -46,131 +58,55 @@ namespace Barotrauma this.requireEquip = requireEquip; this.operateTarget = operateTarget; this.useController = useController; + if (useController) { - controller = component.Item.GetConnectedComponents().FirstOrDefault(); + var controllers = component.Item.GetConnectedComponents(); + if (controllers.Any()) controller = controllers[0]; } + + 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.SelectedConstruction != target.Item) + if (character.CurrentHull == target.Item.CurrentHull) { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; - } - if (component.AIOperate(deltaTime, character, this)) - { - isCompleted = true; + if (character.SelectedConstruction != target.Item && target.CanBeSelected) + { + target.Item.TryInteract(character, false, true); + } + if (component.AIOperate(deltaTime, character, this)) + { + isCompleted = true; + } + return; } } - 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 - abandon = true; + canBeCompleted = false; return; } else if (!character.Inventory.Items.Contains(component.Item)) @@ -190,9 +126,11 @@ 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; } @@ -226,8 +164,10 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) { - if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; } - return (operateItem.component == component || otherObjective.Option == Option); + AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem; + if (operateItem == null) 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 c50a70452..5409a82d5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs @@ -1,73 +1,68 @@ 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"; - private IEnumerable pumpList; + public override bool KeepDivingGearOn => true; + private readonly IEnumerable pumpList; - public AIObjectivePumpWater(Character character, AIObjectiveManager objectiveManager, string option, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier, option) { } + 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 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; } - base.FindTargets(); - if (targets.None() && objectiveManager.CurrentOrder == this) + if (option == null) { return; } + foreach (Item item in Item.ItemList) { - character.Speak(TextManager.Get("DialogNoPumps"), null, 3.0f, "nopumps", 30.0f); + 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); + } + } + } } } - 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.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == pump.Item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; } - if (pump.Item.ConditionPercentage <= 0) { return false; } - if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(pump.Item, true)) { 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))); - } - } + 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; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 92088cb5f..a7d634505 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -11,37 +11,37 @@ 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, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) + private float previousCondition = -1; + + public AIObjectiveRepairItem(Character character, Item item) : base(character, "") { Item = item; } - public override float GetPriority() + public override float GetPriority(AIObjectiveManager objectiveManager) { // 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; } - if (Item.CurrentHull == null) { return 0; } - if (Item.CurrentHull.FireSources.Count > 0) { return 0; } - if (Character.CharacterList.Any(c => c.CurrentHull == Item.CurrentHull && !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 - 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 / Item.MaxCondition); + float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition); float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character))); float isSelected = character.SelectedConstruction == Item ? 50 : 0; - 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)); + float baseLevel = Math.Max(Priority + isSelected, 1); + return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100); } + public override bool CanBeCompleted => !abandon; + public override bool IsCompleted() { bool isCompleted = Item.IsFullCondition; @@ -59,6 +59,15 @@ 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)) @@ -68,14 +77,12 @@ namespace Barotrauma { foreach (RelatedItem requiredItem in kvp.Value) { - AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, objectiveManager, true)); + AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, 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(); @@ -107,31 +114,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("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f); + character?.Speak(TextManager.Get("DialogRepairFailed").Replace("[itemname]", Item.Name), null, 0.0f, "repairfailed", 10.0f); } } repairable.CurrentFixer = abandon && repairable.CurrentFixer == character ? null : character; break; } } - else + else if (goToObjective == null || goToObjective.Target != Item) { - // 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)); + 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); } } + 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 8ad0bf572..2b2afd3f5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Barotrauma.Items.Components; using Barotrauma.Extensions; @@ -8,25 +7,18 @@ 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, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } + public AIObjectiveRepairItems(Character character) : base(character, "") { } + // 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) @@ -46,32 +38,37 @@ namespace Barotrauma protected override bool Filter(Item item) { - 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 (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { return false; } - if (item.Repairables.None()) { 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; } - foreach (Repairable repairable in item.Repairables) + bool ignore = ignoreList.Contains(item) || item.IsFullCondition; + if (!ignore) { - if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold) + 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 { - return false; - } - else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character)) - { - return false; + 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; } + } + } } } - return true; + return ignore; } - protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage); + protected override float Average(Item item) => 100 - item.ConditionPercentage; protected override IEnumerable GetList() => Item.ItemList; - protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item, objectiveManager, PriorityModifier); + protected override AIObjective ObjectiveConstructor(Item item) => new AIObjectiveRepairItem(character, item); } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index c3b15a3e5..72004ee14 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -7,11 +7,11 @@ using System.Linq; namespace Barotrauma { - // TODO: refactor class AIObjectiveRescue : AIObjective { public override string DebugTag => "rescue"; public override bool ForceRun => true; + public override bool KeepDivingGearOn => true; const float TreatmentDelay = 0.5f; @@ -37,8 +37,8 @@ namespace Barotrauma } } - public AIObjectiveRescue(Character character, Character targetCharacter, AIObjectiveManager objectiveManager, float priorityModifier = 1) - : base(character, objectiveManager, priorityModifier) + public AIObjectiveRescue(Character character, Character targetCharacter) + : base(character, "") { Debug.Assert(character != targetCharacter); this.targetCharacter = targetCharacter; @@ -59,7 +59,7 @@ namespace Barotrauma { if (!character.CanInteractWith(targetCharacter)) { - AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character, objectiveManager)); + AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character)); } else { @@ -68,7 +68,7 @@ namespace Barotrauma } else { - AddSubObjective(new AIObjectiveFindSafety(character, objectiveManager)); + AddSubObjective(new AIObjectiveFindSafety(character)); } return; } @@ -76,7 +76,7 @@ namespace Barotrauma //target not in water -> we can start applying treatment if (!character.CanInteractWith(targetCharacter)) { - AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character, objectiveManager)); + AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character)); } else { @@ -93,7 +93,6 @@ namespace Barotrauma } } - // TODO: remove and replace with the priority system protected override bool ShouldInterruptSubObjective(AIObjective subObjective) { if (subObjective is AIObjectiveFindSafety) @@ -188,7 +187,7 @@ namespace Barotrauma } character.DeselectCharacter(); - AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), objectiveManager, equip: true)); + AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true)); } character.AnimController.Anim = AnimController.Animation.CPR; @@ -230,9 +229,8 @@ namespace Barotrauma return isCompleted || targetCharacter.IsDead; } - public override float GetPriority() + public override float GetPriority(AIObjectiveManager objectiveManager) { - // TODO: review if (targetCharacter.AnimController.CurrentHull == null || targetCharacter.IsDead) { return 0.0f; } Vector2 diff = targetCharacter.WorldPosition - character.WorldPosition; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index c1f9fcf06..44e11b40a 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -4,19 +4,18 @@ using Barotrauma.Extensions; namespace Barotrauma { - class AIObjectiveRescueAll : AIObjectiveLoop + class AIObjectiveRescueAll : AIObjective { public override string DebugTag => "rescue all"; + 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; - 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() + public AIObjectiveRescueAll(Character character) : base (character, "") { base.FindTargets(); if (targets.None() && objectiveManager.CurrentOrder == this) @@ -36,14 +35,39 @@ namespace Barotrauma return true; } - protected override IEnumerable GetList() => Character.CharacterList; + 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 AIObjective ObjectiveConstructor(Character target) => new AIObjectiveRescue(character, target, objectiveManager, PriorityModifier); + //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; + } + + 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 float TargetEvaluation() { // TODO: sorting criteria return 100; } + + 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 0073b1bab..dda8d24cb 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Order.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Order.cs @@ -64,7 +64,8 @@ namespace Barotrauma private Order(XElement orderElement) { AITag = orderElement.GetAttributeString("aitag", ""); - Name = TextManager.Get("OrderName." + AITag, true) ?? "Name not found"; + Name = TextManager.Get("OrderName." + AITag, true) ?? orderElement.GetAttributeString("name", "Name not found"); + DoingText = TextManager.Get("OrderNameDoing." + AITag, true) ?? orderElement.GetAttributeString("doingtext", ""); string targetItemType = orderElement.GetAttributeString("targetitemtype", ""); if (!string.IsNullOrWhiteSpace(targetItemType)) @@ -126,6 +127,7 @@ namespace Barotrauma Name = prefab.Name; AITag = prefab.AITag; + DoingText = prefab.DoingText; ItemComponentType = prefab.ItemComponentType; Options = prefab.Options; SymbolSprite = prefab.SymbolSprite; @@ -140,7 +142,8 @@ namespace Barotrauma { if (UseController) { - ConnectedController = targetItem.Item.GetConnectedComponents().FirstOrDefault(); + var controllers = targetItem.Item.GetConnectedComponents(); + if (controllers.Count > 0) ConnectedController = controllers[0]; } TargetEntity = targetItem.Item; TargetItemComponent = targetItem; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs index 7d09d1813..487dd8479 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/PathFinder.cs @@ -233,7 +233,7 @@ namespace Barotrauma //} var body = Submarine.PickBody(end, node.Waypoint.SimPosition, null, - Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs ); + Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform); if (body != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs index 7261ab890..d7329dc9d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/Jobs/JobPrefab.cs @@ -24,10 +24,7 @@ namespace Barotrauma { public static List List; - public readonly XElement Items; - public readonly List ItemNames = new List(); - public readonly List Skills = new List(); - public readonly List AutomaticOrders = new List(); + public List Skills; [Serialize("1,1,1,1", false)] public Color UIColor @@ -121,6 +118,14 @@ 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 f43235f81..cdc0b10cb 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); - var objectiveManager = (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager; - objectiveManager?.SetOrder(new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, objectiveManager, repeat: true, getDivingGearIfNeeded: false)); + (spawnedCharacter.AIController as HumanAIController)?.ObjectiveManager.SetOrder( + new AIObjectiveGoTo(watchmanSpawnpoint, spawnedCharacter, repeat: true, getDivingGearIfNeeded: false)); if (watchmanJob != null) { spawnedCharacter.GiveJobItems(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs index 6f3068515..73b362727 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Door.cs @@ -26,8 +26,6 @@ namespace Barotrauma.Items.Components private bool autoOrientGap; private bool isStuck; - public bool IsStuck => isStuck; - private float resetPredictionTimer; private Rectangle doorRect; @@ -229,7 +227,8 @@ namespace Barotrauma.Items.Components { msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText); } - return isBroken || base.HasRequiredItems(character, addMessage, msg); + if (isBroken) { return true; } + return 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 b24f8bf79..188d307c5 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 * 3.0f) { return true; } + if (dist > Range * 5.0f) return true; // TODO: use the collider size? if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController && @@ -344,19 +344,7 @@ namespace Barotrauma.Items.Components character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist); if (item.RequireAimToUse) { - 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); - } + 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); @@ -412,8 +400,7 @@ namespace Barotrauma.Items.Components object value = property.GetValue(target); if (value.GetType() == typeof(float)) { - 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); } + user.UpdateHUDProgressBar(door, door.Item.WorldPosition, (float)value / 100, Color.DarkGray * 0.5f, Color.White); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index ef697cb74..f91f36eba 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -715,6 +715,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 427f81c40..ee38171f6 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(), objective.objectiveManager) + var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent()) { 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 a251165ce..2aafee648 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -212,33 +212,6 @@ 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 f411cf341..9e116b9b6 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 * aiRechargeTargetRatio) > 0.05f) + if (Math.Abs(rechargeSpeed - maxRechargeSpeed * 0.5f) > 0.05f) { #if SERVER item.CreateServerEvent(this); #endif - RechargeSpeed = maxRechargeSpeed * aiRechargeTargetRatio; + RechargeSpeed = maxRechargeSpeed * 0.5f; #if CLIENT rechargeSpeedSlider.BarScroll = RechargeSpeed / Math.Max(maxRechargeSpeed, 1.0f); #endif diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs index 9019d488a..a91f7103f 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, objective.objectiveManager, option: "", requireEquip: false)); + objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", 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, objective.objectiveManager); + var removeShellObjective = new AIObjectiveDecontainItem(character, container.Inventory.Items[0], container); objective.AddSubObjective(removeShellObjective); } - var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container, objective.objectiveManager); + var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container); 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 72f1fca90..2d165e58c 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1552,6 +1552,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; } @@ -1688,9 +1692,6 @@ 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(); @@ -1743,9 +1744,6 @@ namespace Barotrauma } } - /// - /// 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(); @@ -2084,6 +2082,29 @@ 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 c1016d532..c533a8f74 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -436,6 +436,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/Sprite/Sprite.cs b/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs index 064b5d1e2..26059513f 100644 --- a/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs +++ b/Barotrauma/BarotraumaShared/Source/Sprite/Sprite.cs @@ -109,6 +109,11 @@ namespace Barotrauma if (!ParseTexturePath(path, file)) { return; } Name = SourceElement.GetAttributeString("name", null); Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero); + var overrideElement = GetLocalizationOverrideElement(); + if (overrideElement != null && overrideElement.Attribute("sourcerect") != null) + { + sourceVector = overrideElement.GetAttributeVector4("sourcerect", Vector4.Zero); + } preMultipliedAlpha = preMultiplyAlpha ?? SourceElement.GetAttributeBool("premultiplyalpha", true); bool shouldReturn = false; if (!lazyLoad) @@ -240,8 +245,12 @@ namespace Barotrauma } if (SourceElement != null) { - Vector4 sourceVector = SourceElement.GetAttributeVector4("sourcerect", Vector4.Zero); - sourceRect = new Rectangle((int)sourceVector.X, (int)sourceVector.Y, (int)sourceVector.Z, (int)sourceVector.W); + sourceRect = SourceElement.GetAttributeRect("sourcerect", Rectangle.Empty); + var overrideElement = GetLocalizationOverrideElement(); + if (overrideElement != null && overrideElement.Attribute("sourcerect") != null) + { + sourceRect = overrideElement.GetAttributeRect("sourcerect", Rectangle.Empty); + } size = SourceElement.GetAttributeVector2("size", Vector2.One); size.X *= sourceRect.Width; size.Y *= sourceRect.Height; @@ -256,6 +265,12 @@ namespace Barotrauma if (file == "") { file = SourceElement.GetAttributeString("texture", ""); + var overrideElement = GetLocalizationOverrideElement(); + if (overrideElement != null) + { + string overrideFile = overrideElement.GetAttributeString("texture", ""); + if (!string.IsNullOrEmpty(overrideFile)) { file = overrideFile; } + } } if (file == "") { @@ -273,6 +288,22 @@ namespace Barotrauma } return true; } + + private XElement GetLocalizationOverrideElement() + { + foreach (XElement subElement in SourceElement.Elements()) + { + if (subElement.Name.ToString().ToLowerInvariant() == "override") + { + string language = subElement.GetAttributeString("language", ""); + if (TextManager.Language.ToLower() == language.ToLower()) + { + return subElement; + } + } + } + return null; + } } } diff --git a/Barotrauma/BarotraumaShared/Source/TextManager.cs b/Barotrauma/BarotraumaShared/Source/TextManager.cs index 466c12c65..27d1966d0 100644 --- a/Barotrauma/BarotraumaShared/Source/TextManager.cs +++ b/Barotrauma/BarotraumaShared/Source/TextManager.cs @@ -16,14 +16,6 @@ namespace Barotrauma private static string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" }; public static string Language; - public static bool NoWhiteSpace - { - get - { - if (!textPacks.ContainsKey(Language)) { return false; } - return textPacks[Language].Any(t => t.NoWhiteSpace); - } - } private static HashSet availableLanguages = new HashSet(); public static IEnumerable AvailableLanguages @@ -87,7 +79,7 @@ namespace Barotrauma } } - public static string Get(string textTag, bool returnNull = false) + public static string Get(string textTag, bool returnNull = false, string fallBackTag = null) { if (!textPacks.ContainsKey(Language)) { @@ -102,7 +94,16 @@ namespace Barotrauma foreach (TextPack textPack in textPacks[Language]) { string text = textPack.Get(textTag); - if (text != null) return text; + if (text != null) { return text; } + } + + if (!string.IsNullOrEmpty(fallBackTag)) + { + foreach (TextPack textPack in textPacks[Language]) + { + string text = textPack.Get(fallBackTag); + if (text != null) { return text; } + } } //if text was not found and we're using a language other than English, see if we can find an English version diff --git a/Barotrauma/BarotraumaShared/Source/TextPack.cs b/Barotrauma/BarotraumaShared/Source/TextPack.cs index a4105c57b..18b0170d8 100644 --- a/Barotrauma/BarotraumaShared/Source/TextPack.cs +++ b/Barotrauma/BarotraumaShared/Source/TextPack.cs @@ -11,9 +11,7 @@ namespace Barotrauma public readonly string Language; private Dictionary> texts; - - public readonly bool NoWhiteSpace; - + private readonly string filePath; public TextPack(string filePath) @@ -25,7 +23,6 @@ namespace Barotrauma if (doc == null || doc.Root == null) return; Language = doc.Root.GetAttributeString("language", "Unknown"); - NoWhiteSpace = doc.Root.GetAttributeBool("nowhitespace", false); foreach (XElement subElement in doc.Root.Elements()) { diff --git a/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub b/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub index 8451d5027..0963b9725 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub and b/Barotrauma/BarotraumaShared/Submarines/Bunyip.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub index 63845bb03..1faf47a55 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 51d51cc70..751bb85a9 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Humpback.sub and b/Barotrauma/BarotraumaShared/Submarines/Humpback.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Orca.sub b/Barotrauma/BarotraumaShared/Submarines/Orca.sub index b89be561c..45b257e88 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Orca.sub and b/Barotrauma/BarotraumaShared/Submarines/Orca.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Remora.sub b/Barotrauma/BarotraumaShared/Submarines/Remora.sub index c874b9710..b11988da2 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Remora.sub and b/Barotrauma/BarotraumaShared/Submarines/Remora.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub index db200977b..2f11785db 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub and b/Barotrauma/BarotraumaShared/Submarines/RemoraDrone.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub index 59dc7cc11..2c29b9f4a 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Selkie.sub and b/Barotrauma/BarotraumaShared/Submarines/Selkie.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub index 0a18b4090..1123d84b8 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Typhon.sub and b/Barotrauma/BarotraumaShared/Submarines/Typhon.sub differ diff --git a/Barotrauma/BarotraumaShared/Submarines/Venture.sub b/Barotrauma/BarotraumaShared/Submarines/Venture.sub index f06df8916..7fe946301 100644 Binary files a/Barotrauma/BarotraumaShared/Submarines/Venture.sub and b/Barotrauma/BarotraumaShared/Submarines/Venture.sub differ