From 5f610caadd950f404c87b34d193e5ece362175e3 Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Thu, 16 May 2019 05:31:48 +0300 Subject: [PATCH] (f0837188e) Bots now target only reported targets when the objective is not an order. --- .../Source/Characters/Animation/Ragdoll.cs | 83 ------ .../Source/GameSession/CrewManager.cs | 248 +++--------------- .../Source/Screens/CampaignSetupUI.cs | 2 + .../Source/Characters/AI/HumanAIController.cs | 107 ++++++-- .../Characters/AI/Objectives/AIObjective.cs | 7 + .../Objectives/AIObjectiveChargeBatteries.cs | 1 + .../Objectives/AIObjectiveExtinguishFires.cs | 3 +- .../AI/Objectives/AIObjectiveFixLeaks.cs | 14 +- .../AI/Objectives/AIObjectiveIdle.cs | 22 ++ .../AI/Objectives/AIObjectiveLoop.cs | 28 +- .../AI/Objectives/AIObjectivePumpWater.cs | 1 + .../AI/Objectives/AIObjectiveRepairItems.cs | 40 ++- .../Components/Machines/Deconstructor.cs | 19 ++ .../Items/Components/Machines/Steering.cs | 27 ++ .../BarotraumaShared/Source/Items/Item.cs | 4 + .../BarotraumaShared/Source/Map/Hull.cs | 19 ++ 16 files changed, 272 insertions(+), 353 deletions(-) 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/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 8ec9ebd84..e9bd2ba83 100644 --- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs +++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs @@ -74,17 +74,12 @@ namespace Barotrauma public CrewManager(XElement element, bool isSinglePlayer) : this(isSinglePlayer) { - if (GameMain.Client != null) + if (!isSinglePlayer) { - //let the server create random conversations in MP + DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace); return; } - List availableSpeakers = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - !c.IsDead && - c.SpeechImpediment <= 100.0f); - pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); - } + if (string.IsNullOrEmpty(text)) { return; } var characterInfo = new CharacterInfo(subElement); characterInfos.Add(characterInfo); @@ -95,6 +90,7 @@ namespace Barotrauma break; } } + ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); } partial void InitProjectSpecific() @@ -199,6 +195,7 @@ namespace Barotrauma if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; SetCharacterOrder(null, order, null, Character.Controlled); HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull); + HumanAIController.RefreshTargets(Character.Controlled, order, Character.Controlled.CurrentHull); return true; }, UserData = order, @@ -242,24 +239,27 @@ namespace Barotrauma public IEnumerable GetCharacters() { - if (characterInfos.Contains(characterInfo)) - { - DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace); - return; - } + if (character?.Inventory == null) return null; - characterInfos.Add(characterInfo); + var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent() != null); + if (radioItem == null) return null; + if (requireEquipped && !character.HasEquippedItem(radioItem)) return null; + + return radioItem.GetComponent(); } public IEnumerable GetCharacterInfos() { - if (character == null) + if (GameMain.Client != null) { - DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace); + //let the server create random conversations in MP return; } - characters.Remove(character); - if (removeInfo) characterInfos.Remove(character.Info); + List availableSpeakers = Character.CharacterList.FindAll(c => + c.AIController is HumanAIController && + !c.IsDead && + c.SpeechImpediment <= 100.0f); + pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); } public void AddCharacter(Character character) @@ -633,183 +633,9 @@ namespace Barotrauma { characterListBox.BarScroll = roundedPos; } - var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox") - { - UserData = character, - Color = frame.Color, - SelectedColor = frame.SelectedColor, - HoverColor = frame.HoverColor, - ToolTip = characterToolTip - }; - - var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - "GUISoundIcon") - { - UserData = "soundicon", - CanBeFocused = false, - Visible = true - }; - soundIcon.Color = new Color(soundIcon.Color, 0.0f); - new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) }, - "GUISoundIconDisabled") - { - UserData = "soundicondisabled", - CanBeFocused = true, - Visible = false - }; - - if (isSinglePlayer) - { - characterArea.OnClicked = CharacterClicked; - } - else - { - characterArea.CanBeFocused = false; - characterArea.CanBeSelected = false; - } - - var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft), - onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())) - { - CanBeFocused = false, - HoverColor = Color.White, - SelectedColor = Color.White, - ToolTip = characterToolTip - }; - - var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height), - characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) }, - character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true) - { - Color = frame.Color, - HoverColor = Color.Transparent, - SelectedColor = Color.Transparent, - CanBeFocused = false, - ToolTip = characterToolTip, - AutoScale = true - }; - - //---------------- order buttons ---------------- - - var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform) - { AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) }, - isHorizontal: true, childAnchor: Anchor.CenterLeft) - { - AbsoluteSpacing = (int)(10 * GUI.Scale), - UserData = "orderbuttons", - CanBeFocused = false - }; - - //listbox for holding the orders inappropriate for this character - //(so we can easily toggle their visibility) - var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null) - { - ScrollBarEnabled = false, - ScrollBarVisible = false, - Enabled = false, - Spacing = spacing, - ClampMouseRectToParent = false - }; - wrongOrderList.Content.ClampMouseRectToParent = false; - - for (int i = 0; i < orders.Count; i++) - { - var order = orders[i]; - if (order.TargetAllCharacters) continue; - - RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ? - wrongOrderList.Content.RectTransform : - orderButtonFrame.RectTransform; - - var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft), - style: null) - { - UserData = order - }; - - new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow") - { - Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f, - HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f, - PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f, - UserData = "selected", - CanBeFocused = false, - Visible = false - }; - - var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite); - img.Scale = iconSize / (float)img.SourceRect.Width; - img.Color = Color.Lerp(order.Color, frame.Color, 0.5f); - img.ToolTip = order.Name; - img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f); - - btn.OnClicked += (GUIButton button, object userData) => - { - if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false; - - if (btn.GetChildByUserData("selected").Visible) - { - SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled); - } - else - { - if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1) - { - CreateOrderTargetFrame(button, character, order); - } - else - { - SetCharacterOrder(character, order, null, Character.Controlled); - } - } - return true; - }; - btn.UserData = order; - btn.ToolTip = order.Name; - - //divider between different groups of orders - if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1) - { - //TODO: divider sprite - new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton"); - } - } - - var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), - "", style: "UIToggleButton") - { - UserData = "togglewrongorder", - CanBeFocused = false - }; - - wrongOrderList.RectTransform.NonScaledSize = new Point( - wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing), - wrongOrderList.RectTransform.NonScaledSize.Y); - wrongOrderList.RectTransform.SetAsLastChild(); - - new GUIFrame(new RectTransform(new Point( - wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2, - wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform), - style: null) - { - CanBeFocused = false - }; - - //scale to fit the content - orderButtonFrame.RectTransform.NonScaledSize = new Point( - orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing), - orderButtonFrame.RectTransform.NonScaledSize.Y); - - frame.RectTransform.NonScaledSize = new Point( - characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width), - frame.RectTransform.NonScaledSize.Y); - - characterListBox.RectTransform.NonScaledSize = new Point( - characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width, - characterListBox.RectTransform.NonScaledSize.Y); - characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize; - characterListBox.UpdateScrollBarSize(); - return frame; + soundIcon.Visible = !muted && !mutedLocally; + soundIconDisabled.Visible = muted || mutedLocally; + soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally"); } private IEnumerable KillCharacterAnim(GUIComponent component) @@ -953,12 +779,6 @@ namespace Barotrauma } return; } - List availableSpeakers = Character.CharacterList.FindAll(c => - c.AIController is HumanAIController && - !c.IsDead && - c.SpeechImpediment <= 100.0f); - pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers)); - } character.SetOrder(order, option, orderGiver, speak: orderGiver != character); if (IsSinglePlayer) @@ -1016,23 +836,19 @@ namespace Barotrauma } } } - - character.SetOrder(order, option, orderGiver, speak: orderGiver != character); - if (IsSinglePlayer) + //only one target (or an order with no particular targets), just show options + else { - orderGiver?.Speak( - order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); - } - else if (orderGiver != null) - { - OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver); - if (GameMain.Client != null) + orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas) + { AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) }, + isHorizontal: true, childAnchor: Anchor.BottomLeft) { - GameMain.Client.SendChatMessage(msg); - } - } - DisplayCharacterOrder(character, order); - } + UserData = character, + Stretch = true + }; + //line connecting the order button to the option buttons + //TODO: sprite + new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null); /// /// Create the UI panel that's used to select the target and options for a given order diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs index 6f471dc2e..eb5e878b5 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -142,6 +142,8 @@ namespace Barotrauma private GUILayoutGroup subPreviewContainer; + private GUILayoutGroup subPreviewContainer; + private GUIButton loadGameButton; public Action StartNewGame; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs index 712d50070..0af728fe8 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs @@ -254,41 +254,56 @@ namespace Barotrauma Order newOrder = null; if (Character.CurrentHull != null) { - if (Character.CurrentHull.FireSources.Count > 0) + if (AIObjectiveExtinguishFires.IsValidTarget(Character.CurrentHull)) { var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire"); newOrder = new Order(orderPrefab, Character.CurrentHull, null); + AddTargets(Character, Character.CurrentHull); } - if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f)) + foreach (var gap in Character.CurrentHull.ConnectedGaps) { - var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach"); - newOrder = new Order(orderPrefab, Character.CurrentHull, null); + if (AIObjectiveFixLeaks.IsValidTarget(gap)) + { + 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 (AIObjectiveRepairItems.IsValidTarget(item)) + { + var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbrokendevices"); + newOrder = new Order(orderPrefab, Character.CurrentHull, item.Repairables?.FirstOrDefault()); + AddTargets(Character, item); + } } foreach (Character c in Character.CharacterList) { - if (c.CurrentHull == Character.CurrentHull && !c.IsDead && - (c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC))) + if (AIObjectiveFightIntruders.IsValidTarget(c)) { var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); newOrder = new Order(orderPrefab, Character.CurrentHull, null); + AddTargets(Character, c); } } - } - 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); + // TODO: the objective needs to inherit AIObjectiveLoop + 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); + } } 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); } } } @@ -445,12 +460,9 @@ namespace Barotrauma { foreach (var c in Character.CharacterList) { - if (c.TeamID == character.TeamID) + if (c.AIController is HumanAIController humanAi && humanAi.IsFriendly(character)) { - if (c.AIController is HumanAIController humanAi) - { - humanAi.RefreshHullSafety(hull); - } + humanAi.RefreshHullSafety(hull); } } } @@ -467,6 +479,62 @@ 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 (AIObjectiveRepairItems.IsValidTarget(item)) + { + AddTargets(character, item); + } + } + break; + case "reportintruders": + foreach (var enemy in Character.CharacterList) + { + if (AIObjectiveFightIntruders.IsValidTarget(enemy)) + { + AddTargets(character, enemy); + } + } + break; + case "requestfirstaid": + // TODO + default: + 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) + { + var objective = humanAI.ObjectiveManager.GetObjective(); + if (objective != null) + { + objective.AddTarget(target); + } + } + } + } + public float GetHullSafety(Hull hull) { if (hull == null) { return 0; } @@ -500,7 +568,8 @@ namespace Barotrauma return MathHelper.Clamp(safety * 100, 0, 100); } + public bool IsFriendly(Character other) => IsFriendly(Character, other); // TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species. - public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName; + public static bool IsFriendly(Character me, Character other) => other.TeamID == me.TeamID && other.SpeciesName == me.SpeciesName; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index b9a12922c..39fd43351 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -35,6 +35,8 @@ namespace Barotrauma public IEnumerable SubObjectives => subObjectives; public AIObjective CurrentSubObjective { get; private set; } + public event Action Completed; + protected HumanAIController HumanAIController => character.AIController as HumanAIController; protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering; protected SteeringManager SteeringManager => HumanAIController.SteeringManager; @@ -94,7 +96,12 @@ namespace Barotrauma return; } + bool wasCompleted = IsCompleted(); Act(deltaTime); + if (!wasCompleted && IsCompleted()) + { + Completed?.Invoke(); + } } public void AddSubObjective(AIObjective objective) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs index 1cb024d06..77db2a809 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveChargeBatteries.cs @@ -31,6 +31,7 @@ namespace Barotrauma 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; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index 33cdb2ccf..9dd542361 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -23,11 +23,12 @@ namespace Barotrauma 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; } + //if (Character.CharacterList.Any(c => c.CurrentHull == target && !HumanAIController.IsFriendly(c))) { return false; } return true; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs index f6afcf65e..61c876f6b 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs @@ -25,14 +25,12 @@ namespace Barotrauma protected override bool Filter(Gap gap) { - bool ignore = gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null); - if (!ignore) - { - if (gap.Submarine == null) { ignore = true; } - else if (gap.Submarine.TeamID != character.TeamID) { ignore = true; } - else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(gap, true)) { ignore = true; } - } - return !ignore; + if (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; } public static float GetLeakSeverity(Gap leak) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 835fe8cb0..e0f2083d5 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -73,6 +73,21 @@ namespace Barotrauma } } + public override void Update(float deltaTime) + { + if (objectiveManager.CurrentObjective == this) + { + if (randomTimer > 0) + { + randomTimer -= deltaTime; + } + else + { + SetRandom(); + } + } + } + public override bool IsCompleted() => false; public override bool CanBeCompleted => true; @@ -94,6 +109,13 @@ namespace Barotrauma { character.SelectedConstruction = null; } + if (!character.IsClimbing) + { + character.SelectedConstruction = null; + } + + bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || + (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull))); diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs index 504a751eb..a1a9ee09d 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -7,19 +7,34 @@ namespace Barotrauma { abstract class AIObjectiveLoop : AIObjective { - protected List targets = new List(); + protected HashSet targets = new HashSet(); protected Dictionary objectives = new Dictionary(); protected HashSet ignoreList = new HashSet(); private float ignoreListTimer; 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) + { + 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(); } @@ -118,8 +133,14 @@ namespace Barotrauma { foreach (T item in GetList()) { + if (objectiveManager.CurrentOrder != this) + { + // Battery or pump states cannot be reported 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(item)) { continue; } + } if (!Filter(item)) { continue; } - if (!ignoreList.Contains(item) && !targets.Contains(item)) + if (!ignoreList.Contains(item)) { targets.Add(item); } @@ -133,6 +154,7 @@ namespace Barotrauma if (!objectives.TryGetValue(target, out AIObjective objective)) { objective = ObjectiveConstructor(target); + objective.Completed += () => ReportedTargets.Remove(target); objectives.Add(target, objective); AddSubObjective(objective); } @@ -148,5 +170,7 @@ namespace Barotrauma 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/AIObjectivePumpWater.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs index 7bc550c9c..1875d11d1 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectivePumpWater.cs @@ -30,6 +30,7 @@ namespace Barotrauma 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; } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs index 7a6a7403e..a41d9c3e2 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItems.cs @@ -47,34 +47,26 @@ namespace Barotrauma protected override bool Filter(Item item) { - bool ignore = item.IsFullCondition; - if (!ignore) + 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 || Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { return false; } + foreach (Repairable repairable in item.Repairables) { - if (item.Submarine == null) { ignore = true; } - else if (item.Submarine.TeamID != character.TeamID) { ignore = true; } - else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { ignore = true; } - else + if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold) { - if (item.Repairables.None()) { ignore = true; } - else if (item.CurrentHull != null || item.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == item.CurrentHull && !HumanAIController.IsFriendly(c))) { 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 false; + } + else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character)) + { + return false; } } - return !ignore; + return true; } protected override float TargetEvaluation() => targets.Max(t => 100 - t.ConditionPercentage); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index f91f36eba..e8809c408 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -734,6 +734,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/Steering.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs index 2aafee648..a251165ce 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Steering.cs @@ -212,6 +212,33 @@ namespace Barotrauma.Items.Components } } + public Vector2? PosToMaintain + { + get { return posToMaintain; } + set { posToMaintain = value; } + } + + struct ObstacleDebugInfo + { + public Vector2 Point1; + public Vector2 Point2; + + public Vector2? Intersection; + + public float Dot; + + public Vector2 AvoidStrength; + + public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength) + { + Point1 = edge.Point1; + Point2 = edge.Point2; + Intersection = intersection; + Dot = dot; + AvoidStrength = avoidStrength; + } + } + //edge point 1, edge point 2, avoid strength private List debugDrawObstacles = new List(); diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 998c132fc..e7d735c20 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1444,6 +1444,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; } diff --git a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs index f47eb56e0..990d2e0cc 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -550,6 +550,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