v0.11.0.9

This commit is contained in:
Joonas Rikkonen
2020-12-09 16:34:16 +02:00
parent bbf06f0984
commit f433a7ba10
325 changed files with 13947 additions and 3652 deletions
@@ -10,6 +10,7 @@ namespace Barotrauma
{
partial class HumanAIController : AIController
{
public static bool debugai;
public static bool DisableCrewAI;
private readonly AIObjectiveManager objectiveManager;
@@ -37,12 +38,51 @@ namespace Barotrauma
private float respondToAttackTimer;
private const float RespondToAttackInterval = 1.0f;
private bool wasConscious;
private bool freezeAI;
private readonly float maxSteeringBuffer = 5000;
private readonly float minSteeringBuffer = 500;
private readonly float steeringBufferIncreaseSpeed = 100;
private float steeringBuffer;
private readonly float obstacleRaycastInterval = 1;
private float obstacleRaycastTimer;
/// <summary>
/// List of previous attacks done to this character
/// </summary>
private readonly Dictionary<Character, AttackResult> previousAttackResults = new Dictionary<Character, AttackResult>();
private readonly SteeringManager outsideSteering, insideSteering;
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
public override AIObjectiveManager ObjectiveManager
{
get { return objectiveManager; }
}
public Order CurrentOrder
{
get;
private set;
}
public string CurrentOrderOption
{
get;
private set;
}
public float CurrentHullSafety { get; private set; } = 100;
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
private readonly HashSet<Character> attackers = new HashSet<Character>();
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
private class HullSafety
{
public float safety;
@@ -72,35 +112,6 @@ namespace Barotrauma
}
}
private readonly Dictionary<Hull, HullSafety> knownHulls = new Dictionary<Hull, HullSafety>();
private SteeringManager outsideSteering, insideSteering;
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
public override AIObjectiveManager ObjectiveManager
{
get { return objectiveManager; }
}
public Order CurrentOrder
{
get;
private set;
}
public string CurrentOrderOption
{
get;
private set;
}
public float CurrentHullSafety { get; private set; } = 100;
private readonly Dictionary<Character, float> damageDoneByAttacker = new Dictionary<Character, float>();
private readonly HashSet<Character> attackers = new HashSet<Character>();
public HumanAIController(Character c) : base(c)
{
if (!c.IsHuman)
@@ -117,8 +128,6 @@ namespace Barotrauma
partial void InitProjSpecific();
private bool freezeAI;
public override void Update(float deltaTime)
{
if (DisableCrewAI || Character.Removed) { return; }
@@ -146,6 +155,8 @@ namespace Barotrauma
}
if (isIncapacitated) { return; }
wasConscious = true;
respondToAttackTimer -= deltaTime;
if (respondToAttackTimer <= 0.0f)
{
@@ -176,15 +187,38 @@ namespace Barotrauma
IgnoredItems.Clear();
}
// Use the pathfinding also outside of the sub, but not farther than the extents of the sub + 500 units.
if (Character.Submarine != null || SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null &&
Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + 500, 2))
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
bool hasValidPath = steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath != null && !pathSteering.CurrentPath.Finished && !pathSteering.CurrentPath.Unreachable;
if (Character.Submarine == null && hasValidPath)
{
obstacleRaycastTimer -= deltaTime;
if (obstacleRaycastTimer <= 0)
{
obstacleRaycastTimer = obstacleRaycastInterval;
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
{
if (connectedSub == Submarine.MainSub) { continue; }
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
{
PathSteering.CurrentPath.Unreachable = true;
break;
}
}
}
}
if (Character.Submarine != null || hasValidPath && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer))
{
if (steeringManager != insideSteering)
{
insideSteering.Reset();
}
steeringManager = insideSteering;
steeringBuffer += steeringBufferIncreaseSpeed * deltaTime;
}
else
{
@@ -193,7 +227,9 @@ namespace Barotrauma
outsideSteering.Reset();
}
steeringManager = outsideSteering;
steeringBuffer = minSteeringBuffer;
}
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
AnimController.Crouching = shouldCrouch;
CheckCrouching(deltaTime);
@@ -369,7 +405,8 @@ namespace Barotrauma
if (!NeedsDivingGear(Character.CurrentHull, out bool needsSuit) || !needsSuit || oxygenLow)
{
bool shouldKeepTheGearOn = Character.AnimController.HeadInWater
|| Character.Submarine.TeamID != Character.TeamID && Character.Submarine.TeamID != Character.TeamType.FriendlyNPC
|| Character.Submarine == null
|| Character.Submarine.TeamID != Character.TeamID
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|| ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character // wait order
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
@@ -435,6 +472,7 @@ namespace Barotrauma
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
divingSuit.Drop(Character);
HandleRelocation(divingSuit);
}
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingSuit)
{
@@ -461,6 +499,7 @@ namespace Barotrauma
else
{
divingSuit.Drop(Character);
HandleRelocation(divingSuit);
}
}
}
@@ -478,6 +517,7 @@ namespace Barotrauma
if (ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
{
mask.Drop(Character);
HandleRelocation(mask);
}
else if (findItemState == FindItemState.None || findItemState == FindItemState.DivingMask)
{
@@ -501,6 +541,7 @@ namespace Barotrauma
else
{
mask.Drop(Character);
HandleRelocation(mask);
}
}
}
@@ -548,6 +589,7 @@ namespace Barotrauma
else
{
item.Drop(Character);
HandleRelocation(item);
}
}
}
@@ -556,6 +598,62 @@ namespace Barotrauma
}
}
private readonly HashSet<Item> itemsToRelocate = new HashSet<Item>();
private void HandleRelocation(Item item)
{
if (item.Submarine?.TeamID == Character.TeamType.FriendlyNPC)
{
if (itemsToRelocate.Contains(item)) { return; }
itemsToRelocate.Add(item);
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
{
myPort.OnUnDocked += Relocate;
}
var campaign = GameMain.GameSession.Campaign;
if (campaign != null)
{
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
campaign.BeforeLevelLoading += Relocate;
}
}
void Relocate()
{
if (item == null || item.Removed) { return; }
if (!itemsToRelocate.Contains(item)) { return; }
var mainSub = Submarine.MainSub;
if (item.ParentInventory != null)
{
if (item.ParentInventory.Owner is Character c)
{
if (c.TeamID == Character.TeamType.Team1 || c.TeamID == Character.TeamType.Team2)
{
// Taken by a player/bot (if npc or monster would take the item, we'd probably still want it to spawn back to the main sub.
return;
}
}
else if (item.ParentInventory.Owner.Submarine == mainSub)
{
// Placed inside an inventory that's already in the main sub.
return;
}
}
// Laying on ground inside the main sub.
if (item.Submarine == mainSub)
{
return;
}
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, mainSub);
if (wp != null)
{
item.Submarine = mainSub;
item.SetTransform(wp.SimPosition, 0.0f);
}
itemsToRelocate.Remove(item);
}
}
public void ReequipUnequipped()
{
foreach (var item in unequippedItems)
@@ -585,6 +683,7 @@ namespace Barotrauma
suitableContainer = null;
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredItems, customPriorityFunction: i =>
{
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
var container = i.GetComponent<ItemContainer>();
if (container == null) { return 0; }
if (container.Inventory.IsFull()) { return 0; }
@@ -746,6 +845,13 @@ namespace Barotrauma
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
// The attack incapacitated/killed the character: respond immediately to trigger nearby characters because the update loop no longer runs
if (wasConscious && (Character.IsIncapacitated || Character.Stun > 0.0f))
{
RespondToAttack(attacker, attackResult);
wasConscious = false;
return;
}
if (Character.IsDead) { return; }
if (attacker == null || Character.IsPlayer)
{
@@ -1003,7 +1109,7 @@ namespace Barotrauma
{
var objective = new AIObjectiveCombat(Character, attacker, mode, objectiveManager)
{
HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman",
HoldPosition = Character.Info?.Job?.Prefab.Identifier == "watchman" || Character.CurrentHull == null && ObjectiveManager.IsCurrentOrder<AIObjectiveGoTo>(),
abortCondition = abortCondition,
allowHoldFire = allowHoldFire,
};
@@ -1168,7 +1274,7 @@ namespace Barotrauma
needsSuit = true;
return true;
}
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < CharacterHealth.LowOxygenThreshold)
if (hull.WaterPercentage > 60 || hull.Oxygen < CharacterHealth.LowOxygenThreshold)
{
return true;
}
@@ -1223,6 +1329,14 @@ namespace Barotrauma
}
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
if (!otherCharacter.CanSeeCharacter(thief)) { continue; }
// Don't react if the player is taking an extinguisher and there's any fires on the sub, or diving gear when the sub is flooding
// -> allow them to use the emergency items
if (character.Submarine != null)
{
var connectedHulls = character.Submarine.GetHulls(alsoFromConnectedSubs: true);
if (item.HasTag("fireextinguisher") && connectedHulls.Any(h => h.FireSources.Any())) { continue; }
if (item.HasTag("diving") && connectedHulls.Any(h => h.ConnectedGaps.Any(g => AIObjectiveFixLeaks.IsValidTarget(g, thief)))) { continue; }
}
if (!someoneSpoke && !character.IsIncapacitated && character.Stun <= 0.0f)
{
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
@@ -1236,8 +1350,6 @@ namespace Barotrauma
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
someoneSpoke = true;
}
// Don't react if the player is taking an extinguisher and there's any fires on the sub -> allow them to use the emergency items
if (item.HasTag("fireextinguisher") && character.Submarine.GetHulls(alsoFromConnectedSubs: true).Any(h => h.FireSources.Any())) { continue; }
// React if we are security
if (!TriggerSecurity(otherHumanAI))
{
@@ -1425,7 +1537,7 @@ namespace Barotrauma
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders);
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull)
{
@@ -1464,7 +1576,17 @@ namespace Barotrauma
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
enemyFactor = MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
}
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
float dangerousItemsFactor = 1f;
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull != hull) { continue; }
if (item.Prefab != null && item.Prefab.IsDangerous)
{
dangerousItemsFactor = 0;
}
}
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor * dangerousItemsFactor;
return MathHelper.Clamp(safety * 100, 0, 100);
}