38f1ddb...178a853: v0.8.9.1, removed content folder

This commit is contained in:
Joonas Rikkonen
2019-03-18 19:46:58 +02:00
parent 38f1ddb6fe
commit 6c0679c297
1054 changed files with 151673 additions and 144931 deletions
@@ -38,7 +38,7 @@ namespace Barotrauma
/// </summary>
public void TryComplete(float deltaTime)
{
subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted);
subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted || ShouldInterruptSubObjective(s));
foreach (AIObjective objective in subObjectives)
{
@@ -66,6 +66,18 @@ namespace Barotrauma
return currentSubObjective;
}
public void SortSubObjectives(AIObjectiveManager objectiveManager)
{
if (!subObjectives.Any()) return;
subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager)));
subObjectives[0].SortSubObjectives(objectiveManager);
}
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective)
{
return false;
}
protected abstract void Act(float deltaTime);
public abstract bool IsCompleted();
@@ -0,0 +1,66 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
namespace Barotrauma
{
class AIObjectiveChargeBatteries : AIObjective
{
private List<PowerContainer> availableBatteries;
private string orderOption;
public AIObjectiveChargeBatteries(Character character, string option)
: base(character, option)
{
orderOption = option;
availableBatteries = new List<PowerContainer>();
foreach (Item item in Item.ItemList)
{
if (item.Submarine == null) continue;
if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) continue;
var powerContainer = item.GetComponent<PowerContainer>();
availableBatteries.Add(powerContainer);
}
if (availableBatteries.Count == 0)
{
character?.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
}
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
public override bool IsCompleted()
{
return false;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveChargeBatteries other && other.orderOption == orderOption;
}
protected override void Act(float deltaTime)
{
if (availableBatteries.Count == 0)
{
AddSubObjective(new AIObjectiveIdle(character));
return;
}
foreach (PowerContainer battery in availableBatteries)
{
AddSubObjective(new AIObjectiveOperateItem(battery, character, orderOption, false));
}
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -17,21 +18,13 @@ namespace Barotrauma
private AIObjectiveFindSafety escapeObjective;
float coolDownTimer;
private AIObjectiveContainItem reloadWeaponObjective;
private readonly float enemyStrength;
private float coolDownTimer;
public AIObjectiveCombat(Character character, Character enemy)
: base(character, "")
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
{
this.enemy = enemy;
foreach (Limb limb in enemy.AnimController.Limbs)
{
if (limb.attack == null) continue;
enemyStrength += limb.attack.GetDamage(1.0f);
}
coolDownTimer = CoolDown;
}
@@ -39,26 +32,66 @@ namespace Barotrauma
{
coolDownTimer -= deltaTime;
var weapon = character.Inventory.FindItem("weapon");
var weapon = character.Inventory.FindItemByTag("weapon");
if (weapon == null)
{
Escape(deltaTime);
}
else
{
//TODO: make sure the weapon is ready to use (projectiles/batteries loaded)
if (!character.SelectedItems.Contains(weapon))
{
if (character.Inventory.TryPutItem(weapon, 3, false, false, character))
if (character.Inventory.TryPutItem(weapon, 3, true, false, character))
{
weapon.Equip(character);
}
else
{
//couldn't equip the item, escape
Escape(deltaTime);
return;
}
}
//make sure the weapon is loaded
var weaponComponent =
weapon.GetComponent<RangedWeapon>() as ItemComponent ??
weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
weapon.GetComponent<RepairTool>() as ItemComponent;
if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
Item[] containedItems = weapon.ContainedItems;
foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
Item containedItem = Array.Find(containedItems, it => it != null && it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (containedItem == null)
{
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
if (!newReloadWeaponObjective.IsDuplicate(reloadWeaponObjective))
{
reloadWeaponObjective = newReloadWeaponObjective;
}
}
}
}
if (reloadWeaponObjective != null)
{
if (reloadWeaponObjective.IsCompleted())
{
reloadWeaponObjective = null;
}
else if (!reloadWeaponObjective.CanBeCompleted)
{
Escape(deltaTime);
}
else
{
reloadWeaponObjective.TryComplete(deltaTime);
}
return;
}
character.CursorPosition = enemy.Position;
character.SetInput(InputType.Aim, false, true);
@@ -116,7 +149,7 @@ namespace Barotrauma
//clamp the strength to the health of this character
//(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway)
float enemyDanger = Math.Min(Math.Max(enemyStrength, MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
float enemyDanger = Math.Min(Math.Max(CalculateEnemyStrength(), MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
EnemyAIController enemyAI = enemy.AIController as EnemyAIController;
if (enemyAI != null)
@@ -134,5 +167,19 @@ namespace Barotrauma
return objective.enemy == enemy;
}
private float CalculateEnemyStrength()
{
float enemyStrength = 0;
AttackContext currentContext = character.GetAttackContext();
foreach (Limb limb in enemy.AnimController.Limbs)
{
if (limb.attack == null) continue;
if (!limb.attack.IsValidContext(currentContext)) { continue; }
if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
enemyStrength += limb.attack.GetTotalDamage(false);
}
return enemyStrength;
}
}
}
@@ -9,7 +9,8 @@ namespace Barotrauma
{
public int MinContainedAmount = 1;
private string[] itemNames;
//can either be a tag or an identifier
private string[] itemIdentifiers;
private ItemContainer container;
@@ -21,16 +22,21 @@ namespace Barotrauma
private AIObjectiveGetItem getItemObjective;
private AIObjectiveGoTo goToObjective;
public AIObjectiveContainItem(Character character, string itemName, ItemContainer container)
: this(character, new string[] { itemName }, container)
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container)
: this(character, new string[] { itemIdentifier }, container)
{
}
public AIObjectiveContainItem(Character character, string[] itemNames, ItemContainer container)
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container)
: base (character, "")
{
this.itemNames = itemNames;
this.itemIdentifiers = itemIdentifiers;
for (int i = 0; i < itemIdentifiers.Length; i++)
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
this.container = container;
}
@@ -41,7 +47,7 @@ namespace Barotrauma
int containedItemCount = 0;
foreach (Item item in container.Inventory.Items)
{
if (item != null && itemNames.Any(name => item.Prefab.NameMatches(name) || item.HasTag(name))) containedItemCount++;
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++;
}
return containedItemCount >= MinContainedAmount;
@@ -56,7 +62,7 @@ namespace Barotrauma
return goToObjective.CanBeCompleted;
}
return getItemObjective == null || !getItemObjective.CanBeCompleted;
return getItemObjective == null || getItemObjective.CanBeCompleted;
}
}
@@ -75,12 +81,20 @@ namespace Barotrauma
if (isCompleted) return;
//get the item that should be contained
var itemToContain = character.Inventory.FindItem(itemNames);
Item itemToContain = null;
foreach (string identifier in itemIdentifiers)
{
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
if (itemToContain != null) break;
}
if (itemToContain == null)
{
getItemObjective = new AIObjectiveGetItem(character, itemNames);
getItemObjective.GetItemPriority = GetItemPriority;
getItemObjective.IgnoreContainedItems = IgnoreAlreadyContainedItems;
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
{
GetItemPriority = GetItemPriority,
IgnoreContainedItems = IgnoreAlreadyContainedItems
};
AddSubObjective(getItemObjective);
return;
}
@@ -116,11 +130,11 @@ namespace Barotrauma
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
if (objective == null) return false;
if (objective.container != container) return false;
if (objective.itemNames.Length != itemNames.Length) return false;
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
for (int i = 0; i < itemNames.Length; i++)
for (int i = 0; i < itemIdentifiers.Length; i++)
{
if (objective.itemNames[i] != itemNames[i]) return false;
if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false;
}
return true;
@@ -0,0 +1,106 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveExtinguishFire : AIObjective
{
private Hull targetHull;
private AIObjectiveGetItem getExtinguisherObjective;
private AIObjectiveGoTo gotoObjective;
private float useExtinquisherTimer;
public AIObjectiveExtinguishFire(Character character, Hull targetHull) :
base(character, "")
{
this.targetHull = targetHull;
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
return targetHull.FireSources.Sum(fs => fs.Size.X * 0.1f);
}
public override bool IsCompleted()
{
return targetHull.FireSources.Count == 0;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire;
return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull;
}
public override bool CanBeCompleted
{
get { return getExtinguisherObjective == null || getExtinguisherObjective.CanBeCompleted; }
}
protected override void Act(float deltaTime)
{
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
{
if (getExtinguisherObjective == null)
{
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true);
}
else
{
getExtinguisherObjective.TryComplete(deltaTime);
}
return;
}
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");
return;
}
foreach (FireSource fs in targetHull.FireSources.ToList())
{
if (fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)) || useExtinquisherTimer > 0.0f)
{
useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
character.CursorPosition = fs.Position;
character.SetInput(InputType.Aim, false, true);
character.AIController.SteeringManager.Reset();
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
}
return;
}
}
foreach (FireSource fs in targetHull.FireSources)
{
//go to the first firesource
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
{
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character);
}
else
{
gotoObjective.TryComplete(deltaTime);
}
break;
}
}
}
}
@@ -0,0 +1,47 @@
using System.Linq;
namespace Barotrauma
{
class AIObjectiveExtinguishFires : AIObjective
{
public AIObjectiveExtinguishFires(Character character) :
base(character, "")
{
if (!Hull.hullList.Any(h => h.FireSources.Count > 0))
{
character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
}
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (objectiveManager.CurrentObjective == this)
{
return AIObjectiveManager.OrderPriority;
}
return Hull.hullList.Count(h => h.FireSources.Count > 0) * 10;
}
public override bool IsCompleted()
{
return !Hull.hullList.Any(h => h.FireSources.Count > 0);
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveExtinguishFires;
}
protected override void Act(float deltaTime)
{
foreach (Hull hull in Hull.hullList)
{
if (hull.FireSources.Count > 0)
{
AddSubObjective(new AIObjectiveExtinguishFire(character, hull));
}
}
}
}
}
@@ -7,19 +7,19 @@ namespace Barotrauma
{
private AIObjective subObjective;
private string gearName;
private string gearTag;
public override bool IsCompleted()
{
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
if (CharacterInventory.limbSlots[i] == InvSlotType.Any || character.Inventory.Items[i] == null) continue;
if (character.Inventory.Items[i].Prefab.NameMatches(gearName) || character.Inventory.Items[i].HasTag(gearName))
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || character.Inventory.Items[i] == null) continue;
if (character.Inventory.Items[i].HasTag(gearTag))
{
var containedItems = character.Inventory.Items[i].ContainedItems;
if (containedItems == null) continue;
var oxygenTank = Array.Find(containedItems, it => (it.Prefab.NameMatches("Oxygen Tank") || it.HasTag("oxygensource")) && it.Condition > 0.0f);
var oxygenTank = Array.Find(containedItems, it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
if (oxygenTank != null) return true;
}
}
@@ -30,18 +30,19 @@ namespace Barotrauma
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit)
: base(character, "")
{
gearName = needDivingSuit ? "Diving Suit" : "diving";
gearTag = needDivingSuit ? "divingsuit" : "diving";
}
protected override void Act(float deltaTime)
{
var item = character.Inventory.FindItem(gearName);
if (item == null)
var item = character.Inventory.FindItemByTag(gearTag);
if (item == null || !character.HasEquippedItem(item))
{
//get a diving mask/suit first
if (!(subObjective is AIObjectiveGetItem))
{
subObjective = new AIObjectiveGetItem(character, gearName, true);
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
subObjective = new AIObjectiveGetItem(character, gearTag, true);
}
}
else
@@ -57,7 +58,7 @@ namespace Barotrauma
{
containedItem.Drop();
}
else if (containedItem.Prefab.NameMatches("Oxygen Tank") || containedItem.HasTag("oxygensource"))
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
{
//we've got an oxygen source inside the mask/suit, all good
return;
@@ -66,7 +67,8 @@ namespace Barotrauma
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
{
subObjective = new AIObjectiveContainItem(character, new string[] { "Oxygen Tank", "oxygensource" }, item.GetComponent<ItemContainer>());
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
}
}
@@ -55,7 +55,10 @@ namespace Barotrauma
var bestHull = FindBestHull();
if (bestHull != null)
{
goToObjective = new AIObjectiveGoTo(bestHull, character);
goToObjective = new AIObjectiveGoTo(bestHull, character)
{
AllowGoingOutside = true
};
}
searchHullTimer = SearchHullInterval;
@@ -63,15 +66,57 @@ namespace Barotrauma
if (goToObjective != null)
{
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (pathSteering != null && pathSteering.CurrentPath != null &&
goToObjective.TryComplete(deltaTime);
if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering &&
pathSteering.CurrentPath != null &&
pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target))
{
unreachable.Add(goToObjective.Target as Hull);
goToObjective = null;
}
}
//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
else if (currentHull != null)
{
Vector2 escapeVel = Vector2.Zero;
foreach (FireSource fireSource in currentHull.FireSources)
{
int dir = Math.Sign(character.Position.X - fireSource.Position.X);
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(dir * distMultiplier, 0.0f);
}
foreach (Character enemy in Character.CharacterList)
{
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
escapeVel += new Vector2(Math.Sign(character.Position.X - enemy.Position.X) * distMultiplier, 0.0f);
}
}
goToObjective.TryComplete(deltaTime);
if (escapeVel != Vector2.Zero)
{
//only move if we haven't reached the edge of the room
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
{
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
}
else
{
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
character.AIController.SteeringManager.Reset();
}
}
else
{
character.AIController.SteeringManager.Reset();
}
}
}
@@ -137,26 +182,30 @@ namespace Barotrauma
return 150.0f - character.Oxygen;
}
if (character.AnimController.CurrentHull == null) return 5.0f;
currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character);
if (character.CurrentHull == null) return 5.0f;
currenthullSafety = GetHullSafety(character.CurrentHull, character);
priority = 100.0f - currenthullSafety;
var nearbyHulls = character.AnimController.CurrentHull.GetConnectedHulls(3);
//var nearbyHulls = character.CurrentHull.GetConnectedHulls(3);
foreach (Hull hull in nearbyHulls)
//increase priority slightly if there's a fire in the room
//(will increase more heavily if near the damage range of the fire)
if (character.CurrentHull.FireSources.Count > 0)
{
priority += 5.0f;
}
/*foreach (Hull hull in nearbyHulls)
{
foreach (FireSource fireSource in hull.FireSources)
{
//increase priority if almost within damage range of a fire
if (character.Position.X > fireSource.Position.X - fireSource.DamageRange * 2 &&
character.Position.X < fireSource.Position.X + fireSource.Size.X + fireSource.DamageRange * 2 &&
character.Position.Y > hull.Rect.Y - hull.Rect.Height &&
character.Position.Y < hull.Rect.Y)
//heavily increase priority if almost within damage range of a fire
if (fireSource.IsInDamageRange(character, fireSource.DamageRange * 1.25f))
{
priority += Math.Max(fireSource.Size.X, 50.0f);
}
}
}
}*/
if (NeedsDivingGear())
{
@@ -189,25 +238,36 @@ namespace Barotrauma
if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f;
if (safety <= 0.0f) return 0.0f;
bool extinguishFires =
character.AIController.ObjectiveManager?.CurrentOrder is AIObjectiveExtinguishFires ||
character.AIController.ObjectiveManager?.CurrentOrder is AIObjectiveExtinguishFire;
float fireAmount = 0.0f;
var nearbyHulls = hull.GetConnectedHulls(3);
foreach (Hull hull2 in nearbyHulls)
{
foreach (FireSource fireSource in hull2.FireSources)
{
//increase priority if almost within damage range of a fire
if (character.Position.X > fireSource.Position.X - fireSource.DamageRange * 2 &&
character.Position.X < fireSource.Position.X + fireSource.Size.X + fireSource.DamageRange * 2 &&
character.Position.Y > hull2.Rect.Y - hull2.Rect.Height &&
character.Position.Y < hull2.Rect.Y)
//increase priority if near the damage range of a fire
//if extinguishing fires, the character can go closer the damage range
if (fireSource.IsInDamageRange(character, fireSource.DamageRange * (extinguishFires ? 1.25f : 5.0f)))
{
fireAmount += Math.Max(fireSource.Size.X, 50.0f);
fireAmount += Math.Max(fireSource.Size.X, AIObjectiveManager.OrderPriority + 1.0f);
}
}
}
safety -= fireAmount;
foreach (Character enemy in Character.CharacterList)
{
if (enemy.CurrentHull == hull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{
safety -= 10.0f;
}
}
return MathHelper.Clamp(safety, 0.0f, 100.0f);
}
}
@@ -8,9 +8,7 @@ namespace Barotrauma
class AIObjectiveFixLeak : AIObjective
{
private readonly Gap leak;
private AIObjectiveGoTo gotoObjective;
public Gap Leak
{
get { return leak; }
@@ -47,11 +45,11 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
var weldingTool = character.Inventory.FindItem("Welding Tool");
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
if (weldingTool == null)
{
AddSubObjective(new AIObjectiveGetItem(character, "Welding Tool", true));
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true));
return;
}
else
@@ -59,11 +57,10 @@ namespace Barotrauma
var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return;
var fuelTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Welding Fuel Tank") && i.Condition > 0.0f);
var fuelTank = Array.Find(containedItems, i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
if (fuelTank == null)
{
AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent<ItemContainer>()));
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
return;
}
}
@@ -73,7 +70,15 @@ namespace Barotrauma
Vector2 standPosition = GetStandPosition();
if (Vector2.DistanceSquared(character.WorldPosition, leak.WorldPosition) > 100.0f * 100.0f)
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
Math.Abs(gapDiff.X) < 100.0f && gapDiff.Y < 0.0f && gapDiff.Y > -150.0f)
{
((HumanoidAnimController)character.AnimController).Crouching = true;
}
if (Math.Abs(gapDiff.X) > 100.0f || Math.Abs(gapDiff.Y) > 150.0f)
{
var gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(standPosition), character);
if (!gotoObjective.IsCompleted())
@@ -105,9 +105,20 @@ namespace Barotrauma
{
if (gap.ConnectedWall == null) continue;
if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue;
if (character.TeamID == 0)
{
if (gap.Submarine == null) continue;
}
else
{
//prevent characters from attempting to fix leaks in the enemy sub
//team 1 plays in sub 0, team 2 in sub 1
Submarine mySub = character.TeamID < 1 || character.TeamID > Submarine.MainSubs.Length ?
Submarine.MainSub : Submarine.MainSubs[character.TeamID - 1];
//TODO: prevent the AI characters from fixing leaks in the enemy sub in sub-vs-sub missions if/when multiplayer bots are implemented
if (gap.Submarine == null) continue;
if (gap.Submarine != mySub) continue;
}
float gapPriority = GetGapFixPriority(gap);
@@ -10,7 +10,8 @@ namespace Barotrauma
{
public Func<Item, float> GetItemPriority;
private string[] itemNames;
//can be either tags or identifiers
private string[] itemIdentifiers;
private Item targetItem, moveToTarget;
@@ -45,29 +46,65 @@ namespace Barotrauma
: base(character, "")
{
canBeCompleted = true;
currSearchIndex = -1;
this.equip = equip;
currSearchIndex = 0;
this.targetItem = targetItem;
}
public AIObjectiveGetItem(Character character, string itemName, bool equip = false)
: this(character, new string[] { itemName }, equip)
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false)
: this(character, new string[] { itemIdentifier }, equip)
{
}
public AIObjectiveGetItem(Character character, string[] itemNames, bool equip = false)
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false)
: base(character, "")
{
canBeCompleted = true;
currSearchIndex = -1;
this.equip = equip;
this.itemIdentifiers = itemIdentifiers;
for (int i = 0; i < itemIdentifiers.Length; i++)
{
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
}
currSearchIndex = 0;
CheckInventory();
}
this.itemNames = itemNames;
private void CheckInventory()
{
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 (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
{
targetItem = character.Inventory.Items[i];
moveToTarget = targetItem;
currItemPriority = 100.0f;
break;
}
//check items inside items (tool inside a toolbox etc)
var containedItems = character.Inventory.Items[i].ContainedItems;
if (containedItems != null)
{
foreach (Item containedItem in containedItems)
{
if (containedItem == null || containedItem.Condition <= 0.0f) continue;
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
{
targetItem = containedItem;
moveToTarget = character.Inventory.Items[i];
currItemPriority = 100.0f;
break;
}
}
}
}
}
protected override void Act(float deltaTime)
@@ -99,7 +136,7 @@ namespace Barotrauma
for (int i = 0; i < character.Inventory.Items.Length; i++)
{
//slot not needed by the item, continue
if (!slots.HasFlag(CharacterInventory.limbSlots[i])) continue;
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue;
targetSlot = i;
@@ -117,7 +154,7 @@ namespace Barotrauma
targetItem.TryInteract(character, false, true);
if (targetSlot > -1 && character.Inventory.IsInLimbSlot(targetItem, InvSlotType.Any))
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
{
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
}
@@ -127,14 +164,15 @@ namespace Barotrauma
if (goToObjective == null || moveToTarget != goToObjective.Target)
{
//check if we're already looking for a diving gear
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.NameMatches("Diving Gear") || targetItem.HasTag("diving")) ||
(itemNames != null && (itemNames.Contains("diving") || itemNames.Contains("Diving Gear")));
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
}
goToObjective.TryComplete(deltaTime);
if (!goToObjective.CanBeCompleted) targetItem = null;
}
}
@@ -144,7 +182,7 @@ namespace Barotrauma
/// </summary>
private void FindTargetItem()
{
if (itemNames == null)
if (itemIdentifiers == null)
{
if (targetItem == null) canBeCompleted = false;
return;
@@ -152,7 +190,7 @@ namespace Barotrauma
float currDist = moveToTarget == null ? 0.0f : Vector2.DistanceSquared(moveToTarget.Position, character.Position);
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 2; i++)
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
{
currSearchIndex++;
@@ -160,21 +198,19 @@ namespace Barotrauma
if (item.CurrentHull == null || item.Condition <= 0.0f) continue;
if (IgnoreContainedItems && item.Container != null) continue;
if (!itemNames.Any(name => item.Prefab.NameMatches(name) || item.HasTag(name))) continue;
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) continue;
//if the item is inside a character's inventory, don't steal it unless the character is dead
if (item.ParentInventory is CharacterInventory)
{
Character owner = item.ParentInventory.Owner as Character;
if (owner != null && !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
Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{
Character owner = rootContainer.ParentInventory.Owner as Character;
if (owner != null && !owner.IsDead) continue;
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) continue;
}
float itemPriority = 0.0f;
@@ -194,10 +230,11 @@ namespace Barotrauma
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 && targetItem == null) canBeCompleted = false;
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null) canBeCompleted = false;
}
public override bool IsDuplicate(AIObjective otherObjective)
@@ -205,16 +242,16 @@ namespace Barotrauma
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
if (getItem == null) return false;
if (getItem.equip != equip) return false;
if (getItem.itemNames != null && itemNames != null)
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
{
if (getItem.itemNames.Length != itemNames.Length) return false;
for (int i = 0; i < getItem.itemNames.Length; i++)
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
{
if (getItem.itemNames[i] != itemNames[i]) return false;
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
}
return true;
}
else if (getItem.itemNames == null && itemNames == null)
else if (getItem.itemIdentifiers == null && itemIdentifiers == null)
{
return getItem.targetItem == targetItem;
}
@@ -224,11 +261,11 @@ namespace Barotrauma
public override bool IsCompleted()
{
if (itemNames != null)
if (itemIdentifiers != null)
{
foreach (string itemName in itemNames)
foreach (string itemName in itemIdentifiers)
{
var matchingItem = character.Inventory.FindItem(itemName);
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true;
}
return false;
@@ -7,8 +7,6 @@ namespace Barotrauma
{
class AIObjectiveGoTo : AIObjective
{
private Entity target;
private Vector2 targetPos;
private bool repeat;
@@ -18,8 +16,17 @@ namespace Barotrauma
private bool getDivingGearIfNeeded;
public float CloseEnough = 0.5f;
public bool IgnoreIfTargetDead;
public bool AllowGoingOutside = false;
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (Target != null && Target.Removed) return 0.0f;
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) return 0.0f;
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
@@ -32,25 +39,26 @@ namespace Barotrauma
{
get
{
if (Target != null && Target.Removed) return false;
if (repeat || waitUntilPathUnreachable > 0.0f) return true;
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable
if (pathSteering.CurrentPath == null) return true;
if (pathSteering?.CurrentPath == null) return true;
return (!pathSteering.CurrentPath.Unreachable);
if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) return false;
return !pathSteering.CurrentPath.Unreachable;
}
}
public Entity Target
{
get { return target; }
}
public Entity Target { get; private set; }
public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
: base (character, "")
{
this.target = target;
this.Target = target;
this.repeat = repeat;
waitUntilPathUnreachable = 5.0f;
@@ -70,58 +78,65 @@ namespace Barotrauma
protected override void Act(float deltaTime)
{
if (target == character)
if (Target == character)
{
character.AIController.SteeringManager.Reset();
return;
}
waitUntilPathUnreachable -= deltaTime;
if (character.SelectedConstruction!=null && character.SelectedConstruction.GetComponent<Ladder>()==null)
if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Ladder>() == null)
{
character.SelectedConstruction = null;
}
if (target != null) character.AIController.SelectTarget(target.AiTarget);
if (Target != null) character.AIController.SelectTarget(Target.AiTarget);
Vector2 currTargetPos = Vector2.Zero;
if (target == null)
if (Target == null)
{
currTargetPos = targetPos;
}
else
{
currTargetPos = target.SimPosition;
currTargetPos = Target.SimPosition;
//if character is outside the sub and target isn't, transform the position
if (character.Submarine != null && target.Submarine == null)
//if character is inside the sub and target isn't, transform the position
if (character.Submarine != null && Target.Submarine == null)
{
currTargetPos -= character.Submarine.SimPosition;
}
}
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < 0.5f * 0.5f)
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
{
character.AIController.SteeringManager.Reset();
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
}
else
{
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
var indoorsSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable)
{
indoorsSteering.SteeringWander();
}
else if (getDivingGearIfNeeded && indoorsSteering.CurrentPath != null && indoorsSteering.CurrentPath.HasOutdoorsNodes)
float normalSpeed = character.AnimController.GetCurrentSpeed(false);
character.AIController.SteeringManager.SteeringSeek(currTargetPos, normalSpeed);
if (getDivingGearIfNeeded && Target?.Submarine == null && AllowGoingOutside)
{
AddSubObjective(new AIObjectiveFindDivingGear(character, true));
}
else if (character.AIController.SteeringManager is IndoorsSteeringManager indoorsSteering)
{
if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable)
{
indoorsSteering.SteeringWander(normalSpeed);
}
else if (AllowGoingOutside &&
getDivingGearIfNeeded &&
indoorsSteering.CurrentPath != null &&
indoorsSteering.CurrentPath.HasOutdoorsNodes)
{
AddSubObjective(new AIObjectiveFindDivingGear(character, true));
}
}
}
}
@@ -132,15 +147,18 @@ namespace Barotrauma
bool completed = false;
float allowedDistance = 0.5f;
var item = target as Item;
if (item != null)
if (Target is Item item)
{
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.InteractDistance), allowedDistance);
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
}
else if (Target is Character targetCharacter)
{
if (character.CanInteractWith(targetCharacter)) completed = true;
}
completed = completed || Vector2.DistanceSquared(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance * allowedDistance;
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < allowedDistance * allowedDistance;
if (completed) character.AIController.SteeringManager.Reset();
@@ -152,7 +170,7 @@ namespace Barotrauma
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
if (objective == null) return false;
if (objective.target == target) return true;
if (objective.Target == Target) return true;
return (objective.targetPos == targetPos);
}
@@ -1,4 +1,6 @@
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -12,10 +14,15 @@ namespace Barotrauma
private AITarget currentTarget;
private float newTargetTimer;
private float standStillTimer;
private float walkDuration;
private AIObjectiveFindSafety findSafety;
public AIObjectiveIdle(Character character) : base(character, "")
{
standStillTimer = Rand.Range(-10.0f, 10.0f);
walkDuration = Rand.Range(0.0f, 10.0f);
}
public override bool IsCompleted()
@@ -33,6 +40,16 @@ namespace Barotrauma
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (pathSteering == null) return;
//don't keep dragging others when idling
if (character.SelectedCharacter != null)
{
character.DeselectCharacter();
}
if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Ladder>() == null)
{
character.SelectedConstruction = null;
}
if (character.AnimController.InWater)
{
//attempt to find a safer place if in water
@@ -48,10 +65,13 @@ namespace Barotrauma
if (currentTarget != null)
{
Vector2 pos = character.SimPosition;
if (character != null && character.Submarine == null) pos -= Submarine.MainSub.SimPosition;
var path = pathSteering.PathFinder.FindPath(pos, currentTarget.SimPosition);
if (path.Cost > 200.0f && character.AnimController.CurrentHull!=null) return;
if (character != null && character.Submarine == null) { pos -= Submarine.MainSub.SimPosition; }
string errorMsg = "(Character " + character.Name + " idling, target "
+ ((currentTarget.Entity is Hull hull && hull.RoomName != null) ? hull.RoomName : currentTarget.Entity.ToString()) + ")";
var path = pathSteering.PathFinder.FindPath(pos, currentTarget.SimPosition, errorMsg);
if (path.Cost > 1000.0f && character.AnimController.CurrentHull!=null) return;
pathSteering.SetPath(path);
}
@@ -70,6 +90,19 @@ namespace Barotrauma
if (pathSteering == null || (pathSteering.CurrentPath != null &&
(pathSteering.CurrentPath.NextNode == null || pathSteering.CurrentPath.Unreachable || pathSteering.CurrentPath.HasOutdoorsNodes)))
{
standStillTimer -= deltaTime;
if (standStillTimer > 0.0f)
{
walkDuration = Rand.Range(1.0f, 5.0f);
pathSteering.Reset();
return;
}
if (standStillTimer < -walkDuration)
{
standStillTimer = Rand.Range(1.0f, 10.0f);
}
//steer away from edges of the hull
if (character.AnimController.CurrentHull != null)
{
@@ -102,7 +135,7 @@ namespace Barotrauma
}
}
character.AIController.SteeringManager.SteeringWander();
character.AIController.SteeringManager.SteeringWander(character.AnimController.GetCurrentSpeed(false));
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
@@ -115,20 +148,21 @@ namespace Barotrauma
currentTarget = null;
return;
}
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, 2.0f);
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, character.AnimController.GetCurrentSpeed(true));
}
private AITarget FindRandomTarget()
{
if (Rand.Int(5)==1)
//random chance of navigating back to the room where the character spawned
if (Rand.Int(5) == 1)
{
var idCard = character.Inventory.FindItem("ID Card");
if (idCard==null) return null;
var idCard = character.Inventory.FindItemByIdentifier("idcard");
if (idCard == null) return null;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull==null) continue;
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull == null) continue;
foreach (string tag in wp.IdCardTags)
{
if (idCard.HasTag(tag)) return wp.CurrentHull.AiTarget;
@@ -140,9 +174,31 @@ namespace Barotrauma
List<Hull> targetHulls = new List<Hull>(Hull.hullList);
//ignore all hulls with fires or water in them
targetHulls.RemoveAll(h => h.FireSources.Any() || h.WaterVolume / h.Volume > 0.1f);
if (!targetHulls.Any()) return null;
if (character.Submarine != null)
{
targetHulls.RemoveAll(h => h.Submarine != character.Submarine);
}
return targetHulls[Rand.Range(0, targetHulls.Count)].AiTarget;
//remove ballast hulls
foreach (Item item in Item.ItemList)
{
if (item.HasTag("ballast") && targetHulls.Contains(item.CurrentHull))
{
targetHulls.Remove(item.CurrentHull);
}
}
//ignore hulls that are too low to stand inside
if (character.AnimController is HumanoidAnimController animController)
{
float minHeight = ConvertUnits.ToDisplayUnits(animController.HeadPosition.Value);
targetHulls.RemoveAll(h => h.CeilingHeight < minHeight);
}
if (!targetHulls.Any()) return null;
//prefer larger hulls
var targetHull = ToolBox.SelectWeightedRandom(targetHulls, targetHulls.Select(h => h.Volume).ToList(), Rand.RandSync.Unsynced);
return targetHull?.AiTarget;
}
return null;
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
@@ -12,7 +14,12 @@ namespace Barotrauma
private Character character;
private AIObjective currentOrder;
/// <summary>
/// When set above zero, the character will stand still doing nothing until the timer runs out (assuming they don't a high priority order active)
/// </summary>
public float WaitTimer;
public AIObjective CurrentOrder
{
get { return currentOrder; }
@@ -38,6 +45,23 @@ namespace Barotrauma
objectives.Add(objective);
}
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
public void AddObjective(AIObjective objective, float delay, Action callback = null)
{
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
{
CoroutineManager.StopCoroutines(coroutine);
DelayedObjectives.Remove(objective);
}
coroutine = CoroutineManager.InvokeAfter(() =>
{
DelayedObjectives.Remove(objective);
AddObjective(objective);
callback?.Invoke();
}, delay);
DelayedObjectives.Add(objective, coroutine);
}
public T GetObjective<T>() where T : AIObjective
{
foreach (AIObjective objective in objectives)
@@ -47,15 +71,21 @@ namespace Barotrauma
return null;
}
public float GetCurrentPriority(Character character)
private AIObjective GetCurrentObjective()
{
if (CurrentOrder != null &&
(objectives.Count == 0 || currentOrder.GetPriority(this) > objectives[0].GetPriority(this)))
{
return CurrentOrder.GetPriority(this);
return CurrentOrder;
}
return objectives.Count == 0 ? 0.0f : objectives[0].GetPriority(this);
return objectives.Count == 0 ? null : objectives[0];
}
public float GetCurrentPriority()
{
var currentObjective = GetCurrentObjective();
return currentObjective == null ? 0.0f : currentObjective.GetPriority(this);
}
public void UpdateObjectives()
@@ -67,46 +97,77 @@ namespace Barotrauma
//sort objectives according to priority
objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this)));
GetCurrentObjective()?.SortSubObjectives(this);
}
public void DoCurrentObjective(float deltaTime)
{
if (currentOrder != null && (!objectives.Any() || objectives[0].GetPriority(this) < currentOrder.GetPriority(this)))
CurrentObjective = GetCurrentObjective();
if (CurrentObjective == null || (CurrentObjective.GetPriority(this) < OrderPriority && WaitTimer > 0.0f))
{
CurrentObjective = currentOrder;
currentOrder.TryComplete(deltaTime);
WaitTimer -= deltaTime;
character.AIController.SteeringManager.Reset();
return;
}
if (!objectives.Any()) return;
objectives[0].TryComplete(deltaTime);
CurrentObjective = objectives[0];
CurrentObjective?.TryComplete(deltaTime);
}
public void SetOrder(AIObjective objective)
{
currentOrder = objective;
}
public void SetOrder(Order order, string option)
public void SetOrder(Order order, string option, Character orderGiver)
{
currentOrder = null;
if (order == null) return;
currentOrder = null;
switch (order.Name.ToLowerInvariant())
switch (order.AITag.ToLowerInvariant())
{
case "follow":
currentOrder = new AIObjectiveGoTo(Character.Controlled, character, true);
currentOrder = new AIObjectiveGoTo(orderGiver, character, true)
{
CloseEnough = 1.5f,
AllowGoingOutside = true,
IgnoreIfTargetDead = true
};
break;
case "wait":
currentOrder = new AIObjectiveGoTo(character, character, true);
currentOrder = new AIObjectiveGoTo(character, character, true)
{
AllowGoingOutside = true
};
break;
case "fixleaks":
case "fix leaks":
currentOrder = new AIObjectiveFixLeaks(character);
break;
case "chargebatteries":
currentOrder = new AIObjectiveChargeBatteries(character, option);
break;
case "rescue":
currentOrder = new AIObjectiveRescueAll(character);
break;
case "repairsystems":
currentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
break;
case "pumpwater":
currentOrder = new AIObjectivePumpWater(character, option);
break;
case "extinguishfires":
currentOrder = new AIObjectiveExtinguishFires(character);
break;
case "steer":
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
if (order.TargetItemComponent == null) return;
currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
break;
default:
if (order.TargetItem == null) return;
currentOrder = new AIObjectiveOperateItem(order.TargetItem, character, option, false, null, order.UseController);
if (order.TargetItemComponent == null) return;
currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
break;
}
}
@@ -17,10 +17,18 @@ namespace Barotrauma
private bool requireEquip;
private bool useController;
private AIObjectiveGoTo gotoObjective;
public override bool CanBeCompleted
{
get
{
if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false;
if (useController && controller == null) return false;
return canBeCompleted;
}
}
@@ -43,23 +51,30 @@ namespace Barotrauma
public AIObjectiveOperateItem(ItemComponent item, Character character, string option, bool requireEquip, Entity operateTarget = null, bool useController = false)
: base (character, option)
{
this.component = item;
this.component = item ?? throw new System.ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
this.requireEquip = requireEquip;
this.operateTarget = operateTarget;
this.useController = useController;
if (useController)
{
var controllers = item.Item.GetConnectedComponents<Controller>();
var controllers = component.Item.GetConnectedComponents<Controller>();
if (controllers.Any()) controller = controllers[0];
}
canBeCompleted = true;
}
protected override void Act(float deltaTime)
{
ItemComponent target = controller == null ? component : controller;
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)
{
@@ -75,11 +90,17 @@ namespace Barotrauma
return;
}
AddSubObjective(new AIObjectiveGoTo(target.Item, character));
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character));
}
else
{
if (!character.Inventory.Items.Contains(component.Item))
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;
return;
}
else if (!character.Inventory.Items.Contains(component.Item))
{
AddSubObjective(new AIObjectiveGetItem(character, component.Item, true));
}
@@ -95,10 +116,10 @@ namespace Barotrauma
return;
}
for (int i = 0; i < CharacterInventory.limbSlots.Length; i++)
for (int i = 0; i < character.Inventory.Capacity; i++)
{
if (CharacterInventory.limbSlots[i] == InvSlotType.Any ||
!holdable.AllowedSlots.Any(s => s.HasFlag(CharacterInventory.limbSlots[i])))
if (character.Inventory.SlotTypes[i] == InvSlotType.Any ||
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
{
continue;
}
@@ -0,0 +1,108 @@
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectivePumpWater : AIObjective
{
private const float FindPumpsInterval = 5.0f;
private string orderOption;
private List<Pump> pumps;
private float lastFindPumpsTime;
public AIObjectivePumpWater(Character character, string option)
: base(character, option)
{
orderOption = option;
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (Timing.TotalTime >= lastFindPumpsTime + FindPumpsInterval)
{
FindPumps();
}
if (objectiveManager.CurrentOrder == this && pumps.Count > 0)
{
return AIObjectiveManager.OrderPriority;
}
return 0.0f;
}
public override bool IsCompleted()
{
return false;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectivePumpWater;
}
protected override void Act(float deltaTime)
{
if (Timing.TotalTime < lastFindPumpsTime + FindPumpsInterval)
{
return;
}
FindPumps();
}
private void FindPumps()
{
lastFindPumpsTime = (float)Timing.TotalTime;
pumps = new List<Pump>();
foreach (Item item in Item.ItemList)
{
//don't attempt to use pumps outside the sub
if (item.Submarine == null) { continue; }
var pump = item.GetComponent<Pump>();
if (pump == null) continue;
if (item.HasTag("ballast")) continue;
//if the pump is connected to an item with a steering component, it must be a ballast pump
//(This may not work correctly if the signals are passed through some fancy circuit or a wifi component,
//which is why sub creators are encouraged to tag the ballast pumps)
bool connectedToSteering = false;
foreach (Connection c in item.Connections)
{
if (c.IsPower) continue;
if (item.GetConnectedComponentsRecursive<Steering>(c).Count > 0)
{
connectedToSteering = true;
break;
}
}
if (connectedToSteering) continue;
if (orderOption.ToLowerInvariant() == "stop pumping")
{
if (!pump.IsActive || pump.FlowPercentage == 0.0f) continue;
}
else
{
if (!pump.Item.InWater) continue;
if (pump.IsActive && pump.FlowPercentage <= -90.0f) continue;
}
pumps.Add(pump);
}
foreach (Pump pump in pumps)
{
AddSubObjective(new AIObjectiveOperateItem(pump, character, orderOption, false));
}
}
}
}
@@ -0,0 +1,102 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveRepairItem : AIObjective
{
private Item item;
public AIObjectiveRepairItem(Character character, Item item)
: base(character, "")
{
this.item = item;
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
bool insufficientSkills = true;
bool repairablesFound = false;
foreach (Repairable repairable in item.Repairables)
{
if (item.Condition > repairable.ShowRepairUIThreshold) { continue; }
if (repairable.DegreeOfSuccess(character) >= 0.5f) { insufficientSkills = false; }
repairablesFound = true;
}
if (!repairablesFound) { return 0.0f; }
float priority = 100.0f - item.Condition;
//vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist =
Math.Abs(character.WorldPosition.X - item.WorldPosition.X) +
Math.Abs(character.WorldPosition.Y - item.WorldPosition.Y) * 2.0f;
//heavily increase the priority if the item is already selected
//so characters don't keep switching between nearby damaged items
if (character.SelectedConstruction == item)
{
priority += 50.0f;
}
if (insufficientSkills)
{
return MathHelper.Lerp(0.0f, 50.0f, priority / 100.0f / Math.Max(dist / 100.0f, 1.0f));
}
else
{
return MathHelper.Lerp(50.0f, 100.0f, priority / 100.0f / Math.Max(dist / 100.0f, 1.0f));
}
}
public override bool IsCompleted()
{
foreach (Repairable repairable in item.Repairables)
{
if (item.Condition < Math.Max(repairable.ShowRepairUIThreshold, item.Prefab.Health * 0.98f)) return false;
}
character?.Speak(TextManager.Get("DialogItemRepaired").Replace("[itemname]", item.Name), null, 0.0f, "itemrepaired", 10.0f);
return true;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveRepairItem repairObjective && repairObjective.item == item;
}
protected override void Act(float deltaTime)
{
foreach (Repairable repairable in item.Repairables)
{
//make sure we have all the items required to fix the target item
foreach (var kvp in repairable.requiredItems)
{
foreach (RelatedItem requiredItem in kvp.Value)
{
if (!character.Inventory.Items.Any(it => it != null && requiredItem.MatchesItem(it)))
{
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true));
return;
}
}
}
}
if (character.CanInteractWith(item))
{
foreach (Repairable repairable in item.Repairables)
{
if (character.SelectedConstruction != item) { item.TryInteract(character, true, true); }
repairable.CurrentFixer = character;
break;
}
}
else
{
AddSubObjective(new AIObjectiveGoTo(item, character));
}
}
}
}
@@ -0,0 +1,68 @@
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma
{
class AIObjectiveRepairItems : AIObjective
{
/// <summary>
/// Should the character only attempt to fix items they have the skills to fix, or any damaged item
/// </summary>
public bool RequireAdequateSkills;
public AIObjectiveRepairItems(Character character)
: base(character, "")
{
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
GetBrokenItems();
if (subObjectives.Count > 0 && objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 1.0f;
}
public override bool IsCompleted()
{
return false;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills;
}
protected override void Act(float deltaTime)
{
GetBrokenItems();
}
private void GetBrokenItems()
{
foreach (Item item in Item.ItemList)
{
//ignore items that are in full condition
if (item.Condition >= 100.0f) continue;
foreach (Repairable repairable in item.Repairables)
{
//ignore ones that are already fixed
if (item.Condition > repairable.ShowRepairUIThreshold) continue;
if (RequireAdequateSkills)
{
if (!repairable.HasRequiredSkills(character)) { continue; }
}
AddSubObjective(new AIObjectiveRepairItem(character, item));
break;
}
}
}
}
}
@@ -1,17 +1,37 @@
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Barotrauma
{
/*class AIObjectiveRescue : AIObjective
class AIObjectiveRescue : AIObjective
{
const float TreatmentDelay = 0.5f;
private readonly Character targetCharacter;
private AIObjectiveGoTo goToObjective;
private float treatmentTimer;
public override bool CanBeCompleted
{
get
{
if (targetCharacter.Removed) return false;
if (goToObjective != null && !goToObjective.CanBeCompleted) return false;
return true;
}
}
public AIObjectiveRescue(Character character, Character targetCharacter)
: base (character, "")
: base(character, "")
{
Debug.Assert(character != targetCharacter);
this.targetCharacter = targetCharacter;
}
@@ -21,14 +41,189 @@ namespace Barotrauma
return rescueObjective != null && rescueObjective.targetCharacter == targetCharacter;
}
public override float GetPriority(Character character)
protected override void Act(float deltaTime)
{
if (targetCharacter.AnimController.CurrentHull == null) return 0.0f;
//target in water -> move to a dry place first
if (targetCharacter.AnimController.InWater)
{
if (character.SelectedCharacter != targetCharacter)
{
if (!character.CanInteractWith(targetCharacter))
{
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
}
else
{
character.SelectCharacter(targetCharacter);
}
}
else
{
AddSubObjective(new AIObjectiveFindSafety(character));
}
return;
}
float distance = Vector2.DistanceSquared(character.WorldPosition, targetCharacter.WorldPosition);
//target not in water -> we can start applying treatment
if (!character.CanInteractWith(targetCharacter))
{
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
}
else
{
if (character.SelectedCharacter == null)
{
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.RoomName),
null, 1.0f,
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
}
return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance;
character.SelectCharacter(targetCharacter);
GiveTreatment(deltaTime);
}
}
}*/
protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
{
if (subObjective is AIObjectiveFindSafety)
{
if (character.SelectedCharacter != targetCharacter) return true;
if (character.AnimController.InWater || targetCharacter.AnimController.InWater) return false;
foreach (Limb limb in targetCharacter.AnimController.Limbs)
{
if (!Submarine.RectContains(targetCharacter.CurrentHull.WorldRect, limb.WorldPosition)) return false;
}
return !character.AnimController.InWater && !targetCharacter.AnimController.InWater &&
AIObjectiveFindSafety.GetHullSafety(character.CurrentHull, character) > 50.0f;
}
return false;
}
private void GiveTreatment(float deltaTime)
{
if (treatmentTimer > 0.0f)
{
treatmentTimer -= deltaTime;
}
treatmentTimer = TreatmentDelay;
var allAfflictions = targetCharacter.CharacterHealth.GetAllAfflictions()
.Where(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth) > 0)
.ToList();
allAfflictions.Sort((a1, a2) =>
{
return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth));
});
//check if we already have a suitable treatment for any of the afflictions
foreach (Affliction affliction in allAfflictions)
{
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
if (treatmentSuitability.Value > 0.0f)
{
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key);
if (matchingItem == null) { continue; }
ApplyTreatment(affliction, matchingItem);
return;
}
}
}
//didn't have any suitable treatments available, try to find some medical items
HashSet<string> suitableItemIdentifiers = new HashSet<string>();
foreach (Affliction affliction in allAfflictions)
{
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
{
if (treatmentSuitability.Value > 0.0f)
{
suitableItemIdentifiers.Add(treatmentSuitability.Key);
}
}
}
if (suitableItemIdentifiers.Count > 0)
{
List<string> itemNameList = new List<string>();
foreach (string itemIdentifier in suitableItemIdentifiers)
{
if (MapEntityPrefab.Find(null, itemIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab)
{
itemNameList.Add(itemPrefab.Name);
}
//only list the first 4 items
if (itemNameList.Count >= 4) break;
}
if (itemNameList.Count > 0)
{
string itemListStr = "";
if (itemNameList.Count == 1)
{
itemListStr = itemNameList[0];
}
else
{
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
}
character?.Speak(TextManager.Get("DialogListRequiredTreatments")
.Replace("[targetname]", targetCharacter.Name)
.Replace("[treatmentlist]", itemListStr),
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
}
character.DeselectCharacter();
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true));
}
character.AnimController.Anim = AnimController.Animation.CPR;
}
private void ApplyTreatment(Affliction affliction, Item item)
{
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
bool remove = false;
foreach (ItemComponent ic in item.components)
{
if (!ic.HasRequiredContainedItems(addMessage: false)) continue;
#if CLIENT
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
#endif
ic.WasUsed = true;
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
if (ic.DeleteOnUse) remove = true;
}
if (remove)
{
Entity.Spawner?.AddToRemoveQueue(item);
}
}
public override bool IsCompleted()
{
bool isCompleted = !targetCharacter.IsUnconscious || targetCharacter.IsDead;
if (isCompleted)
{
character?.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
}
return isCompleted;
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (targetCharacter.AnimController.CurrentHull == null) return 0.0f;
float distance = Vector2.DistanceSquared(character.WorldPosition, targetCharacter.WorldPosition);
return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance;
}
}
}
@@ -3,7 +3,7 @@ using System.Linq;
namespace Barotrauma
{
/*class AIObjectiveRescueAll : AIObjective
class AIObjectiveRescueAll : AIObjective
{
private List<Character> rescueTargets;
@@ -18,13 +18,19 @@ namespace Barotrauma
return true;
}
public override float GetPriority(Character character)
public override float GetPriority(AIObjectiveManager objectiveManager)
{
GetRescueTargets();
if (!rescueTargets.Any()) { return 0.0f; }
if (objectiveManager.CurrentObjective == this)
{
return AIObjectiveManager.OrderPriority;
}
//if there are targets to rescue, the priority is slightly less
//than the priority of explicit orders given to the character
return rescueTargets.Any() ? AIObjectiveManager.OrderPriority - 5.0f : 0.0f;
return AIObjectiveManager.OrderPriority - 5.0f;
}
private void GetRescueTargets()
@@ -32,9 +38,7 @@ namespace Barotrauma
rescueTargets = Character.CharacterList.FindAll(c =>
c.AIController is HumanAIController &&
c != character &&
(c.IsDead || c.IsUnconscious) &&
c.AnimController.CurrentHull != null &&
AIObjectiveFindSafety.GetHullSafety(c.AnimController.CurrentHull, c) < 50.0f);
c.IsUnconscious);
}
protected override void Act(float deltaTime)
@@ -44,5 +48,11 @@ namespace Barotrauma
AddSubObjective(new AIObjectiveRescue(character, target));
}
}
}*/
public override bool IsCompleted()
{
return false;
}
}
}