38f1ddb...178a853: v0.8.9.1, removed content folder
This commit is contained in:
@@ -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();
|
||||
|
||||
+66
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-15
@@ -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;
|
||||
|
||||
+106
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-10
@@ -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>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+81
-21
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+30
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-7
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user