aeafa16...4d3cf73

This commit is contained in:
Joonas Rikkonen
2019-03-18 22:57:05 +02:00
parent 3301bed442
commit 23687fbf2f
77 changed files with 2275 additions and 717 deletions
@@ -22,7 +22,6 @@ namespace Barotrauma
public const float HULL_SAFETY_THRESHOLD = 50;
// TODO: update the list when someone gives a report
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
private SteeringManager outsideSteering, insideSteering;
@@ -136,10 +135,11 @@ namespace Barotrauma
Character.AnimController.IgnorePlatforms = ignorePlatforms;
if (Character.IsClimbing)
{
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
}
// Suspect that this causes issues when trying to exit from the ladders -> could try to check if the next node is ladder?
//if (Character.IsClimbing)
//{
// Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
//}
Vector2 targetMovement = AnimController.TargetMovement;
@@ -187,7 +187,7 @@ namespace Barotrauma
// Try to put the mask in an Any slot, and drop it if that fails
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
{
mask.Drop();
mask.Drop(Character);
}
}
}
@@ -198,7 +198,7 @@ namespace Barotrauma
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
{
// TODO: take the item where it was taken from?
extinguisherItem.Drop();
extinguisherItem.Drop(Character);
}
}
@@ -227,6 +227,10 @@ namespace Barotrauma
PropagateHullSafety(Character, Character.CurrentHull);
}
}
private void ReportProblems()
{
if (GameMain.Client != null) return;
protected void ReportProblems()
{
@@ -271,10 +275,6 @@ namespace Barotrauma
}
}
}
private void ReportProblems()
{
if (GameMain.Client != null) return;
private void UpdateSpeaking()
{
@@ -293,41 +293,68 @@ namespace Barotrauma
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
}
}
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
float totalDamage = attackResult.Damage;
if (totalDamage <= 0.0f || attacker == null) return;
if (attacker.SpeciesName == Character.SpeciesName)
float damage = attackResult.Damage;
if (damage < 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
return;
}
if (IsFriendly(attacker))
{
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
{
// Don't react to damage done by friendly ai, because we know that it's accidental
return;
}
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
{
// Don't attack characters that damage you while doing cpr, because let's assume that they are helping you.
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return;
}
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
{
// Don't react to damage done by friendly ai, because we know that it's accidental
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
return;
}
float currentVitality = Character.CharacterHealth.Vitality;
float dmgPercentage = totalDamage / currentVitality * 100;
float dmgPercentage = damage / currentVitality * 100;
if (dmgPercentage < currentVitality / 10)
{
// Don't react to a minor amount of (accidental) dmg done by friendly characters
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
return;
}
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
{
if (combatObjective.Enemy != attacker)
{
// Replace the old objective with the new.
ObjectiveManager.Objectives.Remove(combatObjective);
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
}
}
else
{
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
}
}
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () =>
else
{
//the objective in the manager is not necessarily the same as the one we just instantiated,
//because the objective isn't added if there's already an identical objective in the manager
var combatObjective = objectiveManager.GetObjective<AIObjectiveCombat>();
combatObjective.MaxEnemyDamage = Math.Max(totalDamage, combatObjective.MaxEnemyDamage);
});
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
{
if (combatObjective.Enemy != attacker)
{
// Replace the old objective with the new.
ObjectiveManager.Objectives.Remove(combatObjective);
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
}
}
else
{
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
}
}
}
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
@@ -420,7 +447,8 @@ namespace Barotrauma
bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires;
bool ignoreWater = HasDivingSuit(Character);
bool ignoreOxygen = ignoreWater || HasDivingGear(Character);
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies: false);
bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat;
return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
}
public static float GetHullSafety(Hull hull, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
@@ -443,5 +471,8 @@ namespace Barotrauma
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
return MathHelper.Clamp(safety * 100, 0, 100);
}
// TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species.
public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName;
}
}
@@ -15,7 +15,7 @@ namespace Barotrauma
public virtual bool KeepDivingGearOn => false;
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
protected float priority;
public float Priority { get; set; }
protected readonly Character character;
protected string option;
protected bool abandon;
@@ -26,6 +26,7 @@ namespace Barotrauma
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
public string Option
{
@@ -103,20 +104,20 @@ namespace Barotrauma
CurrentSubObjective.SortSubObjectives(objectiveManager);
}
public virtual float GetPriority(AIObjectiveManager objectiveManager) => priority;
public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority;
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
{
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
if (objectiveManager.CurrentOrder == this)
{
priority = AIObjectiveManager.OrderPriority;
Priority = AIObjectiveManager.OrderPriority;
}
else if (objectiveManager.CurrentObjective == this || subObjective == this)
{
priority += Devotion * deltaTime;
Priority += Devotion * deltaTime;
}
priority = MathHelper.Clamp(priority, 0, 100);
Priority = MathHelper.Clamp(Priority, 0, 100);
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
}
@@ -1,194 +1,268 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
class AIObjectiveCombat : AIObjective
{
public override string DebugTag => "combat";
public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
const float CoolDown = 10.0f;
//the largest amount of damage the enemy has inflicted on this character
//(may be higher than enemyStrength if the enemy is e.g. a human using items)
public float MaxEnemyDamage;
private Character enemy;
private AIObjectiveFindSafety escapeObjective;
public Character Enemy { get; private set; }
private Item _weapon;
private Item Weapon
{
get { return _weapon; }
set
{
_weapon = value;
_weaponComponent = null;
reloadWeaponObjective = null;
}
}
private ItemComponent _weaponComponent;
private ItemComponent WeaponComponent
{
get
{
if (_weaponComponent == null)
{
_weaponComponent =
Weapon.GetComponent<RangedWeapon>() as ItemComponent ??
Weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
Weapon.GetComponent<RepairTool>() as ItemComponent;
}
return _weaponComponent;
}
}
private AIObjectiveContainItem reloadWeaponObjective;
private Hull retreatTarget;
private AIObjectiveGoTo retreatObjective;
private float coolDownTimer;
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
{
this.enemy = enemy;
Enemy = enemy;
coolDownTimer = CoolDown;
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 0;
}
protected override void Act(float deltaTime)
{
coolDownTimer -= deltaTime;
var weapon = character.Inventory.FindItemByTag("weapon");
if (weapon == null)
if (Weapon != null && character.Inventory.Items.Contains(_weapon))
{
Weapon = null;
}
if (Weapon == null)
{
Weapon = GetWeapon();
}
if (Weapon == null)
{
Escape(deltaTime);
}
else
else if (Equip(deltaTime))
{
if (!character.SelectedItems.Contains(weapon))
if (Reload(deltaTime))
{
if (character.Inventory.TryPutItem(weapon, 3, true, false, character))
{
weapon.Equip(character);
}
else
{
//couldn't equip the item, escape
Escape(deltaTime);
return;
}
Attack(deltaTime);
}
}
if (!abandon)
{
Move(deltaTime);
}
}
//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))
private Item GetWeapon()
{
_weaponComponent = null;
var weapon = character.Inventory.FindItemByTag("weapon");
if (weapon == null)
{
foreach (var item in character.Inventory.Items)
{
var containedItems = weapon.ContainedItems;
foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
if (item == null) { continue; }
foreach (var component in item.Components)
{
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (containedItem == null)
if (component is MeleeWeapon || component is RangedWeapon)
{
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
if (!newReloadWeaponObjective.IsDuplicate(reloadWeaponObjective))
return item;
}
var effects = component.statusEffectLists;
if (effects != null)
{
foreach (var statusEffects in effects.Values)
{
reloadWeaponObjective = newReloadWeaponObjective;
foreach (var statusEffect in statusEffects)
{
if (statusEffect.Afflictions.Any())
{
return item;
}
}
}
}
}
}
}
return weapon;
}
if (reloadWeaponObjective != null)
private bool Equip(float deltaTime)
{
if (!character.SelectedItems.Contains(Weapon))
{
if (character.Inventory.TryPutItem(Weapon, 3, true, false, character))
{
if (reloadWeaponObjective.IsCompleted())
{
reloadWeaponObjective = null;
}
else if (!reloadWeaponObjective.CanBeCompleted)
{
Escape(deltaTime);
}
else
{
reloadWeaponObjective.TryComplete(deltaTime);
}
return;
Weapon.Equip(character);
}
character.CursorPosition = enemy.Position;
character.SetInput(InputType.Aim, false, true);
Vector2 enemyDiff = Vector2.Normalize(enemy.Position - character.Position);
if (!MathUtils.IsValid(enemyDiff)) enemyDiff = Rand.Vector(1.0f);
float weaponAngle = ((weapon.body.Dir == 1.0f) ? weapon.body.Rotation : weapon.body.Rotation - MathHelper.Pi);
Vector2 weaponDir = new Vector2((float)Math.Cos(weaponAngle), (float)Math.Sin(weaponAngle));
if (Vector2.Dot(enemyDiff, weaponDir) < 0.9f) return;
List<FarseerPhysics.Dynamics.Body> ignoredBodies = new List<FarseerPhysics.Dynamics.Body>();
foreach (Limb limb in character.AnimController.Limbs)
else
{
ignoredBodies.Add(limb.body.FarseerBody);
//couldn't equip the item, escape
Escape(deltaTime);
return false;
}
}
return true;
}
var pickedBody = Submarine.PickBody(character.SimPosition, enemy.SimPosition, ignoredBodies);
if (pickedBody != null && !(pickedBody.UserData is Limb)) return;
private void Move(float deltaTime)
{
// Retreat to safety
// TODO: aggressive behaviour, chasing?
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
{
retreatTarget = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().FindBestHull();
}
if (retreatTarget != null)
{
if (retreatObjective == null || retreatObjective.Target != retreatTarget)
{
retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true);
}
retreatObjective.TryComplete(deltaTime);
}
}
weapon.Use(deltaTime, character);
private bool Reload(float deltaTime)
{
if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
{
var containedItems = Weapon.ContainedItems;
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
{
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
if (containedItem == null)
{
if (reloadWeaponObjective == null)
{
reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent<ItemContainer>());
}
}
}
}
if (reloadWeaponObjective != null)
{
if (reloadWeaponObjective.IsCompleted())
{
reloadWeaponObjective = null;
}
else if (!reloadWeaponObjective.CanBeCompleted)
{
Escape(deltaTime);
}
else
{
reloadWeaponObjective.TryComplete(deltaTime);
}
return false;
}
return true;
}
private IEnumerable<FarseerPhysics.Dynamics.Body> myBodies;
private void Attack(float deltaTime)
{
character.CursorPosition = Enemy.Position;
character.SetInput(InputType.Aim, false, true);
if (WeaponComponent is MeleeWeapon meleeWeapon)
{
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
{
Weapon.Use(deltaTime, character);
}
}
else
{
if (WeaponComponent is RepairTool repairTool)
{
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
}
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
{
if (myBodies == null)
{
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
}
var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
if (pickedBody != null)
{
Character target = null;
if (pickedBody.UserData is Character c)
{
target = c;
}
else if (pickedBody.UserData is Limb limb)
{
target = limb.character;
}
if (target != null && target == Enemy)
{
Weapon.Use(deltaTime, character);
}
}
}
}
}
private void Escape(float deltaTime)
{
// TODO: just let the find safety run?
if (escapeObjective == null)
{
escapeObjective = new AIObjectiveFindSafety(character);
}
if (enemy.AnimController.CurrentHull == character.AnimController.CurrentHull)
{
escapeObjective.OverrideCurrentHullSafety = 0.0f;
}
else
{
escapeObjective.OverrideCurrentHullSafety = null;
}
escapeObjective.TryComplete(deltaTime);
abandon = true;
SteeringManager.Reset();
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
public override bool IsCompleted()
{
return enemy == null || enemy.Removed || enemy.IsDead || coolDownTimer <= 0.0f;
}
public override float GetPriority(AIObjectiveManager objectiveManager)
{
if (enemy == null || enemy.Removed)
{
return 0.0f;
}
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
//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(CalculateEnemyStrength(), MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
if (enemy.AIController is EnemyAIController enemyAI)
{
if (enemyAI.SelectedAiTarget == character.AiTarget) enemyDanger *= 2.0f;
}
return Math.Max(enemyDanger, AIObjectiveManager.OrderPriority);
}
public override bool IsCompleted() => Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0.0f;
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
public override float GetPriority(AIObjectiveManager objectiveManager) => Enemy == null || Enemy.Removed || Enemy.IsDead ? 0 : 100;
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveCombat objective = otherObjective as AIObjectiveCombat;
if (objective == null) return false;
return objective.enemy == enemy;
if (!(otherObjective is AIObjectiveCombat objective)) return false;
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;
}
//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;
//}
}
}
@@ -32,7 +32,7 @@ namespace Barotrauma
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1));
return MathHelper.Clamp(priority * severityFactor * distanceFactor, 0, 100);
return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100);
}
public override bool IsCompleted()
@@ -62,7 +62,7 @@ namespace Barotrauma
if (containedItem == null) continue;
if (containedItem.Condition <= 0.0f)
{
containedItem.Drop();
containedItem.Drop(character);
}
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
{
@@ -16,18 +16,17 @@ namespace Barotrauma
const float priorityIncrease = 25;
const float priorityDecrease = 10;
const float SearchHullInterval = 3.0f;
const float clearUnreachableInterval = 30;
private List<Hull> unreachable = new List<Hull>();
private float currenthullSafety;
private float unreachableClearTimer;
private float searchHullTimer;
private AIObjectiveGoTo goToObjective;
private AIObjective divingGearObjective;
public float? OverrideCurrentHullSafety;
public AIObjectiveFindSafety(Character character) : base(character, "") { }
public override bool IsCompleted() => false;
@@ -52,7 +51,7 @@ namespace Barotrauma
if (divingGearObjective.IsCompleted())
{
divingGearObjective = null;
priority = 0;
Priority = 0;
}
else if (divingGearObjective.CanBeCompleted)
{
@@ -61,6 +60,16 @@ namespace Barotrauma
}
}
if (unreachableClearTimer > 0)
{
unreachableClearTimer -= deltaTime;
}
else
{
unreachableClearTimer = clearUnreachableInterval;
unreachable.Clear();
}
if (searchHullTimer > 0.0f)
{
searchHullTimer -= deltaTime;
@@ -147,16 +156,35 @@ namespace Barotrauma
}
}
private Hull FindBestHull()
public Hull FindBestHull()
{
Hull bestHull = character.CurrentHull;
float bestValue = currenthullSafety;
foreach (Hull hull in Hull.hullList)
{
if (unreachable.Contains(hull)) { continue; }
if (hull.Submarine == null) { continue; }
float hullSafety = 0;
if (character.Submarine == null)
if (character.Submarine != null && SteeringManager == PathSteering)
{
// Inside or outside near the sub
if (unreachable.Contains(hull)) { continue; }
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
hullSafety = HumanAIController.GetHullSafety(hull, character);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
hullSafety *= distanceFactor;
// Each unsafe node reduces the hull safety value.
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
{
hullSafety /= 10;
}
}
else
{
// Outside
if (hull.RoomName?.ToLowerInvariant() == "airlock")
@@ -177,7 +205,8 @@ namespace Barotrauma
}
// Huge preference for closer targets
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 100000, Vector2.Distance(character.WorldPosition, hull.WorldPosition)));
float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC)
@@ -185,26 +214,6 @@ namespace Barotrauma
hullSafety /= 10;
}
}
else
{
// Inside
// Not connected.
if (!character.Submarine.GetConnectedSubs().Contains(hull.Submarine)) { continue; }
hullSafety = HumanAIController.GetHullSafety(hull, character);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
hullSafety *= distanceFactor;
// Each unsafe node reduces the hull safety value.
hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
{
hullSafety /= 10;
}
}
if (hullSafety > bestValue)
{
bestHull = hull;
@@ -224,24 +233,24 @@ namespace Barotrauma
if (character.CurrentHull == null)
{
currenthullSafety = 0;
priority = 5;
Priority = 5;
return;
}
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { priority = 100; }
currenthullSafety = OverrideCurrentHullSafety ?? HumanAIController.GetHullSafety(character.CurrentHull);
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
priority -= priorityDecrease * deltaTime;
Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
priority += dangerFactor * priorityIncrease * deltaTime;
Priority += dangerFactor * priorityIncrease * deltaTime;
}
priority = MathHelper.Clamp(priority, 0, 100);
Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
{
priority = Math.Max(priority, AIObjectiveManager.OrderPriority + 10);
Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
}
}
}
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string DebugTag => "fix leak";
public override bool KeepDivingGearOn => true;
public override bool ForceRun => true;
private readonly Gap leak;
@@ -10,6 +10,7 @@ namespace Barotrauma
{
public override string DebugTag => "fix leaks";
public override bool KeepDivingGearOn => true;
public override bool ForceRun => true;
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
@@ -144,7 +144,7 @@ namespace Barotrauma
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) continue;
//if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop();
character.Inventory.Items[i].Drop(character);
}
}
}
@@ -45,17 +45,22 @@ namespace Barotrauma
get
{
bool canComplete = !cannotReach && !abandon;
if (FollowControlledCharacter && Character.Controlled == null) { canComplete = false; }
else if (Target != null && Target.Removed) { canComplete = false; }
else if (repeat || waitUntilPathUnreachable > 0.0f) { canComplete = true; }
else if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering)
if (canComplete)
{
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable TODO: add a timer?
if (pathSteering.CurrentPath == null) { canComplete = true; }
else if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { canComplete = false; }
if (canComplete)
if (FollowControlledCharacter && Character.Controlled == null)
{
canComplete = !pathSteering.CurrentPath.Unreachable;
canComplete = false;
}
else if (Target != null && Target.Removed)
{
canComplete = false;
}
else if (!repeat && waitUntilPathUnreachable < 0)
{
if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
{
canComplete = !PathSteering.CurrentPath.Unreachable;
}
}
}
if (!canComplete)
@@ -144,9 +149,9 @@ namespace Barotrauma
}
else
{
var indoorSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
bool targetIsOutside = (Target != null && Target.Submarine == null) || (indoorSteering != null && indoorSteering.CurrentPath != null && indoorSteering.CurrentPath.HasOutdoorsNodes);
if (targetIsOutside && !AllowGoingOutside)
bool targetIsOutside = (Target != null && Target.Submarine == null) ||
(SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.HasOutdoorsNodes);
if (targetIsOutside && character.CurrentHull != null && !AllowGoingOutside)
{
cannotReach = true;
}
@@ -11,6 +11,12 @@ namespace Barotrauma
public override string DebugTag => "idle";
const float WallAvoidDistance = 150.0f;
private readonly float newTargetIntervalMin = 5;
private readonly float newTargetIntervalMax = 15;
private readonly float standStillMin = 1;
private readonly float standStillMax = 10;
private readonly float walkDurationMin = 3;
private readonly float walkDurationMax = 10;
private Hull currentTarget;
private float newTargetTimer;
@@ -46,7 +52,10 @@ namespace Barotrauma
character.SelectedConstruction = null;
}
if (currentTarget == null && (IsForbidden(character.CurrentHull) || HumanAIController.UnsafeHulls.Contains(character.CurrentHull)))
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
if (currentTargetIsInvalid || (currentTarget == null && IsForbidden(character.CurrentHull)))
{
newTargetTimer = 0;
standStillTimer = 0;
@@ -70,7 +79,7 @@ namespace Barotrauma
PathSteering.SetPath(path);
}
newTargetTimer = currentTarget == null ? 5.0f : 15.0f;
newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax);
}
newTargetTimer -= deltaTime;
@@ -79,20 +88,20 @@ namespace Barotrauma
// - if reached the end of the path
// - if the target is unreachable
// - if the path requires going outside
if (PathSteering == null || (PathSteering.CurrentPath != null &&
if (SteeringManager != PathSteering || (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);
walkDuration = Rand.Range(walkDurationMin, walkDurationMax);
PathSteering.Reset();
return;
}
if (standStillTimer < -walkDuration)
{
standStillTimer = Rand.Range(1.0f, 10.0f);
standStillTimer = Rand.Range(standStillMin, standStillMax);
}
//steer away from edges of the hull
@@ -128,12 +137,11 @@ namespace Barotrauma
}
character.AIController.SteeringManager.SteeringWander();
if (!character.IsClimbing)
if (!character.IsClimbing && !character.AnimController.InWater)
{
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
return;
}
@@ -150,6 +158,7 @@ namespace Barotrauma
{
var idCard = character.Inventory.FindItemByIdentifier("idcard");
Hull targetHull = null;
bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
//random chance of navigating back to the room where the character spawned
if (Rand.Int(5) == 1 && idCard != null)
{
@@ -186,9 +195,13 @@ namespace Barotrauma
continue;
}
}
// Check that there is no unsafe or forbidden hulls on the way to the target
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
if (isCurrentHullOK)
{
// Check that there is no unsafe or forbidden hulls on the way to the target
// Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
}
// If we want to do a steering check, we should do it here, before setting the path
//if (path.Cost > 1000.0f) { continue; }
@@ -56,23 +56,6 @@ namespace Barotrauma
DelayedObjectives.Add(objective, coroutine);
}
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)
@@ -200,27 +183,6 @@ namespace Barotrauma
if (order.TargetItemComponent == null) return;
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
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.TargetItemComponent == null) return;
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
@@ -135,7 +135,7 @@ namespace Barotrauma
if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) ||
!character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any }))
{
character.Inventory.Items[i].Drop();
character.Inventory.Items[i].Drop(character);
}
}
if (character.Inventory.TryPutItem(component.Item, i, true, false, character))
@@ -35,7 +35,7 @@ namespace Barotrauma
float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
float baseLevel = Math.Max(priority + isSelected, 1);
float baseLevel = Math.Max(Priority + isSelected, 1);
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100);
}
@@ -84,6 +84,7 @@ namespace Barotrauma
}
if (character.CanInteractWith(Item))
{
OperateRepairTool(deltaTime);
foreach (Repairable repairable in Item.Repairables)
{
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
@@ -123,5 +124,33 @@ namespace Barotrauma
AddSubObjective(goToObjective);
}
}
private void OperateRepairTool(float deltaTime)
{
// Operate repair tool, if required.
foreach (Repairable repairable in Item.Repairables)
{
foreach (var kvp in repairable.requiredItems)
{
foreach (RelatedItem requiredItem in kvp.Value)
{
foreach (var item in character.Inventory.Items)
{
if (requiredItem.MatchesItem(item))
{
var repairTool = item.GetComponent<RepairTool>();
if (repairTool != null)
{
character.CursorPosition = Item.Position;
character.SetInput(InputType.Aim, false, true);
repairTool.Use(deltaTime, character);
return;
}
}
}
}
}
}
}
}
}
@@ -549,8 +549,9 @@ namespace Barotrauma
limbHealths[i].Afflictions.RemoveAt(j);
}
}
foreach (Affliction affliction in limbHealths[i].Afflictions)
for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
{
var affliction = limbHealths[i].Afflictions[j];
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == i);
affliction.Update(this, targetLimb, deltaTime);
affliction.DamagePerSecondTimer += deltaTime;