(d34fb2097) Major refactoring: - Replace custom logic regarding subobjectives with generic logic. - Ensure that all the objectives follow the same logic, reduce duplicate code where possible.

This commit is contained in:
Joonas Rikkonen
2019-05-16 05:32:56 +03:00
parent 05270aa165
commit a3db11876b
23 changed files with 555 additions and 865 deletions
@@ -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; if (character.MemState.Count < 1) return;
overrideTargetMovement = Vector2.Zero; overrideTargetMovement = Vector2.Zero;
@@ -74,17 +74,12 @@ namespace Barotrauma
public CrewManager(XElement element, bool isSinglePlayer) public CrewManager(XElement element, bool isSinglePlayer)
: this(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; return;
} }
List<Character> availableSpeakers = Character.CharacterList.FindAll(c => if (string.IsNullOrEmpty(text)) { return; }
c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
}
var characterInfo = new CharacterInfo(subElement); var characterInfo = new CharacterInfo(subElement);
characterInfos.Add(characterInfo); characterInfos.Add(characterInfo);
@@ -95,6 +90,7 @@ namespace Barotrauma
break; break;
} }
} }
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
} }
partial void InitProjectSpecific() partial void InitProjectSpecific()
@@ -243,24 +239,27 @@ namespace Barotrauma
public IEnumerable<Character> GetCharacters() public IEnumerable<Character> GetCharacters()
{ {
if (characterInfos.Contains(characterInfo)) if (character?.Inventory == null) return null;
{
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
return;
}
characterInfos.Add(characterInfo); var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent<WifiComponent>() != null);
if (radioItem == null) return null;
if (requireEquipped && !character.HasEquippedItem(radioItem)) return null;
return radioItem.GetComponent<WifiComponent>();
} }
public IEnumerable<CharacterInfo> GetCharacterInfos() public IEnumerable<CharacterInfo> 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; return;
} }
characters.Remove(character); List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
if (removeInfo) characterInfos.Remove(character.Info); c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
} }
public void AddCharacter(Character character) public void AddCharacter(Character character)
@@ -634,183 +633,9 @@ namespace Barotrauma
{ {
characterListBox.BarScroll = roundedPos; characterListBox.BarScroll = roundedPos;
} }
var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox") soundIcon.Visible = !muted && !mutedLocally;
{ soundIconDisabled.Visible = muted || mutedLocally;
UserData = character, soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally");
Color = frame.Color,
SelectedColor = frame.SelectedColor,
HoverColor = frame.HoverColor,
ToolTip = characterToolTip
};
var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
"GUISoundIcon")
{
UserData = "soundicon",
CanBeFocused = false,
Visible = true
};
soundIcon.Color = new Color(soundIcon.Color, 0.0f);
new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
"GUISoundIconDisabled")
{
UserData = "soundicondisabled",
CanBeFocused = true,
Visible = false
};
if (isSinglePlayer)
{
characterArea.OnClicked = CharacterClicked;
}
else
{
characterArea.CanBeFocused = false;
characterArea.CanBeSelected = false;
}
var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft),
onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White,
ToolTip = characterToolTip
};
var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height),
characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) },
character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true)
{
Color = frame.Color,
HoverColor = Color.Transparent,
SelectedColor = Color.Transparent,
CanBeFocused = false,
ToolTip = characterToolTip,
AutoScale = true
};
//---------------- order buttons ----------------
var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform)
{ AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) },
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = (int)(10 * GUI.Scale),
UserData = "orderbuttons",
CanBeFocused = false
};
//listbox for holding the orders inappropriate for this character
//(so we can easily toggle their visibility)
var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null)
{
ScrollBarEnabled = false,
ScrollBarVisible = false,
Enabled = false,
Spacing = spacing,
ClampMouseRectToParent = false
};
wrongOrderList.Content.ClampMouseRectToParent = false;
for (int i = 0; i < orders.Count; i++)
{
var order = orders[i];
if (order.TargetAllCharacters) continue;
RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ?
wrongOrderList.Content.RectTransform :
orderButtonFrame.RectTransform;
var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft),
style: null)
{
UserData = order
};
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
{
Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f,
HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f,
PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f,
UserData = "selected",
CanBeFocused = false,
Visible = false
};
var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite);
img.Scale = iconSize / (float)img.SourceRect.Width;
img.Color = Color.Lerp(order.Color, frame.Color, 0.5f);
img.ToolTip = order.Name;
img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f);
btn.OnClicked += (GUIButton button, object userData) =>
{
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
if (btn.GetChildByUserData("selected").Visible)
{
SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled);
}
else
{
if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1)
{
CreateOrderTargetFrame(button, character, order);
}
else
{
SetCharacterOrder(character, order, null, Character.Controlled);
}
}
return true;
};
btn.UserData = order;
btn.ToolTip = order.Name;
//divider between different groups of orders
if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1)
{
//TODO: divider sprite
new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton");
}
}
var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
"", style: "UIToggleButton")
{
UserData = "togglewrongorder",
CanBeFocused = false
};
wrongOrderList.RectTransform.NonScaledSize = new Point(
wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing),
wrongOrderList.RectTransform.NonScaledSize.Y);
wrongOrderList.RectTransform.SetAsLastChild();
new GUIFrame(new RectTransform(new Point(
wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2,
wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
style: null)
{
CanBeFocused = false
};
//scale to fit the content
orderButtonFrame.RectTransform.NonScaledSize = new Point(
orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing),
orderButtonFrame.RectTransform.NonScaledSize.Y);
frame.RectTransform.NonScaledSize = new Point(
characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width),
frame.RectTransform.NonScaledSize.Y);
characterListBox.RectTransform.NonScaledSize = new Point(
characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width,
characterListBox.RectTransform.NonScaledSize.Y);
characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize;
characterListBox.UpdateScrollBarSize();
return frame;
} }
private IEnumerable<object> KillCharacterAnim(GUIComponent component) private IEnumerable<object> KillCharacterAnim(GUIComponent component)
@@ -954,12 +779,6 @@ namespace Barotrauma
} }
return; return;
} }
List<Character> 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); character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
if (IsSinglePlayer) if (IsSinglePlayer)
@@ -1017,23 +836,19 @@ namespace Barotrauma
} }
} }
} }
//only one target (or an order with no particular targets), just show options
character.SetOrder(order, option, orderGiver, speak: orderGiver != character); else
if (IsSinglePlayer)
{ {
orderGiver?.Speak( orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas)
order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null); { AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
} isHorizontal: true, childAnchor: Anchor.BottomLeft)
else if (orderGiver != null)
{
OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver);
if (GameMain.Client != null)
{ {
GameMain.Client.SendChatMessage(msg); UserData = character,
} Stretch = true
} };
DisplayCharacterOrder(character, order); //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);
/// <summary> /// <summary>
/// Create the UI panel that's used to select the target and options for a given order /// Create the UI panel that's used to select the target and options for a given order
@@ -150,6 +150,8 @@ namespace Barotrauma
private GUILayoutGroup subPreviewContainer; private GUILayoutGroup subPreviewContainer;
private GUILayoutGroup subPreviewContainer;
private GUIButton loadGameButton; private GUIButton loadGameButton;
public Action<Submarine, string, string> StartNewGame; public Action<Submarine, string, string> StartNewGame;
@@ -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) public void AddSubObjective(AIObjective objective)
{ {
if (subObjectives.Any(o => o.IsDuplicate(objective))) { return; } if (subObjectives.Any(o => o.IsDuplicate(objective))) { return; }
@@ -156,10 +157,11 @@ namespace Barotrauma
} }
/// <summary> /// <summary>
/// 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. /// Returns true if the objective was created.
/// </summary> /// </summary>
protected bool TryAddSubObjective<T>(T objective, Func<T> constructor, Action onAbandon = null) where T : AIObjective protected bool TryAddSubObjective<T>(ref T objective, Func<T> constructor, Action onAbandon = null) where T : AIObjective
{ {
if (objective != null) if (objective != null)
{ {
@@ -44,12 +44,17 @@ namespace Barotrauma
return _weaponComponent; return _weaponComponent;
} }
} }
private readonly AIObjectiveFindSafety findSafety;
private readonly HashSet<RangedWeapon> rangedWeapons = new HashSet<RangedWeapon>();
private readonly HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
private AIObjectiveContainItem reloadWeaponObjective; private AIObjectiveContainItem reloadWeaponObjective;
private Hull retreatTarget;
private AIObjectiveGoTo retreatObjective; private AIObjectiveGoTo retreatObjective;
private AIObjectiveFindSafety findSafety;
private AIObjectiveGoTo followTargetObjective; private AIObjectiveGoTo followTargetObjective;
private Hull retreatTarget;
private float coolDownTimer; private float coolDownTimer;
public enum CombatMode public enum CombatMode
@@ -105,17 +110,13 @@ namespace Barotrauma
{ {
Mode = CombatMode.Retreat; Mode = CombatMode.Retreat;
} }
else if (Equip(deltaTime)) if (Equip())
{ {
if (Reload(deltaTime)) if (Reload(deltaTime))
{ {
Attack(deltaTime); Attack(deltaTime);
} }
} }
else
{
Mode = CombatMode.Retreat;
}
break; break;
case CombatMode.Retreat: case CombatMode.Retreat:
break; break;
@@ -140,9 +141,6 @@ namespace Barotrauma
} }
} }
private HashSet<RangedWeapon> rangedWeapons = new HashSet<RangedWeapon>();
private HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
private Item GetWeapon() private Item GetWeapon()
{ {
rangedWeapons.Clear(); rangedWeapons.Clear();
@@ -150,7 +148,6 @@ namespace Barotrauma
adHocWeapons.Clear(); adHocWeapons.Clear();
Item weapon = null; Item weapon = null;
_weaponComponent = null; _weaponComponent = null;
foreach (var item in character.Inventory.Items) foreach (var item in character.Inventory.Items)
{ {
if (item == null) { continue; } if (item == null) { continue; }
@@ -220,7 +217,7 @@ namespace Barotrauma
} }
} }
private bool Equip(float deltaTime) private bool Equip()
{ {
if (!character.SelectedItems.Contains(Weapon)) if (!character.SelectedItems.Contains(Weapon))
{ {
@@ -231,7 +228,7 @@ namespace Barotrauma
} }
else else
{ {
//couldn't equip the item -> escape Mode = CombatMode.Retreat;
return false; return false;
} }
} }
@@ -370,7 +367,7 @@ namespace Barotrauma
{ {
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody); 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); var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
if (pickedBody != null) if (pickedBody != null)
{ {
@@ -410,7 +407,11 @@ namespace Barotrauma
return completed; 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 float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
@@ -9,19 +9,16 @@ namespace Barotrauma
{ {
public override string DebugTag => "contain item"; 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<Item, float> GetItemPriority; public Func<Item, float> 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 AIObjectiveGetItem getItemObjective;
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
@@ -44,28 +41,16 @@ namespace Barotrauma
public override bool IsCompleted() public override bool IsCompleted()
{ {
if (isCompleted) return true; if (isCompleted) { return true; }
int containedItemCount = 0; int containedItemCount = 0;
foreach (Item item in container.Inventory.Items) foreach (Item item in container.Inventory.Items)
{ {
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++; if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)))
}
return containedItemCount >= MinContainedAmount;
}
public override bool CanBeCompleted
{
get
{
if (goToObjective != null)
{ {
return goToObjective.CanBeCompleted; containedItemCount++;
} }
return getItemObjective == null || getItemObjective.CanBeCompleted;
} }
return containedItemCount >= MinContainedAmount;
} }
public override float GetPriority() public override float GetPriority()
@@ -74,69 +59,66 @@ namespace Barotrauma
{ {
return AIObjectiveManager.OrderPriority; return AIObjectiveManager.OrderPriority;
} }
return 1.0f; return 1.0f;
} }
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
if (isCompleted) return; if (isCompleted) { return; }
//get the item that should be contained //get the item that should be contained
Item itemToContain = null; Item itemToContain = null;
foreach (string identifier in itemIdentifiers) foreach (string identifier in itemIdentifiers)
{ {
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier); 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) if (itemToContain == null)
{ {
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager) TryAddSubObjective(ref getItemObjective, () =>
{ new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
GetItemPriority = GetItemPriority, {
ignoredContainerIdentifiers = ignoredContainerIdentifiers GetItemPriority = GetItemPriority,
}; ignoredContainerIdentifiers = ignoredContainerIdentifiers
AddSubObjective(getItemObjective); });
return; return;
} }
if (container.Item.ParentInventory == character.Inventory) if (container.Item.ParentInventory == character.Inventory)
{ {
var containedItems = container.Inventory.Items; var containedItems = container.Inventory.Items;
//if there's already something in the mask (empty oxygen tank?), drop it //if there's already something in the mask (empty oxygen tank?), drop it
var existingItem = containedItems.FirstOrDefault(i => i != null); var existingItem = containedItems.FirstOrDefault(i => i != null);
if (existingItem != null) existingItem.Drop(character); if (existingItem != null)
{
existingItem.Drop(character);
}
character.Inventory.RemoveItem(itemToContain); character.Inventory.RemoveItem(itemToContain);
container.Inventory.TryPutItem(itemToContain, null); container.Inventory.TryPutItem(itemToContain, null);
} }
else 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); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
AddSubObjective(goToObjective);
return; return;
} }
container.Combine(itemToContain); container.Combine(itemToContain);
} }
isCompleted = true; isCompleted = true;
} }
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem; if (!(otherObjective is AIObjectiveContainItem objective)) { return false; }
if (objective == null) return false; if (objective.container != container) { return false; }
if (objective.container != container) return false; if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
for (int i = 0; i < itemIdentifiers.Length; i++) 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; return true;
} }
} }
@@ -8,18 +8,15 @@ namespace Barotrauma
{ {
public override string DebugTag => "decontain item"; 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<Item, float> GetItemPriority; public Func<Item, float> 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 AIObjectiveGoTo goToObjective;
private Item targetItem; private bool isCompleted;
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1) public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) : base(character, objectiveManager, priorityModifier)
@@ -40,27 +37,10 @@ namespace Barotrauma
{ {
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
} }
this.container = container; this.container = container;
} }
public override bool IsCompleted() public override bool IsCompleted() => isCompleted;
{
return isCompleted;
}
public override bool CanBeCompleted
{
get
{
if (goToObjective != null)
{
return goToObjective.CanBeCompleted;
}
return getItemObjective == null || getItemObjective.CanBeCompleted;
}
}
public override float GetPriority() public override float GetPriority()
{ {
@@ -68,16 +48,13 @@ namespace Barotrauma
{ {
return AIObjectiveManager.OrderPriority; return AIObjectiveManager.OrderPriority;
} }
return 1.0f; return 1.0f;
} }
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
if (isCompleted) return; if (isCompleted) { return; }
Item itemToDecontain = null; Item itemToDecontain = null;
//get the item that should be de-contained //get the item that should be de-contained
if (targetItem == null) if (targetItem == null)
{ {
@@ -86,7 +63,7 @@ namespace Barotrauma
foreach (string identifier in itemIdentifiers) foreach (string identifier in itemIdentifiers)
{ {
itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier); 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; itemToDecontain = targetItem;
} }
if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete
{ {
isCompleted = true; isCompleted = true;
return; return;
} }
if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory) if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory)
{ {
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))
&& !container.Item.IsInsideTrigger(character.WorldPosition))
{ {
goToObjective = new AIObjectiveGoTo(container.Item, character, objectiveManager); TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
AddSubObjective(goToObjective);
return; return;
} }
} }
itemToDecontain.Drop(character); itemToDecontain.Drop(character);
isCompleted = true; isCompleted = true;
} }
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem; if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; }
if (decontainItem == null) return false;
if (decontainItem.itemIdentifiers != null && itemIdentifiers != null) 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++) 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; return true;
} }
@@ -133,7 +104,6 @@ namespace Barotrauma
{ {
return decontainItem.targetItem == targetItem; return decontainItem.targetItem == targetItem;
} }
return false; return false;
} }
} }
@@ -13,12 +13,10 @@ namespace Barotrauma
public override bool ForceRun => true; public override bool ForceRun => true;
public override bool KeepDivingGearOn => true; public override bool KeepDivingGearOn => true;
private Hull targetHull; private readonly Hull targetHull;
private AIObjectiveGetItem getExtinguisherObjective; private AIObjectiveGetItem getExtinguisherObjective;
private AIObjectiveGoTo gotoObjective; private AIObjectiveGoTo gotoObjective;
private float useExtinquisherTimer; private float useExtinquisherTimer;
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1) 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)); return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
} }
public override bool IsCompleted() public override bool IsCompleted() => targetHull.FireSources.None();
{
return targetHull.FireSources.Count == 0;
}
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull;
{
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);
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher"); var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem)) 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); character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true); return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
} });
else
{
getExtinguisherObjective.TryComplete(deltaTime);
}
return;
} }
else
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
if (extinguisher == null)
{ {
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher"); var extinguisher = extinguisherItem.GetComponent<RepairTool>();
return; if (extinguisher == null)
}
foreach (FireSource fs in targetHull.FireSources)
{
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
bool move = !inRange;
if (inRange || useExtinquisherTimer > 0.0f)
{ {
useExtinquisherTimer += deltaTime; #if DEBUG
if (useExtinquisherTimer > 2.0f) 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; useExtinquisherTimer += deltaTime;
} if (useExtinquisherTimer > 2.0f)
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
{ {
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor; useExtinquisherTimer = 0.0f;
if (door != null && !door.IsOpen) }
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
{ {
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any(); var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
{
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(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 (move)
if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
{ {
sightLimb = character.AnimController.GetLimb(LimbType.RightHand); //go to the first firesource
} TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager));
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);
}
} }
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;
} }
} }
} }
@@ -1,6 +1,7 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -9,8 +10,10 @@ namespace Barotrauma
public override string DebugTag => "find diving gear"; public override string DebugTag => "find diving gear";
public override bool ForceRun => true; public override bool ForceRun => true;
private AIObjective subObjective; private readonly string gearTag;
private string gearTag;
private AIObjectiveGetItem getDivingGear;
private AIObjectiveContainItem getOxygen;
public override bool IsCompleted() public override bool IsCompleted()
{ {
@@ -21,14 +24,15 @@ namespace Barotrauma
{ {
var containedItems = character.Inventory.Items[i].ContainedItems; var containedItems = character.Inventory.Items[i].ContainedItems;
if (containedItems == null) { continue; } if (containedItems == null) { continue; }
return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
if (oxygenTank != null) { return true; }
} }
} }
return false; return false;
} }
public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{ {
gearTag = needDivingSuit ? "divingsuit" : "diving"; gearTag = needDivingSuit ? "divingsuit" : "diving";
@@ -39,45 +43,39 @@ namespace Barotrauma
var item = character.Inventory.FindItemByTag(gearTag); var item = character.Inventory.FindItemByTag(gearTag);
if (item == null || !character.HasEquippedItem(item)) if (item == null || !character.HasEquippedItem(item))
{ {
//get a diving mask/suit first TryAddSubObjective(ref getDivingGear, () =>
if (!(subObjective is AIObjectiveGetItem))
{ {
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f); 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; TryAddSubObjective(ref getOxygen, () =>
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())
{ {
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager); return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
} });
}
if (subObjective != null)
{
subObjective.TryComplete(deltaTime);
} }
} }
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;
} }
} }
@@ -18,7 +18,7 @@ namespace Barotrauma
const float SearchHullInterval = 3.0f; const float SearchHullInterval = 3.0f;
const float clearUnreachableInterval = 30; const float clearUnreachableInterval = 30;
public readonly List<Hull> unreachable = new List<Hull>(); public readonly HashSet<Hull> unreachable = new HashSet<Hull>();
private float currenthullSafety; private float currenthullSafety;
private float unreachableClearTimer; private float unreachableClearTimer;
@@ -32,44 +32,10 @@ namespace Barotrauma
public override bool IsCompleted() => false; public override bool IsCompleted() => false;
public override bool CanBeCompleted => true; 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) if (unreachableClearTimer > 0)
{ {
unreachableClearTimer -= deltaTime; unreachableClearTimer -= deltaTime;
@@ -79,99 +45,116 @@ namespace Barotrauma
unreachableClearTimer = clearUnreachableInterval; unreachableClearTimer = clearUnreachableInterval;
unreachable.Clear(); 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) if (searchHullTimer > 0.0f)
{ {
searchHullTimer -= deltaTime; searchHullTimer -= deltaTime;
} }
else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD) else
{ {
searchHullTimer = SearchHullInterval;
var bestHull = FindBestHull(); var bestHull = FindBestHull();
if (bestHull != null && bestHull != currentHull) 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. // 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) AllowGoingOutside = HumanAIController.HasDivingSuit(character)
}; }, onAbandon: () => unreachable.Add(goToObjective.Target as Hull));
}
}
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<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
} }
} }
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) Vector2 dir = character.Position - fireSource.Position;
// -> attempt to manually steer away from hazards float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
Vector2 escapeVel = Vector2.Zero; escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
foreach (FireSource fireSource in currentHull.FireSources) }
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; Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f); 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); 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 character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
if (escapeVel != Vector2.Zero)
{
//only move if we haven't reached the edge of the room
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
{
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
{
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset();
}
} }
else else
{ {
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset(); character.AIController.SteeringManager.Reset();
} }
} }
else
{
character.AIController.SteeringManager.Reset();
}
} }
public Hull FindBestHull(IEnumerable<Hull> ignoredHulls = null) public Hull FindBestHull(IEnumerable<Hull> ignoredHulls = null)
@@ -250,36 +233,5 @@ namespace Barotrauma
} }
return bestHull; 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));
}
}
} }
} }
@@ -3,6 +3,7 @@ using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -13,36 +14,31 @@ namespace Barotrauma
public override bool KeepDivingGearOn => true; public override bool KeepDivingGearOn => true;
public override bool ForceRun => true; public override bool ForceRun => true;
private readonly Gap leak; public Gap Leak { get; private set; }
private AIObjectiveFindDivingGear findDivingGear; private AIObjectiveFindDivingGear findDivingGear;
private AIObjectiveGetItem getWeldingTool;
private AIObjectiveContainItem refuelObjective;
private AIObjectiveGoTo gotoObjective; private AIObjectiveGoTo gotoObjective;
private AIObjectiveOperateItem operateObjective; private AIObjectiveOperateItem operateObjective;
public Gap Leak
{
get { return leak; }
}
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier) public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
{ {
this.leak = leak; Leak = leak;
} }
public override bool IsCompleted() 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() 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) // 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 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 max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float devotion = Math.Min(Priority, 10) / 100; float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1)); 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) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveFixLeak fixLeak = otherObjective as AIObjectiveFixLeak; if (!(otherObjective is AIObjectiveFixLeak fixLeak)) { return false; }
if (fixLeak == null) return false; return fixLeak.Leak == Leak;
return fixLeak.leak == leak;
} }
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
if (!leak.IsRoomToRoom) if (!Leak.IsRoomToRoom)
{ {
if (findDivingGear == null) TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
{ if (findDivingGear != null) { return; }
findDivingGear = new AIObjectiveFindDivingGear(character, true, objectiveManager);
AddSubObjective(findDivingGear);
}
else if (!findDivingGear.CanBeCompleted)
{
abandon = true;
return;
}
} }
var weldingTool = character.Inventory.FindItemByTag("weldingtool"); var weldingTool = character.Inventory.FindItemByTag("weldingtool");
if (weldingTool == null) if (weldingTool == null)
{ {
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true)); TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
return; if (getWeldingTool != null) { return; }
} }
else else
{ {
var containedItems = weldingTool.ContainedItems; var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return; if (containedItems == null)
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
if (fuelTank == null)
{ {
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>(), objectiveManager)); #if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
#endif
abandon = true;
return; 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<ItemContainer>(), objectiveManager));
if (refuelObjective != null) { return; }
}
} }
var repairTool = weldingTool.GetComponent<RepairTool>(); var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null) { return; } if (repairTool == null)
{
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition; #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? // TODO: use the collider size/reach?
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{ {
HumanAIController.AnimController.Crouching = true; HumanAIController.AnimController.Crouching = true;
} }
float reach = ConvertUnits.ToSimUnits(repairTool.Range); float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach; bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
if (canReach) if (canReach)
@@ -115,65 +117,29 @@ namespace Barotrauma
{ {
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand); sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
} }
canReach = character.CanSeeTarget(leak, sightLimb); canReach = character.CanSeeTarget(Leak, sightLimb);
} }
else if (!canReach)
{ {
if (gotoObjective != null) TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach });
{
// 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);
}
}
} }
if (gotoObjective == null || gotoObjective.IsCompleted()) if (gotoObjective != null) { return; }
{ TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
if (operateObjective == null)
{
operateObjective = new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: leak);
AddSubObjective(operateObjective);
}
else if (!subObjectives.Contains(operateObjective))
{
operateObjective = null;
}
}
} }
private Vector2 GetStandPosition() private Vector2 GetStandPosition()
{ {
Vector2 standPos = leak.Position; Vector2 standPos = Leak.Position;
var hull = leak.FlowTargetHull; var hull = Leak.FlowTargetHull;
if (hull == null) { return standPos; }
if (hull == null) return standPos; if (Leak.IsHorizontal)
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 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; return standPos;
} }
} }
@@ -11,6 +11,9 @@ namespace Barotrauma
{ {
public override string DebugTag => "get item"; public override string DebugTag => "get item";
private readonly bool equip;
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
public Func<Item, float> GetItemPriority; public Func<Item, float> GetItemPriority;
//can be either tags or identifiers //can be either tags or identifiers
@@ -20,12 +23,6 @@ namespace Barotrauma
public string[] ignoredContainerIdentifiers; public string[] ignoredContainerIdentifiers;
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private float currItemPriority; private float currItemPriority;
private bool equip;
private HashSet<Item> ignoredItems = new HashSet<Item>();
private bool canBeCompleted = true;
public override bool CanBeCompleted => canBeCompleted;
public override float GetPriority() public override float GetPriority()
{ {
@@ -57,20 +54,15 @@ namespace Barotrauma
{ {
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant(); itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
} }
CheckInventory(); CheckInventory();
} }
private void CheckInventory() private void CheckInventory()
{ {
if (itemIdentifiers == null) if (itemIdentifiers == null) { return; }
{
return;
}
for (int i = 0; i < character.Inventory.Items.Length; i++) 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))) if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
{ {
targetItem = character.Inventory.Items[i]; targetItem = character.Inventory.Items[i];
@@ -84,7 +76,7 @@ namespace Barotrauma
{ {
foreach (Item containedItem in containedItems) 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))) if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
{ {
targetItem = containedItem; targetItem = containedItem;
@@ -103,10 +95,8 @@ namespace Barotrauma
if (targetItem == null || moveToTarget == null) if (targetItem == null || moveToTarget == null)
{ {
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime); objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
return; return;
} }
if (moveToTarget.CurrentHull == character.CurrentHull && if (moveToTarget.CurrentHull == character.CurrentHull &&
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2)) Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
{ {
@@ -116,36 +106,28 @@ namespace Barotrauma
var pickable = targetItem.GetComponent<Pickable>(); var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null) if (pickable == null)
{ {
canBeCompleted = false; abandon = true;
return; return;
} }
//check if all the slots required by the item are free //check if all the slots required by the item are free
foreach (InvSlotType slots in pickable.AllowedSlots) 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++) for (int i = 0; i < character.Inventory.Items.Length; i++)
{ {
//slot not needed by the item, continue //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; targetSlot = i;
//slot free, continue //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 //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>() { InvSlotType.Any })) continue; if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) { continue; }
//if everything else fails, simply drop the existing item //if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop(character); character.Inventory.Items[i].Drop(character);
} }
} }
} }
targetItem.TryInteract(character, false, true); targetItem.TryInteract(character, false, true);
if (targetSlot > -1 && !character.HasEquippedItem(targetItem)) if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
{ {
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character); 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 //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); goToObjective = new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear);
if (!subObjectives.Contains(goToObjective))
{
AddSubObjective(goToObjective);
}
} }
else if (goToObjective != null && !goToObjective.CanBeCompleted)
goToObjective.TryComplete(deltaTime);
if (!goToObjective.CanBeCompleted)
{ {
goToObjective = null;
targetItem = null; targetItem = null;
moveToTarget = null; moveToTarget = null;
ignoredItems.Add(targetItem); ignoredItems.Add(targetItem);
@@ -185,24 +170,20 @@ namespace Barotrauma
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined."); DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined.");
#endif #endif
canBeCompleted = false; abandon = true;
} }
return; return;
} }
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++) for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
{ {
currSearchIndex++; currSearchIndex++;
var item = Item.ItemList[currSearchIndex]; var item = Item.ItemList[currSearchIndex];
if (ignoredItems.Contains(item)) { continue; } if (ignoredItems.Contains(item)) { continue; }
if (item.Submarine == null) { continue; } if (item.Submarine == null) { continue; }
else if (item.Submarine.TeamID != character.TeamID) { continue; } else if (item.Submarine.TeamID != character.TeamID) { continue; }
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; } if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; } if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null) if (ignoredContainerIdentifiers != null && item.Container != null)
{ {
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; } if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
@@ -217,14 +198,12 @@ namespace Barotrauma
{ {
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; } 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 //if the item is inside an item, which is inside a character's inventory, don't steal it
Item rootContainer = item.GetRootContainer(); Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory) if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{ {
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; } if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
} }
float itemPriority = 0.0f; float itemPriority = 0.0f;
if (GetItemPriority != null) if (GetItemPriority != null)
{ {
@@ -232,40 +211,35 @@ namespace Barotrauma
itemPriority = GetItemPriority(item); itemPriority = GetItemPriority(item);
if (itemPriority <= 0.0f) { continue; } if (itemPriority <= 0.0f) { continue; }
} }
itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
//ignore if the item has a lower priority than the currently selected one //ignore if the item has a lower priority than the currently selected one
if (moveToTarget != null && itemPriority < currItemPriority) { continue; } if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
currItemPriority = itemPriority; currItemPriority = itemPriority;
targetItem = item; targetItem = item;
moveToTarget = rootContainer ?? item; moveToTarget = rootContainer ?? item;
} }
//if searched through all the items and a target wasn't found, can't be completed //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 (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null)
{ {
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}"); DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}");
#endif #endif
canBeCompleted = false; abandon = true;
} }
} }
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem; AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
if (getItem == null) return false; if (getItem == null) { return false; }
if (getItem.equip != equip) return false; if (getItem.equip != equip) { return false; }
if (getItem.itemIdentifiers != null && itemIdentifiers != null) 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++) 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; return true;
} }
@@ -273,7 +247,6 @@ namespace Barotrauma
{ {
return getItem.targetItem == targetItem; return getItem.targetItem == targetItem;
} }
return false; return false;
} }
@@ -284,10 +257,12 @@ namespace Barotrauma
foreach (string itemName in itemIdentifiers) foreach (string itemName in itemIdentifiers)
{ {
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName); 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; return false;
} }
else if (targetItem != null) else if (targetItem != null)
{ {
@@ -9,21 +9,14 @@ namespace Barotrauma
public override string DebugTag => "go to"; public override string DebugTag => "go to";
private AIObjectiveFindDivingGear findDivingGear; private AIObjectiveFindDivingGear findDivingGear;
private Vector2 targetPos; private Vector2 targetPos;
private bool repeat; private bool repeat;
private bool cannotReach;
//how long until the path to the target is declared unreachable //how long until the path to the target is declared unreachable
private float waitUntilPathUnreachable; private float waitUntilPathUnreachable;
private bool getDivingGearIfNeeded; private bool getDivingGearIfNeeded;
public float CloseEnough { get; set; } = 0.5f; public float CloseEnough { get; set; } = 0.5f;
public bool IgnoreIfTargetDead { get; set; } public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; } public bool AllowGoingOutside { get; set; }
public bool CheckVisibility { get; set; } public bool CheckVisibility { get; set; }
@@ -31,13 +24,11 @@ namespace Barotrauma
{ {
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; } if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
if (Target != null && Target.Removed) { 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) if (objectiveManager.CurrentOrder == this)
{ {
return AIObjectiveManager.OrderPriority; return AIObjectiveManager.OrderPriority;
} }
return 1.0f; return 1.0f;
} }
@@ -45,26 +36,25 @@ namespace Barotrauma
{ {
get get
{ {
bool canComplete = !cannotReach && !abandon; if (!abandon)
if (canComplete)
{ {
if (FollowControlledCharacter && Character.Controlled == null) if (FollowControlledCharacter && Character.Controlled == null)
{ {
canComplete = false; abandon = true;
} }
else if (Target != null && Target.Removed) else if (Target != null && Target.Removed)
{ {
canComplete = false; abandon = true;
} }
else if (!repeat && waitUntilPathUnreachable < 0) else if (!repeat && waitUntilPathUnreachable < 0)
{ {
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null) if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
{ {
canComplete = !PathSteering.CurrentPath.Unreachable; abandon = PathSteering.CurrentPath.Unreachable;
} }
} }
} }
if (!canComplete) if (abandon)
{ {
#if DEBUG #if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow); 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(); character.AIController.SteeringManager.Reset();
} }
return canComplete; return base.CanBeCompleted;
} }
} }
@@ -115,24 +105,18 @@ namespace Barotrauma
if (Character.Controlled == null) { return; } if (Character.Controlled == null) { return; }
Target = Character.Controlled; Target = Character.Controlled;
} }
if (Target == character) if (Target == character)
{ {
character.AIController.SteeringManager.Reset(); character.AIController.SteeringManager.Reset();
return; return;
} }
waitUntilPathUnreachable -= deltaTime; waitUntilPathUnreachable -= deltaTime;
if (!character.IsClimbing) if (!character.IsClimbing)
{ {
character.SelectedConstruction = null; character.SelectedConstruction = null;
} }
if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); } if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); }
Vector2 currTargetPos = Vector2.Zero; Vector2 currTargetPos = Vector2.Zero;
if (Target == null) if (Target == null)
{ {
currTargetPos = targetPos; currTargetPos = targetPos;
@@ -147,7 +131,6 @@ namespace Barotrauma
currTargetPos -= character.Submarine.SimPosition; currTargetPos -= character.Submarine.SimPosition;
} }
} }
bool sightCheck = true; bool sightCheck = true;
if (CheckVisibility && Target != null) if (CheckVisibility && Target != null)
{ {
@@ -165,12 +148,12 @@ namespace Barotrauma
bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes); bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
if (isInside && targetIsOutside && !AllowGoingOutside) if (isInside && targetIsOutside && !AllowGoingOutside)
{ {
cannotReach = true; abandon = true;
return; return;
} }
if (insideSteering && !PathSteering.HasAccessToPath(PathSteering.CurrentPath)) if (insideSteering && !PathSteering.HasAccessToPath(PathSteering.CurrentPath))
{ {
cannotReach = true; abandon = true;
return; return;
} }
character.AIController.SteeringManager.SteeringSeek(currTargetPos); character.AIController.SteeringManager.SteeringSeek(currTargetPos);
@@ -181,15 +164,7 @@ namespace Barotrauma
Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) || Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) ||
Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull)) Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull))
{ {
if (findDivingGear == null) TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
{
findDivingGear = new AIObjectiveFindDivingGear(character, true, objectiveManager);
AddSubObjective(findDivingGear);
}
else if (!findDivingGear.CanBeCompleted)
{
abandon = true;
}
} }
} }
} }
@@ -198,33 +173,31 @@ namespace Barotrauma
public override bool IsCompleted() public override bool IsCompleted()
{ {
if (repeat) { return false; } if (repeat) { return false; }
bool completed = false; bool completed = false;
if (Target is Item item) 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) 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; 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; return completed;
} }
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo; if (!(otherObjective is AIObjectiveGoTo objective)) { return false; }
if (objective == null) return false; if (objective.Target == Target) { return true; }
return objective.targetPos == targetPos;
}
if (objective.Target == Target) return true; private void CalculateCloseEnough()
{
return (objective.targetPos == targetPos); float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0;
CloseEnough = Math.Max(interactionDistance, CloseEnough);
} }
private void CalculateCloseEnough() private void CalculateCloseEnough()
@@ -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 IsCompleted() => false;
public override bool CanBeCompleted => true; public override bool CanBeCompleted => true;
@@ -84,7 +99,7 @@ namespace Barotrauma
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
if (PathSteering == null) return; if (PathSteering == null) { return; }
//don't keep dragging others when idling //don't keep dragging others when idling
if (character.SelectedCharacter != null) if (character.SelectedCharacter != null)
@@ -136,7 +151,6 @@ namespace Barotrauma
{ {
//choose a random available hull //choose a random available hull
var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced); var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull); bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
if (isCurrentHullOK) if (isCurrentHullOK)
{ {
@@ -269,7 +283,6 @@ namespace Barotrauma
private void FindTargetHulls() private void FindTargetHulls()
{ {
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
targetHulls.Clear(); targetHulls.Clear();
hullWeights.Clear(); hullWeights.Clear();
foreach (var hull in Hull.hullList) foreach (var hull in Hull.hullList)
@@ -133,9 +133,10 @@ namespace Barotrauma
{ {
foreach (T target in GetList()) foreach (T target in GetList())
{ {
// The bots always find targets when the objective is an order.
if (objectiveManager.CurrentOrder != this) 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; bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
if (!ignore && !ReportedTargets.Contains(target)) { continue; } if (!ignore && !ReportedTargets.Contains(target)) { continue; }
} }
@@ -11,41 +11,21 @@ namespace Barotrauma
public override string DebugTag => "operate item"; public override string DebugTag => "operate item";
private ItemComponent component, controller; private ItemComponent component, controller;
private Entity operateTarget; private Entity operateTarget;
private bool isCompleted; private bool isCompleted;
private bool canBeCompleted;
private bool requireEquip; private bool requireEquip;
private bool useController; private bool useController;
private AIObjectiveGoTo goToObjective;
private AIObjectiveGetItem getItemObjective;
private AIObjectiveGoTo gotoObjective; public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
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 Entity OperateTarget => operateTarget;
public ItemComponent Component => component; public ItemComponent Component => component;
public override float GetPriority() 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 (component.Item.ConditionPercentage <= 0) { return 0; }
if (objectiveManager.CurrentOrder == this) if (objectiveManager.CurrentOrder == this)
{ {
@@ -65,33 +45,28 @@ namespace Barotrauma
this.requireEquip = requireEquip; this.requireEquip = requireEquip;
this.operateTarget = operateTarget; this.operateTarget = operateTarget;
this.useController = useController; this.useController = useController;
if (useController) if (useController)
{ {
var controllers = component.Item.GetConnectedComponents<Controller>(); var controllers = component.Item.GetConnectedComponents<Controller>();
if (controllers.Any()) controller = controllers[0]; if (controllers.Any()) controller = controllers[0];
} }
canBeCompleted = true;
} }
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
ItemComponent target = useController ? controller : component; ItemComponent target = useController ? controller : component;
if (useController && controller == null) if (useController && controller == null)
{ {
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f); character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
return; return;
} }
if (target.CanBeSelected) if (target.CanBeSelected)
{ {
if (Vector2.Distance(character.Position, target.Item.Position) < target.Item.InteractDistance bool inSameRoom = character.CurrentHull == target.Item.CurrentHull;
|| target.Item.IsInsideTrigger(character.WorldPosition)) 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) if (character.SelectedConstruction != target.Item)
{ {
@@ -103,21 +78,27 @@ namespace Barotrauma
} }
return; 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 else
{ {
if (component.Item.GetComponent<Pickable>() == null) if (component.Item.GetComponent<Pickable>() == null)
{ {
//controller/target can't be selected and the item cannot be picked -> objective can't be completed //controller/target can't be selected and the item cannot be picked -> objective can't be completed
canBeCompleted = false; abandon = true;
return; return;
} }
else if (!character.Inventory.Items.Contains(component.Item)) 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 else
{ {
@@ -127,18 +108,17 @@ namespace Barotrauma
var holdable = component.Item.GetComponent<Holdable>(); var holdable = component.Item.GetComponent<Holdable>();
if (holdable == null) if (holdable == null)
{ {
#if DEBUG
DebugConsole.ThrowError("AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component"); DebugConsole.ThrowError("AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component");
#endif
return; return;
} }
for (int i = 0; i < character.Inventory.Capacity; i++) for (int i = 0; i < character.Inventory.Capacity; i++)
{ {
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
{ {
continue; continue;
} }
//equip slot already taken //equip slot already taken
if (character.Inventory.Items[i] != null) if (character.Inventory.Items[i] != null)
{ {
@@ -157,7 +137,6 @@ namespace Barotrauma
} }
return; return;
} }
if (component.AIOperate(deltaTime, character, this)) if (component.AIOperate(deltaTime, character, this))
{ {
isCompleted = true; isCompleted = true;
@@ -166,17 +145,12 @@ namespace Barotrauma
} }
} }
public override bool IsCompleted() public override bool IsCompleted() => isCompleted && !IsLoop;
{
return isCompleted && !IsLoop;
}
public override bool IsDuplicate(AIObjective otherObjective) public override bool IsDuplicate(AIObjective otherObjective)
{ {
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem; if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; }
if (operateItem == null) return false; return (operateItem.component == component || otherObjective.Option == Option);
return (operateItem.component == component ||otherObjective.Option == Option);
} }
} }
} }
@@ -16,8 +16,8 @@ namespace Barotrauma
public Item Item { get; private set; } public Item Item { get; private set; }
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private float previousCondition = -1; private float previousCondition = -1;
private RepairTool repairTool;
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) 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)); return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
} }
public override bool CanBeCompleted => !abandon;
public override bool IsCompleted() public override bool IsCompleted()
{ {
bool isCompleted = Item.IsFullCondition; bool isCompleted = Item.IsFullCondition;
@@ -61,19 +59,10 @@ namespace Barotrauma
protected override void Act(float deltaTime) 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;
{
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)));
} }
foreach (Repairable repairable in Item.Repairables) foreach (Repairable repairable in Item.Repairables)
{ {
@@ -90,6 +79,8 @@ namespace Barotrauma
return; return;
} }
} }
// Only continue when the get item sub objectives have been completed.
if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; }
if (repairTool == null) if (repairTool == null)
{ {
FindRepairTool(); FindRepairTool();
@@ -128,24 +119,24 @@ namespace Barotrauma
break; break;
} }
} }
else if (goToObjective == null || goToObjective.Target != Item) else
{ {
previousCondition = -1; // If cannot reach the item, approach it.
if (goToObjective != null) TryAddSubObjective(ref goToObjective,
{ constructor: () =>
subObjectives.Remove(goToObjective); {
} previousCondition = -1;
goToObjective = new AIObjectiveGoTo(Item, character, objectiveManager); var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
if (repairTool != null) if (repairTool != null)
{ {
//goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f; objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range);
goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range); }
} return objective;
AddSubObjective(goToObjective); },
onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f));
} }
} }
private RepairTool repairTool;
private void FindRepairTool() private void FindRepairTool()
{ {
foreach (Repairable repairable in Item.Repairables) foreach (Repairable repairable in Item.Repairables)
@@ -7,6 +7,7 @@ using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
// TODO: refactor
class AIObjectiveRescue : AIObjective class AIObjectiveRescue : AIObjective
{ {
public override string DebugTag => "rescue"; 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) protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
{ {
if (subObjective is AIObjectiveFindSafety) if (subObjective is AIObjectiveFindSafety)
@@ -3,7 +3,7 @@ using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
// TODO: Ensure that this works well enough. Consider using AIObjectiveLoop class. // TODO: Refactor using AIObjectiveLoop class.
class AIObjectiveRescueAll : AIObjective class AIObjectiveRescueAll : AIObjective
{ {
public override string DebugTag => "rescue all"; public override string DebugTag => "rescue all";
@@ -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()) if (targetItem.Prefab.DeconstructItems.Any())
{ {
inputContainer.Inventory.RemoveItem(targetItem); inputContainer.Inventory.RemoveItem(targetItem);
@@ -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 //edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>(); private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
@@ -1460,6 +1460,10 @@ namespace Barotrauma
{ {
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime); 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); ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; } if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }
@@ -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 public override Rectangle Rect
{ {
get get