diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs index 8f9276859..bcf133b30 100644 --- a/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/Source/Characters/Animation/Ragdoll.cs @@ -152,6 +152,32 @@ namespace Barotrauma } + if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); + character.MemState.Clear(); + } + } + + partial void ImpactProjSpecific(float impact, Body body) + { + float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f); + + if (body.UserData is Limb limb && character.Stun <= 0f) + { + if (impact > 3.0f) { PlayImpactSound(limb); } + } + else if (body.UserData is Limb || body == Collider.FarseerBody) + { + if (!character.IsRemotePlayer && impact > ImpactTolerance) + { + SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider); + } + } + if (Character.Controlled == character) + { + GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f); + } + } + if (character.MemState.Count < 1) return; overrideTargetMovement = Vector2.Zero; diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs index 8b52b1fd9..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() @@ -243,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) @@ -634,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) @@ -954,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) @@ -1017,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 7406205ad..f1f3bb3a3 100644 --- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs +++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignSetupUI.cs @@ -150,6 +150,8 @@ namespace Barotrauma private GUILayoutGroup subPreviewContainer; + private GUILayoutGroup subPreviewContainer; + private GUIButton loadGameButton; public Action StartNewGame; diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs index c6d31f827..d6abae37e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs @@ -104,6 +104,7 @@ namespace Barotrauma } } + // TODO: go through AIOperate methods where subobjectives are added and ensure that they add the subobjectives correctly -> use TryAddSubObjective method instead? public void AddSubObjective(AIObjective objective) { if (subObjectives.Any(o => o.IsDuplicate(objective))) { return; } @@ -156,10 +157,11 @@ namespace Barotrauma } /// - /// Checks if the objective already is created and added in subobjectives. If not, creates it. Handles objectives that cannot be completed. + /// 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(T objective, Func constructor, Action onAbandon = null) where T : AIObjective + protected bool TryAddSubObjective(ref T objective, Func constructor, Action onAbandon = null) where T : AIObjective { if (objective != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs index f176ef135..3cbc7861f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -44,12 +44,17 @@ 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 Hull retreatTarget; private AIObjectiveGoTo retreatObjective; - private AIObjectiveFindSafety findSafety; private AIObjectiveGoTo followTargetObjective; + private Hull retreatTarget; private float coolDownTimer; public enum CombatMode @@ -105,17 +110,13 @@ namespace Barotrauma { Mode = CombatMode.Retreat; } - else if (Equip(deltaTime)) + if (Equip()) { if (Reload(deltaTime)) { Attack(deltaTime); } } - else - { - Mode = CombatMode.Retreat; - } break; case CombatMode.Retreat: break; @@ -140,9 +141,6 @@ namespace Barotrauma } } - private HashSet rangedWeapons = new HashSet(); - private HashSet meleeWeapons = new HashSet(); - private readonly HashSet adHocWeapons = new HashSet(); private Item GetWeapon() { rangedWeapons.Clear(); @@ -150,7 +148,6 @@ namespace Barotrauma adHocWeapons.Clear(); Item weapon = null; _weaponComponent = null; - foreach (var item in character.Inventory.Items) { if (item == null) { continue; } @@ -220,7 +217,7 @@ namespace Barotrauma } } - private bool Equip(float deltaTime) + private bool Equip() { if (!character.SelectedItems.Contains(Weapon)) { @@ -231,7 +228,7 @@ namespace Barotrauma } else { - //couldn't equip the item -> escape + Mode = CombatMode.Retreat; return false; } } @@ -370,7 +367,7 @@ namespace Barotrauma { myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody); } - var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking; + var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall; var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories); if (pickedBody != null) { @@ -410,7 +407,11 @@ namespace Barotrauma return completed; } - public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted); + 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 IsDuplicate(AIObjective otherObjective) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs index dfd8b5477..ebb59710e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveContainItem.cs @@ -9,19 +9,16 @@ 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; - - private bool isCompleted; - - public string[] ignoredContainerIdentifiers; - public Func GetItemPriority; + public int MinContainedAmount = 1; + public string[] ignoredContainerIdentifiers; + + //can either be a tag or an identifier + private readonly string[] itemIdentifiers; + private readonly ItemContainer container; + + private bool isCompleted; private AIObjectiveGetItem getItemObjective; private AIObjectiveGoTo goToObjective; @@ -44,28 +41,16 @@ namespace Barotrauma public override bool IsCompleted() { - if (isCompleted) return true; - + if (isCompleted) { return true; } int containedItemCount = 0; foreach (Item item in container.Inventory.Items) { - if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++; - } - - return containedItemCount >= MinContainedAmount; - } - - public override bool CanBeCompleted - { - get - { - if (goToObjective != null) + if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { - return goToObjective.CanBeCompleted; + containedItemCount++; } - - return getItemObjective == null || getItemObjective.CanBeCompleted; } + return containedItemCount >= MinContainedAmount; } public override float GetPriority() @@ -74,69 +59,66 @@ namespace Barotrauma { return AIObjectiveManager.OrderPriority; } - return 1.0f; } protected override void Act(float deltaTime) { - if (isCompleted) return; - + if (isCompleted) { return; } //get the item that should be contained Item itemToContain = null; foreach (string identifier in itemIdentifiers) { itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier); - if (itemToContain != null && itemToContain.Condition > 0.0f) break; - } - + if (itemToContain != null && itemToContain.Condition > 0.0f) { break; } + } if (itemToContain == null) { - getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager) - { - GetItemPriority = GetItemPriority, - ignoredContainerIdentifiers = ignoredContainerIdentifiers - }; - AddSubObjective(getItemObjective); + TryAddSubObjective(ref getItemObjective, () => + new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager) + { + GetItemPriority = GetItemPriority, + ignoredContainerIdentifiers = ignoredContainerIdentifiers + }); return; } - if (container.Item.ParentInventory == character.Inventory) { var containedItems = container.Inventory.Items; //if there's already something in the mask (empty oxygen tank?), drop it var existingItem = containedItems.FirstOrDefault(i => i != null); - if (existingItem != null) existingItem.Drop(character); - + if (existingItem != null) + { + existingItem.Drop(character); + } character.Inventory.RemoveItem(itemToContain); container.Inventory.TryPutItem(itemToContain, null); } else { - if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition))) + if (container.Item.CurrentHull != character.CurrentHull || + (Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))) { - goToObjective = new AIObjectiveGoTo(container.Item, character, objectiveManager); - AddSubObjective(goToObjective); + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager)); return; } container.Combine(itemToContain); } - isCompleted = true; } public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem; - if (objective == null) return false; - if (objective.container != container) return false; - if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false; - + if (!(otherObjective is AIObjectiveContainItem objective)) { return false; } + if (objective.container != container) { return false; } + if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } for (int i = 0; i < itemIdentifiers.Length; i++) { - if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false; + if (objective.itemIdentifiers[i] != itemIdentifiers[i]) + { + return false; + } } - return true; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs index d76307459..43a357106 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveDecontainItem.cs @@ -8,18 +8,15 @@ namespace Barotrauma { public override string DebugTag => "decontain item"; - //can either be a tag or an identifier - private string[] itemIdentifiers; - - private ItemContainer container; - - private bool isCompleted; - public Func GetItemPriority; - private AIObjectiveGetItem getItemObjective; + //can either be a tag or an identifier + private readonly string[] itemIdentifiers; + private readonly ItemContainer container; + private readonly Item targetItem; + private AIObjectiveGoTo goToObjective; - private Item targetItem; + private bool isCompleted; public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) @@ -40,27 +37,10 @@ namespace Barotrauma { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); } - this.container = container; } - public override bool IsCompleted() - { - return isCompleted; - } - - public override bool CanBeCompleted - { - get - { - if (goToObjective != null) - { - return goToObjective.CanBeCompleted; - } - - return getItemObjective == null || getItemObjective.CanBeCompleted; - } - } + public override bool IsCompleted() => isCompleted; public override float GetPriority() { @@ -68,16 +48,13 @@ namespace Barotrauma { 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) { @@ -86,7 +63,7 @@ namespace Barotrauma foreach (string identifier in itemIdentifiers) { itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier); - if (itemToDecontain != null) break; + if (itemToDecontain != null) { break; } } } } @@ -94,38 +71,32 @@ namespace Barotrauma { itemToDecontain = targetItem; } - if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete { isCompleted = true; return; } - if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory) { - if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance - && !container.Item.IsInsideTrigger(character.WorldPosition)) + if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)) { - goToObjective = new AIObjectiveGoTo(container.Item, character, objectiveManager); - AddSubObjective(goToObjective); + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager)); return; } } - itemToDecontain.Drop(character); isCompleted = true; } public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem; - if (decontainItem == null) return false; + if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; } if (decontainItem.itemIdentifiers != null && itemIdentifiers != null) { - if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) return false; + if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++) { - if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) return false; + if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; } } return true; } @@ -133,7 +104,6 @@ namespace Barotrauma { return decontainItem.targetItem == targetItem; } - return false; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index 1d4ef4f42..8621ef49e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -13,12 +13,10 @@ namespace Barotrauma public override bool ForceRun => true; public override bool KeepDivingGearOn => true; - private Hull targetHull; + private readonly Hull targetHull; private AIObjectiveGetItem getExtinguisherObjective; - private AIObjectiveGoTo gotoObjective; - private float useExtinquisherTimer; public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1) @@ -40,111 +38,91 @@ namespace Barotrauma return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1)); } - public override bool IsCompleted() - { - return targetHull.FireSources.Count == 0; - } + public override bool IsCompleted() => targetHull.FireSources.None(); - public override bool IsDuplicate(AIObjective otherObjective) - { - var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire; - return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull; - } - - public override bool CanBeCompleted => - (getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted) && - (gotoObjective == null || gotoObjective.IsCompleted() || gotoObjective.CanBeCompleted); + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull; protected override void Act(float deltaTime) { var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher"); if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem)) { - if (getExtinguisherObjective == null) + TryAddSubObjective(ref getExtinguisherObjective, () => { character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f); - getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true); - } - else - { - getExtinguisherObjective.TryComplete(deltaTime); - } - - return; + return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true); + }); } - - var extinguisher = extinguisherItem.GetComponent(); - if (extinguisher == null) + else { - DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); - return; - } - - foreach (FireSource fs in targetHull.FireSources) - { - bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); - bool move = !inRange; - if (inRange || useExtinquisherTimer > 0.0f) + var extinguisher = extinguisherItem.GetComponent(); + if (extinguisher == null) { - useExtinquisherTimer += deltaTime; - if (useExtinquisherTimer > 2.0f) +#if DEBUG + DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); +#endif + abandon = true; + return; + } + foreach (FireSource fs in targetHull.FireSources) + { + bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); + bool move = !inRange; + if (inRange || useExtinquisherTimer > 0.0f) { - useExtinquisherTimer = 0.0f; - } - character.AIController.SteeringManager.Reset(); - character.CursorPosition = fs.Position; - if (extinguisher.Item.RequireAimToUse) - { - bool isOperatingButtons = false; - if (SteeringManager == PathSteering) + useExtinquisherTimer += deltaTime; + if (useExtinquisherTimer > 2.0f) { - var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor; - if (door != null && !door.IsOpen) + useExtinquisherTimer = 0.0f; + } + character.AIController.SteeringManager.Reset(); + character.CursorPosition = fs.Position; + if (extinguisher.Item.RequireAimToUse) + { + bool isOperatingButtons = false; + if (SteeringManager == PathSteering) { - isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents(true).Any(); + 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); } } - if (!isOperatingButtons) + Limb sightLimb = null; + if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand)) { - character.SetInput(InputType.Aim, false, true); + 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); + } } } - Limb sightLimb = null; - if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand)) + if (move) { - 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); - } + //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, objectiveManager); - } - else - { - gotoObjective.TryComplete(deltaTime); - } - } - break; } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs index b89a9f56a..7867f1fb4 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs @@ -1,6 +1,7 @@ using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -9,8 +10,10 @@ namespace Barotrauma public override string DebugTag => "find diving gear"; public override bool ForceRun => true; - private AIObjective subObjective; - private string gearTag; + private readonly string gearTag; + + private AIObjectiveGetItem getDivingGear; + private AIObjectiveContainItem getOxygen; public override bool IsCompleted() { @@ -21,14 +24,15 @@ namespace Barotrauma { var containedItems = character.Inventory.Items[i].ContainedItems; if (containedItems == null) { continue; } - - var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); - if (oxygenTank != null) { return true; } + return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); } } return false; } + public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100); + public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear; + public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { gearTag = needDivingSuit ? "divingsuit" : "diving"; @@ -39,45 +43,39 @@ namespace Barotrauma var item = character.Inventory.FindItemByTag(gearTag); if (item == null || !character.HasEquippedItem(item)) { - //get a diving mask/suit first - if (!(subObjective is AIObjectiveGetItem)) + TryAddSubObjective(ref getDivingGear, () => { character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); - subObjective = new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true); + return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true); + }); + } + if (getDivingGear != null) { return; } + 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 + foreach (Item containedItem in containedItems) + { + if (containedItem == null) { continue; } + if (containedItem.Condition <= 0.0f) + { + containedItem.Drop(character); } } - else + if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f)) { - var containedItems = item.ContainedItems; - if (containedItems == null) { return; } - //check if there's an oxygen tank in the mask/suit - foreach (Item containedItem in containedItems) - { - if (containedItem == null) { continue; } - if (containedItem.Condition <= 0.0f) - { - containedItem.Drop(character); - } - else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource")) - { - //we've got an oxygen source inside the mask/suit, all good - return; - } - } - if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted()) + TryAddSubObjective(ref getOxygen, () => { character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); - subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent(), objectiveManager); - } - } - if (subObjective != null) - { - subObjective.TryComplete(deltaTime); + return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent(), objectiveManager); + }); } } - - public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted; - public override float GetPriority() => 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 1578f37c0..ef8f87ce6 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs @@ -18,7 +18,7 @@ namespace Barotrauma const float SearchHullInterval = 3.0f; const float clearUnreachableInterval = 30; - public readonly List unreachable = new List(); + public readonly HashSet unreachable = new HashSet(); private float currenthullSafety; private float unreachableClearTimer; @@ -32,44 +32,10 @@ namespace Barotrauma public override bool IsCompleted() => false; public override bool CanBeCompleted => true; - protected override void Act(float deltaTime) + 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, objectiveManager); - } - } - if (divingGearObjective != null) - { - divingGearObjective.TryComplete(deltaTime); - if (divingGearObjective.IsCompleted()) - { - divingGearObjective = null; - Priority = 0; - } - else if (divingGearObjective.CanBeCompleted) - { - // If diving gear objective is active and can be completed, wait for it to complete. - return; - } - else - { - divingGearObjective = null; - // Reduce the timer so that we get a safe hull target faster. - searchHullTimer = Math.Min(1, searchHullTimer); - } - } - - if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) - { - searchHullTimer = Math.Min(1, searchHullTimer); - } - if (unreachableClearTimer > 0) { unreachableClearTimer -= deltaTime; @@ -79,99 +45,116 @@ namespace Barotrauma unreachableClearTimer = clearUnreachableInterval; unreachable.Clear(); } + if (character.CurrentHull == null) + { + currenthullSafety = 0; + Priority = 100; + 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, Math.Min(AIObjectiveManager.OrderPriority + 20, 100)); + } + } + protected override void Act(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) + { + TryAddSubObjective(ref divingGearObjective, + () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager), + onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer)); + } + } + if (divingGearObjective != null) { return; } + // Reset the devotion. + Priority = 0; + if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) + { + searchHullTimer = Math.Min(1, searchHullTimer); + } if (searchHullTimer > 0.0f) { searchHullTimer -= deltaTime; } - else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) + else { + searchHullTimer = SearchHullInterval; var bestHull = FindBestHull(); if (bestHull != null && bestHull != currentHull) { - if (goToObjective != null) + if (goToObjective?.Target != bestHull) { - 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. - goToObjective = new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false) - { - AllowGoingOutside = HumanAIController.HasDivingSuit(character) - }; - } - } - else - { - goToObjective = new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false) - { AllowGoingOutside = HumanAIController.HasDivingSuit(character) - }; - } - } - searchHullTimer = SearchHullInterval; - } - - if (goToObjective != null) - { - 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(); + }, onAbandon: () => unreachable.Add(goToObjective.Target as Hull)); } } - else if (currentHull != null) + 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) { - //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 - 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); + 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); } - - foreach (Character enemy in Character.CharacterList) + } + 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)) { - //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(); - } + 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(); + } } public Hull FindBestHull(IEnumerable ignoredHulls = null) @@ -250,36 +233,5 @@ namespace Barotrauma } return bestHull; } - - public override bool IsDuplicate(AIObjective otherObjective) - { - return (otherObjective is AIObjectiveFindSafety); - } - - public override void Update(float deltaTime) - { - if (character.CurrentHull == null) - { - currenthullSafety = 0; - Priority = 100; - 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, Math.Min(AIObjectiveManager.OrderPriority + 20, 100)); - } - } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs index 1d2d2bea3..b0cffa229 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs @@ -3,6 +3,7 @@ using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Linq; +using Barotrauma.Extensions; namespace Barotrauma { @@ -13,36 +14,31 @@ namespace Barotrauma public override bool KeepDivingGearOn => true; public override bool ForceRun => true; - private readonly Gap leak; + public Gap Leak { get; private set; } private AIObjectiveFindDivingGear findDivingGear; + private AIObjectiveGetItem getWeldingTool; + private AIObjectiveContainItem refuelObjective; private AIObjectiveGoTo gotoObjective; private AIObjectiveOperateItem operateObjective; - - public Gap Leak - { - get { return leak; } - } public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier) { - this.leak = leak; + Leak = leak; } public override bool IsCompleted() { - return leak.Open <= 0.0f || leak.Removed; + return Leak.Open <= 0.0f || Leak.Removed; } - public override bool CanBeCompleted => !abandon && base.CanBeCompleted; - public override float GetPriority() { - if (leak.Open == 0.0f) { return 0.0f; } + if (Leak.Open == 0.0f) { return 0.0f; } // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) - float dist = Math.Abs(character.WorldPosition.X - leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - leak.WorldPosition.Y) * 2.0f; + float 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 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)); @@ -50,58 +46,64 @@ namespace Barotrauma public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveFixLeak fixLeak = otherObjective as AIObjectiveFixLeak; - if (fixLeak == null) return false; - return fixLeak.leak == leak; + if (!(otherObjective is AIObjectiveFixLeak fixLeak)) { return false; } + return fixLeak.Leak == Leak; } protected override void Act(float deltaTime) { - if (!leak.IsRoomToRoom) + if (!Leak.IsRoomToRoom) { - if (findDivingGear == null) - { - findDivingGear = new AIObjectiveFindDivingGear(character, true, objectiveManager); - AddSubObjective(findDivingGear); - } - else if (!findDivingGear.CanBeCompleted) - { - abandon = true; - return; - } + TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager)); + if (findDivingGear != null) { return; } } - var weldingTool = character.Inventory.FindItemByTag("weldingtool"); - if (weldingTool == null) { - AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true)); - return; + TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true)); + if (getWeldingTool != null) { return; } } else { var containedItems = weldingTool.ContainedItems; - if (containedItems == null) return; - - var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f); - if (fuelTank == null) + if (containedItems == null) { - AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent(), objectiveManager)); +#if DEBUG + DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory"); +#endif + abandon = true; return; } + // Drop empty tanks + foreach (Item containedItem in containedItems) + { + if (containedItem == null) { continue; } + if (containedItem.Condition <= 0.0f) + { + containedItem.Drop(character); + } + } + if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f)) + { + TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent(), objectiveManager)); + if (refuelObjective != null) { return; } + } } - var repairTool = weldingTool.GetComponent(); - if (repairTool == null) { return; } - - Vector2 gapDiff = leak.WorldPosition - character.WorldPosition; - + 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 canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach; if (canReach) @@ -115,65 +117,29 @@ namespace Barotrauma { sightLimb = character.AnimController.GetLimb(LimbType.LeftHand); } - canReach = character.CanSeeTarget(leak, sightLimb); + canReach = character.CanSeeTarget(Leak, sightLimb); } - else + if (!canReach) { - if (gotoObjective != null) - { - // Check if the objective is already removed -> completed/impossible - if (!subObjectives.Contains(gotoObjective)) - { - if (!gotoObjective.CanBeCompleted) - { - abandon = true; - } - gotoObjective = null; - return; - } - } - else - { - gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) - { - CloseEnough = reach - }; - if (!subObjectives.Contains(gotoObjective)) - { - AddSubObjective(gotoObjective); - } - } + TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach }); } - if (gotoObjective == null || gotoObjective.IsCompleted()) - { - if (operateObjective == null) - { - operateObjective = new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: leak); - AddSubObjective(operateObjective); - } - else if (!subObjectives.Contains(operateObjective)) - { - operateObjective = null; - } - } + if (gotoObjective != null) { return; } + TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak)); } private Vector2 GetStandPosition() { - Vector2 standPos = leak.Position; - var hull = leak.FlowTargetHull; - - if (hull == null) return standPos; - - if (leak.IsHorizontal) + Vector2 standPos = Leak.Position; + var hull = Leak.FlowTargetHull; + if (hull == null) { return standPos; } + if (Leak.IsHorizontal) { - standPos += Vector2.UnitX * Math.Sign(hull.Position.X - leak.Position.X) * leak.Rect.Width; + standPos += Vector2.UnitX * Math.Sign(hull.Position.X - Leak.Position.X) * Leak.Rect.Width; } else { - standPos += Vector2.UnitY * Math.Sign(hull.Position.Y - leak.Position.Y) * leak.Rect.Height; + standPos += Vector2.UnitY * Math.Sign(hull.Position.Y - Leak.Position.Y) * Leak.Rect.Height; } - return standPos; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs index 3935d25fd..3dac925f0 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -11,6 +11,9 @@ namespace Barotrauma { public override string DebugTag => "get item"; + private readonly bool equip; + private readonly HashSet ignoredItems = new HashSet(); + public Func GetItemPriority; //can be either tags or identifiers @@ -20,12 +23,6 @@ namespace Barotrauma public string[] ignoredContainerIdentifiers; private AIObjectiveGoTo goToObjective; private float currItemPriority; - private bool equip; - - private HashSet ignoredItems = new HashSet(); - - private bool canBeCompleted = true; - public override bool CanBeCompleted => canBeCompleted; public override float GetPriority() { @@ -57,20 +54,15 @@ namespace Barotrauma { itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); } - CheckInventory(); } private void CheckInventory() { - if (itemIdentifiers == null) - { - return; - } - + if (itemIdentifiers == null) { return; } for (int i = 0; i < character.Inventory.Items.Length; i++) { - if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue; + if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) { continue; } if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id))) { targetItem = character.Inventory.Items[i]; @@ -84,7 +76,7 @@ namespace Barotrauma { foreach (Item containedItem in containedItems) { - if (containedItem == null || containedItem.Condition <= 0.0f) continue; + if (containedItem == null || containedItem.Condition <= 0.0f) { continue; } if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id))) { targetItem = containedItem; @@ -103,10 +95,8 @@ namespace Barotrauma if (targetItem == null || moveToTarget == null) { objectiveManager.GetObjective().Wander(deltaTime); - //SteeringManager.SteeringWander(); return; } - if (moveToTarget.CurrentHull == character.CurrentHull && Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2)) { @@ -116,36 +106,28 @@ namespace Barotrauma var pickable = targetItem.GetComponent(); if (pickable == null) { - canBeCompleted = false; + abandon = true; return; } - //check if all the slots required by the item are free foreach (InvSlotType slots in pickable.AllowedSlots) { - if (slots.HasFlag(InvSlotType.Any)) continue; - + if (slots.HasFlag(InvSlotType.Any)) { continue; } 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; - + if (character.Inventory.Items[i] == null) { continue; } //try to move the existing item to LimbSlot.Any and continue if successful - if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List() { InvSlotType.Any })) continue; - + if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List() { InvSlotType.Any })) { continue; } //if everything else fails, simply drop the existing item character.Inventory.Items[i].Drop(character); } } } - targetItem.TryInteract(character, false, true); - if (targetSlot > -1 && !character.HasEquippedItem(targetItem)) { character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character); @@ -161,11 +143,14 @@ namespace Barotrauma //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, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear); + if (!subObjectives.Contains(goToObjective)) + { + AddSubObjective(goToObjective); + } } - - goToObjective.TryComplete(deltaTime); - if (!goToObjective.CanBeCompleted) + else if (goToObjective != null && !goToObjective.CanBeCompleted) { + goToObjective = null; targetItem = null; moveToTarget = null; ignoredItems.Add(targetItem); @@ -185,24 +170,20 @@ namespace Barotrauma #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined."); #endif - canBeCompleted = false; + abandon = true; } return; } - for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++) { currSearchIndex++; - var item = Item.ItemList[currSearchIndex]; if (ignoredItems.Contains(item)) { continue; } 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; } @@ -217,14 +198,12 @@ namespace Barotrauma { if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; } } - //if the item is inside an item, which is inside a character's inventory, don't steal it Item rootContainer = item.GetRootContainer(); if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory) { if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; } } - float itemPriority = 0.0f; if (GetItemPriority != null) { @@ -232,40 +211,35 @@ namespace Barotrauma itemPriority = GetItemPriority(item); if (itemPriority <= 0.0f) { continue; } } - - itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f; - + 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) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}"); #endif - canBeCompleted = false; + abandon = true; } } public override bool IsDuplicate(AIObjective otherObjective) { AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem; - if (getItem == null) return false; - if (getItem.equip != equip) return false; + if (getItem == null) { return false; } + if (getItem.equip != equip) { return false; } if (getItem.itemIdentifiers != null && itemIdentifiers != null) { - if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false; + if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; } for (int i = 0; i < getItem.itemIdentifiers.Length; i++) { - if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false; + if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; } } return true; } @@ -273,7 +247,6 @@ namespace Barotrauma { return getItem.targetItem == targetItem; } - return false; } @@ -284,10 +257,12 @@ 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; - } else if (targetItem != null) { diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs index f408294f9..cc348b883 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -9,21 +9,14 @@ namespace Barotrauma public override string DebugTag => "go to"; 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; } @@ -31,13 +24,11 @@ namespace Barotrauma { 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; } - return 1.0f; } @@ -45,26 +36,25 @@ namespace Barotrauma { get { - bool canComplete = !cannotReach && !abandon; - if (canComplete) + if (!abandon) { if (FollowControlledCharacter && Character.Controlled == null) { - canComplete = false; + abandon = true; } else if (Target != null && Target.Removed) { - canComplete = false; + abandon = true; } else if (!repeat && waitUntilPathUnreachable < 0) { if (SteeringManager == PathSteering && PathSteering.CurrentPath != null) { - canComplete = !PathSteering.CurrentPath.Unreachable; + abandon = PathSteering.CurrentPath.Unreachable; } } } - if (!canComplete) + if (abandon) { #if DEBUG DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow); @@ -75,7 +65,7 @@ namespace Barotrauma } character.AIController.SteeringManager.Reset(); } - return canComplete; + return base.CanBeCompleted; } } @@ -115,24 +105,18 @@ namespace Barotrauma if (Character.Controlled == null) { return; } Target = Character.Controlled; } - if (Target == character) { character.AIController.SteeringManager.Reset(); return; - } - + } waitUntilPathUnreachable -= deltaTime; - if (!character.IsClimbing) { character.SelectedConstruction = null; } - if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); } - Vector2 currTargetPos = Vector2.Zero; - if (Target == null) { currTargetPos = targetPos; @@ -147,7 +131,6 @@ namespace Barotrauma currTargetPos -= character.Submarine.SimPosition; } } - bool sightCheck = true; if (CheckVisibility && Target != null) { @@ -165,12 +148,12 @@ namespace Barotrauma bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); if (isInside && targetIsOutside && !AllowGoingOutside) { - cannotReach = true; + abandon = true; return; } if (insideSteering && !PathSteering.HasAccessToPath(PathSteering.CurrentPath)) { - cannotReach = true; + abandon = true; return; } character.AIController.SteeringManager.SteeringSeek(currTargetPos); @@ -181,15 +164,7 @@ namespace Barotrauma Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) { - if (findDivingGear == null) - { - findDivingGear = new AIObjectiveFindDivingGear(character, true, objectiveManager); - AddSubObjective(findDivingGear); - } - else if (!findDivingGear.CanBeCompleted) - { - abandon = true; - } + TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager)); } } } @@ -198,33 +173,31 @@ namespace Barotrauma public override bool IsCompleted() { if (repeat) { return false; } - bool completed = false; - if (Target is Item item) { - if (item.IsInsideTrigger(character.WorldPosition)) completed = true; + if (item.IsInsideTrigger(character.WorldPosition)) { completed = true; } } else if (Target is Character targetCharacter) { - if (character.CanInteractWith(targetCharacter)) completed = true; + if (character.CanInteractWith(targetCharacter)) { completed = true; } } - completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough; - - if (completed) character.AIController.SteeringManager.Reset(); - + if (completed) { character.AIController.SteeringManager.Reset(); } return completed; } public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo; - if (objective == null) return false; + if (!(otherObjective is AIObjectiveGoTo objective)) { return false; } + if (objective.Target == Target) { return true; } + return objective.targetPos == targetPos; + } - if (objective.Target == Target) return true; - - return (objective.targetPos == targetPos); + private void CalculateCloseEnough() + { + float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0; + CloseEnough = Math.Max(interactionDistance, CloseEnough); } private void CalculateCloseEnough() diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs index 290871b55..ef625ff7f 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -74,6 +74,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; @@ -84,7 +99,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) @@ -136,7 +151,6 @@ namespace Barotrauma { //choose a random available hull var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); - bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); if (isCurrentHullOK) { @@ -269,7 +283,6 @@ namespace Barotrauma private void FindTargetHulls() { - bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); targetHulls.Clear(); hullWeights.Clear(); foreach (var hull in Hull.hullList) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs index bce5a1a9d..aaa36082e 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveLoop.cs @@ -133,9 +133,10 @@ namespace Barotrauma { foreach (T target in GetList()) { + // The bots always find targets when the objective is an order. 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. + // 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; } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs index b4659b3c7..f516d47bf 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -11,41 +11,21 @@ namespace Barotrauma public override string DebugTag => "operate item"; private ItemComponent component, controller; - private Entity operateTarget; - private bool isCompleted; - - private bool canBeCompleted; - private bool requireEquip; - private bool useController; + private AIObjectiveGoTo goToObjective; + private AIObjectiveGetItem getItemObjective; - 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 - { - get { return operateTarget; } - } + public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null); + public Entity OperateTarget => operateTarget; public ItemComponent Component => component; public override float GetPriority() { - if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; } + if (goToObjective != null && !goToObjective.CanBeCompleted) { return 0; } if (component.Item.ConditionPercentage <= 0) { return 0; } if (objectiveManager.CurrentOrder == this) { @@ -65,33 +45,28 @@ namespace Barotrauma this.requireEquip = requireEquip; this.operateTarget = operateTarget; this.useController = useController; - if (useController) { 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); return; } - - if (target.CanBeSelected) - { - if (Vector2.Distance(character.Position, target.Item.Position) < target.Item.InteractDistance - || target.Item.IsInsideTrigger(character.WorldPosition)) + { + bool inSameRoom = character.CurrentHull == target.Item.CurrentHull; + bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2); + if (inSameRoom && withinReach) { - if (character.CurrentHull == target.Item.CurrentHull) + if (character.SelectedConstruction != target.Item) { if (character.SelectedConstruction != target.Item) { @@ -103,21 +78,27 @@ namespace Barotrauma } return; } + if (component.AIOperate(deltaTime, character, this)) + { + isCompleted = true; + } + } + else + { + TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager)); } - - AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character, objectiveManager)); } else { if (component.Item.GetComponent() == null) { //controller/target can't be selected and the item cannot be picked -> objective can't be completed - canBeCompleted = false; + abandon = true; return; } else if (!character.Inventory.Items.Contains(component.Item)) { - AddSubObjective(new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true)); + TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true)); } else { @@ -127,18 +108,17 @@ namespace Barotrauma var holdable = component.Item.GetComponent(); if (holdable == null) { +#if DEBUG DebugConsole.ThrowError("AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component"); +#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; } - //equip slot already taken if (character.Inventory.Items[i] != null) { @@ -157,7 +137,6 @@ namespace Barotrauma } return; } - if (component.AIOperate(deltaTime, character, this)) { isCompleted = true; @@ -166,17 +145,12 @@ namespace Barotrauma } } - public override bool IsCompleted() - { - return isCompleted && !IsLoop; - } + public override bool IsCompleted() => isCompleted && !IsLoop; public override bool IsDuplicate(AIObjective otherObjective) { - AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem; - if (operateItem == null) return false; - - return (operateItem.component == component ||otherObjective.Option == Option); + if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; } + return (operateItem.component == component || otherObjective.Option == Option); } } } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs index 037bd6ca4..7c9fed722 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs @@ -16,8 +16,8 @@ namespace Barotrauma 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) { @@ -42,8 +42,6 @@ namespace Barotrauma return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1)); } - public override bool CanBeCompleted => !abandon; - public override bool IsCompleted() { bool isCompleted = Item.IsFullCondition; @@ -61,19 +59,10 @@ namespace Barotrauma protected override void Act(float deltaTime) { - if (goToObjective != null && !subObjectives.Contains(goToObjective)) + // Don't allow to repair items in rooms that have a fire or an enemies inside or if outside the sub + if (Item.CurrentHull == null || Item.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == Item.CurrentHull && !HumanAIController.IsFriendly(c))) { - if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted) - { - abandon = true; - character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f); - } - goToObjective = null; - } - if (!abandon) - { - // Don't allow to repair items in rooms that have a fire or an enemy inside - abandon = Item.CurrentHull != null && (Item.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == Item.CurrentHull && !HumanAIController.IsFriendly(c))); + abandon = true; } foreach (Repairable repairable in Item.Repairables) { @@ -90,6 +79,8 @@ namespace Barotrauma return; } } + // Only continue when the get item sub objectives have been completed. + if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; } if (repairTool == null) { FindRepairTool(); @@ -128,24 +119,24 @@ namespace Barotrauma break; } } - else if (goToObjective == null || goToObjective.Target != Item) + else { - previousCondition = -1; - if (goToObjective != null) - { - subObjectives.Remove(goToObjective); - } - goToObjective = new AIObjectiveGoTo(Item, character, objectiveManager); - if (repairTool != null) - { - //goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f; - goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range); - } - AddSubObjective(goToObjective); + // If cannot reach the item, approach it. + TryAddSubObjective(ref goToObjective, + constructor: () => + { + previousCondition = -1; + var objective = new AIObjectiveGoTo(Item, character, objectiveManager); + if (repairTool != null) + { + objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range); + } + return objective; + }, + onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f)); } } - private RepairTool repairTool; private void FindRepairTool() { foreach (Repairable repairable in Item.Repairables) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs index c264d58e8..1bca90381 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescue.cs @@ -7,6 +7,7 @@ using System.Linq; namespace Barotrauma { + // TODO: refactor class AIObjectiveRescue : AIObjective { public override string DebugTag => "rescue"; @@ -93,6 +94,7 @@ namespace Barotrauma } } + // TODO: remove and replace with the priority system protected override bool ShouldInterruptSubObjective(AIObjective subObjective) { if (subObjective is AIObjectiveFindSafety) diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs index b0f7577ff..3aea970de 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Barotrauma { - // TODO: Ensure that this works well enough. Consider using AIObjectiveLoop class. + // TODO: Refactor using AIObjectiveLoop class. class AIObjectiveRescueAll : AIObjective { public override string DebugTag => "rescue all"; diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs index 740f3302f..36645d27e 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Deconstructor.cs @@ -810,6 +810,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 697b1db0b..224ab7913 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -1460,6 +1460,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 df5baa438..5160aa6f5 100644 --- a/Barotrauma/BarotraumaShared/Source/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/Source/Map/Hull.cs @@ -626,6 +626,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