(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

View File

@@ -152,6 +152,32 @@ namespace Barotrauma
}
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
character.MemState.Clear();
}
}
partial void ImpactProjSpecific(float impact, Body body)
{
float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f);
if (body.UserData is Limb limb && character.Stun <= 0f)
{
if (impact > 3.0f) { PlayImpactSound(limb); }
}
else if (body.UserData is Limb || body == Collider.FarseerBody)
{
if (!character.IsRemotePlayer && impact > ImpactTolerance)
{
SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
}
}
if (Character.Controlled == character)
{
GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f);
}
}
if (character.MemState.Count < 1) return;
overrideTargetMovement = Vector2.Zero;

View File

@@ -74,17 +74,12 @@ namespace Barotrauma
public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer)
{
if (GameMain.Client != null)
if (!isSinglePlayer)
{
//let the server create random conversations in MP
DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
return;
}
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
}
if (string.IsNullOrEmpty(text)) { return; }
var characterInfo = new CharacterInfo(subElement);
characterInfos.Add(characterInfo);
@@ -95,6 +90,7 @@ namespace Barotrauma
break;
}
}
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
}
partial void InitProjectSpecific()
@@ -243,24 +239,27 @@ namespace Barotrauma
public IEnumerable<Character> GetCharacters()
{
if (characterInfos.Contains(characterInfo))
{
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
return;
}
if (character?.Inventory == null) return null;
characterInfos.Add(characterInfo);
var radioItem = character.Inventory.Items.FirstOrDefault(it => it != null && it.GetComponent<WifiComponent>() != null);
if (radioItem == null) return null;
if (requireEquipped && !character.HasEquippedItem(radioItem)) return null;
return radioItem.GetComponent<WifiComponent>();
}
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;
}
characters.Remove(character);
if (removeInfo) characterInfos.Remove(character.Info);
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
}
public void AddCharacter(Character character)
@@ -634,183 +633,9 @@ namespace Barotrauma
{
characterListBox.BarScroll = roundedPos;
}
var characterArea = new GUIButton(new RectTransform(new Point(characterInfoWidth, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft), style: "GUITextBox")
{
UserData = character,
Color = frame.Color,
SelectedColor = frame.SelectedColor,
HoverColor = frame.HoverColor,
ToolTip = characterToolTip
};
var soundIcon = new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
"GUISoundIcon")
{
UserData = "soundicon",
CanBeFocused = false,
Visible = true
};
soundIcon.Color = new Color(soundIcon.Color, 0.0f);
new GUIImage(new RectTransform(new Point((int)(characterArea.Rect.Height * 0.5f)), characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(5, 0) },
"GUISoundIconDisabled")
{
UserData = "soundicondisabled",
CanBeFocused = true,
Visible = false
};
if (isSinglePlayer)
{
characterArea.OnClicked = CharacterClicked;
}
else
{
characterArea.CanBeFocused = false;
characterArea.CanBeSelected = false;
}
var characterImage = new GUICustomComponent(new RectTransform(new Point(characterArea.Rect.Height), characterArea.RectTransform, Anchor.CenterLeft),
onDraw: (sb, component) => character.Info.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White,
ToolTip = characterToolTip
};
var characterName = new GUITextBlock(new RectTransform(new Point(characterArea.Rect.Width - characterImage.Rect.Width - soundIcon.Rect.Width - 10, characterArea.Rect.Height),
characterArea.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(soundIcon.Rect.Width + 10, 0) },
character.Name, textColor: frame.Color, font: GUI.SmallFont, wrap: true)
{
Color = frame.Color,
HoverColor = Color.Transparent,
SelectedColor = Color.Transparent,
CanBeFocused = false,
ToolTip = characterToolTip,
AutoScale = true
};
//---------------- order buttons ----------------
var orderButtonFrame = new GUILayoutGroup(new RectTransform(new Point(100, frame.Rect.Height), frame.RectTransform)
{ AbsoluteOffset = new Point(characterInfoWidth + spacing, 0) },
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = (int)(10 * GUI.Scale),
UserData = "orderbuttons",
CanBeFocused = false
};
//listbox for holding the orders inappropriate for this character
//(so we can easily toggle their visibility)
var wrongOrderList = new GUIListBox(new RectTransform(new Point(50, orderButtonFrame.Rect.Height), orderButtonFrame.RectTransform), isHorizontal: true, style: null)
{
ScrollBarEnabled = false,
ScrollBarVisible = false,
Enabled = false,
Spacing = spacing,
ClampMouseRectToParent = false
};
wrongOrderList.Content.ClampMouseRectToParent = false;
for (int i = 0; i < orders.Count; i++)
{
var order = orders[i];
if (order.TargetAllCharacters) continue;
RectTransform btnParent = (i >= correctOrderCount + neutralOrderCount) ?
wrongOrderList.Content.RectTransform :
orderButtonFrame.RectTransform;
var btn = new GUIButton(new RectTransform(new Point(iconSize, iconSize), btnParent, Anchor.CenterLeft),
style: null)
{
UserData = order
};
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
{
Color = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.8f,
HoverColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 1.0f,
PressedColor = Color.Lerp(order.Color, frame.Color, 0.5f) * 0.6f,
UserData = "selected",
CanBeFocused = false,
Visible = false
};
var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite);
img.Scale = iconSize / (float)img.SourceRect.Width;
img.Color = Color.Lerp(order.Color, frame.Color, 0.5f);
img.ToolTip = order.Name;
img.HoverColor = Color.Lerp(img.Color, Color.White, 0.5f);
btn.OnClicked += (GUIButton button, object userData) =>
{
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
if (btn.GetChildByUserData("selected").Visible)
{
SetCharacterOrder(character, Order.PrefabList.Find(o => o.AITag == "dismissed"), null, Character.Controlled);
}
else
{
if (order.ItemComponentType != null || order.ItemIdentifiers.Length > 0 || order.Options.Length > 1)
{
CreateOrderTargetFrame(button, character, order);
}
else
{
SetCharacterOrder(character, order, null, Character.Controlled);
}
}
return true;
};
btn.UserData = order;
btn.ToolTip = order.Name;
//divider between different groups of orders
if (i == correctOrderCount - 1 || i == correctOrderCount + neutralOrderCount - 1)
{
//TODO: divider sprite
new GUIFrame(new RectTransform(new Point(8, iconSize), orderButtonFrame.RectTransform), style: "GUIButton");
}
}
var toggleWrongOrderBtn = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
"", style: "UIToggleButton")
{
UserData = "togglewrongorder",
CanBeFocused = false
};
wrongOrderList.RectTransform.NonScaledSize = new Point(
wrongOrderList.Content.Children.Sum(c => c.Rect.Width + wrongOrderList.Spacing),
wrongOrderList.RectTransform.NonScaledSize.Y);
wrongOrderList.RectTransform.SetAsLastChild();
new GUIFrame(new RectTransform(new Point(
wrongOrderList.Rect.Width - toggleWrongOrderBtn.Rect.Width - wrongOrderList.Spacing * 2,
wrongOrderList.Rect.Height), wrongOrderList.Content.RectTransform),
style: null)
{
CanBeFocused = false
};
//scale to fit the content
orderButtonFrame.RectTransform.NonScaledSize = new Point(
orderButtonFrame.Children.Sum(c => c.Rect.Width + orderButtonFrame.AbsoluteSpacing),
orderButtonFrame.RectTransform.NonScaledSize.Y);
frame.RectTransform.NonScaledSize = new Point(
characterInfoWidth + spacing + (orderButtonFrame.Rect.Width - wrongOrderList.Rect.Width),
frame.RectTransform.NonScaledSize.Y);
characterListBox.RectTransform.NonScaledSize = new Point(
characterListBox.Content.Children.Max(c => c.Rect.Width) + wrongOrderList.Rect.Width,
characterListBox.RectTransform.NonScaledSize.Y);
characterListBox.Content.RectTransform.NonScaledSize = characterListBox.RectTransform.NonScaledSize;
characterListBox.UpdateScrollBarSize();
return frame;
soundIcon.Visible = !muted && !mutedLocally;
soundIconDisabled.Visible = muted || mutedLocally;
soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ? "MutedLocally" : "MutedGlobally");
}
private IEnumerable<object> KillCharacterAnim(GUIComponent component)
@@ -954,12 +779,6 @@ namespace Barotrauma
}
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);
if (IsSinglePlayer)
@@ -1017,23 +836,19 @@ namespace Barotrauma
}
}
}
character.SetOrder(order, option, orderGiver, speak: orderGiver != character);
if (IsSinglePlayer)
//only one target (or an order with no particular targets), just show options
else
{
orderGiver?.Speak(
order.GetChatMessage(character.Name, orderGiver.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option), null);
}
else if (orderGiver != null)
{
OrderChatMessage msg = new OrderChatMessage(order, option, order.TargetItemComponent?.Item, character, orderGiver);
if (GameMain.Client != null)
orderTargetFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f + order.Options.Length * 0.1f, 0.18f), GUI.Canvas)
{ AbsoluteOffset = new Point(orderButton.Rect.Center.X, orderButton.Rect.Bottom) },
isHorizontal: true, childAnchor: Anchor.BottomLeft)
{
GameMain.Client.SendChatMessage(msg);
}
}
DisplayCharacterOrder(character, order);
}
UserData = character,
Stretch = true
};
//line connecting the order button to the option buttons
//TODO: sprite
new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), orderTargetFrame.RectTransform), style: null);
/// <summary>
/// Create the UI panel that's used to select the target and options for a given order

View File

@@ -150,6 +150,8 @@ namespace Barotrauma
private GUILayoutGroup subPreviewContainer;
private GUILayoutGroup subPreviewContainer;
private GUIButton loadGameButton;
public Action<Submarine, string, string> StartNewGame;

View File

@@ -104,6 +104,7 @@ namespace Barotrauma
}
}
// TODO: go through AIOperate methods where subobjectives are added and ensure that they add the subobjectives correctly -> use TryAddSubObjective method instead?
public void AddSubObjective(AIObjective objective)
{
if (subObjectives.Any(o => o.IsDuplicate(objective))) { return; }
@@ -156,10 +157,11 @@ namespace Barotrauma
}
/// <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.
/// </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)
{

View File

@@ -44,12 +44,17 @@ namespace Barotrauma
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 Hull retreatTarget;
private AIObjectiveGoTo retreatObjective;
private AIObjectiveFindSafety findSafety;
private AIObjectiveGoTo followTargetObjective;
private Hull retreatTarget;
private float coolDownTimer;
public enum CombatMode
@@ -105,17 +110,13 @@ namespace Barotrauma
{
Mode = CombatMode.Retreat;
}
else if (Equip(deltaTime))
if (Equip())
{
if (Reload(deltaTime))
{
Attack(deltaTime);
}
}
else
{
Mode = CombatMode.Retreat;
}
break;
case CombatMode.Retreat:
break;
@@ -140,9 +141,6 @@ namespace Barotrauma
}
}
private HashSet<RangedWeapon> rangedWeapons = new HashSet<RangedWeapon>();
private HashSet<MeleeWeapon> meleeWeapons = new HashSet<MeleeWeapon>();
private readonly HashSet<Item> adHocWeapons = new HashSet<Item>();
private Item GetWeapon()
{
rangedWeapons.Clear();
@@ -150,7 +148,6 @@ namespace Barotrauma
adHocWeapons.Clear();
Item weapon = null;
_weaponComponent = null;
foreach (var item in character.Inventory.Items)
{
if (item == null) { continue; }
@@ -220,7 +217,7 @@ namespace Barotrauma
}
}
private bool Equip(float deltaTime)
private bool Equip()
{
if (!character.SelectedItems.Contains(Weapon))
{
@@ -231,7 +228,7 @@ namespace Barotrauma
}
else
{
//couldn't equip the item -> escape
Mode = CombatMode.Retreat;
return false;
}
}
@@ -370,7 +367,7 @@ namespace Barotrauma
{
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
}
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall;
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies, collisionCategories);
if (pickedBody != null)
{
@@ -410,7 +407,11 @@ namespace Barotrauma
return completed;
}
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
public override bool CanBeCompleted => !abandon &&
(reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) &&
(retreatObjective == null || retreatObjective.CanBeCompleted) &&
(followTargetObjective == null || followTargetObjective.CanBeCompleted);
public override float GetPriority() => (Enemy != null && (Enemy.Removed || Enemy.IsDead)) ? 0 : Math.Min(100 * PriorityModifier, 100);
public override bool IsDuplicate(AIObjective otherObjective)

View File

@@ -9,19 +9,16 @@ namespace Barotrauma
{
public override string DebugTag => "contain item";
public int MinContainedAmount = 1;
//can either be a tag or an identifier
private string[] itemIdentifiers;
private ItemContainer container;
private bool isCompleted;
public string[] ignoredContainerIdentifiers;
public Func<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 AIObjectiveGoTo goToObjective;
@@ -44,28 +41,16 @@ namespace Barotrauma
public override bool IsCompleted()
{
if (isCompleted) return true;
if (isCompleted) { return true; }
int containedItemCount = 0;
foreach (Item item in container.Inventory.Items)
{
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++;
}
return containedItemCount >= MinContainedAmount;
}
public override bool CanBeCompleted
{
get
{
if (goToObjective != null)
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id)))
{
return goToObjective.CanBeCompleted;
containedItemCount++;
}
return getItemObjective == null || getItemObjective.CanBeCompleted;
}
return containedItemCount >= MinContainedAmount;
}
public override float GetPriority()
@@ -74,69 +59,66 @@ namespace Barotrauma
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
protected override void Act(float deltaTime)
{
if (isCompleted) return;
if (isCompleted) { return; }
//get the item that should be contained
Item itemToContain = null;
foreach (string identifier in itemIdentifiers)
{
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
if (itemToContain != null && itemToContain.Condition > 0.0f) break;
}
if (itemToContain != null && itemToContain.Condition > 0.0f) { break; }
}
if (itemToContain == null)
{
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
{
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers
};
AddSubObjective(getItemObjective);
TryAddSubObjective(ref getItemObjective, () =>
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager)
{
GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers
});
return;
}
if (container.Item.ParentInventory == character.Inventory)
{
var containedItems = container.Inventory.Items;
//if there's already something in the mask (empty oxygen tank?), drop it
var existingItem = containedItems.FirstOrDefault(i => i != null);
if (existingItem != null) existingItem.Drop(character);
if (existingItem != null)
{
existingItem.Drop(character);
}
character.Inventory.RemoveItem(itemToContain);
container.Inventory.TryPutItem(itemToContain, null);
}
else
{
if (container.Item.CurrentHull != character.CurrentHull || (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance && !container.Item.IsInsideTrigger(character.WorldPosition)))
if (container.Item.CurrentHull != character.CurrentHull ||
(Vector2.DistanceSquared(character.Position, container.Item.Position) > Math.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition)))
{
goToObjective = new AIObjectiveGoTo(container.Item, character, objectiveManager);
AddSubObjective(goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
return;
}
container.Combine(itemToContain);
}
isCompleted = true;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
if (objective == null) return false;
if (objective.container != container) return false;
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
if (!(otherObjective is AIObjectiveContainItem objective)) { return false; }
if (objective.container != container) { return false; }
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
for (int i = 0; i < itemIdentifiers.Length; i++)
{
if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false;
if (objective.itemIdentifiers[i] != itemIdentifiers[i])
{
return false;
}
}
return true;
}
}

View File

@@ -8,18 +8,15 @@ namespace Barotrauma
{
public override string DebugTag => "decontain item";
//can either be a tag or an identifier
private string[] itemIdentifiers;
private ItemContainer container;
private bool isCompleted;
public Func<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 Item targetItem;
private bool isCompleted;
public AIObjectiveDecontainItem(Character character, Item targetItem, ItemContainer container, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier)
@@ -40,27 +37,10 @@ namespace Barotrauma
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
this.container = container;
}
public override bool IsCompleted()
{
return isCompleted;
}
public override bool CanBeCompleted
{
get
{
if (goToObjective != null)
{
return goToObjective.CanBeCompleted;
}
return getItemObjective == null || getItemObjective.CanBeCompleted;
}
}
public override bool IsCompleted() => isCompleted;
public override float GetPriority()
{
@@ -68,16 +48,13 @@ namespace Barotrauma
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
protected override void Act(float deltaTime)
{
if (isCompleted) return;
if (isCompleted) { return; }
Item itemToDecontain = null;
//get the item that should be de-contained
if (targetItem == null)
{
@@ -86,7 +63,7 @@ namespace Barotrauma
foreach (string identifier in itemIdentifiers)
{
itemToDecontain = container.Inventory.FindItemByIdentifier(identifier) ?? container.Inventory.FindItemByTag(identifier);
if (itemToDecontain != null) break;
if (itemToDecontain != null) { break; }
}
}
}
@@ -94,38 +71,32 @@ namespace Barotrauma
{
itemToDecontain = targetItem;
}
if (itemToDecontain == null || itemToDecontain.Container != container.Item) // Item not found or already de-contained, consider complete
{
isCompleted = true;
return;
}
if (itemToDecontain.OwnInventory != character.Inventory && itemToDecontain.ParentInventory != character.Inventory)
{
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.InteractDistance
&& !container.Item.IsInsideTrigger(character.WorldPosition))
if (Vector2.DistanceSquared(character.Position, container.Item.Position) > MathUtils.Pow(container.Item.InteractDistance, 2) && !container.Item.IsInsideTrigger(character.WorldPosition))
{
goToObjective = new AIObjectiveGoTo(container.Item, character, objectiveManager);
AddSubObjective(goToObjective);
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager));
return;
}
}
itemToDecontain.Drop(character);
isCompleted = true;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveDecontainItem decontainItem = otherObjective as AIObjectiveDecontainItem;
if (decontainItem == null) return false;
if (!(otherObjective is AIObjectiveDecontainItem decontainItem)) { return false; }
if (decontainItem.itemIdentifiers != null && itemIdentifiers != null)
{
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
if (decontainItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
for (int i = 0; i < decontainItem.itemIdentifiers.Length; i++)
{
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
if (decontainItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
}
return true;
}
@@ -133,7 +104,6 @@ namespace Barotrauma
{
return decontainItem.targetItem == targetItem;
}
return false;
}
}

View File

@@ -13,12 +13,10 @@ namespace Barotrauma
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
private Hull targetHull;
private readonly Hull targetHull;
private AIObjectiveGetItem getExtinguisherObjective;
private AIObjectiveGoTo gotoObjective;
private float useExtinquisherTimer;
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
@@ -40,111 +38,91 @@ namespace Barotrauma
return MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + severityFactor * distanceFactor, 0, 1));
}
public override bool IsCompleted()
{
return targetHull.FireSources.Count == 0;
}
public override bool IsCompleted() => targetHull.FireSources.None();
public override bool IsDuplicate(AIObjective otherObjective)
{
var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire;
return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull;
}
public override bool CanBeCompleted =>
(getExtinguisherObjective == null || getExtinguisherObjective.IsCompleted() || getExtinguisherObjective.CanBeCompleted) &&
(gotoObjective == null || gotoObjective.IsCompleted() || gotoObjective.CanBeCompleted);
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveExtinguishFire otherExtinguishFire && otherExtinguishFire.targetHull == targetHull;
protected override void Act(float deltaTime)
{
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
if (getExtinguisherObjective == null)
TryAddSubObjective(ref getExtinguisherObjective, () =>
{
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
}
else
{
getExtinguisherObjective.TryComplete(deltaTime);
}
return;
return new AIObjectiveGetItem(character, "extinguisher", objectiveManager, equip: true);
});
}
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
if (extinguisher == null)
else
{
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
return;
}
foreach (FireSource fs in targetHull.FireSources)
{
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
bool move = !inRange;
if (inRange || useExtinquisherTimer > 0.0f)
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
if (extinguisher == null)
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f)
#if DEBUG
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
#endif
abandon = true;
return;
}
foreach (FireSource fs in targetHull.FireSources)
{
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
bool move = !inRange;
if (inRange || useExtinquisherTimer > 0.0f)
{
useExtinquisherTimer = 0.0f;
}
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f)
{
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
useExtinquisherTimer = 0.0f;
}
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
bool isOperatingButtons = false;
if (SteeringManager == PathSteering)
{
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<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 (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.RightHand))
if (move)
{
sightLimb = character.AnimController.GetLimb(LimbType.RightHand);
}
else if (character.Inventory.IsInLimbSlot(extinguisherItem, InvSlotType.LeftHand))
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
if (!character.CanSeeTarget(fs, sightLimb))
{
move = true;
}
else
{
move = false;
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
}
//go to the first firesource
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager));
}
break;
}
if (move)
{
//go to the first firesource
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
{
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character, objectiveManager);
}
else
{
gotoObjective.TryComplete(deltaTime);
}
}
break;
}
}
}

View File

@@ -1,6 +1,7 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -9,8 +10,10 @@ namespace Barotrauma
public override string DebugTag => "find diving gear";
public override bool ForceRun => true;
private AIObjective subObjective;
private string gearTag;
private readonly string gearTag;
private AIObjectiveGetItem getDivingGear;
private AIObjectiveContainItem getOxygen;
public override bool IsCompleted()
{
@@ -21,14 +24,15 @@ namespace Barotrauma
{
var containedItems = character.Inventory.Items[i].ContainedItems;
if (containedItems == null) { continue; }
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
if (oxygenTank != null) { return true; }
return containedItems.Any(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
}
}
return false;
}
public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{
gearTag = needDivingSuit ? "divingsuit" : "diving";
@@ -39,45 +43,39 @@ namespace Barotrauma
var item = character.Inventory.FindItemByTag(gearTag);
if (item == null || !character.HasEquippedItem(item))
{
//get a diving mask/suit first
if (!(subObjective is AIObjectiveGetItem))
TryAddSubObjective(ref getDivingGear, () =>
{
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
subObjective = new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true);
return new AIObjectiveGetItem(character, gearTag, objectiveManager, equip: true);
});
}
if (getDivingGear != null) { return; }
var containedItems = item.ContainedItems;
if (containedItems == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFindDivingGear failed - the item \"" + item + "\" has no proper inventory");
#endif
abandon = true;
return;
}
// Drop empty tanks
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(character);
}
}
else
if (containedItems.None(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f))
{
var containedItems = item.ContainedItems;
if (containedItems == null) { return; }
//check if there's an oxygen tank in the mask/suit
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(character);
}
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
{
//we've got an oxygen source inside the mask/suit, all good
return;
}
}
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
TryAddSubObjective(ref getOxygen, () =>
{
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
}
}
if (subObjective != null)
{
subObjective.TryComplete(deltaTime);
return new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>(), objectiveManager);
});
}
}
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
public override float GetPriority() => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
}
}

View File

@@ -18,7 +18,7 @@ namespace Barotrauma
const float SearchHullInterval = 3.0f;
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 unreachableClearTimer;
@@ -32,44 +32,10 @@ namespace Barotrauma
public override bool IsCompleted() => false;
public override bool CanBeCompleted => true;
protected override void Act(float deltaTime)
public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindSafety;
public override void Update(float deltaTime)
{
var currentHull = character.AnimController.CurrentHull;
if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
{
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
if (!hasEquipment)
{
divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager);
}
}
if (divingGearObjective != null)
{
divingGearObjective.TryComplete(deltaTime);
if (divingGearObjective.IsCompleted())
{
divingGearObjective = null;
Priority = 0;
}
else if (divingGearObjective.CanBeCompleted)
{
// If diving gear objective is active and can be completed, wait for it to complete.
return;
}
else
{
divingGearObjective = null;
// Reduce the timer so that we get a safe hull target faster.
searchHullTimer = Math.Min(1, searchHullTimer);
}
}
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
searchHullTimer = Math.Min(1, searchHullTimer);
}
if (unreachableClearTimer > 0)
{
unreachableClearTimer -= deltaTime;
@@ -79,99 +45,116 @@ namespace Barotrauma
unreachableClearTimer = clearUnreachableInterval;
unreachable.Clear();
}
if (character.CurrentHull == null)
{
currenthullSafety = 0;
Priority = 100;
return;
}
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
{
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
}
}
protected override void Act(float deltaTime)
{
var currentHull = character.AnimController.CurrentHull;
if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
{
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
if (!hasEquipment)
{
TryAddSubObjective(ref divingGearObjective,
() => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
onAbandon: () => searchHullTimer = Math.Min(1, searchHullTimer));
}
}
if (divingGearObjective != null) { return; }
// Reset the devotion.
Priority = 0;
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
{
searchHullTimer = Math.Min(1, searchHullTimer);
}
if (searchHullTimer > 0.0f)
{
searchHullTimer -= deltaTime;
}
else if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
else
{
searchHullTimer = SearchHullInterval;
var bestHull = FindBestHull();
if (bestHull != null && bestHull != currentHull)
{
if (goToObjective != null)
if (goToObjective?.Target != bestHull)
{
if (goToObjective.Target != bestHull)
goToObjective = null;
}
TryAddSubObjective(ref goToObjective, () =>
new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
{
// If we need diving gear, we should already have it, if possible.
goToObjective = new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
{
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
};
}
}
else
{
goToObjective = new AIObjectiveGoTo(bestHull, character, objectiveManager, getDivingGearIfNeeded: false)
{
AllowGoingOutside = HumanAIController.HasDivingSuit(character)
};
}
}
searchHullTimer = SearchHullInterval;
}
if (goToObjective != null)
{
goToObjective.TryComplete(deltaTime);
if (!goToObjective.CanBeCompleted)
{
if (!unreachable.Contains(goToObjective.Target))
{
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective = null;
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
}, onAbandon: () => unreachable.Add(goToObjective.Target as Hull));
}
}
else if (currentHull != null)
if (goToObjective != null) { return; }
if (currentHull == null) { return; }
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
// -> attempt to manually steer away from hazards
Vector2 escapeVel = Vector2.Zero;
foreach (FireSource fireSource in currentHull.FireSources)
{
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
// -> attempt to manually steer away from hazards
Vector2 escapeVel = Vector2.Zero;
foreach (FireSource fireSource in currentHull.FireSources)
Vector2 dir = character.Position - fireSource.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
foreach (Character enemy in Character.CharacterList)
{
//don't run from friendly NPCs
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
Vector2 dir = character.Position - fireSource.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
foreach (Character enemy in Character.CharacterList)
}
if (escapeVel != Vector2.Zero)
{
//only move if we haven't reached the edge of the room
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
{
//don't run from friendly NPCs
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
Vector2 dir = character.Position - enemy.Position;
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(dir.X) * distMultiplier, !character.IsClimbing ? 0 : Math.Sign(dir.Y) * distMultiplier);
}
}
if (escapeVel != Vector2.Zero)
{
//only move if we haven't reached the edge of the room
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
{
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
{
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset();
}
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
{
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset();
}
}
else
{
character.AIController.SteeringManager.Reset();
}
}
public Hull FindBestHull(IEnumerable<Hull> ignoredHulls = null)
@@ -250,36 +233,5 @@ namespace Barotrauma
}
return bestHull;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return (otherObjective is AIObjectiveFindSafety);
}
public override void Update(float deltaTime)
{
if (character.CurrentHull == null)
{
currenthullSafety = 0;
Priority = 100;
return;
}
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
{
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.OrderPriority + 20, 100));
}
}
}
}

View File

@@ -3,6 +3,7 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -13,36 +14,31 @@ namespace Barotrauma
public override bool KeepDivingGearOn => true;
public override bool ForceRun => true;
private readonly Gap leak;
public Gap Leak { get; private set; }
private AIObjectiveFindDivingGear findDivingGear;
private AIObjectiveGetItem getWeldingTool;
private AIObjectiveContainItem refuelObjective;
private AIObjectiveGoTo gotoObjective;
private AIObjectiveOperateItem operateObjective;
public Gap Leak
{
get { return leak; }
}
public AIObjectiveFixLeak(Gap leak, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base (character, objectiveManager, priorityModifier)
{
this.leak = leak;
Leak = leak;
}
public override bool IsCompleted()
{
return leak.Open <= 0.0f || leak.Removed;
return Leak.Open <= 0.0f || Leak.Removed;
}
public override bool CanBeCompleted => !abandon && base.CanBeCompleted;
public override float GetPriority()
{
if (leak.Open == 0.0f) { return 0.0f; }
if (Leak.Open == 0.0f) { return 0.0f; }
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - leak.WorldPosition.Y) * 2.0f;
float dist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 10000, dist));
float severity = AIObjectiveFixLeaks.GetLeakSeverity(leak);
float severity = AIObjectiveFixLeaks.GetLeakSeverity(Leak);
float max = Math.Min((AIObjectiveManager.OrderPriority - 1), 90);
float devotion = Math.Min(Priority, 10) / 100;
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + severity * distanceFactor * PriorityModifier, 0, 1));
@@ -50,58 +46,64 @@ namespace Barotrauma
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveFixLeak fixLeak = otherObjective as AIObjectiveFixLeak;
if (fixLeak == null) return false;
return fixLeak.leak == leak;
if (!(otherObjective is AIObjectiveFixLeak fixLeak)) { return false; }
return fixLeak.Leak == Leak;
}
protected override void Act(float deltaTime)
{
if (!leak.IsRoomToRoom)
if (!Leak.IsRoomToRoom)
{
if (findDivingGear == null)
{
findDivingGear = new AIObjectiveFindDivingGear(character, true, objectiveManager);
AddSubObjective(findDivingGear);
}
else if (!findDivingGear.CanBeCompleted)
{
abandon = true;
return;
}
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
if (findDivingGear != null) { return; }
}
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
if (weldingTool == null)
{
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
return;
TryAddSubObjective(ref getWeldingTool, () => new AIObjectiveGetItem(character, "weldingtool", objectiveManager, true));
if (getWeldingTool != null) { return; }
}
else
{
var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return;
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
if (fuelTank == null)
if (containedItems == null)
{
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>(), objectiveManager));
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no proper inventory");
#endif
abandon = true;
return;
}
// Drop empty tanks
foreach (Item containedItem in containedItems)
{
if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop(character);
}
}
if (containedItems.None(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f))
{
TryAddSubObjective(ref refuelObjective, () => new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>(), objectiveManager));
if (refuelObjective != null) { return; }
}
}
var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null) { return; }
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
if (repairTool == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveFixLeak failed - the item \"" + weldingTool + "\" has no RepairTool component but is tagged as a welding tool");
#endif
abandon = true;
return;
}
Vector2 gapDiff = Leak.WorldPosition - character.WorldPosition;
// TODO: use the collider size/reach?
if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{
HumanAIController.AnimController.Crouching = true;
}
float reach = ConvertUnits.ToSimUnits(repairTool.Range);
bool canReach = ConvertUnits.ToSimUnits(gapDiff.Length()) < reach;
if (canReach)
@@ -115,65 +117,29 @@ namespace Barotrauma
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
canReach = character.CanSeeTarget(leak, sightLimb);
canReach = character.CanSeeTarget(Leak, sightLimb);
}
else
if (!canReach)
{
if (gotoObjective != null)
{
// Check if the objective is already removed -> completed/impossible
if (!subObjectives.Contains(gotoObjective))
{
if (!gotoObjective.CanBeCompleted)
{
abandon = true;
}
gotoObjective = null;
return;
}
}
else
{
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager)
{
CloseEnough = reach
};
if (!subObjectives.Contains(gotoObjective))
{
AddSubObjective(gotoObjective);
}
}
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character, objectiveManager) { CloseEnough = reach });
}
if (gotoObjective == null || gotoObjective.IsCompleted())
{
if (operateObjective == null)
{
operateObjective = new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: leak);
AddSubObjective(operateObjective);
}
else if (!subObjectives.Contains(operateObjective))
{
operateObjective = null;
}
}
if (gotoObjective != null) { return; }
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: "", requireEquip: true, operateTarget: Leak));
}
private Vector2 GetStandPosition()
{
Vector2 standPos = leak.Position;
var hull = leak.FlowTargetHull;
if (hull == null) return standPos;
if (leak.IsHorizontal)
Vector2 standPos = Leak.Position;
var hull = Leak.FlowTargetHull;
if (hull == null) { return standPos; }
if (Leak.IsHorizontal)
{
standPos += Vector2.UnitX * Math.Sign(hull.Position.X - leak.Position.X) * leak.Rect.Width;
standPos += Vector2.UnitX * Math.Sign(hull.Position.X - Leak.Position.X) * Leak.Rect.Width;
}
else
{
standPos += Vector2.UnitY * Math.Sign(hull.Position.Y - leak.Position.Y) * leak.Rect.Height;
standPos += Vector2.UnitY * Math.Sign(hull.Position.Y - Leak.Position.Y) * Leak.Rect.Height;
}
return standPos;
}
}

View File

@@ -11,6 +11,9 @@ namespace Barotrauma
{
public override string DebugTag => "get item";
private readonly bool equip;
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
public Func<Item, float> GetItemPriority;
//can be either tags or identifiers
@@ -20,12 +23,6 @@ namespace Barotrauma
public string[] ignoredContainerIdentifiers;
private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private bool equip;
private HashSet<Item> ignoredItems = new HashSet<Item>();
private bool canBeCompleted = true;
public override bool CanBeCompleted => canBeCompleted;
public override float GetPriority()
{
@@ -57,20 +54,15 @@ namespace Barotrauma
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
CheckInventory();
}
private void CheckInventory()
{
if (itemIdentifiers == null)
{
return;
}
if (itemIdentifiers == null) { return; }
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue;
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) { continue; }
if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
{
targetItem = character.Inventory.Items[i];
@@ -84,7 +76,7 @@ namespace Barotrauma
{
foreach (Item containedItem in containedItems)
{
if (containedItem == null || containedItem.Condition <= 0.0f) continue;
if (containedItem == null || containedItem.Condition <= 0.0f) { continue; }
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
{
targetItem = containedItem;
@@ -103,10 +95,8 @@ namespace Barotrauma
if (targetItem == null || moveToTarget == null)
{
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
//SteeringManager.SteeringWander();
return;
}
if (moveToTarget.CurrentHull == character.CurrentHull &&
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance, 2))
{
@@ -116,36 +106,28 @@ namespace Barotrauma
var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null)
{
canBeCompleted = false;
abandon = true;
return;
}
//check if all the slots required by the item are free
foreach (InvSlotType slots in pickable.AllowedSlots)
{
if (slots.HasFlag(InvSlotType.Any)) continue;
if (slots.HasFlag(InvSlotType.Any)) { continue; }
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
//slot not needed by the item, continue
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue;
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) { continue; }
targetSlot = i;
//slot free, continue
if (character.Inventory.Items[i] == null) continue;
if (character.Inventory.Items[i] == null) { continue; }
//try to move the existing item to LimbSlot.Any and continue if successful
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { 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
character.Inventory.Items[i].Drop(character);
}
}
}
targetItem.TryInteract(character, false, true);
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
{
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
@@ -161,11 +143,14 @@ namespace Barotrauma
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear
goToObjective = new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: !gettingDivingGear);
if (!subObjectives.Contains(goToObjective))
{
AddSubObjective(goToObjective);
}
}
goToObjective.TryComplete(deltaTime);
if (!goToObjective.CanBeCompleted)
else if (goToObjective != null && !goToObjective.CanBeCompleted)
{
goToObjective = null;
targetItem = null;
moveToTarget = null;
ignoredItems.Add(targetItem);
@@ -185,24 +170,20 @@ namespace Barotrauma
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item, because neither identifiers nor item is was defined.");
#endif
canBeCompleted = false;
abandon = true;
}
return;
}
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
if (ignoredItems.Contains(item)) { continue; }
if (item.Submarine == null) { continue; }
else if (item.Submarine.TeamID != character.TeamID) { continue; }
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
@@ -217,14 +198,12 @@ namespace Barotrauma
{
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
}
//if the item is inside an item, which is inside a character's inventory, don't steal it
Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
}
float itemPriority = 0.0f;
if (GetItemPriority != null)
{
@@ -232,40 +211,35 @@ namespace Barotrauma
itemPriority = GetItemPriority(item);
if (itemPriority <= 0.0f) { continue; }
}
itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
itemPriority -= Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
//ignore if the item has a lower priority than the currently selected one
if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
currItemPriority = itemPriority;
targetItem = item;
moveToTarget = rootContainer ?? item;
}
//if searched through all the items and a target wasn't found, can't be completed
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", itemIdentifiers)}");
#endif
canBeCompleted = false;
abandon = true;
}
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
if (getItem == null) return false;
if (getItem.equip != equip) return false;
if (getItem == null) { return false; }
if (getItem.equip != equip) { return false; }
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
{
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) { return false; }
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
{
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) { return false; }
}
return true;
}
@@ -273,7 +247,6 @@ namespace Barotrauma
{
return getItem.targetItem == targetItem;
}
return false;
}
@@ -284,10 +257,12 @@ namespace Barotrauma
foreach (string itemName in itemIdentifiers)
{
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true;
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem)))
{
return true;
}
}
return false;
}
else if (targetItem != null)
{

View File

@@ -9,21 +9,14 @@ namespace Barotrauma
public override string DebugTag => "go to";
private AIObjectiveFindDivingGear findDivingGear;
private Vector2 targetPos;
private bool repeat;
private bool cannotReach;
//how long until the path to the target is declared unreachable
private float waitUntilPathUnreachable;
private bool getDivingGearIfNeeded;
public float CloseEnough { get; set; } = 0.5f;
public bool IgnoreIfTargetDead { get; set; }
public bool AllowGoingOutside { get; set; }
public bool CheckVisibility { get; set; }
@@ -31,13 +24,11 @@ namespace Barotrauma
{
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
if (Target != null && Target.Removed) { return 0.0f; }
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
@@ -45,26 +36,25 @@ namespace Barotrauma
{
get
{
bool canComplete = !cannotReach && !abandon;
if (canComplete)
if (!abandon)
{
if (FollowControlledCharacter && Character.Controlled == null)
{
canComplete = false;
abandon = true;
}
else if (Target != null && Target.Removed)
{
canComplete = false;
abandon = true;
}
else if (!repeat && waitUntilPathUnreachable < 0)
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
{
canComplete = !PathSteering.CurrentPath.Unreachable;
abandon = PathSteering.CurrentPath.Unreachable;
}
}
}
if (!canComplete)
if (abandon)
{
#if DEBUG
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {(Target != null ? Target.ToString() : TargetPos.ToString())}", Color.Yellow);
@@ -75,7 +65,7 @@ namespace Barotrauma
}
character.AIController.SteeringManager.Reset();
}
return canComplete;
return base.CanBeCompleted;
}
}
@@ -115,24 +105,18 @@ namespace Barotrauma
if (Character.Controlled == null) { return; }
Target = Character.Controlled;
}
if (Target == character)
{
character.AIController.SteeringManager.Reset();
return;
}
}
waitUntilPathUnreachable -= deltaTime;
if (!character.IsClimbing)
{
character.SelectedConstruction = null;
}
if (Target != null) { character.AIController.SelectTarget(Target.AiTarget); }
Vector2 currTargetPos = Vector2.Zero;
if (Target == null)
{
currTargetPos = targetPos;
@@ -147,7 +131,6 @@ namespace Barotrauma
currTargetPos -= character.Submarine.SimPosition;
}
}
bool sightCheck = true;
if (CheckVisibility && Target != null)
{
@@ -165,12 +148,12 @@ namespace Barotrauma
bool targetIsOutside = (Target != null && Target.Submarine == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
if (isInside && targetIsOutside && !AllowGoingOutside)
{
cannotReach = true;
abandon = true;
return;
}
if (insideSteering && !PathSteering.HasAccessToPath(PathSteering.CurrentPath))
{
cannotReach = true;
abandon = true;
return;
}
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
@@ -181,15 +164,7 @@ namespace Barotrauma
Target is Item i && HumanAIController.NeedsDivingGear(i.CurrentHull) ||
Target is Character c && HumanAIController.NeedsDivingGear(c.CurrentHull))
{
if (findDivingGear == null)
{
findDivingGear = new AIObjectiveFindDivingGear(character, true, objectiveManager);
AddSubObjective(findDivingGear);
}
else if (!findDivingGear.CanBeCompleted)
{
abandon = true;
}
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, true, objectiveManager));
}
}
}
@@ -198,33 +173,31 @@ namespace Barotrauma
public override bool IsCompleted()
{
if (repeat) { return false; }
bool completed = false;
if (Target is Item item)
{
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
if (item.IsInsideTrigger(character.WorldPosition)) { completed = true; }
}
else if (Target is Character targetCharacter)
{
if (character.CanInteractWith(targetCharacter)) completed = true;
if (character.CanInteractWith(targetCharacter)) { completed = true; }
}
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < CloseEnough * CloseEnough;
if (completed) character.AIController.SteeringManager.Reset();
if (completed) { character.AIController.SteeringManager.Reset(); }
return completed;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
if (objective == null) return false;
if (!(otherObjective is AIObjectiveGoTo objective)) { return false; }
if (objective.Target == Target) { return true; }
return objective.targetPos == targetPos;
}
if (objective.Target == Target) return true;
return (objective.targetPos == targetPos);
private void CalculateCloseEnough()
{
float interactionDistance = Target is Item i ? ConvertUnits.ToSimUnits(i.InteractDistance * 0.9f) : 0;
CloseEnough = Math.Max(interactionDistance, CloseEnough);
}
private void CalculateCloseEnough()

View File

@@ -74,6 +74,21 @@ namespace Barotrauma
}
}
public override void Update(float deltaTime)
{
if (objectiveManager.CurrentObjective == this)
{
if (randomTimer > 0)
{
randomTimer -= deltaTime;
}
else
{
SetRandom();
}
}
}
public override bool IsCompleted() => false;
public override bool CanBeCompleted => true;
@@ -84,7 +99,7 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (PathSteering == null) return;
if (PathSteering == null) { return; }
//don't keep dragging others when idling
if (character.SelectedCharacter != null)
@@ -136,7 +151,6 @@ namespace Barotrauma
{
//choose a random available hull
var randomHull = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
if (isCurrentHullOK)
{
@@ -269,7 +283,6 @@ namespace Barotrauma
private void FindTargetHulls()
{
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
targetHulls.Clear();
hullWeights.Clear();
foreach (var hull in Hull.hullList)

View File

@@ -133,9 +133,10 @@ namespace Barotrauma
{
foreach (T target in GetList())
{
// The bots always find targets when the objective is an order.
if (objectiveManager.CurrentOrder != this)
{
// Battery or pump states cannot be reported and therefore we must ignore them -> the bots always know if they require attention.
// Battery or pump states cannot currently be reported (not implemented) and therefore we must ignore them -> the bots always know if they require attention.
bool ignore = this is AIObjectiveChargeBatteries || this is AIObjectivePumpWater;
if (!ignore && !ReportedTargets.Contains(target)) { continue; }
}

View File

@@ -11,41 +11,21 @@ namespace Barotrauma
public override string DebugTag => "operate item";
private ItemComponent component, controller;
private Entity operateTarget;
private bool isCompleted;
private bool canBeCompleted;
private bool requireEquip;
private bool useController;
private AIObjectiveGoTo goToObjective;
private AIObjectiveGetItem getItemObjective;
private AIObjectiveGoTo gotoObjective;
public override bool CanBeCompleted
{
get
{
if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false;
if (useController && controller == null) return false;
return canBeCompleted;
}
}
public Entity OperateTarget
{
get { return operateTarget; }
}
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
public Entity OperateTarget => operateTarget;
public ItemComponent Component => component;
public override float GetPriority()
{
if (gotoObjective != null && !gotoObjective.CanBeCompleted) { return 0; }
if (goToObjective != null && !goToObjective.CanBeCompleted) { return 0; }
if (component.Item.ConditionPercentage <= 0) { return 0; }
if (objectiveManager.CurrentOrder == this)
{
@@ -65,33 +45,28 @@ namespace Barotrauma
this.requireEquip = requireEquip;
this.operateTarget = operateTarget;
this.useController = useController;
if (useController)
{
var controllers = component.Item.GetConnectedComponents<Controller>();
if (controllers.Any()) controller = controllers[0];
}
canBeCompleted = true;
}
protected override void Act(float deltaTime)
{
ItemComponent target = useController ? controller : component;
if (useController && controller == null)
{
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
return;
}
if (target.CanBeSelected)
{
if (Vector2.Distance(character.Position, target.Item.Position) < target.Item.InteractDistance
|| target.Item.IsInsideTrigger(character.WorldPosition))
{
bool inSameRoom = character.CurrentHull == target.Item.CurrentHull;
bool withinReach = target.Item.IsInsideTrigger(character.WorldPosition) || Vector2.DistanceSquared(character.Position, target.Item.Position) < MathUtils.Pow(target.Item.InteractDistance, 2);
if (inSameRoom && withinReach)
{
if (character.CurrentHull == target.Item.CurrentHull)
if (character.SelectedConstruction != target.Item)
{
if (character.SelectedConstruction != target.Item)
{
@@ -103,21 +78,27 @@ namespace Barotrauma
}
return;
}
if (component.AIOperate(deltaTime, character, this))
{
isCompleted = true;
}
}
else
{
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager));
}
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character, objectiveManager));
}
else
{
if (component.Item.GetComponent<Pickable>() == null)
{
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
canBeCompleted = false;
abandon = true;
return;
}
else if (!character.Inventory.Items.Contains(component.Item))
{
AddSubObjective(new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true));
TryAddSubObjective(ref getItemObjective, () => new AIObjectiveGetItem(character, component.Item, objectiveManager, equip: true));
}
else
{
@@ -127,18 +108,17 @@ namespace Barotrauma
var holdable = component.Item.GetComponent<Holdable>();
if (holdable == null)
{
#if DEBUG
DebugConsole.ThrowError("AIObjectiveOperateItem failed - equipping item " + component.Item + " is required but the item has no Holdable component");
#endif
return;
}
for (int i = 0; i < character.Inventory.Capacity; i++)
{
if (character.Inventory.SlotTypes[i] == InvSlotType.Any ||
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || !holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
{
continue;
}
//equip slot already taken
if (character.Inventory.Items[i] != null)
{
@@ -157,7 +137,6 @@ namespace Barotrauma
}
return;
}
if (component.AIOperate(deltaTime, character, this))
{
isCompleted = true;
@@ -166,17 +145,12 @@ namespace Barotrauma
}
}
public override bool IsCompleted()
{
return isCompleted && !IsLoop;
}
public override bool IsCompleted() => isCompleted && !IsLoop;
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
if (operateItem == null) return false;
return (operateItem.component == component ||otherObjective.Option == Option);
if (!(otherObjective is AIObjectiveOperateItem operateItem)) { return false; }
return (operateItem.component == component || otherObjective.Option == Option);
}
}
}

View File

@@ -16,8 +16,8 @@ namespace Barotrauma
public Item Item { get; private set; }
private AIObjectiveGoTo goToObjective;
private float previousCondition = -1;
private RepairTool repairTool;
public AIObjectiveRepairItem(Character character, Item item, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier)
{
@@ -42,8 +42,6 @@ namespace Barotrauma
return MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + damagePriority * distanceFactor * successFactor * PriorityModifier, 0, 1));
}
public override bool CanBeCompleted => !abandon;
public override bool IsCompleted()
{
bool isCompleted = Item.IsFullCondition;
@@ -61,19 +59,10 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (goToObjective != null && !subObjectives.Contains(goToObjective))
// Don't allow to repair items in rooms that have a fire or an enemies inside or if outside the sub
if (Item.CurrentHull == null || Item.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == Item.CurrentHull && !HumanAIController.IsFriendly(c)))
{
if (!goToObjective.IsCompleted() && !goToObjective.CanBeCompleted)
{
abandon = true;
character?.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f);
}
goToObjective = null;
}
if (!abandon)
{
// Don't allow to repair items in rooms that have a fire or an enemy inside
abandon = Item.CurrentHull != null && (Item.CurrentHull.FireSources.Count > 0 || Character.CharacterList.Any(c => c.CurrentHull == Item.CurrentHull && !HumanAIController.IsFriendly(c)));
abandon = true;
}
foreach (Repairable repairable in Item.Repairables)
{
@@ -90,6 +79,8 @@ namespace Barotrauma
return;
}
}
// Only continue when the get item sub objectives have been completed.
if (subObjectives.Any(so => so is AIObjectiveGetItem)) { return; }
if (repairTool == null)
{
FindRepairTool();
@@ -128,24 +119,24 @@ namespace Barotrauma
break;
}
}
else if (goToObjective == null || goToObjective.Target != Item)
else
{
previousCondition = -1;
if (goToObjective != null)
{
subObjectives.Remove(goToObjective);
}
goToObjective = new AIObjectiveGoTo(Item, character, objectiveManager);
if (repairTool != null)
{
//goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f;
goToObjective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range);
}
AddSubObjective(goToObjective);
// If cannot reach the item, approach it.
TryAddSubObjective(ref goToObjective,
constructor: () =>
{
previousCondition = -1;
var objective = new AIObjectiveGoTo(Item, character, objectiveManager);
if (repairTool != null)
{
objective.CloseEnough = ConvertUnits.ToSimUnits(repairTool.Range);
}
return objective;
},
onAbandon: () => character.Speak(TextManager.Get("DialogCannotRepair").Replace("[itemname]", Item.Name), null, 0.0f, "cannotrepair", 10.0f));
}
}
private RepairTool repairTool;
private void FindRepairTool()
{
foreach (Repairable repairable in Item.Repairables)

View File

@@ -7,6 +7,7 @@ using System.Linq;
namespace Barotrauma
{
// TODO: refactor
class AIObjectiveRescue : AIObjective
{
public override string DebugTag => "rescue";
@@ -93,6 +94,7 @@ namespace Barotrauma
}
}
// TODO: remove and replace with the priority system
protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
{
if (subObjective is AIObjectiveFindSafety)

View File

@@ -3,7 +3,7 @@ using System.Linq;
namespace Barotrauma
{
// TODO: Ensure that this works well enough. Consider using AIObjectiveLoop class.
// TODO: Refactor using AIObjectiveLoop class.
class AIObjectiveRescueAll : AIObjective
{
public override string DebugTag => "rescue all";

View File

@@ -810,6 +810,25 @@ namespace Barotrauma.Items.Components
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
}
else
{
if (outputContainer.Inventory.Items.All(i => i != null))
{
targetItem.Drop(dropper: null);
}
else
{
outputContainer.Inventory.TryPutItem(targetItem, user: null, createNetworkEvent: true);
}
}
if (targetItem.Prefab.DeconstructItems.Any())
{
inputContainer.Inventory.RemoveItem(targetItem);

View File

@@ -212,6 +212,33 @@ namespace Barotrauma.Items.Components
}
}
public Vector2? PosToMaintain
{
get { return posToMaintain; }
set { posToMaintain = value; }
}
struct ObstacleDebugInfo
{
public Vector2 Point1;
public Vector2 Point2;
public Vector2? Intersection;
public float Dot;
public Vector2 AvoidStrength;
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
{
Point1 = edge.Point1;
Point2 = edge.Point2;
Intersection = intersection;
Dot = dot;
AvoidStrength = avoidStrength;
}
}
//edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();

View File

@@ -1460,6 +1460,10 @@ namespace Barotrauma
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
if (!broken)
{
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
}
ApplyStatusEffects(!waterProof && inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
if (body == null || !body.Enabled || !inWater || ParentInventory != null || Removed) { return; }

View File

@@ -626,6 +626,25 @@ namespace Barotrauma
}
}
public string DisplayName
{
get;
private set;
}
private string roomName;
[Editable, Serialize("", true, translationTextTag: "RoomName.")]
public string RoomName
{
get { return roomName; }
set
{
if (roomName == value) { return; }
roomName = value;
DisplayName = TextManager.Get(roomName, returnNull: true) ?? roomName;
}
}
public override Rectangle Rect
{
get